]> git.giacomolli.uk - onehouse/blobdiff - protocol/message_test.go
Preserve separate Modbus phase timeouts
[onehouse] / protocol / message_test.go
index 770ae501455b6364d83b8c82e88cb451c18982d7..660885d2b764a958df26102a20446642cfdf7545 100644 (file)
@@ -154,7 +154,7 @@ func TestPayloadValidationRejectsInvalidContracts(t *testing.T) {
 
        for _, tc := range testCases {
                t.Run(tc.name, func(t *testing.T) {
-                       data, err := EncodeEnvelope(metadata, json.RawMessage(tc.payload))
+                       data, err := encodeEnvelope(metadata, json.RawMessage(tc.payload))
                        if tc.kind == Event {
                                require.Error(t, err)
 
@@ -167,7 +167,7 @@ func TestPayloadValidationRejectsInvalidContracts(t *testing.T) {
                                Kind:   tc.kind,
                                ID:     deviceID,
                                Target: tc.target,
-                       }).Topic()
+                       }).topic()
                        require.NoError(t, err)
 
                        _, err = Parse(topic, data, false)
@@ -294,3 +294,134 @@ func TestBuildersApplyCommandAndResultRules(t *testing.T) {
        _, err = BuildDescriptor(deviceID, metadata, descriptor)
        require.Error(t, err)
 }
+
+func TestBuildersKeepNullValuesWithoutChangingInputs(t *testing.T) {
+       metadata := Metadata{MessageID: messageID}
+       payload := ValuePayload{}
+       publication, err := BuildReported(deviceID, "relay", metadata, payload)
+       require.NoError(t, err)
+       require.Nil(t, payload.Value)
+
+       parsed, err := ParseReported(publication.Topic, publication.Data, false)
+       require.NoError(t, err)
+       require.JSONEq(t, `null`, string(parsed.Payload.Value))
+
+       deadline := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+       batch := testBatch(deadline)
+       batch.Commands[0].Payload.Value = nil
+       publication, err = BuildCommandBatch(deviceID, metadata, batch)
+       require.NoError(t, err)
+       require.Nil(t, batch.Commands[0].Payload.Value)
+
+       commands, err := ParseCommandBatch(publication.Topic, publication.Data, false)
+       require.NoError(t, err)
+       require.JSONEq(t, `null`, string(commands.Payload.Commands[0].Payload.Value))
+}
+
+func TestBuildEventRejectsNonObjectAndInvalidValues(t *testing.T) {
+       testCases := []struct {
+               name    string
+               payload EventPayload
+               valid   bool
+       }{
+               {
+                       name: "rejects null instead of an object",
+               },
+               {
+                       name:    "rejects malformed event data",
+                       payload: EventPayload{"action": json.RawMessage(`bad`)},
+               },
+               {
+                       name:    "rejects empty nonnull event data",
+                       payload: EventPayload{"action": json.RawMessage{}},
+               },
+               {
+                       name:    "keeps null event members",
+                       payload: EventPayload{"action": nil},
+                       valid:   true,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       publication, err := BuildEvent(deviceID, "press", Metadata{MessageID: messageID}, tc.payload)
+                       if !tc.valid {
+                               require.Error(t, err)
+
+                               return
+                       }
+
+                       require.NoError(t, err)
+
+                       parsed, err := ParseEvent(publication.Topic, publication.Data, false)
+                       require.NoError(t, err)
+                       require.JSONEq(t, `null`, string(parsed.Payload["action"]))
+               })
+       }
+}
+
+func TestBuildRejectsInvalidMetadataBeforeReturningPublications(t *testing.T) {
+       deadline := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+       metadata := Metadata{MessageID: messageID}
+       testCases := []struct {
+               name   string
+               mutate func(*Metadata)
+       }{
+               {
+                       name: "rejects malformed message identity",
+                       mutate: func(metadata *Metadata) {
+                               metadata.MessageID = "bad"
+                       },
+               },
+               {
+                       name: "rejects malformed correlation identity",
+                       mutate: func(metadata *Metadata) {
+                               metadata.CorrelationID = "bad"
+                       },
+               },
+               {
+                       name: "rejects malformed causation identity",
+                       mutate: func(metadata *Metadata) {
+                               metadata.CausationID = "bad"
+                       },
+               },
+               {
+                       name: "rejects non UTC expiry",
+                       mutate: func(metadata *Metadata) {
+                               expiry := deadline.In(time.FixedZone("test", 3600))
+                               metadata.ExpiresAt = &expiry
+                       },
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       invalid := metadata
+                       tc.mutate(&invalid)
+
+                       _, err := BuildReported(deviceID, "relay", invalid, ValuePayload{Value: json.RawMessage(`true`)})
+                       require.Error(t, err)
+
+                       batch := testBatch(deadline)
+                       tc.mutate(&batch.Commands[1].Metadata)
+
+                       _, err = BuildCommandBatch(deviceID, metadata, batch)
+                       require.Error(t, err)
+               })
+       }
+}
+
+func TestDescriptorAttributionLimitUsesEncodedText(t *testing.T) {
+       payload := testDescriptor()
+       payload.Attribution = strings.Repeat("\xff", 667)
+       _, err := BuildDescriptor(deviceID, Metadata{MessageID: messageID}, payload)
+       require.Error(t, err)
+
+       payload.Attribution = strings.Repeat("\xff", 666)
+       publication, err := BuildDescriptor(deviceID, Metadata{MessageID: messageID}, payload)
+       require.NoError(t, err)
+
+       parsed, err := ParseDescriptor(publication.Topic, publication.Data, true)
+       require.NoError(t, err)
+       require.Len(t, parsed.Payload.Attribution, 1998)
+}