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

relay_test.gno

7.95 Kb · 245 lines
  1package relay
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/moul/agents/msg/v0"
  8	"gno.land/p/moul/x/daily/ringbuffer/v0"
  9	"gno.land/p/nt/testutils/v0"
 10	"gno.land/p/nt/uassert/v0"
 11)
 12
 13var (
 14	alice = testutils.TestAddress("alice")
 15	bob   = testutils.TestAddress("bob")
 16)
 17
 18// reset puts the realm back to its init() state. Realm globals persist for the
 19// whole test binary and examples run after every Test, so every test that
 20// writes starts here and ExampleRender builds its own state.
 21func reset() {
 22	log = ringbuffer.New(Slots)
 23	seq = 0
 24	seen = 0
 25}
 26
 27func TestPostAssignsSeqAndAuthor(cur realm, t *testing.T) {
 28	reset()
 29	testing.SetRealm(testing.NewUserRealm(alice))
 30	got := Post(cross(cur), "agent-a", "build", "note", "pr/1", "first")
 31
 32	uassert.Equal(t, uint64(1), got)
 33	uassert.Equal(t, 1, Len())
 34	uassert.Equal(t, 1, Seen())
 35
 36	e := Latest(0)[0]
 37	uassert.Equal(t, uint64(1), e.Msg.Seq)
 38	uassert.Equal(t, "agent-a", e.Msg.From)
 39	uassert.Equal(t, alice.String(), e.Author.String())
 40}
 41
 42func TestSeqIsMonotonicAcrossAuthors(cur realm, t *testing.T) {
 43	reset()
 44	testing.SetRealm(testing.NewUserRealm(alice))
 45	Post(cross(cur), "agent-a", "build", "", "", "one")
 46	testing.SetRealm(testing.NewUserRealm(bob))
 47	Post(cross(cur), "agent-b", "build", "", "", "two")
 48
 49	all := Latest(0) // newest first
 50	uassert.Equal(t, uint64(2), all[0].Msg.Seq)
 51	uassert.Equal(t, bob.String(), all[0].Author.String())
 52	uassert.Equal(t, uint64(1), all[1].Msg.Seq)
 53	uassert.Equal(t, alice.String(), all[1].Author.String())
 54}
 55
 56func TestRingDropsTheOldestAndSeenKeepsCounting(cur realm, t *testing.T) {
 57	reset()
 58	testing.SetRealm(testing.NewUserRealm(alice))
 59	for i := 0; i < Slots+5; i++ {
 60		Post(cross(cur), "agent-a", "build", "", "", "body")
 61	}
 62	uassert.Equal(t, Slots, Len())
 63	uassert.Equal(t, Slots+5, Seen())
 64
 65	// The five oldest fell off the back; the newest is the last one posted.
 66	all := Latest(0)
 67	uassert.Equal(t, uint64(Slots+5), all[0].Msg.Seq)
 68	uassert.Equal(t, uint64(6), all[len(all)-1].Msg.Seq)
 69}
 70
 71func TestSinceAndTopicFilter(cur realm, t *testing.T) {
 72	reset()
 73	testing.SetRealm(testing.NewUserRealm(alice))
 74	Post(cross(cur), "agent-a", "build", "", "", "b1")
 75	Post(cross(cur), "agent-a", "deploy", "", "", "d1")
 76	Post(cross(cur), "agent-a", "build", "", "", "b2")
 77
 78	since := Since(1) // oldest first
 79	uassert.Equal(t, 2, len(since))
 80	uassert.Equal(t, uint64(2), since[0].Msg.Seq)
 81	uassert.Equal(t, uint64(3), since[1].Msg.Seq)
 82	uassert.Equal(t, 0, len(Since(3)))
 83
 84	build := Topic("build") // newest first
 85	uassert.Equal(t, 2, len(build))
 86	uassert.Equal(t, "b2", build[0].Msg.Body)
 87	uassert.Equal(t, 0, len(Topic("nothing-here")))
 88}
 89
 90func TestLatestRespectsN(cur realm, t *testing.T) {
 91	reset()
 92	testing.SetRealm(testing.NewUserRealm(alice))
 93	for i := 0; i < 5; i++ {
 94		Post(cross(cur), "agent-a", "build", "", "", "body")
 95	}
 96	uassert.Equal(t, 2, len(Latest(2)))
 97	uassert.Equal(t, 5, len(Latest(0)))
 98	uassert.Equal(t, 5, len(Latest(-1)))
 99}
100
101func TestPostRejectsInvalidFields(cur realm, t *testing.T) {
102	reset()
103	testing.SetRealm(testing.NewUserRealm(alice))
104	uassert.AbortsWithMessage(t, cur, "empty topic", func() {
105		Post(cross(cur), "agent-a", "", "", "", "body")
106	})
107	uassert.AbortsWithMessage(t, cur, "topic must be lowercase a-z, 0-9, dash or dot", func() {
108		Post(cross(cur), "agent-a", "Build", "", "", "body")
109	})
110	uassert.AbortsWithMessage(t, cur, "empty body", func() {
111		Post(cross(cur), "agent-a", "build", "", "", "")
112	})
113	uassert.Equal(t, 0, Len())
114	uassert.Equal(t, 0, Seen())
115}
116
117// TestBodySurvivesTheEncoding is the regression test for the framing: a body
118// that looks like the encoding, or ends in the digits a length prefix is made
119// of, must come back unchanged with its author still attached.
120func TestBodySurvivesTheEncoding(cur realm, t *testing.T) {
121	reset()
122	testing.SetRealm(testing.NewUserRealm(alice))
123	nasty := []string{
124		"ends in 40",
125		"3:abc",
126		"has\na newline",
127		msg.Msg{Seq: 9, Height: 9, From: "x", Topic: "y", Kind: "z", Ref: "w", Body: "v"}.Encode(),
128		"| breaks | a | table |",
129	}
130	for _, body := range nasty {
131		Post(cross(cur), "agent-a", "build", "", "", body)
132	}
133	got := Latest(0) // newest first
134	for i, body := range nasty {
135		e := got[len(nasty)-1-i]
136		uassert.Equal(t, body, e.Msg.Body)
137		uassert.Equal(t, alice.String(), e.Author.String())
138	}
139}
140
141// TestRenderEscapesACallersString pins what guard-untrusted-render exists for:
142// a pipe in a body must not open a column in the rendered table.
143func TestRenderEscapesACallersString(cur realm, t *testing.T) {
144	reset()
145	testing.SetRealm(testing.NewUserRealm(alice))
146	Post(cross(cur), "agent-a", "build", "", "", "a | b")
147
148	out := Render("")
149	uassert.False(t, strings.Contains(out, "| a | b |"),
150		"an unescaped pipe opened a column in the rendered table")
151}
152
153// TestRenderEscapesABodyExactlyOnce is the regression test for the v0 bug.
154// ui.Cell(ui.Excerpt(s)) escaped twice, so a hyphen reached mainnet as
155// backslash-backslash-backslash-hyphen. Over-escaping is not a security hole,
156// which is exactly why the pipe test above passed while this was broken: a
157// doubly escaped pipe is still safe and still wrong.
158func TestRenderEscapesABodyExactlyOnce(cur realm, t *testing.T) {
159	reset()
160	testing.SetRealm(testing.NewUserRealm(alice))
161	Post(cross(cur), "agent-a", "build", "", "", "evicting in-call")
162
163	out := Render("")
164	uassert.True(t, strings.Contains(out, `evicting in\-call`),
165		"a hyphen in a short body should be escaped once")
166	uassert.False(t, strings.Contains(out, `in\\-call`),
167		"the body was escaped more than once")
168}
169
170// TestRenderEscapesALongBodyExactlyOnce covers the other branch of
171// excerptCell, where the body is cut before it is escaped.
172func TestRenderEscapesALongBodyExactlyOnce(cur realm, t *testing.T) {
173	reset()
174	testing.SetRealm(testing.NewUserRealm(alice))
175	long := "evicting in-call"
176	for len(long) < 200 {
177		long += " and-again"
178	}
179	Post(cross(cur), "agent-a", "build", "", "", long)
180
181	out := Render("")
182	uassert.True(t, strings.Contains(out, `evicting in\-call`),
183		"a hyphen in a cut body should be escaped once")
184	uassert.False(t, strings.Contains(out, `in\\-call`),
185		"the cut body was escaped more than once")
186}
187
188// TestPostRecordsNoSessionForAMasterKey pins the empty-session branch. A test
189// signs as a plain user realm, never through a session, so isSession is false
190// and Session must stay empty rather than inherit the author.
191func TestPostRecordsNoSessionForAMasterKey(cur realm, t *testing.T) {
192	reset()
193	testing.SetRealm(testing.NewUserRealm(alice))
194	Post(cross(cur), "agent-a", "build", "", "", "body")
195
196	e := Latest(0)[0]
197	uassert.Equal(t, alice.String(), e.Author.String())
198	uassert.Equal(t, "", e.Session.String())
199}
200
201// TestWireRoundTripsThroughAReader walks the wire format exactly as an
202// off-chain reader must: unframe one entry at a time, then unframe the author
203// and the session off the front, then decode the message. The bodies are the
204// ones that break a qeval parser.
205func TestWireRoundTripsThroughAReader(cur realm, t *testing.T) {
206	reset()
207	testing.SetRealm(testing.NewUserRealm(alice))
208	bodies := []string{
209		`a body with " string),( in it`,
210		"ends in 40",
211		"has\na newline",
212		"| and | pipes |",
213	}
214	for _, b := range bodies {
215		Post(cross(cur), "agent-a", "build", "k", "r", b)
216	}
217
218	rest := Wire(0)
219	for i, want := range bodies {
220		framed, tail, ok := msg.Unframe(rest)
221		uassert.True(t, ok, "entry did not unframe")
222		rest = tail
223
224		author, r2, ok := msg.Unframe(framed)
225		uassert.True(t, ok, "author did not unframe")
226		session, r3, ok := msg.Unframe(r2)
227		uassert.True(t, ok, "session did not unframe")
228		m, ok := msg.Decode(r3)
229		uassert.True(t, ok, "message did not decode")
230
231		uassert.Equal(t, want, m.Body)
232		uassert.Equal(t, uint64(i+1), m.Seq)
233		uassert.Equal(t, alice.String(), author)
234		uassert.Equal(t, "", session)
235	}
236	uassert.Equal(t, "", rest, "the wire had trailing bytes")
237}
238
239func TestWireIsEmptyWhenNothingIsNewer(cur realm, t *testing.T) {
240	reset()
241	testing.SetRealm(testing.NewUserRealm(alice))
242	Post(cross(cur), "agent-a", "build", "", "", "one")
243	uassert.Equal(t, "", Wire(1))
244	uassert.True(t, Wire(0) != "", "Wire(0) should carry the only entry")
245}