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

closestguess_test.gno

4.13 Kb · 151 lines
  1package closestguess
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8)
  9
 10// resetGame re-initializes package globals so each test starts clean; init()
 11// runs only once for the whole test binary.
 12func resetGame(height int64) {
 13	current = newRound(1, height)
 14	entries = nil
 15	leaderboard = nil
 16}
 17
 18func TestTargetFromHeight(t *testing.T) {
 19	tests := []struct {
 20		name   string
 21		height int64
 22	}{
 23		{"zero", 0},
 24		{"one", 1},
 25		{"large", 123456},
 26		{"negative", -42},
 27	}
 28	for _, tt := range tests {
 29		got := targetFromHeight(tt.height, 1)
 30		if got < minTarget || got > maxTarget {
 31			t.Errorf("%s: targetFromHeight(%d,1)=%d, want in %d..%d", tt.name, tt.height, got, minTarget, maxTarget)
 32		}
 33		if again := targetFromHeight(tt.height, 1); again != got {
 34			t.Errorf("%s: non-deterministic: %d != %d", tt.name, again, got)
 35		}
 36	}
 37
 38	// Different round numbers at the same height should (almost always) mix
 39	// to different targets, so consecutive rounds don't repeat.
 40	a := targetFromHeight(100, 1)
 41	b := targetFromHeight(100, 2)
 42	if a == b {
 43		t.Errorf("targetFromHeight(100,1) == targetFromHeight(100,2) == %d, want them to differ", a)
 44	}
 45}
 46
 47func TestDistance(t *testing.T) {
 48	cases := []struct{ a, b, want int }{
 49		{5, 5, 0},
 50		{5, 8, 3},
 51		{8, 5, 3},
 52		{0, 1000, 1000},
 53	}
 54	for _, c := range cases {
 55		if got := distance(c.a, c.b); got != c.want {
 56			t.Errorf("distance(%d,%d) = %d, want %d", c.a, c.b, got, c.want)
 57		}
 58	}
 59}
 60
 61// TestGuessRejectsOutOfRangeAndDuplicate exercises the crossing Guess
 62// function, so it takes cur realm and calls through cross(cur). A panic
 63// raised across the crossing call boundary isn't catchable by a plain
 64// defer/recover; revive() is the boundary-aware way to assert on it.
 65func TestGuessRejectsOutOfRangeAndDuplicate(cur realm, t *testing.T) {
 66	resetGame(10)
 67
 68	alice := testutils.TestAddress("alice")
 69	testing.SetRealm(testing.NewUserRealm(alice))
 70	if r := revive(func() { Guess(cross(cur), 0) }); r == nil {
 71		t.Error("Guess(0) should panic: out of range")
 72	}
 73
 74	testing.SetRealm(testing.NewUserRealm(alice))
 75	msg := Guess(cross(cur), 500)
 76	if !strings.Contains(msg, "recorded") {
 77		t.Errorf("Guess(500) = %q, want it to mention 'recorded'", msg)
 78	}
 79
 80	testing.SetRealm(testing.NewUserRealm(alice))
 81	if r := revive(func() { Guess(cross(cur), 501) }); r == nil {
 82		t.Error("second Guess from the same address should panic")
 83	}
 84}
 85
 86// TestRevealPicksClosestAndStartsNewRound exercises Guess + Reveal together.
 87func TestRevealPicksClosestAndStartsNewRound(cur realm, t *testing.T) {
 88	resetGame(50)
 89	target := current.target
 90	prevRound := current.number
 91
 92	alice := testutils.TestAddress("alice")
 93	bob := testutils.TestAddress("bob")
 94
 95	far := target + 100
 96	if far > maxTarget {
 97		far = target - 100
 98	}
 99	if far < minTarget {
100		far = minTarget
101	}
102	near := target + 1
103	if near > maxTarget {
104		near = target - 1
105	}
106	if near < minTarget {
107		near = minTarget
108	}
109
110	testing.SetRealm(testing.NewUserRealm(alice))
111	Guess(cross(cur), far)
112	testing.SetRealm(testing.NewUserRealm(bob))
113	Guess(cross(cur), near)
114
115	msg := Reveal(cross(cur))
116	if !strings.Contains(msg, "revealed") {
117		t.Errorf("Reveal() = %q, want it to mention 'revealed'", msg)
118	}
119	if len(leaderboard) != 1 {
120		t.Fatalf("leaderboard len = %d, want 1", len(leaderboard))
121	}
122	if leaderboard[0].winner != bob.String() {
123		t.Errorf("winner = %q, want %q (closer guess)", leaderboard[0].winner, bob.String())
124	}
125	if current.number != prevRound+1 {
126		t.Errorf("round number = %d, want %d", current.number, prevRound+1)
127	}
128	if len(entries) != 0 {
129		t.Errorf("entries should reset after Reveal, got %d", len(entries))
130	}
131}
132
133// TestRevealPanicsWithNoGuesses checks the panic path via revive() (see the
134// note on TestGuessRejectsOutOfRangeAndDuplicate).
135func TestRevealPanicsWithNoGuesses(cur realm, t *testing.T) {
136	resetGame(1)
137	if r := revive(func() { Reveal(cross(cur)) }); r == nil {
138		t.Error("Reveal() with no guesses should panic")
139	}
140}
141
142// TestRenderContainsState is a plain read — no cur.
143func TestRenderContainsState(t *testing.T) {
144	resetGame(3)
145	out := Render("")
146	for _, want := range []string{"Closest Guess", "Current round", "Leaderboard"} {
147		if !strings.Contains(out, want) {
148			t.Errorf("Render() missing %q", want)
149		}
150	}
151}