]> git.giacomolli.uk - onehouse/blobdiff - driver/alamo/fake_helper_test.go
Poll and command shared Alamo thermostats
[onehouse] / driver / alamo / fake_helper_test.go
index 03d36f2a2132d179ad197782b858c8518d49b2fb..bef18e28ac1fe7e15d9c681c7d103dfc42efa8fd 100644 (file)
@@ -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 {