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

threads_test.gno

7.80 Kb · 217 lines
  1package threads
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"testing"
  7
  8	sc "gno.land/p/moul/x/social/coin/v0"
  9	th "gno.land/p/moul/x/social/threads/v0"
 10	"gno.land/p/nt/testutils/v0"
 11	"gno.land/p/nt/uassert/v0"
 12	"gno.land/p/nt/urequire/v0"
 13)
 14
 15const homePage = "gno.land/r/moul/home"
 16
 17var (
 18	alice = testutils.TestAddress("alice")
 19	bob   = testutils.TestAddress("bob")
 20	carol = testutils.TestAddress("carol")
 21)
 22
 23// reset clears realm state. Realm globals live for the whole test binary, so
 24// everything a later test reads, including the ledger, starts here.
 25//
 26// The ledger is rebuilt through a same-realm cross because grc20.NewToken
 27// needs a code-realm frame, which a test's EOA-origin cur is not. It is NOT
 28// re-registered with grc20reg: the registry is another realm and its entry
 29// survives this one's state.
 30func reset(cur realm) {
 31	board = th.NewBoard()
 32	func(cur realm) { // the name is forced: a realm argument must be called cur
 33		points = sc.New("Thread Points", "THREAD", 0, 0, tokenPolicy, 0, cur)
 34	}(cross(cur))
 35}
 36
 37// earn gives who n THREAD the only way the realm allows: n distinct addresses
 38// replying to a thread they wrote.
 39func earn(cur realm, t *testing.T, who address, n int) int64 {
 40	t.Helper()
 41	testing.SetRealm(testing.NewUserRealm(who))
 42	id := Post(cross(cur), homePage, "a thread by "+who.String())
 43	for i := 0; i < n; i++ {
 44		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("replier" + strconv.Itoa(i))))
 45		Reply(cross(cur), id, "hello")
 46	}
 47	return id
 48}
 49
 50func TestAnAuthorEarnsPeopleAndNotMessages(cur realm, t *testing.T) {
 51	reset(cur)
 52
 53	testing.SetRealm(testing.NewUserRealm(alice))
 54	id := Post(cross(cur), homePage, "first thread")
 55	uassert.Equal(t, int64(0), BalanceOf(alice), "writing earns nothing on its own")
 56
 57	testing.SetRealm(testing.NewUserRealm(bob))
 58	Reply(cross(cur), id, "nice")
 59	uassert.Equal(t, int64(1), BalanceOf(alice))
 60
 61	testing.SetRealm(testing.NewUserRealm(bob))
 62	Reply(cross(cur), id, "still me")
 63	uassert.Equal(t, int64(1), BalanceOf(alice), "one person is one point, however much they say")
 64
 65	testing.SetRealm(testing.NewUserRealm(alice))
 66	Reply(cross(cur), id, "thanks")
 67	uassert.Equal(t, int64(1), BalanceOf(alice), "replying to yourself is not an audience")
 68
 69	testing.SetRealm(testing.NewUserRealm(carol))
 70	Reply(cross(cur), id, "hi")
 71	uassert.Equal(t, int64(2), BalanceOf(alice))
 72
 73	uassert.Equal(t, 2, Repliers(id))
 74	uassert.Equal(t, int64(2), TotalSupply())
 75	uassert.Equal(t, int64(0), BalanceOf(bob), "a replier earns nothing")
 76}
 77
 78func TestBoostIsTheOnlyThingThatBurns(cur realm, t *testing.T) {
 79	reset(cur)
 80	id := earn(cur, t, alice, 10)
 81	urequire.Equal(t, int64(10), BalanceOf(alice))
 82
 83	testing.SetRealm(testing.NewUserRealm(alice))
 84	Boost(cross(cur), id)
 85
 86	uassert.Equal(t, int64(0), BalanceOf(alice))
 87	uassert.Equal(t, int64(0), TotalSupply(), "the sink removes supply, it does not move it")
 88	uassert.Equal(t, int64(123+PinBlocks), PinnedUntil(id), "tests start at height 123")
 89}
 90
 91func TestBoostRefusesWhatTheCallerCannotAfford(cur realm, t *testing.T) {
 92	reset(cur)
 93	id := earn(cur, t, alice, 1)
 94
 95	testing.SetRealm(testing.NewUserRealm(alice))
 96	uassert.AbortsContains(t, cur, "balance 1 is short of 10", func() {
 97		Boost(cross(cur), id)
 98	})
 99	uassert.Equal(t, int64(0), PinnedUntil(id), "a refused boost pins nothing")
100	uassert.Equal(t, int64(1), BalanceOf(alice))
101}
102
103// A second boost extends from the current pin, not from now, so the thread
104// never loses time it was already paid for.
105func TestASecondBoostExtendsRatherThanRestarts(cur realm, t *testing.T) {
106	reset(cur)
107	id := earn(cur, t, alice, 20)
108
109	testing.SetRealm(testing.NewUserRealm(alice))
110	Boost(cross(cur), id)
111	first := PinnedUntil(id)
112
113	testing.SetRealm(testing.NewUserRealm(alice))
114	Boost(cross(cur), id)
115	uassert.Equal(t, first+PinBlocks, PinnedUntil(id))
116	uassert.Equal(t, int64(0), BalanceOf(alice))
117}
118
119// Anyone may boost anyone's thread, and the buyer has to get THREAD from
120// somebody who earned it. That is the whole market.
121func TestPlacementIsBoughtFromAnEarner(cur realm, t *testing.T) {
122	reset(cur)
123	id := earn(cur, t, alice, 10)
124
125	testing.SetRealm(testing.NewUserRealm(alice))
126	Transfer(cross(cur), bob, 10)
127	uassert.Equal(t, int64(0), BalanceOf(alice))
128	uassert.Equal(t, int64(10), BalanceOf(bob))
129
130	testing.SetRealm(testing.NewUserRealm(bob))
131	Boost(cross(cur), id)
132	uassert.True(t, PinnedUntil(id) > 0, "bob pinned alice's thread")
133	uassert.Equal(t, int64(0), TotalSupply())
134}
135
136func TestPostRefusals(cur realm, t *testing.T) {
137	reset(cur)
138	testing.SetRealm(testing.NewUserRealm(alice))
139	uassert.AbortsContains(t, cur, "not a page key", func() {
140		Post(cross(cur), "nopath", "body")
141	})
142	testing.SetRealm(testing.NewUserRealm(alice))
143	uassert.AbortsContains(t, cur, "body is empty", func() {
144		Post(cross(cur), homePage, "   ")
145	})
146	testing.SetRealm(testing.NewUserRealm(alice))
147	uassert.AbortsContains(t, cur, "no such thread", func() {
148		Reply(cross(cur), 99, "into the void")
149	})
150	uassert.Equal(t, 0, Total())
151}
152
153func TestCountsAreScopedToTheirPage(cur realm, t *testing.T) {
154	reset(cur)
155	testing.SetRealm(testing.NewUserRealm(alice))
156	Post(cross(cur), homePage, "on home")
157	testing.SetRealm(testing.NewUserRealm(alice))
158	Post(cross(cur), "gno.land/r/moul/blog", "on the blog")
159
160	uassert.Equal(t, 1, Count(homePage))
161	uassert.Equal(t, 0, Count("gno.land/r/nobody/here"))
162	uassert.Equal(t, 2, Total())
163	uassert.Equal(t, 2, Pages())
164}
165
166// RenderBlockFor is the embed, and it escapes what it shows: a raw pipe in a
167// body would otherwise open a column in the host realm's page.
168func TestRenderBlockForEscapesTheBody(cur realm, t *testing.T) {
169	reset(cur)
170	testing.SetRealm(testing.NewUserRealm(alice))
171	Post(cross(cur), homePage, "a | pipe")
172
173	got := RenderBlockFor(homePage)
174	uassert.True(t, strings.Contains(got, "func=Post"), "the block offers the transaction: "+got)
175	uassert.True(t, strings.Contains(got, `| a \| pipe |`),
176		"the body cell escapes the pipe rather than opening a column: "+got)
177	uassert.False(t, strings.Contains(got, "| a | pipe"), "no raw pipe survives: "+got)
178	uassert.True(t, strings.Contains(RenderBlockFor("gno.land/r/nobody/here"), "No discussion yet."))
179}
180
181// TestRender uses uassert rather than an Example because every view contains
182// consecutive blank lines, which gno collapses inside an // Output: block.
183func TestRender(cur realm, t *testing.T) {
184	reset(cur)
185	testing.SetRealm(testing.NewUserRealm(alice))
186	id := Post(cross(cur), homePage, "a thread about [links](x)")
187	testing.SetRealm(testing.NewUserRealm(bob))
188	Reply(cross(cur), id, "a reply with a | pipe")
189
190	index := Render("")
191	uassert.True(t, strings.Contains(index, "| 1 | 1 | 1 |"), "threads, pages, supply: "+index)
192	uassert.True(t, strings.Contains(index, tokenPolicy.Sink), "the index declares the sink")
193	uassert.True(t, strings.Contains(index, `import "`+realmPath+`"`), "the index is the embed doc")
194
195	page := Render("page/" + homePage)
196	uassert.True(t, strings.Contains(page, "/r/moul/home"), "it links the page itself: "+page)
197	uassert.True(t, strings.Contains(page, "func=Post"))
198
199	sid := strconv.FormatInt(id, 10)
200	thread := Render("thread/" + sid)
201	uassert.True(t, strings.Contains(thread, "# Thread #"+sid))
202	uassert.True(t, strings.Contains(thread, "a thread about \\[links\\]\\(x\\)"),
203		"the body is escaped where it is shown: "+thread)
204	uassert.True(t, strings.Contains(thread, "a reply with a \\| pipe"),
205		"a reply cell cannot open a column: "+thread)
206	uassert.True(t, strings.Contains(thread, "func=Boost"))
207
208	uassert.Equal(t, "# Threads\nNo thread #4242", Render("thread/4242"))
209	uassert.Equal(t, "# Threads\nNot a thread id: nope", Render("thread/nope"))
210	uassert.Equal(t, "# Threads\nNot a page key: nope", Render("page/nope"))
211	uassert.True(t, strings.HasPrefix(Render("elsewhere"), "# Not found"))
212}
213
214func TestTokenIsDiscoverableWithoutImportingThisRealm(t *testing.T) {
215	uassert.True(t, strings.HasSuffix(TokenKey(), ".THREAD"), TokenKey())
216	uassert.True(t, strings.Contains(TokenKey(), realmPath), TokenKey())
217}