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

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}