]> git.giacomolli.uk - onehouse/blob - driver/m31/connection_test.go
Apply M31 commands through the shared ledger
[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 read failure", change: func(c *fakeClient) {
62 c.registerReadErr = errors.New("configuration read failed")
63 }},
64 {name: "rejects pulse width mismatch", change: func(c *fakeClient) {
65 c.wrongRegister = pulseWidthBase + 2
66 }},
67 {name: "rejects configuration mismatch", change: func(c *fakeClient) {
68 c.wrongConfig = true
69 }},
70 {name: "rejects input read failure", change: func(c *fakeClient) {
71 c.inputErr = errors.New("input read failed")
72 }},
73 {name: "rejects coil read failure", change: func(c *fakeClient) {
74 c.readErr = errors.New("coil read failed")
75 }},
76 {name: "rejects incomplete read", change: func(c *fakeClient) {
77 c.shortRead = true
78 }},
79 {name: "rejects unsafe voltage", change: func(c *fakeClient) {
80 c.analog[0] = 10001
81 }},
82 }
83
84 for _, tc := range testCases {
85 t.Run(tc.name, func(t *testing.T) {
86 d, client, transport, _ := testAssembly(t)
87 tc.change(client)
88
89 c := &connection{owner: d}
90 err := c.start(t.Context())
91
92 var failure *deviceError
93
94 require.ErrorAs(t, err, &failure)
95 require.Empty(t, transport.messages)
96 })
97 }
98 }
99
100 func TestConnectionReturnsPublicationFailures(t *testing.T) {
101 testCases := []struct {
102 name string
103 kind protocol.Kind
104 change func(*fakeClient)
105 }{
106 {name: "returns input report failure", kind: protocol.Reported, change: func(c *fakeClient) {
107 c.inputs[0] = true
108 }},
109 {name: "returns transition failure", kind: protocol.Event, change: func(c *fakeClient) {
110 c.inputs[0] = true
111 }},
112 {name: "returns output report failure", kind: protocol.Reported, change: func(c *fakeClient) {
113 c.coils[0] = true
114 }},
115 {name: "returns voltage report failure", kind: protocol.Reported, change: func(c *fakeClient) {
116 c.analog[0] = 5000
117 }},
118 }
119
120 for _, tc := range testCases {
121 t.Run(tc.name, func(t *testing.T) {
122 c, client, transport := testConnection(t)
123 failure := errors.New("publish failed")
124 transport.failKind, transport.err = tc.kind, failure
125 tc.change(client)
126 require.ErrorIs(t, c.poll(t.Context()), failure)
127 })
128 }
129 }
130
131 func eventJSON(t *testing.T, value any) string {
132 t.Helper()
133
134 data, err := json.Marshal(value)
135 require.NoError(t, err)
136
137 return string(data)
138 }