X-Git-Url: https://git.giacomolli.uk/onehouse/blobdiff_plain/b9a4f3bdd2609a8b178dd42f313c383ccb59049b..HEAD:/driver/alamo/fake_helper_test.go?ds=inline diff --git a/driver/alamo/fake_helper_test.go b/driver/alamo/fake_helper_test.go index 03d36f2..bef18e2 100644 --- a/driver/alamo/fake_helper_test.go +++ b/driver/alamo/fake_helper_test.go @@ -2,20 +2,43 @@ package alamo import ( "context" + "errors" + "fmt" + "sync" + "testing" "time" + + "github.com/stretchr/testify/require" + + "onehouse/driver" + "onehouse/protocol" ) -const testMAC = "fc610e7f98aa" +const ( + validRecord = `{"mac":"FC610E7F98AA","power":0,"stemp":20,"temp":19.5,"sensor":23,"ativo":0,"bloqueio":1}` + + testMAC = "fc610e7f98aa" + testDeviceID = "01ARZ3NDEKTSV4RRFFQ69G5FAV" + testSecondID = "01ARZ3NDEKTSV4RRFFQ69G5FAW" + testCommandID = "01ARZ3NDEKTSV4RRFFQ69G5FAX" +) type fakeGateway struct { - states []State - starts []time.Time - actions []Action - err error - delay time.Duration + mu sync.Mutex + failActionAt int + ignorePower bool + ignoreTarget bool + states []State + starts []time.Time + actions []Action + err error + delay time.Duration } func (g *fakeGateway) Status(ctx context.Context) ([]State, error) { + g.mu.Lock() + defer g.mu.Unlock() + err := g.request(ctx) if err != nil { return nil, err @@ -24,13 +47,36 @@ func (g *fakeGateway) Status(ctx context.Context) ([]State, error) { return append([]State(nil), g.states...), nil } +func (g *fakeGateway) request(ctx context.Context) error { + g.starts = append(g.starts, time.Now()) + if g.delay > 0 { + timer := time.NewTimer(g.delay) + defer timer.Stop() + + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + } + } + + return g.err +} + func (g *fakeGateway) Action(ctx context.Context, mac string, action Action) (State, error) { + g.mu.Lock() + defer g.mu.Unlock() + + g.actions = append(g.actions, action) + err := g.request(ctx) if err != nil { return State{}, err } - g.actions = append(g.actions, action) + if len(g.actions) == g.failActionAt { + return State{}, errors.New("action unavailable") + } for index := range g.states { state := &g.states[index] @@ -38,11 +84,11 @@ func (g *fakeGateway) Action(ctx context.Context, mac string, action Action) (St continue } - if action.Power != nil { + if action.Power != nil && !g.ignorePower { state.Power = *action.Power } - if action.TargetTemperature != nil { + if action.TargetTemperature != nil && !g.ignoreTarget { state.TargetTemperature = *action.TargetTemperature } @@ -52,20 +98,110 @@ func (g *fakeGateway) Action(ctx context.Context, mac string, action Action) (St return State{}, nil } -func (g *fakeGateway) request(ctx context.Context) error { - g.starts = append(g.starts, time.Now()) - if g.delay > 0 { - timer := time.NewTimer(g.delay) - defer timer.Stop() +func (g *fakeGateway) startCount() int { + g.mu.Lock() + defer g.mu.Unlock() - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: + return len(g.starts) +} + +type fakeTransport struct { + mu sync.Mutex + publications []protocol.Publication + failKind protocol.Kind + failure error +} + +func (p *fakeTransport) Publish(_ context.Context, publication protocol.Publication) error { + p.mu.Lock() + defer p.mu.Unlock() + + p.publications = append(p.publications, publication) + message, err := protocol.Parse(publication.Topic, publication.Data, publication.Retain) + if err != nil { + return fmt.Errorf("parse fake publication: %w", err) + } + + if message.Address.Kind == p.failKind { + return p.failure + } + + return nil +} + +func (p *fakeTransport) messages(t *testing.T, kind protocol.Kind) []protocol.Message[any] { + t.Helper() + p.mu.Lock() + defer p.mu.Unlock() + + var messages []protocol.Message[any] + + for _, publication := range p.publications { + message, err := protocol.Parse(publication.Topic, publication.Data, publication.Retain) + require.NoError(t, err) + + if message.Address.Kind == kind { + messages = append(messages, message) } } - return g.err + return messages +} + +type cancelingGateway struct { + cancel context.CancelFunc + failure error +} + +func (g *cancelingGateway) Status(_ context.Context) ([]State, error) { + g.cancel() + + return nil, errors.Join(context.Canceled, g.failure) +} + +func (g *cancelingGateway) Action(_ context.Context, _ string, _ Action) (State, error) { + g.cancel() + + return State{}, errors.Join(context.Canceled, g.failure) +} + +func testDriver(t *testing.T, client Client, transport *fakeTransport, thermostats ...Thermostat) (*Driver, *driver.Router) { + t.Helper() + + if len(thermostats) == 0 { + thermostats = []Thermostat{{ + DeviceID: testDeviceID, + MAC: testMAC, + }} + } + + var ids []string + + for _, thermostat := range thermostats { + ids = append(ids, thermostat.DeviceID) + } + + router, err := driver.NewRouter(ids) + require.NoError(t, err) + + integration, err := New(Settings{Thermostats: thermostats}, driver.NewPublisher(transport), router, client) + require.NoError(t, err) + + return integration, router +} + +func testCommand() driver.Command { + expires := time.Now().Add(time.Minute).UTC() + + return driver.Command{ + DeviceID: testDeviceID, + Capability: "control", + Metadata: protocol.Metadata{ + MessageID: testCommandID, + ExpiresAt: &expires, + }, + Payload: protocol.ValuePayload{Value: []byte(`{"mode":"heat","target_temperature":22}`)}, + } } func testState() State {