package main import ( "errors" "testing" ) func TestCreateMain(t *testing.T) { h := seeded(t) id, err := createMain(h.db, "Uunikala", []string{"fish"}, true) if err != nil { t.Fatalf("createMain: %v", err) } dish, err := dishByID(h.db, id) if err != nil { t.Fatalf("dishByID: %v", err) } if dish.Name != "Uunikala" || !dish.HasSides { t.Errorf("got %+v", dish) } if len(dish.Categories) != 1 || dish.Categories[0] != "fish" { t.Errorf("categories = %v, want [fish]", dish.Categories) } } func TestCreateMainRejectsDuplicateName(t *testing.T) { h := seeded(t) // Seeded bundle already has Lohikeitto; casing must not matter. _, err := createMain(h.db, "lohikeitto", []string{"fish"}, true) if !errors.Is(err, errNameTaken) { t.Errorf("err = %v, want errNameTaken", err) } } func TestUpdateMainReplacesCategories(t *testing.T) { h := seeded(t) id := h.mainNamed(t, "Tortillat") if err := updateMain(h.db, id, "Tortillat", []string{"chicken"}, false); err != nil { t.Fatalf("updateMain: %v", err) } dish, err := dishByID(h.db, id) if err != nil { t.Fatalf("dishByID: %v", err) } if len(dish.Categories) != 1 || dish.Categories[0] != "chicken" { t.Errorf("categories = %v, want [chicken]", dish.Categories) } if dish.HasSides { t.Error("has_sides should have been cleared") } if got := dish.CategoryKey(); got != "kana" { t.Errorf("CategoryKey = %q, want kana", got) } } func TestUpdateMainRejectsAnotherDishesName(t *testing.T) { h := seeded(t) err := updateMain(h.db, h.mainNamed(t, "Tortillat"), "Lohikeitto", []string{"fish"}, true) if !errors.Is(err, errNameTaken) { t.Errorf("err = %v, want errNameTaken", err) } } func TestSideCRUD(t *testing.T) { h := seeded(t) if err := createSide(h.db, "Lohkoperunat"); err != nil { t.Fatalf("createSide: %v", err) } if err := createSide(h.db, "lohkoperunat"); !errors.Is(err, errNameTaken) { t.Errorf("duplicate side err = %v, want errNameTaken", err) } id := h.sideNamed(t, "Lohkoperunat") if err := updateSide(h.db, id, "Lohkoperunat uunista"); err != nil { t.Fatalf("updateSide: %v", err) } side, err := sideByID(h.db, id) if err != nil { t.Fatalf("sideByID: %v", err) } if side.Name != "Lohkoperunat uunista" { t.Errorf("name = %q", side.Name) } } func TestSoftDeleteHidesDishButKeepsHistory(t *testing.T) { h := seeded(t) id := h.mainNamed(t, "Lohikeitto") date := day(t, "2026-09-05") if err := saveEntry(h.db, date, id, nil); err != nil { t.Fatalf("saveEntry: %v", err) } if err := softDeleteMain(h.db, id); err != nil { t.Fatalf("softDeleteMain: %v", err) } // Gone from the pickers... dishes, err := listDishes(h.db, "") if err != nil { t.Fatalf("listDishes: %v", err) } for _, d := range dishes { if d.ID == id { t.Fatal("soft-deleted dish still appears in the catalog") } } if _, err := dishByID(h.db, id); err == nil { t.Error("dishByID returned a soft-deleted dish") } // ...but the log entry still resolves its name (PRD ยง6). entry, err := entryFor(h.db, date) if err != nil || entry == nil { t.Fatalf("entryFor: %v, %v", entry, err) } if entry.Main.Name != "Lohikeitto" { t.Errorf("historical name = %q, want Lohikeitto", entry.Main.Name) } // And the freed name can be reused. if _, err := createMain(h.db, "Lohikeitto", []string{"fish"}, true); err != nil { t.Errorf("name still blocked after soft delete: %v", err) } } func TestSoftDeleteSideHidesItFromPickers(t *testing.T) { h := seeded(t) id := h.sideNamed(t, "Riisi") if err := softDeleteSide(h.db, id); err != nil { t.Fatalf("softDeleteSide: %v", err) } sides, err := listSides(h.db) if err != nil { t.Fatalf("listSides: %v", err) } for _, s := range sides { if s.ID == id { t.Fatal("soft-deleted side still appears") } } }