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

vouch_test.gno

9.71 Kb · 292 lines
  1package vouch
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"chain"
  8	"chain/banker"
  9
 10	vo "gno.land/p/moul/x/social/vouch/v0"
 11	"gno.land/p/nt/testutils/v0"
 12	"gno.land/p/nt/uassert/v0"
 13)
 14
 15var (
 16	alice = testutils.TestAddress("alice")
 17	bob   = testutils.TestAddress("bob")
 18	carol = testutils.TestAddress("carol")
 19
 20	realmAddr = chain.PackageAddress(realmPath)
 21)
 22
 23// reset clears realm state. Realm globals live for the whole test binary, so
 24// every test that reads a score, a bond or a refund starts here.
 25func reset() { graph = vo.NewGraph() }
 26
 27// fund simulates the coins attached to the next call: the runtime credits the
 28// realm's address first, and the crossing function then reads the envelope off
 29// the transaction. fund(0) clears it, which is the ordinary unbonded case.
 30//
 31// testing.SetRealm is call-frame scoped, so the caller still switches account
 32// inline in the test body right before crossing.
 33func fund(amount int64) {
 34	if amount <= 0 {
 35		testing.SetOriginSend(chain.Coins{})
 36		return
 37	}
 38	testing.IssueCoins(realmAddr, chain.Coins{{denom, amount}})
 39	testing.SetOriginSend(chain.Coins{{denom, amount}})
 40}
 41
 42func balanceOf(addr address) int64 {
 43	return banker.NewReadonlyBanker().GetCoins(addr).AmountOf(denom)
 44}
 45
 46// The score counts people, which is the property every caller of IsTrusted
 47// depends on: a repeated vouch does not raise it, a revoke lowers it.
 48func TestScoreCountsPeopleAndNotVouches(cur realm, t *testing.T) {
 49	reset()
 50
 51	fund(0)
 52	testing.SetRealm(testing.NewUserRealm(bob))
 53	Vouch(cross(cur), alice, "worked with them for two years")
 54	uassert.Equal(t, 1, ScoreOf(alice))
 55
 56	fund(0)
 57	testing.SetRealm(testing.NewUserRealm(bob))
 58	Vouch(cross(cur), alice, "still true")
 59	uassert.Equal(t, 1, ScoreOf(alice), "one person is one point, however often they say it")
 60	uassert.Equal(t, "still true", ReasonFrom(bob, alice), "the reason was updated")
 61	uassert.Equal(t, 1, Count())
 62
 63	fund(0)
 64	testing.SetRealm(testing.NewUserRealm(carol))
 65	Vouch(cross(cur), alice, "we shipped together")
 66	uassert.Equal(t, 2, ScoreOf(alice))
 67	uassert.Equal(t, 1, People(), "one address has been vouched for")
 68
 69	fund(0)
 70	testing.SetRealm(testing.NewUserRealm(carol))
 71	Revoke(cross(cur), alice)
 72	uassert.Equal(t, 1, ScoreOf(alice), "revoking lowers the score")
 73	uassert.Equal(t, 1, Count())
 74}
 75
 76func TestVouchRefusals(cur realm, t *testing.T) {
 77	reset()
 78
 79	fund(0)
 80	testing.SetRealm(testing.NewUserRealm(alice))
 81	uassert.AbortsContains(t, cur, "cannot vouch for itself", func() {
 82		Vouch(cross(cur), alice, "I am great")
 83	})
 84
 85	fund(0)
 86	testing.SetRealm(testing.NewUserRealm(alice))
 87	uassert.AbortsContains(t, cur, "reason is empty", func() {
 88		Vouch(cross(cur), bob, "   ")
 89	})
 90
 91	fund(0)
 92	testing.SetRealm(testing.NewUserRealm(alice))
 93	uassert.AbortsContains(t, cur, "reason is empty", func() {
 94		Vouch(cross(cur), bob, "one line\nonly")
 95	})
 96
 97	fund(0)
 98	testing.SetRealm(testing.NewUserRealm(alice))
 99	uassert.AbortsContains(t, cur, "no such vouch", func() {
100		Revoke(cross(cur), bob)
101	})
102
103	fund(0)
104	testing.SetRealm(testing.NewUserRealm(alice))
105	uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
106		Withdraw(cross(cur))
107	})
108
109	uassert.Equal(t, 0, Count())
110}
111
112// The bond follows the vouch in both directions, and the coins only move on
113// Withdraw. Balances are asserted here because the bank does not carry over
114// between test functions.
115func TestTheBondIsLockedUntilTheVouchIsRevoked(cur realm, t *testing.T) {
116	reset()
117	before := balanceOf(bob)
118
119	fund(1_000_000)
120	testing.SetRealm(testing.NewUserRealm(bob))
121	Vouch(cross(cur), alice, "1 GNOT says this is a real person")
122	uassert.Equal(t, int64(1_000_000), BondedFor(alice))
123	uassert.Equal(t, int64(1_000_000), TotalBonded())
124	uassert.Equal(t, int64(0), Owed(bob), "nothing is owed while the vouch stands")
125
126	// Vouching again adds to the bond rather than replacing it.
127	fund(500_000)
128	testing.SetRealm(testing.NewUserRealm(bob))
129	Vouch(cross(cur), alice, "doubling down")
130	uassert.Equal(t, int64(1_500_000), BondedFor(alice))
131	uassert.Equal(t, int64(1_500_000), BondFrom(bob, alice))
132
133	// Revoking moves the bond to the ledger and nothing else.
134	fund(0)
135	testing.SetRealm(testing.NewUserRealm(bob))
136	refund := Revoke(cross(cur), alice)
137	uassert.Equal(t, int64(1_500_000), refund)
138	uassert.Equal(t, int64(0), BondedFor(alice))
139	uassert.Equal(t, int64(1_500_000), Owed(bob))
140	uassert.Equal(t, int64(1_500_000), TotalOwed())
141	uassert.Equal(t, before, balanceOf(bob), "revoking sends nothing")
142
143	// Withdrawing is what moves the coins, once.
144	fund(0)
145	testing.SetRealm(testing.NewUserRealm(bob))
146	got := Withdraw(cross(cur))
147	uassert.Equal(t, int64(1_500_000), got)
148	uassert.Equal(t, before+1_500_000, balanceOf(bob), "the bond came back")
149	uassert.Equal(t, int64(0), Owed(bob))
150	uassert.Equal(t, int64(0), TotalOwed())
151
152	fund(0)
153	testing.SetRealm(testing.NewUserRealm(bob))
154	uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
155		Withdraw(cross(cur))
156	})
157	uassert.Equal(t, before+1_500_000, balanceOf(bob), "and only once")
158}
159
160// A bond belongs to whoever posted it, not to whoever it was posted on.
161func TestOnlyTheVoucherGetsTheBondBack(cur realm, t *testing.T) {
162	reset()
163
164	fund(700_000)
165	testing.SetRealm(testing.NewUserRealm(bob))
166	Vouch(cross(cur), alice, "my money, my claim")
167
168	fund(0)
169	testing.SetRealm(testing.NewUserRealm(alice))
170	uassert.AbortsContains(t, cur, "no such vouch", func() {
171		Revoke(cross(cur), bob)
172	})
173
174	fund(0)
175	testing.SetRealm(testing.NewUserRealm(alice))
176	uassert.AbortsContains(t, cur, "nothing to withdraw", func() {
177		Withdraw(cross(cur))
178	})
179	uassert.Equal(t, int64(700_000), BondedFor(alice), "the bond is untouched")
180}
181
182func TestIsTrustedIsTheGate(cur realm, t *testing.T) {
183	reset()
184
185	uassert.False(t, IsTrusted(alice, 1), "nobody starts trusted")
186	uassert.False(t, IsTrusted(alice, 0), "a zero min does not open the gate for nobody")
187
188	fund(0)
189	testing.SetRealm(testing.NewUserRealm(bob))
190	Vouch(cross(cur), alice, "one")
191	uassert.True(t, IsTrusted(alice, 1))
192	uassert.False(t, IsTrusted(alice, 2))
193
194	fund(0)
195	testing.SetRealm(testing.NewUserRealm(carol))
196	Vouch(cross(cur), alice, "two")
197	uassert.True(t, IsTrusted(alice, 2))
198	uassert.False(t, IsTrusted(alice, 3))
199
200	// The cheapest sybil shape, and the reason a real gate asks for two.
201	fund(0)
202	testing.SetRealm(testing.NewUserRealm(alice))
203	Vouch(cross(cur), bob, "back at you")
204	uassert.True(t, Mutual(alice, bob))
205	uassert.True(t, IsTrusted(bob, 1), "a mutual pair passes a gate set at one")
206	uassert.False(t, IsTrusted(bob, 2))
207}
208
209func TestTheTwoDirections(cur realm, t *testing.T) {
210	reset()
211
212	fund(0)
213	testing.SetRealm(testing.NewUserRealm(alice))
214	Vouch(cross(cur), bob, "a to b")
215	fund(0)
216	testing.SetRealm(testing.NewUserRealm(alice))
217	Vouch(cross(cur), carol, "a to c")
218	fund(0)
219	testing.SetRealm(testing.NewUserRealm(bob))
220	Vouch(cross(cur), carol, "b to c")
221
222	uassert.Equal(t, 2, len(VouchesOf(alice)))
223	uassert.Equal(t, 0, len(VouchedBy(alice)))
224	uassert.Equal(t, 2, len(VouchedBy(carol)))
225	uassert.Equal(t, 0, len(VouchesOf(carol)))
226	uassert.Equal(t, "b to c", ReasonFrom(bob, carol))
227	uassert.Equal(t, "", ReasonFrom(carol, bob))
228	uassert.Equal(t, int64(0), BondFrom(alice, bob), "an unbonded vouch is worth nothing")
229}
230
231// Badge is a borrowed read: a host realm calls it with no realm token and
232// gets a line pointing back at this realm's page.
233func TestBadge(cur realm, t *testing.T) {
234	reset()
235	uassert.True(t, strings.Contains(Badge(alice), "not vouched for"))
236
237	fund(2_000_000)
238	testing.SetRealm(testing.NewUserRealm(bob))
239	Vouch(cross(cur), alice, "one")
240
241	got := Badge(alice)
242	uassert.True(t, strings.Contains(got, "1 voucher]"), got)
243	uassert.True(t, strings.Contains(got, "2 GNOT bonded"), got)
244	uassert.True(t, strings.Contains(got, "/r/moul/x/social/vouch/v0:addr/"+alice.String()), got)
245}
246
247// TestRender uses uassert rather than an Example because every view contains
248// consecutive blank lines, which gno collapses inside an // Output: block.
249func TestRender(cur realm, t *testing.T) {
250	reset()
251
252	fund(3_000_000)
253	testing.SetRealm(testing.NewUserRealm(bob))
254	Vouch(cross(cur), alice, "a reason with [a link](x) and a | pipe")
255	fund(0)
256	testing.SetRealm(testing.NewUserRealm(carol))
257	Vouch(cross(cur), alice, "second voucher")
258
259	index := Render("")
260	uassert.True(t, strings.Contains(index, "| 2 | 1 | 3 GNOT | 0 GNOT |"),
261		"vouches, people, bonded, owed: "+index)
262	uassert.True(t, strings.Contains(index, `import "`+realmPath+`"`), "the index is the gate doc")
263	uassert.True(t, strings.Contains(index, "func=Vouch"), "the index offers the transaction")
264	uassert.True(t, strings.Contains(index, "\U0001F947"), "the board has a podium: "+index)
265
266	page := Render("addr/" + alice.String())
267	uassert.True(t, strings.Contains(page, alice.String()), "the page names the address")
268	uassert.True(t, strings.Contains(page, "a reason with \\[a link\\]\\(x\\) and a \\| pipe"),
269		"the reason is escaped where it is shown: "+page)
270	uassert.True(t, strings.Contains(page, "func=Revoke"))
271
272	// The other direction of the same edge, from the voucher's page.
273	vouchers := Render("addr/" + bob.String())
274	uassert.True(t, strings.Contains(vouchers, "This address vouches for nobody.") == false,
275		"bob vouches for alice: "+vouchers)
276	uassert.True(t, strings.Contains(vouchers, "Nobody vouches for this address."))
277
278	uassert.Equal(t, "# Vouch\nNot an address: nope", Render("addr/nope"))
279	uassert.True(t, strings.HasPrefix(Render("elsewhere"), "# Not found"))
280
281	// An empty graph says so rather than rendering an empty table.
282	reset()
283	uassert.True(t, strings.Contains(Render(""), "Nobody has been vouched for yet."))
284}
285
286func TestRenderOfAnUnknownAddress(cur realm, t *testing.T) {
287	reset()
288	page := Render("addr/" + carol.String())
289	uassert.True(t, strings.Contains(page, "Nobody vouches for this address."), page)
290	uassert.True(t, strings.Contains(page, "This address vouches for nobody."), page)
291	uassert.True(t, strings.Contains(page, "| 0 | 0 GNOT | no | 0 GNOT |"), page)
292}