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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}