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.

game_daily_test.gno

11.98 Kb · 429 lines
  1package game31
  2
  3import (
  4	"strconv"
  5	"testing"
  6	"time"
  7)
  8
  9// ── DailyCheckin helpers ──────────────────────────────────────────────────────
 10
 11// resetDailyCheckin removes any stored check-in state for the address.
 12func resetDailyCheckin(addr string) {
 13	dailyCheckins.Remove(addr)
 14}
 15
 16// setDailyCheckinRaw directly writes a raw value into dailyCheckins.
 17// Use this to seed "yesterday", "old format", etc. without going through
 18// the DailyCheckin transaction path.
 19func setDailyCheckinRaw(addr, raw string) {
 20	dailyCheckins.Set(addr, raw)
 21}
 22
 23// getDailyCheckinRaw reads the raw stored value (for assertions).
 24func getDailyCheckinRaw(addr string) string {
 25	if !dailyCheckins.Has(addr) {
 26		return ""
 27	}
 28	return dailyCheckins.Get(addr).(string)
 29}
 30
 31// ── parseDailyCheckin tests ───────────────────────────────────────────────────
 32
 33func TestParseDailyCheckinNewFormat(t *testing.T) {
 34	lastDay, streak := parseDailyCheckin("20000:5")
 35	if lastDay != 20000 {
 36		t.Errorf("lastDay: got %d, want 20000", lastDay)
 37	}
 38	if streak != 5 {
 39		t.Errorf("streak: got %d, want 5", streak)
 40	}
 41}
 42
 43func TestParseDailyCheckinLegacyFormat(t *testing.T) {
 44	// Old format: raw unix seconds.  day 19000 = 19000 * 86400 = 1641600000
 45	seconds := int64(19000 * 86400)
 46	raw := strconv.FormatInt(seconds, 10)
 47	lastDay, streak := parseDailyCheckin(raw)
 48	if lastDay != 19000 {
 49		t.Errorf("lastDay: got %d, want 19000", lastDay)
 50	}
 51	if streak != 1 {
 52		t.Errorf("legacy format must give streak=1, got %d", streak)
 53	}
 54}
 55
 56func TestParseDailyCheckinEmpty(t *testing.T) {
 57	// Edge: value "0" (no prior check-in default)
 58	lastDay, streak := parseDailyCheckin("0")
 59	if lastDay != 0 {
 60		t.Errorf("lastDay: got %d, want 0", lastDay)
 61	}
 62	if streak != 1 {
 63		t.Errorf("streak: got %d, want 1", streak)
 64	}
 65}
 66
 67// ── DailyCheckin streak logic tests ──────────────────────────────────────────
 68
 69func TestDailyCheckinFirstCheckin(t *testing.T) {
 70	addr := "g1dailytest001"
 71	resetDailyCheckin(addr)
 72
 73	today := time.Now().Unix() / 86400
 74
 75	// Simulate first check-in: no prior state → DailyCheckin writes today:1
 76	// We call the logic inline (mirrors BuyToolDirect approach) since DailyCheckin
 77	// requires a cur realm that isn't available in unit tests.
 78	// Precondition: nothing stored
 79	if dailyCheckins.Has(addr) {
 80		t.Fatal("precondition: must have no prior check-in")
 81	}
 82
 83	// Perform the streak update logic directly (same as DailyCheckin body)
 84	var lastDay, streak int64
 85	// No entry → both stay 0
 86	_ = lastDay
 87	_ = streak
 88	// today != 0 and 0 != today-1 → streak = 1
 89	newStreak := int64(1)
 90	val := strconv.FormatInt(today, 10) + ":" + strconv.FormatInt(newStreak, 10)
 91	dailyCheckins.Set(addr, val)
 92
 93	// Verify
 94	stored := getDailyCheckinRaw(addr)
 95	gotDay, gotStreak := parseDailyCheckin(stored)
 96	if gotDay != today {
 97		t.Errorf("lastDay: got %d, want %d", gotDay, today)
 98	}
 99	if gotStreak != 1 {
100		t.Errorf("streak after first check-in: got %d, want 1", gotStreak)
101	}
102}
103
104func TestDailyCheckinConsecutiveDays(t *testing.T) {
105	addr := "g1dailytest002"
106	resetDailyCheckin(addr)
107
108	today := time.Now().Unix() / 86400
109	yesterday := today - 1
110
111	// Seed: player checked in yesterday with streak=3
112	setDailyCheckinRaw(addr, strconv.FormatInt(yesterday, 10)+":3")
113
114	// Now simulate today's check-in
115	raw := getDailyCheckinRaw(addr)
116	lastDay, streak := parseDailyCheckin(raw)
117
118	if lastDay == today {
119		t.Fatal("test setup error: lastDay should be yesterday")
120	}
121	var newStreak int64
122	if lastDay == today-1 {
123		newStreak = streak + 1 // should be 4
124	} else {
125		newStreak = 1
126	}
127
128	val := strconv.FormatInt(today, 10) + ":" + strconv.FormatInt(newStreak, 10)
129	dailyCheckins.Set(addr, val)
130
131	gotDay, gotStreak := parseDailyCheckin(getDailyCheckinRaw(addr))
132	if gotDay != today {
133		t.Errorf("lastDay: got %d, want %d", gotDay, today)
134	}
135	if gotStreak != 4 {
136		t.Errorf("streak after consecutive day: got %d, want 4", gotStreak)
137	}
138}
139
140func TestDailyCheckinSkippedDay(t *testing.T) {
141	addr := "g1dailytest003"
142	resetDailyCheckin(addr)
143
144	today := time.Now().Unix() / 86400
145	twoDaysAgo := today - 2
146
147	// Seed: player last checked in 2 days ago with a long streak
148	setDailyCheckinRaw(addr, strconv.FormatInt(twoDaysAgo, 10)+":42")
149
150	raw := getDailyCheckinRaw(addr)
151	lastDay, streak := parseDailyCheckin(raw)
152	_ = streak
153
154	var newStreak int64
155	if lastDay == today {
156		t.Fatal("same-day check detected unexpectedly")
157	} else if lastDay == today-1 {
158		newStreak = streak + 1
159	} else {
160		newStreak = 1 // streak resets
161	}
162
163	val := strconv.FormatInt(today, 10) + ":" + strconv.FormatInt(newStreak, 10)
164	dailyCheckins.Set(addr, val)
165
166	_, gotStreak := parseDailyCheckin(getDailyCheckinRaw(addr))
167	if gotStreak != 1 {
168		t.Errorf("streak after missed day: got %d, want 1", gotStreak)
169	}
170}
171
172func TestDailyCheckinSameDayPanics(t *testing.T) {
173	addr := "g1dailytest004"
174	resetDailyCheckin(addr)
175
176	today := time.Now().Unix() / 86400
177
178	// Seed: already checked in today
179	setDailyCheckinRaw(addr, strconv.FormatInt(today, 10)+":7")
180
181	// The DailyCheckin function should panic with "already checked in today"
182	// when lastDay == today. We replicate the same panic condition here.
183	mustPanic(t, "already checked in today", func() {
184		raw := getDailyCheckinRaw(addr)
185		lastDay, _ := parseDailyCheckin(raw)
186		if lastDay == today {
187			panic("already checked in today")
188		}
189	})
190}
191
192func TestDailyCheckinOldUnixSecondsFormat(t *testing.T) {
193	addr := "g1dailytest005"
194	resetDailyCheckin(addr)
195
196	today := time.Now().Unix() / 86400
197	yesterdaySeconds := (today - 1) * 86400
198
199	// Seed: old format — raw unix seconds for yesterday
200	setDailyCheckinRaw(addr, strconv.FormatInt(yesterdaySeconds, 10))
201
202	// parseDailyCheckin must interpret this as lastDay = yesterdaySeconds/86400 = today-1, streak=1
203	raw := getDailyCheckinRaw(addr)
204	lastDay, streak := parseDailyCheckin(raw)
205
206	if lastDay != today-1 {
207		t.Errorf("old format lastDay: got %d, want %d", lastDay, today-1)
208	}
209	if streak != 1 {
210		t.Errorf("old format streak: got %d, want 1", streak)
211	}
212
213	// Checking in today from old-format yesterday → streak becomes 2
214	var newStreak int64
215	if lastDay == today-1 {
216		newStreak = streak + 1
217	} else {
218		newStreak = 1
219	}
220	if newStreak != 2 {
221		t.Errorf("streak after old-format yesterday: got %d, want 2", newStreak)
222	}
223}
224
225// ── DailyCheckinStatus tests ──────────────────────────────────────────────────
226
227func TestDailyCheckinStatusNeverCheckedIn(t *testing.T) {
228	addr := "g1statustest001"
229	resetDailyCheckin(addr)
230
231	status := DailyCheckinStatus(addr)
232	if !strContains(status, `"ok":true`) {
233		t.Errorf("expected ok:true, got %s", status)
234	}
235	if !strContains(status, `"done":false`) {
236		t.Errorf("expected done:false for no check-in, got %s", status)
237	}
238	if !strContains(status, `"streak":0`) {
239		t.Errorf("expected streak:0 for no check-in, got %s", status)
240	}
241}
242
243func TestDailyCheckinStatusCheckedInToday(t *testing.T) {
244	addr := "g1statustest002"
245	resetDailyCheckin(addr)
246
247	today := time.Now().Unix() / 86400
248	// Simulate checked in today with streak=5
249	setDailyCheckinRaw(addr, strconv.FormatInt(today, 10)+":5")
250
251	status := DailyCheckinStatus(addr)
252	if !strContains(status, `"done":true`) {
253		t.Errorf("expected done:true when checked in today, got %s", status)
254	}
255	if !strContains(status, `"streak":5`) {
256		t.Errorf("expected streak:5, got %s", status)
257	}
258}
259
260func TestDailyCheckinStatusCheckedInYesterday(t *testing.T) {
261	addr := "g1statustest003"
262	resetDailyCheckin(addr)
263
264	today := time.Now().Unix() / 86400
265	yesterday := today - 1
266	// Checked in yesterday — streak is still live (not broken yet)
267	setDailyCheckinRaw(addr, strconv.FormatInt(yesterday, 10)+":12")
268
269	status := DailyCheckinStatus(addr)
270	if !strContains(status, `"done":false`) {
271		t.Errorf("expected done:false (not checked in today), got %s", status)
272	}
273	// Streak is still live (lastDay == today-1)
274	if !strContains(status, `"streak":12`) {
275		t.Errorf("expected streak:12 (live streak), got %s", status)
276	}
277}
278
279func TestDailyCheckinStatusStreakBrokenAfterMissedDay(t *testing.T) {
280	addr := "g1statustest004"
281	resetDailyCheckin(addr)
282
283	today := time.Now().Unix() / 86400
284	twoDaysAgo := today - 2
285	// Last check-in was 2 days ago — streak is broken
286	setDailyCheckinRaw(addr, strconv.FormatInt(twoDaysAgo, 10)+":30")
287
288	status := DailyCheckinStatus(addr)
289	if !strContains(status, `"done":false`) {
290		t.Errorf("expected done:false, got %s", status)
291	}
292	// Streak must report 0 when broken
293	if !strContains(status, `"streak":0`) {
294		t.Errorf("expected streak:0 after missed day, got %s", status)
295	}
296}
297
298func TestDailyCheckinStatusNextMidnight(t *testing.T) {
299	addr := "g1statustest005"
300	resetDailyCheckin(addr)
301
302	today := time.Now().Unix() / 86400
303	setDailyCheckinRaw(addr, strconv.FormatInt(today, 10)+":1")
304
305	status := DailyCheckinStatus(addr)
306	// next field must be (today+1)*86400
307	wantNext := (today + 1) * 86400
308	wantStr := `"next":` + strconv.FormatInt(wantNext, 10)
309	if !strContains(status, wantStr) {
310		t.Errorf("expected %s in status, got %s", wantStr, status)
311	}
312}
313
314func TestDailyCheckinStatusOldFormat(t *testing.T) {
315	addr := "g1statustest006"
316	resetDailyCheckin(addr)
317
318	today := time.Now().Unix() / 86400
319	// Seed yesterday in old unix-seconds format
320	yesterdaySeconds := (today - 1) * 86400
321	setDailyCheckinRaw(addr, strconv.FormatInt(yesterdaySeconds, 10))
322
323	status := DailyCheckinStatus(addr)
324	// Old format is yesterday → streak lives at 1, not yet done today
325	if !strContains(status, `"done":false`) {
326		t.Errorf("expected done:false (old format, yesterday), got %s", status)
327	}
328	if !strContains(status, `"streak":1`) {
329		t.Errorf("expected streak:1 (old format gives streak=1), got %s", status)
330	}
331}
332
333// ── helpers (from the game28 test suite) ──
334func mustPanic(t *testing.T, wantSubstr string, f func()) {
335	t.Helper()
336	var msg string
337	panicked := false
338	func() {
339		defer func() {
340			r := recover()
341			if r != nil {
342				panicked = true
343				switch v := r.(type) {
344				case string:
345					msg = v
346				case error:
347					msg = v.Error()
348				default:
349					msg = "non-string panic"
350				}
351			}
352		}()
353		f()
354	}()
355	if !panicked {
356		t.Errorf("expected panic containing %q, but no panic occurred", wantSubstr)
357		return
358	}
359	if wantSubstr != "" && !strContains(msg, wantSubstr) {
360		t.Errorf("panic message %q does not contain %q", msg, wantSubstr)
361	}
362}
363
364func strContains(s, sub string) bool {
365	if len(sub) == 0 {
366		return true
367	}
368	if len(sub) > len(s) {
369		return false
370	}
371	for i := 0; i <= len(s)-len(sub); i++ {
372		if s[i:i+len(sub)] == sub {
373			return true
374		}
375	}
376	return false
377}
378
379
380func TestRepairCostCeil30Pct(t *testing.T) {
381	type tc struct {
382		toolId   string
383		coinId   string
384		base     uint64
385		expected uint64 // ceil(base × 30%) = (base×30 + 99) / 100
386	}
387	cases := []tc{
388		// wood: seedling:19 → ceil(5.7) = 6
389		{"wood", "seedling", 19, 6},
390		// wood: party:8 → ceil(2.4) = 3
391		{"wood", "party", 8, 3},
392		// iron: gear:42 → ceil(12.6) = 13
393		{"iron", "gear", 42, 13},
394		// iron: magnet:4 → ceil(1.2) = 2
395		{"iron", "magnet", 4, 2},
396		// quasar: lotus:3510 → ceil(1053.0) = 1053
397		{"quasar", "lotus", 3510, 1053},
398		// omega: lotus:26970 → ceil(8091.0) = 8091
399		{"omega", "lotus", 26970, 8091},
400		// stone: cactus:15 → ceil(4.5) = 5
401		{"stone", "cactus", 15, 5},
402	}
403	for _, c := range cases {
404		costs, hasCosts := toolCosts[c.toolId]
405		if !hasCosts {
406			t.Errorf("toolCosts missing for %s", c.toolId)
407			continue
408		}
409		found := false
410		for _, pair := range costs {
411			if pair[0] != c.coinId {
412				continue
413			}
414			found = true
415			base, _ := strconv.ParseUint(pair[1], 10, 64)
416			if base != c.base {
417				t.Errorf("%s[%s]: expected base=%d, got %d", c.toolId, c.coinId, c.base, base)
418			}
419			got := (base*30 + 99) / 100
420			if got != c.expected {
421				t.Errorf("%s[%s]: ceil(30%%)=%d expected, got %d", c.toolId, c.coinId, c.expected, got)
422			}
423			break
424		}
425		if !found {
426			t.Errorf("%s does not contain ingredient %s", c.toolId, c.coinId)
427		}
428	}
429}