clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

tech debt 8

Dami committed 1ddddc6
commit
1ddddc695440a93c00d061471f1ff95e73421c0a
parent
4480c349b2dc20e854c7bdd92ade6f553e837035
author
Dami <dami.etoile@gmail.com>
7 files changed, +146 -35
Mapp/update_test.go13+++++
Mapp/view_test.go1+
Marchitecture_test.go24++++++++++
Mcmd/config_test.go1+
Mstore/stats_test.go72+++++++++++++++++++++++-------
Mstore/store.go15+------
Mstore/store_test.go55+++++++++++++++++++++--
M app/update_test.go +13 -0
@@ -193,6 +193,8 @@ func TestHandleSeedConfirmDoesNotResumeStatusWhenImportFails(t *testing.T) {
193193 InitialState: "{}",194194 SaveState: "{}",195195 Status: store.StatusAbandoned,196+ RunKind: store.RunKindSeeded,197+ SeedText: seed,196198 }197199 if err := s.CreateGame(rec); err != nil {198200 t.Fatal(err)
@@ -317,6 +319,7 @@ func TestHandleDailyPuzzleDoesNotResumeStatusWhenImportFails(t *testing.T) {
317319 continue318320 }319321 seen[name] = true322+ day := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())320323 rec := &store.GameRecord{321324 Name: name,322325 GameType: "NoSuchGameType",
@@ -324,6 +327,8 @@ func TestHandleDailyPuzzleDoesNotResumeStatusWhenImportFails(t *testing.T) {
324327 InitialState: "{}",325328 SaveState: "{}",326329 Status: store.StatusAbandoned,330+ RunKind: store.RunKindDaily,331+ RunDate: &day,327332 }328333 if err := s.CreateGame(rec); err != nil {329334 t.Fatal(err)
@@ -473,6 +478,10 @@ func TestEnterOnSolvedLatestWeeklyCompletesAndQueuesNext(t *testing.T) {
473478 InitialState: "{}",474479 SaveState: "{}",475480 Status: store.StatusInProgress,481+ RunKind: store.RunKindWeekly,482+ WeekYear: year,483+ WeekNumber: weekNumber,484+ WeekIndex: 1,476485 }477486 if err := s.CreateGame(rec); err != nil {478487 t.Fatal(err)
@@ -549,6 +558,10 @@ func TestCurrentWeeklyMenuIndexTracksNextPlayableSlot(t *testing.T) {
549558 InitialState: "{}",550559 SaveState: "{}",551560 Status: store.StatusCompleted,561+ RunKind: store.RunKindWeekly,562+ WeekYear: year,563+ WeekNumber: weekNumber,564+ WeekIndex: index,552565 }553566 if err := s.CreateGame(rec); err != nil {554567 t.Fatal(err)
M app/view_test.go +1 -0
M architecture_test.go +24 -0
M cmd/config_test.go +1 -0
M store/stats_test.go +55 -17
@@ -81,10 +81,14 @@ func TestGetCategoryStats(t *testing.T) {
8181 t.Run("daily games counted", func(t *testing.T) {8282 s := openTestStore(t)8383 games := []*GameRecord{84- {Name: "Daily Feb 16 26 - amber-fox", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},85- {Name: "Daily Feb 15 26 - blue-cat", GameType: "Sudoku", Mode: "Medium", InitialState: "{}", SaveState: "{}", Status: StatusNew},84+ newDailyTestRecord("Daily Feb 16 26 - amber-fox", time.Date(2026, time.February, 16, 12, 0, 0, 0, time.Local)),85+ newDailyTestRecord("Daily Feb 15 26 - blue-cat", time.Date(2026, time.February, 15, 12, 0, 0, 0, time.Local)),8686 {Name: "regular-game", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},8787 }88+ games[0].GameType, games[0].Mode = "Sudoku", "Easy"89+ games[0].InitialState, games[0].SaveState, games[0].Status = "{}", "{}", StatusNew90+ games[1].GameType, games[1].Mode = "Sudoku", "Medium"91+ games[1].InitialState, games[1].SaveState, games[1].Status = "{}", "{}", StatusNew8892 for _, g := range games {8993 if err := s.CreateGame(g); err != nil {9094 t.Fatal(err)
@@ -131,8 +135,10 @@ func TestGetModeStats(t *testing.T) {
131135 {Name: "a", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},132136 {Name: "b", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},133137 {Name: "c", GameType: "Sudoku", Mode: "Medium", InitialState: "{}", SaveState: "{}", Status: StatusNew},134- {Name: "Daily Feb 16 26 - x", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},138+ newDailyTestRecord("Daily Feb 16 26 - x", time.Date(2026, time.February, 16, 12, 0, 0, 0, time.Local)),135139 }140+ games[3].GameType, games[3].Mode = "Sudoku", "Easy"141+ games[3].InitialState, games[3].SaveState, games[3].Status = "{}", "{}", StatusNew136142 for _, g := range games {137143 if err := s.CreateGame(g); err != nil {138144 t.Fatal(err)
@@ -176,8 +182,15 @@ func TestGetModeStats(t *testing.T) {
176182 t.Run("weekly bonus xp is aggregated by parsed name", func(t *testing.T) {177183 s := openTestStore(t)178184 games := []*GameRecord{179- {Name: weekly.Name(2026, 1, 10), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},180- {Name: weekly.Name(2026, 1, 25), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},185+ newWeeklyTestRecord(weekly.Name(2026, 1, 10), 2026, 1, 10),186+ newWeeklyTestRecord(weekly.Name(2026, 1, 25), 2026, 1, 25),187+ }188+ for _, g := range games {189+ g.GameType = "Sudoku"190+ g.Mode = "Easy"191+ g.InitialState = "{}"192+ g.SaveState = "{}"193+ g.Status = StatusNew181194 }182195 for _, g := range games {183196 if err := s.CreateGame(g); err != nil {
@@ -230,10 +243,14 @@ func TestGetDailyStreakDates(t *testing.T) {
230243 yesterday := now.AddDate(0, 0, -1)231244 232245 games := []*GameRecord{233- {Name: "Daily Feb 16 26 - a", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},234- {Name: "Daily Feb 15 26 - b", GameType: "Nonogram", Mode: "Mini", InitialState: "{}", SaveState: "{}", Status: StatusNew},246+ newDailyTestRecord("Daily Feb 16 26 - a", time.Date(2026, time.February, 16, 12, 0, 0, 0, time.Local)),247+ newDailyTestRecord("Daily Feb 15 26 - b", time.Date(2026, time.February, 15, 12, 0, 0, 0, time.Local)),235248 {Name: "regular-game", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},236249 }250+ games[0].GameType, games[0].Mode = "Sudoku", "Easy"251+ games[0].InitialState, games[0].SaveState, games[0].Status = "{}", "{}", StatusNew252+ games[1].GameType, games[1].Mode = "Nonogram", "Mini"253+ games[1].InitialState, games[1].SaveState, games[1].Status = "{}", "{}", StatusNew237254 for _, g := range games {238255 if err := s.CreateGame(g); err != nil {239256 t.Fatal(err)
@@ -268,8 +285,15 @@ func TestGetDailyStreakDates(t *testing.T) {
268285 t.Run("excludes non-completed dailies", func(t *testing.T) {269286 s := openTestStore(t)270287 games := []*GameRecord{271- {Name: "Daily Feb 16 26 - a", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},272- {Name: "Daily Feb 15 26 - b", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},288+ newDailyTestRecord("Daily Feb 16 26 - a", time.Date(2026, time.February, 16, 12, 0, 0, 0, time.Local)),289+ newDailyTestRecord("Daily Feb 15 26 - b", time.Date(2026, time.February, 15, 12, 0, 0, 0, time.Local)),290+ }291+ for _, g := range games {292+ g.GameType = "Sudoku"293+ g.Mode = "Easy"294+ g.InitialState = "{}"295+ g.SaveState = "{}"296+ g.Status = StatusNew273297 }274298 for _, g := range games {275299 if err := s.CreateGame(g); err != nil {
@@ -295,12 +319,19 @@ func TestGetCompletedWeeklyGauntlets(t *testing.T) {
295319 s := openTestStore(t)296320 297321 games := []*GameRecord{298- {Name: weekly.Name(2026, 1, 98), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},299- {Name: weekly.Name(2026, 1, 99), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},300- {Name: weekly.Name(2026, 2, 50), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},301- {Name: weekly.Name(2026, 3, 99), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},322+ newWeeklyTestRecord(weekly.Name(2026, 1, 98), 2026, 1, 98),323+ newWeeklyTestRecord(weekly.Name(2026, 1, 99), 2026, 1, 99),324+ newWeeklyTestRecord(weekly.Name(2026, 2, 50), 2026, 2, 50),325+ newWeeklyTestRecord(weekly.Name(2026, 3, 99), 2026, 3, 99),302326 {Name: "Week 3-2026 - #99", GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},303327 }328+ for _, g := range games[:4] {329+ g.GameType = "Sudoku"330+ g.Mode = "Easy"331+ g.InitialState = "{}"332+ g.SaveState = "{}"333+ g.Status = StatusNew334+ }304335 for _, g := range games {305336 if err := s.CreateGame(g); err != nil {306337 t.Fatal(err)
@@ -322,10 +353,17 @@ func TestGetCompletedWeeklyGauntlets(t *testing.T) {
322353 func TestGetCurrentWeeklyHighestCompletedIndex(t *testing.T) {323354 s := openTestStore(t)324355 games := []*GameRecord{325- {Name: weekly.Name(2026, 10, 1), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},326- {Name: weekly.Name(2026, 10, 17), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},327- {Name: weekly.Name(2026, 10, 9), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},328- {Name: weekly.Name(2026, 11, 30), GameType: "Sudoku", Mode: "Easy", InitialState: "{}", SaveState: "{}", Status: StatusNew},356+ newWeeklyTestRecord(weekly.Name(2026, 10, 1), 2026, 10, 1),357+ newWeeklyTestRecord(weekly.Name(2026, 10, 17), 2026, 10, 17),358+ newWeeklyTestRecord(weekly.Name(2026, 10, 9), 2026, 10, 9),359+ newWeeklyTestRecord(weekly.Name(2026, 11, 30), 2026, 11, 30),360+ }361+ for _, g := range games {362+ g.GameType = "Sudoku"363+ g.Mode = "Easy"364+ g.InitialState = "{}"365+ g.SaveState = "{}"366+ g.Status = StatusNew329367 }330368 for _, g := range games {331369 if err := s.CreateGame(g); err != nil {
M store/store.go +1 -14
M store/store_test.go +51 -4
@@ -27,9 +27,27 @@ func newTestRecord(name string) *GameRecord {
2727 InitialState: `{"grid":"..."}`,2828 SaveState: `{"grid":"..."}`,2929 Status: StatusNew,30+ RunKind: RunKindNormal,3031 }3132 }3233 34+func newDailyTestRecord(name string, date time.Time) *GameRecord {35+ rec := newTestRecord(name)36+ day := dayOnly(date)37+ rec.RunKind = RunKindDaily38+ rec.RunDate = &day39+ return rec40+}41+42+func newWeeklyTestRecord(name string, year, weekNumber, index int) *GameRecord {43+ rec := newTestRecord(name)44+ rec.RunKind = RunKindWeekly45+ rec.WeekYear = year46+ rec.WeekNumber = weekNumber47+ rec.WeekIndex = index48+ return rec49+}50+3351 func TestOpen(t *testing.T) {3452 t.Run("creates directory and database", func(t *testing.T) {3553 dir := t.TempDir()
@@ -202,6 +220,7 @@ func TestCreateGame(t *testing.T) {
202220 InitialState: `{"init":true}`,203221 SaveState: `{"save":true}`,204222 Status: StatusNew,223+ RunKind: RunKindNormal,205224 }206225 if err := s.CreateGame(rec); err != nil {207226 t.Fatal(err)
@@ -245,6 +264,34 @@ func TestCreateGame(t *testing.T) {
245264 t.Fatal("expected error for duplicate name")246265 }247266 })267+268+ t.Run("does not infer deterministic metadata from name", func(t *testing.T) {269+ s := openTestStore(t)270+ rec := newTestRecord("Daily Feb 16 26 - amber-fox")271+ if err := s.CreateGame(rec); err != nil {272+ t.Fatal(err)273+ }274+275+ got, err := s.GetGameByName(rec.Name)276+ if err != nil {277+ t.Fatal(err)278+ }279+ if got == nil {280+ t.Fatal("expected record")281+ }282+ if got.RunKind != RunKindNormal {283+ t.Fatalf("RunKind = %q, want %q", got.RunKind, RunKindNormal)284+ }285+ if got.RunDate != nil {286+ t.Fatalf("RunDate = %v, want nil", got.RunDate)287+ }288+ if got.SeedText != "" {289+ t.Fatalf("SeedText = %q, want empty", got.SeedText)290+ }291+ if got.WeekYear != 0 || got.WeekNumber != 0 || got.WeekIndex != 0 {292+ t.Fatalf("weekly metadata = (%d, %d, %d), want zero values", got.WeekYear, got.WeekNumber, got.WeekIndex)293+ }294+ })248295 }249296 250297 func TestUpdateSaveState(t *testing.T) {
@@ -607,7 +654,7 @@ func TestGetGameByName(t *testing.T) {
607654 608655 func TestGetDeterministicGame(t *testing.T) {609656 s := openTestStore(t)610- rec := newTestRecord("Week 01-2026 - #01")657+ rec := newWeeklyTestRecord("Week 01-2026 - #01", 2026, 1, 1)611658 if err := s.CreateGame(rec); err != nil {612659 t.Fatal(err)613660 }
@@ -631,9 +678,9 @@ func TestWeeklyQueries(t *testing.T) {
631678 s := openTestStore(t)632679 633680 records := []*GameRecord{634- newTestRecord(weekly.Name(2026, 1, 1)),635- newTestRecord(weekly.Name(2026, 1, 2)),636- newTestRecord(weekly.Name(2026, 2, 1)),681+ newWeeklyTestRecord(weekly.Name(2026, 1, 1), 2026, 1, 1),682+ newWeeklyTestRecord(weekly.Name(2026, 1, 2), 2026, 1, 2),683+ newWeeklyTestRecord(weekly.Name(2026, 2, 1), 2026, 2, 1),637684 newTestRecord("Week 1-2026 - #01"),638685 }639686 for _, rec := range records {