msg_test.gno
4.55 Kb · 133 lines
1package msg
2
3import "testing"
4
5func TestEncodeDecodeRoundTrip(t *testing.T) {
6 tests := []struct {
7 name string
8 m Msg
9 }{
10 {"plain", Msg{1, 100, "claude@hermey", "build", "note", "pr/12", "ci is green"}},
11 {"empty optional fields", Msg{2, 0, "a", "t", "", "", "b"}},
12 {"body holds the separator", Msg{3, 7, "a", "t", "k", "r", "3:abc:def"}},
13 {"body holds a newline", Msg{4, 7, "a", "t", "k", "r", "line one\nline two"}},
14 {"body holds an encoded message", Msg{5, 7, "a", "t", "k", "r", Msg{1, 1, "x", "y", "z", "w", "v"}.Encode()}},
15 {"unicode body", Msg{6, 7, "a", "t", "k", "r", "héllo … 世界"}},
16 {"max height", Msg{7, 9223372036854775807, "a", "t", "k", "r", "b"}},
17 }
18 for _, tt := range tests {
19 got, ok := Decode(tt.m.Encode())
20 if !ok {
21 t.Errorf("%s: Decode reported failure", tt.name)
22 continue
23 }
24 if got != tt.m {
25 t.Errorf("%s: round trip changed the message", tt.name)
26 }
27 }
28}
29
30func TestDecodeRejects(t *testing.T) {
31 tests := []struct {
32 name string
33 s string
34 }{
35 {"empty, which is an unwritten ring slot", ""},
36 {"not length-prefixed", "hello"},
37 {"too few fields", "1:1" + "1:2"},
38 {"length runs past the end", "99:short"},
39 {"trailing bytes after the last field", Msg{1, 1, "a", "t", "k", "r", "b"}.Encode() + "x"},
40 {"seq is not a number", "1:x1:00:00:00:00:01:b"},
41 {"negative length", "-1:a"},
42 {"no colon", "5abcde"},
43 }
44 for _, tt := range tests {
45 if _, ok := Decode(tt.s); ok {
46 t.Errorf("%s: Decode accepted it", tt.name)
47 }
48 }
49}
50
51func TestEncodeIsUnambiguous(t *testing.T) {
52 // The reason for length prefixes: two different splits of the same bytes
53 // must not encode to the same string.
54 a := Msg{1, 1, "ab", "c", "k", "r", "b"}.Encode()
55 b := Msg{1, 1, "a", "bc", "k", "r", "b"}.Encode()
56 if a == b {
57 t.Error("two distinct messages share an encoding")
58 }
59}
60
61func TestValidate(t *testing.T) {
62 long := func(n int) string {
63 s := ""
64 for i := 0; i < n; i++ {
65 s += "x"
66 }
67 return s
68 }
69 ok := Msg{From: "a", Topic: "t", Body: "b"}
70
71 tests := []struct {
72 name string
73 m Msg
74 want string
75 }{
76 {"valid", ok, ""},
77 {"valid with dots and digits", Msg{From: "a", Topic: "build.v2-0", Body: "b"}, ""},
78 {"empty from", Msg{Topic: "t", Body: "b"}, "empty from"},
79 {"from too long", Msg{From: long(MaxFrom + 1), Topic: "t", Body: "b"}, "from too long"},
80 {"empty topic", Msg{From: "a", Body: "b"}, "empty topic"},
81 {"topic too long", Msg{From: "a", Topic: long(MaxTopic + 1), Body: "b"}, "topic too long"},
82 {"uppercase topic", Msg{From: "a", Topic: "Build", Body: "b"}, "topic must be lowercase a-z, 0-9, dash or dot"},
83 {"topic with a slash", Msg{From: "a", Topic: "a/b", Body: "b"}, "topic must be lowercase a-z, 0-9, dash or dot"},
84 {"topic with a space", Msg{From: "a", Topic: "a b", Body: "b"}, "topic must be lowercase a-z, 0-9, dash or dot"},
85 {"kind too long", Msg{From: "a", Topic: "t", Kind: long(MaxKind + 1), Body: "b"}, "kind too long"},
86 {"ref too long", Msg{From: "a", Topic: "t", Ref: long(MaxRef + 1), Body: "b"}, "ref too long"},
87 {"empty body", Msg{From: "a", Topic: "t"}, "empty body"},
88 {"body too long", Msg{From: "a", Topic: "t", Body: long(MaxBody + 1)}, "body too long"},
89 {"body at the limit", Msg{From: "a", Topic: "t", Body: long(MaxBody)}, ""},
90 }
91 for _, tt := range tests {
92 if got := tt.m.Validate(); got != tt.want {
93 t.Errorf("%s: got %q, want %q", tt.name, got, tt.want)
94 }
95 }
96}
97
98func TestFrameUnframe(t *testing.T) {
99 tests := []struct {
100 name string
101 v string
102 tail string
103 }{
104 {"plain", "g1abc", ""},
105 {"empty value", "", "rest"},
106 {"value holds a colon", "a:b", "rest"},
107 // The bug this pair exists to prevent: scanning backwards for the
108 // length prefix cannot tell a body's trailing digits from it.
109 {"preceded by a message ending in digits", "g1abc", ""},
110 }
111 for _, tt := range tests {
112 got, rest, ok := Unframe(Frame(tt.v) + tt.tail)
113 if !ok || got != tt.v || rest != tt.tail {
114 t.Errorf("%s: got (%q, %q, %v), want (%q, %q, true)", tt.name, got, rest, ok, tt.v, tt.tail)
115 }
116 }
117}
118
119func TestFrameSurvivesABodyEndingInDigits(t *testing.T) {
120 // A relay frames the author and concatenates. Decoding must recover both
121 // even when the message body ends in the digits a backwards scan would
122 // mistake for the author's length prefix.
123 m := Msg{1, 1, "a", "t", "k", "r", "ends in 40"}
124 addr := "g1manfred47kzduec920z88wfr64ylksmdcedlf5"
125 author, rest, ok := Unframe(Frame(addr) + m.Encode())
126 if !ok || author != addr {
127 t.Fatalf("author not recovered: got %q, ok=%v", author, ok)
128 }
129 got, ok := Decode(rest)
130 if !ok || got != m {
131 t.Errorf("message not recovered after the framed author")
132 }
133}