]> git.giacomolli.uk - onehouse/blob - protocol/wire_envelope_test.go
Validate typed publications before encoding
[onehouse] / protocol / wire_envelope_test.go
1 package protocol
2
3 import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestEnvelopeRoundTripPreservesMetadata(t *testing.T) {
12 now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
13 metadata := Metadata{
14 MessageID: messageID,
15 SentAt: &now,
16 ExpiresAt: &now,
17 CorrelationID: commandID,
18 CausationID: deviceID,
19 }
20 data, err := encodeEnvelope(metadata, map[string]bool{"value": true})
21 require.NoError(t, err)
22
23 envelope, err := parseEnvelope(data)
24 require.NoError(t, err)
25 require.Equal(t, Schema, envelope.Schema)
26 require.Equal(t, metadata, envelope.Metadata)
27 require.JSONEq(t, `{"value":true}`, string(envelope.Payload))
28 require.Equal(t, 5*time.Minute, MeasurementRepeatInterval)
29 require.Equal(t, 5*time.Minute, OnlineRepeatInterval)
30 require.Equal(t, 15*time.Minute, DeviceContactTimeout)
31 }
32
33 func TestEnvelopeRejectsInvalidWireData(t *testing.T) {
34 valid := `{"schema":1,"message_id":"` + messageID + `","payload":{}}`
35
36 testCases := []struct {
37 name string
38 data string
39 }{
40 {
41 name: "rejects unsupported schema",
42 data: strings.Replace(valid, `"schema":1`, `"schema":2`, 1),
43 },
44 {
45 name: "rejects unknown fields",
46 data: strings.Replace(valid, `"payload":{}`, `"payload":{},"extra":true`, 1),
47 },
48 {
49 name: "rejects missing payload",
50 data: `{"schema":1,"message_id":"` + messageID + `"}`,
51 },
52 {
53 name: "rejects null payload",
54 data: strings.Replace(valid, `"payload":{}`, `"payload":null`, 1),
55 },
56 {
57 name: "rejects non-object payload",
58 data: strings.Replace(valid, `"payload":{}`, `"payload":[]`, 1),
59 },
60 {
61 name: "rejects missing identity",
62 data: `{"schema":1,"payload":{}}`,
63 },
64 {
65 name: "rejects invalid correlation",
66 data: strings.Replace(valid, `"payload":{}`, `"payload":{},"correlation_id":"bad"`, 1),
67 },
68 {
69 name: "rejects invalid causation",
70 data: strings.Replace(valid, `"payload":{}`, `"payload":{},"causation_id":"bad"`, 1),
71 },
72 {
73 name: "rejects non-UTC time",
74 data: strings.Replace(valid, `"payload":{}`, `"payload":{},"sent_at":"2026-01-01T01:00:00+01:00"`, 1),
75 },
76 {
77 name: "rejects invalid expiry",
78 data: strings.Replace(valid, `"payload":{}`, `"payload":{},"expires_at":"bad"`, 1),
79 },
80 {
81 name: "rejects trailing JSON",
82 data: valid + `{}`,
83 },
84 }
85
86 for _, tc := range testCases {
87 t.Run(tc.name, func(t *testing.T) {
88 _, err := parseEnvelope([]byte(tc.data))
89 require.Error(t, err)
90 })
91 }
92
93 _, err := encodeEnvelope(Metadata{MessageID: "bad"}, map[string]bool{})
94 require.Error(t, err)
95
96 _, err = encodeEnvelope(Metadata{MessageID: messageID}, make(chan int))
97 require.Error(t, err)
98
99 _, err = encodeEnvelope(Metadata{MessageID: messageID}, []int{1})
100 require.Error(t, err)
101 }