coinflipduel_test.gno
2.96 Kb · 121 lines
1package coinflipduel
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 flipNonce = 0
15 totalDone = 0
16 var zero address
17 champion = zero
18 championWins = 0
19}
20
21func TestParseCall(t *testing.T) {
22 for _, s := range []string{"heads", "Heads", " h ", "tails", "T"} {
23 if _, ok := parseCall(s); !ok {
24 t.Fatalf("parseCall(%q) should be valid", s)
25 }
26 }
27 if _, ok := parseCall("sideways"); ok {
28 t.Fatal(`parseCall("sideways") should be invalid`)
29 }
30}
31
32// Crossing: Challenge and Accept mutate realm state, so the test needs `cur realm`.
33func TestChallengeAndAccept(cur realm, t *testing.T) {
34 resetState()
35 alice := testutils.TestAddress("alice")
36 bob := testutils.TestAddress("bob")
37
38 testing.SetRealm(testing.NewUserRealm(alice))
39 Challenge(cross(cur), bob.String(), "heads")
40
41 if !duels.Has("1") {
42 t.Fatal("expected duel #1 to exist")
43 }
44
45 testing.SetRealm(testing.NewUserRealm(bob))
46 Accept(cross(cur), "1")
47
48 v := duels.Get("1")
49 d := v.(*duel)
50 if d.Status != statusResolved {
51 t.Fatalf("Status = %v, want resolved", d.Status)
52 }
53 if d.Result != "heads" && d.Result != "tails" {
54 t.Fatalf("Result = %q, want heads or tails", d.Result)
55 }
56 if totalDone != 1 {
57 t.Fatalf("totalDone = %d, want 1", totalDone)
58 }
59
60 winnerStats := getOrCreate(d.Winner)
61 if winnerStats.Wins != 1 {
62 t.Fatalf("winner Wins = %d, want 1", winnerStats.Wins)
63 }
64 if !champion.IsValid() || champion != d.Winner {
65 t.Fatal("champion should be the duel winner")
66 }
67}
68
69func TestChallengeSelfRejected(cur realm, t *testing.T) {
70 resetState()
71 alice := testutils.TestAddress("alice")
72 testing.SetRealm(testing.NewUserRealm(alice))
73
74 // Challenge panics inside a cross-realm call; a plain defer/recover in
75 // this test does not catch a cross-boundary panic, so use revive().
76 rec := revive(func() { Challenge(cross(cur), alice.String(), "heads") })
77 if rec == nil {
78 t.Fatal("expected panic when challenging yourself")
79 }
80}
81
82func TestOnlyOpponentCanAccept(cur realm, t *testing.T) {
83 resetState()
84 alice := testutils.TestAddress("alice")
85 bob := testutils.TestAddress("bob")
86 mallory := testutils.TestAddress("mallory")
87
88 testing.SetRealm(testing.NewUserRealm(alice))
89 Challenge(cross(cur), bob.String(), "tails")
90
91 testing.SetRealm(testing.NewUserRealm(mallory))
92 rec := revive(func() { Accept(cross(cur), "1") })
93 if rec == nil {
94 t.Fatal("expected panic when a non-opponent accepts")
95 }
96}
97
98func TestCancel(cur realm, t *testing.T) {
99 resetState()
100 alice := testutils.TestAddress("alice")
101 bob := testutils.TestAddress("bob")
102
103 testing.SetRealm(testing.NewUserRealm(alice))
104 Challenge(cross(cur), bob.String(), "heads")
105 Cancel(cross(cur), "1")
106
107 v := duels.Get("1")
108 d := v.(*duel)
109 if d.Status != statusCancelled {
110 t.Fatalf("Status = %v, want cancelled", d.Status)
111 }
112}
113
114// Non-crossing: Render is a plain read.
115func TestRenderHome(t *testing.T) {
116 resetState()
117 out := Render("")
118 if out == "" {
119 t.Fatal(`Render("") should not be empty`)
120 }
121}