Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}