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

idle_test.gno

9.25 Kb · 286 lines
  1package idle
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/testutils/v0"
  9	"gno.land/p/nt/uassert/v0"
 10)
 11
 12// reset clears the realm between tests. Package state carries over from one
 13// test to the next even though the block height does not, so every test that
 14// looks at the board has to start from a known tree.
 15func reset() { mines = avl.Tree{} }
 16
 17// t0 is an arbitrary unix second to hang the tests off. Nothing asserts an
 18// absolute time, only differences from this one.
 19//
 20// It is only legal for the non-crossing helpers, which take the time as an
 21// argument. Anything reaching now() (Render and the read helpers) reads the
 22// BLOCK timestamp, which in a test is far below t0, so a mine anchored here
 23// would make the chain's clock look like it ran backwards. Those tests anchor
 24// at now() instead.
 25const t0 = int64(1700000000)
 26
 27func TestStartOpensALevelOneMine(t *testing.T) {
 28	reset()
 29	alice := testutils.TestAddress("alice")
 30
 31	out := start(alice, t0)
 32	uassert.True(t, strings.Contains(out, "level 1"), "expected the opening message")
 33
 34	m, ok := get(alice)
 35	uassert.True(t, ok, "expected a mine")
 36	uassert.Equal(t, int64(1), m.level)
 37	uassert.Equal(t, int64(0), m.prestige)
 38	uassert.Equal(t, t0, m.anchor)
 39	uassert.Equal(t, int64(0), m.pending)
 40}
 41
 42func TestStartTwicePanics(cur realm, t *testing.T) {
 43	reset()
 44	alice := testutils.TestAddress("alice")
 45	start(alice, t0)
 46	uassert.PanicsContains(t, cur, "already have a mine", func() { start(alice, t0) })
 47}
 48
 49func TestActingWithoutAMinePanics(cur realm, t *testing.T) {
 50	reset()
 51	bob := testutils.TestAddress("bob")
 52	uassert.PanicsContains(t, cur, "no mine yet", func() { claim(bob, t0) })
 53	uassert.PanicsContains(t, cur, "no mine yet", func() { upgrade(bob, t0) })
 54	uassert.PanicsContains(t, cur, "no mine yet", func() { prestige(bob, t0) })
 55}
 56
 57func TestAccrualAndClaim(t *testing.T) {
 58	reset()
 59	alice := testutils.TestAddress("alice")
 60	start(alice, t0)
 61
 62	m, _ := get(alice)
 63	uassert.Equal(t, int64(0), m.view(t0+Period-1), "nothing before the first whole period")
 64	uassert.Equal(t, int64(1), m.view(t0+Period), "one ore after one period")
 65	uassert.Equal(t, int64(30), m.view(t0+30*Period))
 66
 67	out := claim(alice, t0+30*Period)
 68	uassert.True(t, strings.Contains(out, "Claimed 30 ore"), "got: "+out)
 69	uassert.Equal(t, int64(30), m.banked)
 70	uassert.Equal(t, int64(30), m.lifetime)
 71	uassert.Equal(t, int64(0), m.pending)
 72	uassert.Equal(t, int64(1), m.claims)
 73}
 74
 75func TestClaimingNothingPanics(cur realm, t *testing.T) {
 76	reset()
 77	alice := testutils.TestAddress("alice")
 78	start(alice, t0)
 79	uassert.PanicsContains(t, cur, "nothing to claim", func() { claim(alice, t0+Period-1) })
 80}
 81
 82// TestClaimCadenceDoesNotMatter is the game-level statement of the invariant
 83// the accrual package exists for: two players who dig for the same span end up
 84// with the same ore whatever their claiming habits. A realm that re-anchored
 85// to now would pay the busy player less, and a realm that decayed would pay
 86// them more.
 87//
 88// "Whatever their habits" has exactly one limit, and it is the design rather
 89// than the arithmetic: a cadence longer than the mine takes to fill spills,
 90// and is meant to. Every cadence here stays under that, and
 91// TestTheMineSpills covers the other side.
 92func TestClaimCadenceDoesNotMatter(t *testing.T) {
 93	const span = 600 * Period
 94	fill := CapPeriods * Period
 95
 96	for _, cadence := range []int64{Period, 2*Period + 1, 7 * Period, 599, 6001, fill} {
 97		if cadence > fill {
 98			t.Fatalf("cadence %d would spill, which is not what this test is about", cadence)
 99		}
100		reset()
101		alice := testutils.TestAddress("alice")
102		start(alice, t0)
103		m, _ := get(alice)
104
105		for at := t0 + cadence; at <= t0+span; at += cadence {
106			if m.view(at) > 0 {
107				claim(alice, at)
108			}
109		}
110		if m.view(t0+span) > 0 {
111			claim(alice, t0+span)
112		}
113		uassert.Equal(t, int64(600), m.lifetime, "cadence changed the payout")
114	}
115}
116
117// TestTheMineSpills pins the ceiling: production past the cap is lost, and
118// coming back late is what costs you, which is decision 1 of the design.
119func TestTheMineSpills(t *testing.T) {
120	reset()
121	alice := testutils.TestAddress("alice")
122	start(alice, t0)
123	m, _ := get(alice)
124
125	uassert.Equal(t, CapPeriods, m.view(t0+CapPeriods*Period), "full exactly at the cap")
126	uassert.Equal(t, CapPeriods, m.view(t0+10*CapPeriods*Period), "and no more, ever")
127
128	claim(alice, t0+10*CapPeriods*Period)
129	uassert.Equal(t, CapPeriods, m.lifetime, "ten cap-fulls of absence paid one")
130}
131
132func TestFullAtIsTheOnlyDeadline(t *testing.T) {
133	reset()
134	alice := testutils.TestAddress("alice")
135	start(alice, t0)
136	m, _ := get(alice)
137
138	uassert.Equal(t, t0+CapPeriods*Period, m.fullAt(t0), "an empty mine fills in CapPeriods")
139
140	half := t0 + (CapPeriods/2)*Period
141	uassert.Equal(t, half+(CapPeriods/2)*Period, m.fullAt(half), "half full, half the time left")
142
143	late := t0 + 99*CapPeriods*Period
144	uassert.Equal(t, late, m.fullAt(late), "already full reads as now")
145}
146
147func TestUpgrade(t *testing.T) {
148	reset()
149	alice := testutils.TestAddress("alice")
150	start(alice, t0)
151	m, _ := get(alice)
152
153	uassert.Equal(t, UpgradeBase, upgradeCost(1))
154	uassert.Equal(t, 4*UpgradeBase, upgradeCost(2))
155
156	claim(alice, t0+UpgradeBase*Period)
157	out := upgrade(alice, t0+UpgradeBase*Period)
158	uassert.True(t, strings.Contains(out, "Level 2"), "got: "+out)
159	uassert.Equal(t, int64(2), m.level)
160	uassert.Equal(t, int64(0), m.banked, "the upgrade spent the bank")
161
162	// The rate and the ceiling both doubled, so the fill time did not move.
163	uassert.Equal(t, int64(2), rate(m.level, m.prestige).Amount)
164	uassert.Equal(t, 2*CapPeriods, rate(m.level, m.prestige).Cap)
165}
166
167func TestUpgradeTooPoorPanics(cur realm, t *testing.T) {
168	reset()
169	alice := testutils.TestAddress("alice")
170	start(alice, t0)
171	uassert.PanicsContains(t, cur, "upgrade costs 10 ore, you have 0",
172		func() { upgrade(alice, t0) })
173}
174
175func TestPrestigeIsLockedUntilLevelTen(cur realm, t *testing.T) {
176	reset()
177	alice := testutils.TestAddress("alice")
178	start(alice, t0)
179	uassert.PanicsContains(t, cur, "prestige unlocks at level 10",
180		func() { prestige(alice, t0) })
181}
182
183func TestPrestigeResetsButKeepsLifetime(t *testing.T) {
184	reset()
185	alice := testutils.TestAddress("alice")
186	start(alice, t0)
187	m, _ := get(alice)
188
189	// Hand the mine to level 10 rather than grinding it, then bank something
190	// so the reset has ore to burn.
191	m.level = PrestigeAt
192	claim(alice, t0+50*Period)
193	banked, lifetime := m.banked, m.lifetime
194	uassert.True(t, banked > 0, "expected ore to burn")
195
196	out := prestige(alice, t0+50*Period)
197	uassert.True(t, strings.Contains(out, "Prestige 1"), "got: "+out)
198	uassert.Equal(t, int64(1), m.prestige)
199	uassert.Equal(t, int64(1), m.level)
200	uassert.Equal(t, int64(0), m.banked, "prestige burns the bank")
201	uassert.Equal(t, int64(0), m.pending)
202	uassert.Equal(t, lifetime, m.lifetime, "lifetime survives the reset")
203
204	// A level-1 prestige-1 mine digs what a level-2 mine does.
205	uassert.Equal(t, int64(2), rate(m.level, m.prestige).Amount)
206}
207
208func TestReadHelpersAreSafeOnAStranger(t *testing.T) {
209	reset()
210	carol := testutils.TestAddress("carol")
211	uassert.Equal(t, int64(0), Pending(carol))
212	uassert.Equal(t, int64(0), Banked(carol))
213	uassert.Equal(t, int64(0), FullAt(carol))
214	lvl, pre := Level(carol)
215	uassert.Equal(t, int64(0), lvl)
216	uassert.Equal(t, int64(0), pre)
217}
218
219func TestSpan(t *testing.T) {
220	cases := []struct {
221		secs int64
222		want string
223	}{
224		{0, "0s"},
225		{-5, "0s"},
226		{1, "1s"},
227		{59, "59s"},
228		{60, "1m"},
229		{61, "1m 1s"},
230		{3600, "1h"},
231		{3661, "1h 1m"},
232		{86400, "1d"},
233		{90061, "1d 1h"},
234		{7200, "2h"},
235	}
236	for _, tc := range cases {
237		uassert.Equal(t, tc.want, span(tc.secs), "span("+tc.want+")")
238	}
239}
240
241func TestRenderBoard(t *testing.T) {
242	reset()
243	uassert.True(t, strings.Contains(board(t0), "Nobody is digging yet"),
244		"expected the empty placeholder")
245
246	alice := testutils.TestAddress("alice")
247	start(alice, t0)
248	out := board(t0 + 30*Period)
249	uassert.True(t, strings.Contains(out, "The Mine"), "expected the title")
250	uassert.True(t, strings.Contains(out, "30"), "expected the live pending figure")
251	uassert.True(t, strings.Contains(out, "to spill"), "expected the spill countdown")
252}
253
254func TestRenderCard(t *testing.T) {
255	reset()
256	alice := testutils.TestAddress("alice")
257	start(alice, t0)
258
259	out := card(alice, t0+30*Period)
260	uassert.True(t, strings.Contains(out, "Level **1**"), "got: "+out)
261	uassert.True(t, strings.Contains(out, "30 / 120"), "expected stock over cap")
262	uassert.True(t, strings.Contains(out, "1h 30m"), "expected the spill countdown")
263	uassert.True(t, strings.Contains(out, "10 short"), "expected the shortfall")
264	uassert.True(t, strings.Contains(out, "9 to go"), "expected the prestige distance")
265
266	full := card(alice, t0+999*Period)
267	uassert.True(t, strings.Contains(full, "already full"), "expected the waste warning")
268
269	bob := testutils.TestAddress("bob")
270	uassert.True(t, strings.Contains(card(bob, t0), "No mine at"), "expected the stranger card")
271}
272
273// TestRenderIsLive is the property the whole design is for: the page changes
274// with nothing but the passage of time, so gnoweb serves a live dashboard and
275// no transaction was needed to move it.
276func TestRenderIsLive(t *testing.T) {
277	reset()
278	alice := testutils.TestAddress("alice")
279	start(alice, t0)
280
281	early := board(t0 + 10*Period)
282	later := board(t0 + 20*Period)
283	uassert.NotEqual(t, early, later, "the board did not move with time")
284	uassert.Equal(t, int64(0), Pending(testutils.TestAddress("nobody")),
285		"and reading it wrote nothing")
286}