- commit
- 1ddddc695440a93c00d061471f1ff95e73421c0a
- parent
- 4480c349b2dc20e854c7bdd92ade6f553e837035
- author
- Dami <dami.etoile@gmail.com>
| M | app/ | 13 | +++++ |
| M | app/ | 1 | + |
| M | architecture_test.go | 24 | ++++++++++ |
| M | cmd/ | 1 | + |
| M | store/ | 72 | +++++++++++++++++++++++------- |
| M | store/ | 15 | +------ |
| M | store/ | 55 | +++++++++++++++++++++-- |
@@ -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)@@ -47,6 +47,7 @@ func TestContinueViewStaysAtNaturalTableWidth(t *testing.T) {
4747 GameType: "Sudoku",4848 Mode: "Medium",4949 Status: store.StatusInProgress,50+ RunKind: store.RunKindNormal,5051 }); err != nil {5152 t.Fatal(err)5253 }@@ -89,6 +89,15 @@ func TestSessionPackageDoesNotUseNameDerivedRunMetadata(t *testing.T) {
8989 })9090 }9191 92+func TestStoreCreateGameDoesNotUseNameDerivedRunMetadata(t *testing.T) {93+ assertFileDoesNotContain(t, filepath.Join("store", "store.go"), []string{94+ "RunKindForName(",95+ "RunDateForName(",96+ "SeedTextForName(",97+ "WeeklyIdentityForName(",98+ })99+}100+92101 func TestPDFExportPackageDoesNotContainMarkdownParseEntrypoints(t *testing.T) {93102 assertFilesDoNotContain(t, "pdfexport", []string{94103 "func ParseMarkdown(",@@ -134,6 +143,21 @@ func assertPackageDoesNotImport(t *testing.T, dir string, forbidden []string) {
134143 }135144 }136145 146+func assertFileDoesNotContain(t *testing.T, path string, forbidden []string) {147+ t.Helper()148+149+ data, err := os.ReadFile(path)150+ if err != nil {151+ t.Fatalf("read %s: %v", path, err)152+ }153+ content := string(data)154+ for _, pattern := range forbidden {155+ if strings.Contains(content, pattern) {156+ t.Fatalf("%s contains forbidden pattern %q", path, pattern)157+ }158+ }159+}160+137161 func assertFilesDoNotContain(t *testing.T, dir string, forbidden []string) {138162 t.Helper()139163 @@ -82,6 +82,7 @@ func TestListCmdUsesActiveConfig(t *testing.T) {
8282 InitialState: "{}",8383 SaveState: "{}",8484 Status: store.StatusNew,85+ RunKind: store.RunKindNormal,8586 }); err != nil {8687 t.Fatal(err)8788 }@@ -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 {@@ -180,20 +180,7 @@ func (s *Store) CreateGame(rec *GameRecord) error {
180180 rec.ModeID = CanonicalModeID(rec.Mode)181181 }182182 if rec.RunKind == "" {183- rec.RunKind = RunKindForName(rec.Name)184- }185- if rec.RunDate == nil {186- rec.RunDate = RunDateForName(rec.Name)187- }188- if rec.SeedText == "" {189- rec.SeedText = SeedTextForName(rec.Name)190- }191- if rec.WeekYear == 0 || rec.WeekNumber == 0 || rec.WeekIndex == 0 {192- if year, week, index, ok := WeeklyIdentityForName(rec.Name); ok {193- rec.WeekYear = year194- rec.WeekNumber = week195- rec.WeekIndex = index196- }183+ rec.RunKind = RunKindNormal197184 }198185 199186 result, err := s.db.Exec(@@ -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 {