Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}