rpsduel_test.gno
6.08 Kb · 241 lines
1package rpsduel
2
3import (
4 "testing"
5
6 "gno.land/p/nt/avl/v0"
7 "gno.land/p/nt/testutils/v0"
8)
9
10func resetState() {
11 duels = avl.Tree{}
12 players = avl.Tree{}
13 nextID = 0
14 totalResolved = 0
15 var zero address
16 champion = zero
17 championWins = 0
18}
19
20func TestParseMove(t *testing.T) {
21 for _, s := range []string{"rock", "Rock", " r ", "paper", "P", "scissors", "S"} {
22 if _, ok := parseMove(s); !ok {
23 t.Fatalf("parseMove(%q) should be valid", s)
24 }
25 }
26 if _, ok := parseMove("lizard"); ok {
27 t.Fatal(`parseMove("lizard") should be invalid`)
28 }
29}
30
31func TestJudge(t *testing.T) {
32 cases := []struct {
33 p, h Move
34 want int
35 }{
36 {Rock, Rock, 0},
37 {Paper, Paper, 0},
38 {Scissors, Scissors, 0},
39 {Paper, Rock, 1},
40 {Scissors, Paper, 1},
41 {Rock, Scissors, 1},
42 {Rock, Paper, 2},
43 {Paper, Scissors, 2},
44 {Scissors, Rock, 2},
45 }
46 for _, c := range cases {
47 if got := judge(c.p, c.h); got != c.want {
48 t.Fatalf("judge(%v,%v) = %d, want %d", c.p, c.h, got, c.want)
49 }
50 }
51}
52
53func TestComputeCommitDeterministicAndSalted(t *testing.T) {
54 a := ComputeCommit("rock", "salt1")
55 b := ComputeCommit("rock", "salt1")
56 if a != b {
57 t.Fatal("ComputeCommit should be deterministic for the same move+salt")
58 }
59 if a == ComputeCommit("rock", "salt2") {
60 t.Fatal("different salts should produce different commits")
61 }
62 if a == ComputeCommit("paper", "salt1") {
63 t.Fatal("different moves should produce different commits")
64 }
65 if len(a) != 64 {
66 t.Fatalf("commit length = %d, want 64 (hex sha256)", len(a))
67 }
68}
69
70// Crossing: Open/Accept/Reveal mutate realm state, so the test needs `cur realm`.
71func TestFullDuelChallengerWins(cur realm, t *testing.T) {
72 resetState()
73 alice := testutils.TestAddress("alice")
74 bob := testutils.TestAddress("bob")
75
76 commit := ComputeCommit("paper", "s3cr3t")
77
78 testing.SetRealm(testing.NewUserRealm(alice))
79 Open(cross(cur), bob.String(), commit)
80
81 v := duels.Get("1")
82 if v == nil {
83 t.Fatal("expected duel #1 to exist")
84 }
85 d := v.(*duel)
86 if d.Status != statusPending {
87 t.Fatalf("Status = %v, want pending", d.Status)
88 }
89
90 testing.SetRealm(testing.NewUserRealm(bob))
91 Accept(cross(cur), "1", "rock")
92
93 if d.Status != statusAwaitingReveal {
94 t.Fatalf("Status = %v, want awaiting reveal", d.Status)
95 }
96
97 testing.SetRealm(testing.NewUserRealm(alice))
98 Reveal(cross(cur), "1", "paper", "s3cr3t")
99
100 if d.Status != statusResolved {
101 t.Fatalf("Status = %v, want resolved", d.Status)
102 }
103 if d.Result != "challenger" {
104 t.Fatalf("Result = %q, want challenger (paper beats rock)", d.Result)
105 }
106 if d.Winner != alice {
107 t.Fatal("expected alice to win")
108 }
109 if totalResolved != 1 {
110 t.Fatalf("totalResolved = %d, want 1", totalResolved)
111 }
112
113 aliceStats := getOrCreate(alice)
114 if aliceStats.Wins != 1 {
115 t.Fatalf("alice.Wins = %d, want 1", aliceStats.Wins)
116 }
117 bobStats := getOrCreate(bob)
118 if bobStats.Losses != 1 {
119 t.Fatalf("bob.Losses = %d, want 1", bobStats.Losses)
120 }
121 if !champion.IsValid() || champion != alice {
122 t.Fatal("champion should be alice")
123 }
124}
125
126func TestRevealMismatchPanics(cur realm, t *testing.T) {
127 resetState()
128 alice := testutils.TestAddress("alice")
129 bob := testutils.TestAddress("bob")
130
131 testing.SetRealm(testing.NewUserRealm(alice))
132 Open(cross(cur), bob.String(), ComputeCommit("rock", "s3cr3t"))
133
134 testing.SetRealm(testing.NewUserRealm(bob))
135 Accept(cross(cur), "1", "scissors")
136
137 testing.SetRealm(testing.NewUserRealm(alice))
138 rec := revive(func() { Reveal(cross(cur), "1", "paper", "s3cr3t") })
139 if rec == nil {
140 t.Fatal("expected panic when revealed move doesn't match the commitment")
141 }
142}
143
144func TestOnlyOpponentCanAccept(cur realm, t *testing.T) {
145 resetState()
146 alice := testutils.TestAddress("alice")
147 bob := testutils.TestAddress("bob")
148 mallory := testutils.TestAddress("mallory")
149
150 testing.SetRealm(testing.NewUserRealm(alice))
151 Open(cross(cur), bob.String(), ComputeCommit("rock", "s3cr3t"))
152
153 testing.SetRealm(testing.NewUserRealm(mallory))
154 rec := revive(func() { Accept(cross(cur), "1", "paper") })
155 if rec == nil {
156 t.Fatal("expected panic when a non-opponent accepts")
157 }
158}
159
160func TestClaimForfeitBeforeDeadlinePanics(cur realm, t *testing.T) {
161 resetState()
162 alice := testutils.TestAddress("alice")
163 bob := testutils.TestAddress("bob")
164
165 testing.SetRealm(testing.NewUserRealm(alice))
166 Open(cross(cur), bob.String(), ComputeCommit("rock", "s3cr3t"))
167
168 testing.SetRealm(testing.NewUserRealm(bob))
169 Accept(cross(cur), "1", "paper")
170
171 rec := revive(func() { ClaimForfeit(cross(cur), "1") })
172 if rec == nil {
173 t.Fatal("expected panic when claiming a forfeit before the reveal window expires")
174 }
175}
176
177func TestClaimForfeitAfterDeadline(cur realm, t *testing.T) {
178 resetState()
179 alice := testutils.TestAddress("alice")
180 bob := testutils.TestAddress("bob")
181
182 testing.SetRealm(testing.NewUserRealm(alice))
183 Open(cross(cur), bob.String(), ComputeCommit("rock", "s3cr3t"))
184
185 testing.SetRealm(testing.NewUserRealm(bob))
186 Accept(cross(cur), "1", "paper")
187
188 testing.SkipHeights(revealWindow + 1)
189
190 ClaimForfeit(cross(cur), "1")
191
192 v := duels.Get("1")
193 d := v.(*duel)
194 if d.Status != statusForfeited {
195 t.Fatalf("Status = %v, want forfeited", d.Status)
196 }
197 if d.Winner != bob {
198 t.Fatal("expected bob to win by forfeit")
199 }
200
201 aliceStats := getOrCreate(alice)
202 if aliceStats.Forfeits != 1 {
203 t.Fatalf("alice.Forfeits = %d, want 1", aliceStats.Forfeits)
204 }
205}
206
207func TestCancel(cur realm, t *testing.T) {
208 resetState()
209 alice := testutils.TestAddress("alice")
210 bob := testutils.TestAddress("bob")
211
212 testing.SetRealm(testing.NewUserRealm(alice))
213 Open(cross(cur), bob.String(), ComputeCommit("rock", "s3cr3t"))
214 Cancel(cross(cur), "1")
215
216 v := duels.Get("1")
217 d := v.(*duel)
218 if d.Status != statusCancelled {
219 t.Fatalf("Status = %v, want cancelled", d.Status)
220 }
221}
222
223func TestOpenSelfRejected(cur realm, t *testing.T) {
224 resetState()
225 alice := testutils.TestAddress("alice")
226 testing.SetRealm(testing.NewUserRealm(alice))
227
228 rec := revive(func() { Open(cross(cur), alice.String(), ComputeCommit("rock", "s3cr3t")) })
229 if rec == nil {
230 t.Fatal("expected panic when challenging yourself")
231 }
232}
233
234// Non-crossing: Render is a plain read.
235func TestRenderHome(t *testing.T) {
236 resetState()
237 out := Render("")
238 if out == "" {
239 t.Fatal(`Render("") should not be empty`)
240 }
241}