]> git.giacomolli.uk - onehouse/blob - driver/m31/connection_test.go
f2e67a6c22e10e333a4ae8b841ac27593adf460b
[onehouse] / driver / m31 / connection_test.go
1 package m31
2
3 import (
4 "encoding/json"
5 "errors"
6 "testing"
7 "testing/synctest"
8 "time"
9
10 "github.com/stretchr/testify/require"
11
12 "onehouse/protocol"
13 )
14
15 func TestConnectionBaselineAndInputEvents(t *testing.T) {
16 synctest.Test(t, func(t *testing.T) {
17 c, client, transport := testConnection(t)
18 require.Empty(t, transport.ofKind(protocol.Event))
19
20 client.inputs[0], client.inputs[15] = true, true
21 client.inputDelay = time.Millisecond
22 observedAt := time.Now().Add(time.Millisecond).UTC()
23
24 require.NoError(t, c.poll(t.Context()))
25
26 events := transport.ofKind(protocol.Event)
27 require.Len(t, events, 2)
28
29 for _, event := range events {
30 require.Equal(t, &observedAt, event.Metadata.SentAt)
31 require.JSONEq(t, `{"action":"pressed","value":true}`, eventJSON(t, event.Payload))
32 }
33
34 require.NoError(t, c.poll(t.Context()))
35 require.Len(t, transport.ofKind(protocol.Event), 2)
36
37 client.inputs[0] = false
38
39 require.NoError(t, c.poll(t.Context()))
40
41 events = transport.ofKind(protocol.Event)
42 require.Len(t, events, 3)
43 require.JSONEq(t, `{"action":"released","value":false}`, eventJSON(t, events[2].Payload))
44
45 client.coils[2] = true
46 transport.messages = nil
47
48 require.NoError(t, c.poll(t.Context()))
49 require.Empty(t, transport.messages)
50 })
51 }
52
53 func TestConnectionRejectsConfigurationAndReadFailuresBeforeOnline(t *testing.T) {
54 testCases := []struct {
55 name string
56 change func(*fakeClient)
57 }{
58 {name: "rejects pulse register write failure", change: func(c *fakeClient) {
59 c.registerErr = errors.New("configuration failed")
60 }},
61 {name: "rejects configuration mismatch", change: func(c *fakeClient) {
62 c.wrongConfig = true
63 }},
64 {name: "rejects input read failure", change: func(c *fakeClient) {
65 c.inputErr = errors.New("input read failed")
66 }},
67 {name: "rejects coil read failure", change: func(c *fakeClient) {
68 c.readErr = errors.New("coil read failed")
69 }},
70 {name: "rejects incomplete read", change: func(c *fakeClient) {
71 c.shortRead = true
72 }},
73 {name: "rejects unsafe voltage", change: func(c *fakeClient) {
74 c.analog[0] = 10001
75 }},
76 }
77
78 for _, tc := range testCases {
79 t.Run(tc.name, func(t *testing.T) {
80 d, client, transport := testAssembly(t)
81 tc.change(client)
82
83 c := &connection{owner: d}
84 err := c.start(t.Context())
85
86 var failure *deviceError
87
88 require.ErrorAs(t, err, &failure)
89 require.Empty(t, transport.messages)
90 })
91 }
92 }
93
94 func TestConnectionReturnsPublicationFailures(t *testing.T) {
95 testCases := []struct {
96 name string
97 kind protocol.Kind
98 change func(*fakeClient)
99 }{
100 {name: "returns input report failure", kind: protocol.Reported, change: func(c *fakeClient) {
101 c.inputs[0] = true
102 }},
103 {name: "returns transition failure", kind: protocol.Event, change: func(c *fakeClient) {
104 c.inputs[0] = true
105 }},
106 {name: "returns output report failure", kind: protocol.Reported, change: func(c *fakeClient) {
107 c.coils[0] = true
108 }},
109 {name: "returns voltage report failure", kind: protocol.Reported, change: func(c *fakeClient) {
110 c.analog[0] = 5000
111 }},
112 }
113
114 for _, tc := range testCases {
115 t.Run(tc.name, func(t *testing.T) {
116 c, client, transport := testConnection(t)
117 failure := errors.New("publish failed")
118 transport.failKind, transport.err = tc.kind, failure
119 tc.change(client)
120 require.ErrorIs(t, c.poll(t.Context()), failure)
121 })
122 }
123 }
124
125 func eventJSON(t *testing.T, value any) string {
126 t.Helper()
127
128 data, err := json.Marshal(value)
129 require.NoError(t, err)
130
131 return string(data)
132 }