package alamo import ( "context" "errors" "fmt" "sync" "testing" "time" "github.com/stretchr/testify/require" "onehouse/driver" "onehouse/protocol" ) 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 { 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 } 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 } if len(g.actions) == g.failActionAt { return State{}, errors.New("action unavailable") } for index := range g.states { state := &g.states[index] if state.MAC != mac { continue } if action.Power != nil && !g.ignorePower { state.Power = *action.Power } if action.TargetTemperature != nil && !g.ignoreTarget { state.TargetTemperature = *action.TargetTemperature } return *state, nil } return State{}, nil } func (g *fakeGateway) startCount() int { g.mu.Lock() defer g.mu.Unlock() 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 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 { return State{ MAC: testMAC, TargetTemperature: 20, CurrentTemperature: 19.5, FloorTemperature: 23, } }