qvote_test.gno
4.22 Kb · 150 lines
1package qvote
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/nt/avl/v0"
8 "gno.land/p/nt/testutils/v0"
9)
10
11func resetState() {
12 polls = avl.Tree{}
13 voters = avl.Tree{}
14 nextID = 0
15}
16
17func TestSquare(t *testing.T) {
18 cases := []struct {
19 n, want int64
20 }{
21 {0, 0}, {1, 1}, {2, 4}, {3, 9}, {10, 100},
22 }
23 for _, c := range cases {
24 if got := square(c.n); got != c.want {
25 t.Fatalf("square(%d) = %d, want %d", c.n, got, c.want)
26 }
27 }
28}
29
30// Crossing: CreatePoll mutates realm state, so the test needs `cur realm`.
31func TestCreatePollValidatesOptions(cur realm, t *testing.T) {
32 resetState()
33 alice := testutils.TestAddress("alice")
34 testing.SetRealm(testing.NewUserRealm(alice))
35
36 id := CreatePoll(cross(cur), "best color?", "red, green , blue")
37 if id != "poll #1 created with 3 options" {
38 t.Fatalf("unexpected message: %q", id)
39 }
40 p := getPoll("1")
41 if len(p.Options) != 3 || p.Options[0] != "red" || p.Options[2] != "blue" {
42 t.Fatalf("options not parsed/trimmed correctly: %#v", p.Options)
43 }
44
45 if rec := revive(func() { CreatePoll(cross(cur), "one option?", "only-one") }); rec == nil {
46 t.Fatal("expected panic for too few options")
47 }
48 if rec := revive(func() { CreatePoll(cross(cur), " ", "a,b") }); rec == nil {
49 t.Fatal("expected panic for empty question")
50 }
51}
52
53func TestVoteQuadraticCostAndBudget(cur realm, t *testing.T) {
54 resetState()
55 alice := testutils.TestAddress("alice")
56 testing.SetRealm(testing.NewUserRealm(alice))
57 CreatePoll(cross(cur), "pizza topping?", "pineapple,mushroom,pepperoni")
58
59 // 1st vote costs 1, 2nd costs 3 more (4 total), 3rd costs 5 more (9 total).
60 Vote(cross(cur), "1", 0, 1)
61 vs := getOrCreateVoter("1", alice, 3)
62 if vs.CreditsUsed != 1 {
63 t.Fatalf("credits used = %d, want 1", vs.CreditsUsed)
64 }
65 Vote(cross(cur), "1", 0, 2)
66 if vs.CreditsUsed != 9 || vs.Votes[0] != 3 {
67 t.Fatalf("after buying to 3 votes: credits=%d votes=%d, want 9/3", vs.CreditsUsed, vs.Votes[0])
68 }
69
70 // Spending the rest of the 100-credit budget on another option should work
71 // right up to the edge, then fail past it.
72 Vote(cross(cur), "1", 1, 9) // 81 credits, total 90
73 if vs.CreditsUsed != 90 {
74 t.Fatalf("credits used = %d, want 90", vs.CreditsUsed)
75 }
76 if rec := revive(func() { Vote(cross(cur), "1", 2, 4) }); rec == nil { // would cost 16 more, total 106 > 100
77 t.Fatal("expected panic for exceeding voice-credit budget")
78 }
79
80 p := getPoll("1")
81 if p.Tally[0] != 3 || p.Tally[1] != 9 {
82 t.Fatalf("unexpected tally: %#v", p.Tally)
83 }
84}
85
86func TestVoteSellBackRefundsCredits(cur realm, t *testing.T) {
87 resetState()
88 alice := testutils.TestAddress("alice")
89 testing.SetRealm(testing.NewUserRealm(alice))
90 CreatePoll(cross(cur), "best sea?", "north,baltic")
91
92 Vote(cross(cur), "1", 0, 4) // 16 credits, 4 votes
93 Vote(cross(cur), "1", 0, -1) // back to 3 votes: cost delta = 9-16 = -7
94
95 vs := getOrCreateVoter("1", alice, 2)
96 if vs.Votes[0] != 3 {
97 t.Fatalf("votes = %d, want 3", vs.Votes[0])
98 }
99 if vs.CreditsUsed != 9 {
100 t.Fatalf("credits used = %d, want 9", vs.CreditsUsed)
101 }
102 if getPoll("1").Tally[0] != 3 {
103 t.Fatalf("tally = %d, want 3", getPoll("1").Tally[0])
104 }
105
106 if rec := revive(func() { Vote(cross(cur), "1", 0, -10) }); rec == nil {
107 t.Fatal("expected panic for selling more votes than held")
108 }
109}
110
111func TestClosePollOnlyCreator(cur realm, t *testing.T) {
112 resetState()
113 alice := testutils.TestAddress("alice")
114 bob := testutils.TestAddress("bob")
115
116 testing.SetRealm(testing.NewUserRealm(alice))
117 CreatePoll(cross(cur), "close me?", "yes,no")
118
119 testing.SetRealm(testing.NewUserRealm(bob))
120 if rec := revive(func() { ClosePoll(cross(cur), "1") }); rec == nil {
121 t.Fatal("expected panic when a non-creator closes the poll")
122 }
123
124 testing.SetRealm(testing.NewUserRealm(alice))
125 ClosePoll(cross(cur), "1")
126 if !getPoll("1").Closed {
127 t.Fatal("poll should be closed")
128 }
129
130 if rec := revive(func() { Vote(cross(cur), "1", 0, 1) }); rec == nil {
131 t.Fatal("expected panic when voting on a closed poll")
132 }
133}
134
135func TestRenderNoPolls(t *testing.T) {
136 resetState()
137 out := Render("")
138 if !strings.Contains(out, "no polls yet") {
139 t.Fatalf("expected empty-state message, got: %s", out)
140 }
141}
142
143func TestRenderPollNotFound(t *testing.T) {
144 resetState()
145 out := Render("42")
146 if !strings.Contains(out, "No such poll") {
147 t.Fatalf("expected not-found message, got: %s", out)
148 }
149}
150