organizer_test.gno
6.79 Kb · 192 lines
1package bolo
2
3import (
4 "chain"
5 "chain/banker"
6 "math"
7 "strings"
8 "testing"
9
10 "gno.land/p/nt/seqid/v0"
11 "gno.land/p/nt/testutils/v0"
12)
13
14const testCID = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"
15
16// storeCampaign stores c as CreateCampaign leaves a campaign, in campaigns and the
17// active indexes, and returns its ID and what removes it again. The organizer's
18// writes are called straight from each test's body, as AssertOriginCall requires
19// (plan §3.3); their refusals are in txtar (organizer.txtar).
20func storeCampaign(c *campaign) (string, func()) {
21 key := c.id.Binary()
22 campaigns.Set(key, c)
23 active.Set(key, true)
24 activeByCategory.Set(c.category+"/"+key, true)
25 return idString(c.id), func() {
26 campaigns.Remove(key)
27 dropActive(c)
28 }
29}
30
31func TestPostBond(cur realm, t *testing.T) {
32 c := testCampaign(9101, modeEscrow, 0)
33 id, unstore := storeCampaign(c)
34 defer unstore()
35 rb := banker.NewReadonlyBanker()
36 bondBefore, realmBefore := rb.GetCoin(bondAddr(c.id), "ugnot"), rb.GetCoin(realmAddr(), "ugnot")
37
38 testing.SetRealm(testing.NewUserRealm(c.organizer))
39 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", 500)))
40 testing.IssueCoins(realmAddr(), chain.NewCoins(chain.NewCoin("ugnot", 500)))
41 PostBond(cross(cur), id)
42 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", 300)))
43 testing.IssueCoins(realmAddr(), chain.NewCoins(chain.NewCoin("ugnot", 300)))
44 PostBond(cross(cur), id)
45
46 // The bond grew by both envelopes, at the bond address; the realm keeps nothing
47 // (INV-3).
48 if c.state.bond != 800 || GetCampaign(id).Bond != 800 {
49 t.Errorf("bond = %d, want 800", c.state.bond)
50 }
51 if got := rb.GetCoin(bondAddr(c.id), "ugnot") - bondBefore; got != 800 {
52 t.Errorf("the bond address got %d, want 800", got)
53 }
54 if got := rb.GetCoin(realmAddr(), "ugnot"); got != realmBefore {
55 t.Errorf("the realm holds %d, it held %d", got, realmBefore)
56 }
57}
58
59func TestCheckBond(t *testing.T) {
60 defer func(p bool) { paused = p }(paused)
61 c := testCampaign(9102, modeEscrow, 0)
62 checkBond(c, 1)
63 c.state.bond = math.MaxInt64 - 5
64 checkBond(c, 5)
65 for name, k := range map[string]struct {
66 status string
67 slashed bool
68 paused bool
69 bond int64
70 amount int64
71 err Error
72 }{
73 "nothing sent": {statusOpen, false, false, 0, 0, ErrBadAmount},
74 "a negative amount": {statusOpen, false, false, 0, -1, ErrBadAmount},
75 "closed": {statusClosed, false, false, 0, 1, ErrCampaignClosed},
76 "settled": {statusSettled, false, false, 0, 1, ErrCampaignSettled},
77 "slashed": {statusOpen, true, false, 0, 1, ErrBondSlashed},
78 "paused": {statusOpen, false, true, 0, 1, ErrPaused},
79 "past int64": {statusOpen, false, false, math.MaxInt64 - 5, 6, ErrBadAmount},
80 "nothing sent, and closed": {statusClosed, false, false, 0, 0, ErrBadAmount},
81 "closed, and slashed": {statusClosed, true, false, 0, 1, ErrCampaignClosed},
82 "slashed, and paused": {statusOpen, true, true, 0, 1, ErrBondSlashed},
83 } {
84 c.state.status, c.state.bondSlashed, c.state.bond = k.status, k.slashed, k.bond
85 paused = k.paused
86 if !panicsWith(func() { checkBond(c, k.amount) }, k.err) {
87 t.Errorf("%s: not %s", name, k.err)
88 }
89 }
90}
91
92func TestAssertOpen(t *testing.T) {
93 c := testCampaign(9103, modeEscrow, 0)
94 assertOpen(c)
95 for status, err := range map[string]Error{statusClosed: ErrCampaignClosed, statusSettled: ErrCampaignSettled} {
96 c.state.status = status
97 if !panicsWith(func() { assertOpen(c) }, err) {
98 t.Errorf("%s: not %s", status, err)
99 }
100 }
101}
102
103func TestPostUpdate(cur realm, t *testing.T) {
104 defer func(p bool) { paused = p }(paused)
105 c := testCampaign(9104, modeEscrow, 0)
106 id, unstore := storeCampaign(c)
107 defer unstore()
108 testing.SkipHeights(10 * heightsPerDay)
109
110 testing.SetRealm(testing.NewUserRealm(c.organizer))
111 if seq := PostUpdate(cross(cur), id, " Day one:\r\nthe pump is ordered. "); seq != "1" {
112 t.Fatalf("first update: seq %q, want \"1\"", seq)
113 }
114 u := c.updates.Get(seqid.ID(1).Binary()).(*update)
115 if u.seq != 1 || u.at != now() || u.body != "Day one:\nthe pump is ordered." {
116 t.Errorf("stored update = %v", *u)
117 }
118 // An update resets the dormancy clock.
119 if s := c.state; s.updateCount != 1 || s.lastUpdateAt != now() || dormantAt(c) != now()+c.params.DormancyPeriod {
120 t.Errorf("after an update: count %d, last %d, dormant at %d", s.updateCount, s.lastUpdateAt, dormantAt(c))
121 }
122
123 // In every status, and while paused.
124 c.state.status = statusSettled
125 paused = true
126 if seq := PostUpdate(cross(cur), id, "The surgery went well."); seq != "2" {
127 t.Errorf("update on a settled campaign, paused: seq %q, want \"2\"", seq)
128 }
129 if c.state.updateCount != 2 || c.updates.Size() != 2 {
130 t.Errorf("%d updates counted, %d stored, want 2", c.state.updateCount, c.updates.Size())
131 }
132}
133
134func TestCheckUpdate(t *testing.T) {
135 if got := checkStory(" News ", updateMin, updateMax, ErrUpdate); got != "News" {
136 t.Errorf("checkStory = %q", got)
137 }
138 for name, body := range map[string]string{
139 "empty": "",
140 "spaces": " \n ",
141 "an accent alone": "́",
142 "one too many": strings.Repeat("a", updateMax+1),
143 "invisible filler": "ㅤ⠀",
144 } {
145 if !panicsWith(func() { checkStory(body, updateMin, updateMax, ErrUpdate) }, ErrUpdate) {
146 t.Errorf("%s: not ErrUpdate", name)
147 }
148 }
149 if !panicsWith(func() { checkStory("", updateMin, updateMax, ErrUpdate) }, ErrMarkup) {
150 t.Errorf("an image in an update: not ErrMarkup")
151 }
152}
153
154func TestHideImage(cur realm, t *testing.T) {
155 // The organizer hides the photo; the CID stays, and hiding again changes nothing.
156 c := testCampaign(9105, modeEscrow, 0)
157 c.text = &campaignText{title: "Clean water", imageCID: testCID, imageAlt: "The well"}
158 id, unstore := storeCampaign(c)
159 defer unstore()
160 testing.SetRealm(testing.NewUserRealm(c.organizer))
161 HideImage(cross(cur), id)
162 if !c.state.imageHidden || c.text.imageCID != testCID || !GetCampaign(id).ImageHidden {
163 t.Errorf("organizer: hidden %t, CID %q", c.state.imageHidden, c.text.imageCID)
164 }
165 HideImage(cross(cur), id)
166 if !c.state.imageHidden {
167 t.Errorf("hiding again showed the photo")
168 }
169
170 // The moderator hides another campaign's photo.
171 d := testCampaign(9106, modeEscrow, 0)
172 d.text = &campaignText{title: "Clean water", imageCID: testCID, imageAlt: "The well"}
173 idD, unstoreD := storeCampaign(d)
174 defer unstoreD()
175 testing.SetRealm(testing.NewUserRealm(admin))
176 HideImage(cross(cur), idD)
177 if !d.state.imageHidden {
178 t.Errorf("moderator: the photo isn't hidden")
179 }
180
181 // A campaign without a photo has nothing to hide.
182 e := testCampaign(9107, modeEscrow, 0)
183 idE, unstoreE := storeCampaign(e)
184 defer unstoreE()
185 testing.SetRealm(testing.NewUserRealm(e.organizer))
186 HideImage(cross(cur), idE)
187 if e.state.imageHidden {
188 t.Errorf("a campaign without a photo got one hidden")
189 }
190}
191
192var passerby = testutils.TestAddress("passerby")