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

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