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
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]
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
}
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 {