tipjar_test.gno
3.59 Kb · 137 lines
1package tipjar
2
3import (
4 "strings"
5 "testing"
6
7 "chain"
8 "chain/banker"
9
10 "gno.land/p/nt/avl/v0"
11 "gno.land/p/nt/testutils/v0"
12)
13
14var realmAddr = chain.PackageAddress("gno.land/r/moul/x/daily/tipjar/v1")
15
16// reset clears package globals between tests, pinning owner to a known test
17// address rather than whatever init() captured from the test harness.
18func reset() {
19 owner = testutils.TestAddress("owner")
20 tippers = avl.Tree{}
21 recent = nil
22 totalReceived = 0
23 withdrawn = 0
24 tipCount = 0
25}
26
27// fund simulates a tipper sending `amount` ugnot alongside the tx: the
28// runtime deposits it at the realm's address first, then unsafe.OriginSend
29// reports it to the crossing function. SetRealm is call-frame scoped (see
30// build.md), so the caller must still SetRealm directly before crossing.
31func fund(amount int64) {
32 testing.IssueCoins(realmAddr, chain.Coins{{denom, amount}})
33 testing.SetOriginSend(chain.Coins{{denom, amount}})
34}
35
36func TestTipCreditsAndTracks(cur realm, t *testing.T) {
37 reset()
38 alice := testutils.TestAddress("alice")
39
40 fund(500)
41 testing.SetRealm(testing.NewUserRealm(alice))
42 Tip(cross(cur), "gm")
43
44 if got := Balance(); got != 500 {
45 t.Fatalf("Balance() = %d, want 500", got)
46 }
47 if tipCount != 1 {
48 t.Fatalf("tipCount = %d, want 1", tipCount)
49 }
50 ts := get(alice)
51 if ts.total != 500 || ts.count != 1 || ts.lastMessage != "gm" {
52 t.Fatalf("unexpected tipper stats: %+v", ts)
53 }
54
55 fund(250)
56 testing.SetRealm(testing.NewUserRealm(alice))
57 Tip(cross(cur), "again")
58 if got := get(alice).total; got != 750 {
59 t.Fatalf("cumulative total = %d, want 750", got)
60 }
61 if got := Balance(); got != 750 {
62 t.Fatalf("Balance() after second tip = %d, want 750", got)
63 }
64}
65
66func TestTipRejectsZeroAmount(cur realm, t *testing.T) {
67 reset()
68 alice := testutils.TestAddress("alice")
69 testing.SetRealm(testing.NewUserRealm(alice))
70
71 rec := revive(func() { Tip(cross(cur), "freeloading") })
72 if rec == nil {
73 t.Fatal("expected panic when tipping with no ugnot sent")
74 }
75}
76
77func TestTipRejectsNonUserCall(cur realm, t *testing.T) {
78 reset()
79 testing.SetRealm(testing.NewCodeRealm("gno.land/r/g1attacker/relay"))
80
81 rec := revive(func() { Tip(cross(cur), "sneaky") })
82 if rec == nil {
83 t.Fatal("expected panic when the caller isn't an EOA via MsgCall")
84 }
85}
86
87func TestWithdrawOwnerOnly(cur realm, t *testing.T) {
88 reset()
89 alice := testutils.TestAddress("alice")
90 mallory := testutils.TestAddress("mallory")
91
92 fund(1000)
93 testing.SetRealm(testing.NewUserRealm(alice))
94 Tip(cross(cur), "for the jar")
95
96 testing.SetRealm(testing.NewUserRealm(mallory))
97 rec := revive(func() { Withdraw(cross(cur), 100) })
98 if rec == nil {
99 t.Fatal("expected panic when a non-owner withdraws")
100 }
101
102 testing.SetRealm(testing.NewUserRealm(owner))
103 Withdraw(cross(cur), 400)
104
105 if got := Balance(); got != 600 {
106 t.Fatalf("Balance() after withdraw = %d, want 600", got)
107 }
108 ro := banker.NewReadonlyBanker()
109 if got := ro.GetCoins(owner).AmountOf(denom); got != 400 {
110 t.Fatalf("owner received = %d, want 400", got)
111 }
112
113 rec = revive(func() { Withdraw(cross(cur), 10000) })
114 if rec == nil {
115 t.Fatal("expected panic when withdrawing more than the balance")
116 }
117}
118
119func TestRenderShowsLeaderboardAndFeed(cur realm, t *testing.T) {
120 reset()
121 if !strings.Contains(Render(""), "No tips yet") {
122 t.Fatal("expected empty-jar placeholder")
123 }
124
125 alice := testutils.TestAddress("alice")
126 fund(300)
127 testing.SetRealm(testing.NewUserRealm(alice))
128 Tip(cross(cur), "nice work")
129
130 out := Render("")
131 if !strings.Contains(out, "Leaderboard") {
132 t.Fatal("expected leaderboard section")
133 }
134 if !strings.Contains(out, "nice work") {
135 t.Fatal("expected the tip message in the recent-tips feed")
136 }
137}