10 "github.com/stretchr/testify/require"
13 func TestBatchKeepsMemberMetadataAndEarliestDeadline(t *testing.T) {
14 deadline := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
15 later := deadline.Add(time.Minute)
16 payload := testBatch(deadline)
17 payload.Commands[0].ExpiresAt = &later
18 payload.Commands[0].CorrelationID = deviceID
19 payload.Commands[1].CausationID = messageID
20 publication, err := BuildCommandBatch(deviceID, Metadata{MessageID: messageID}, payload)
21 require.NoError(t, err)
22 require.Equal(t, &deadline, publication.ExpiresAt)
24 parsed, err := ParseCommandBatch(publication.Topic, publication.Data, false)
25 require.NoError(t, err)
26 require.Equal(t, payload, parsed.Payload)
28 for index := 2; index < MaximumCommandBatchSize; index++ {
30 MessageID: fmt.Sprintf("%026d", index+20),
33 payload.Commands = append(payload.Commands, BatchCommand{
35 Capability: fmt.Sprintf("relay.%d", index+1),
36 Payload: ValuePayload{Value: json.RawMessage(`false`)},
40 _, err = BuildCommandBatch(deviceID, Metadata{MessageID: messageID}, payload)
41 require.NoError(t, err)
43 payload.Commands = append(payload.Commands, payload.Commands[0])
44 _, err = BuildCommandBatch(deviceID, Metadata{MessageID: messageID}, payload)
48 func TestBatchRejectsMalformedMembersBeforeReturningCommands(t *testing.T) {
49 deadline := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
50 metadata := Metadata{MessageID: messageID}
51 publication, err := BuildCommandBatch(deviceID, metadata, testBatch(deadline))
52 require.NoError(t, err)
54 testCases := []struct {
60 name: "rejects member schema",
62 replacement: `"schema":2`,
65 name: "rejects unknown batch field",
67 replacement: `"extra":1,"commands":[`,
70 name: "rejects unknown member field",
71 old: `"capability":"relay.1"`,
72 replacement: `"capability":"relay.1","extra":1`,
75 name: "rejects unknown member value field",
77 replacement: `"value":true,"extra":1`,
80 name: "rejects duplicate member ID",
82 replacement: messageID,
85 name: "rejects duplicate target",
87 replacement: `"relay.1"`,
90 name: "rejects invalid target",
92 replacement: `"relay/+"`,
95 name: "requires member expiry",
96 old: `"expires_at":"2026-01-01T00:00:00Z",`,
100 name: "requires member value",
106 for _, tc := range testCases {
107 t.Run(tc.name, func(t *testing.T) {
108 data := strings.ReplaceAll(string(publication.Data), tc.old, tc.replacement)
110 var parsed Message[BatchPayload]
112 parsed, err = ParseCommandBatch(publication.Topic, []byte(data), false)
113 require.Error(t, err)
114 require.Empty(t, parsed.Payload.Commands)
118 payload := testBatch(deadline)
119 payload.Commands = payload.Commands[:1]
120 _, err = BuildCommandBatch(deviceID, metadata, payload)
121 require.Error(t, err)
123 _, err = BuildCommandBatch(deviceID, Metadata{MessageID: commandID}, testBatch(deadline))
124 require.Error(t, err)
126 data := strings.Replace(string(publication.Data), `"capability":"relay.1"`, `"capability":"relay.1","schema":2`, 1)
127 _, err = ParseCommandBatch(publication.Topic, []byte(data), false)
128 require.Error(t, err)