package api import ( "testing" "time" "github.com/ponzischeme89/memby/server/internal/store" ) var heroRevisionZone = time.FixedZone("NZST", 12*60*60) func heroRevisionAt(policy store.HeroPolicy, user string, at time.Time) string { return heroRevision(policy, user, at, heroRevisionZone) } func pinnedPolicy(placement string, ids ...string) store.HeroPolicy { return store.HeroPolicy{Placements: map[string]store.HeroPlacementPolicy{ placement: {PinnedItemIDs: ids}, }} } func TestHeroRevisionIsStableWithinASlot(t *testing.T) { policy := pinnedPolicy(store.HeroPlacementHome, "film-1") morning := time.Date(2026, 8, 20, 8, 0, 0, 0, heroRevisionZone) if heroRevisionAt(policy, "user-1", morning) != heroRevisionAt(policy, "user-1", morning.Add(90*time.Minute)) { t.Fatal("hero revision moved without anything changing; every poll would refetch") } } func TestHeroRevisionMovesWhenAnOperatorEditsPins(t *testing.T) { now := time.Date(2026, 8, 20, 8, 0, 0, 0, heroRevisionZone) before := heroRevisionAt(pinnedPolicy(store.HeroPlacementHome, "film-1"), "user-1", now) for name, policy := range map[string]store.HeroPolicy{ "added": pinnedPolicy(store.HeroPlacementHome, "film-1", "film-2"), "removed": pinnedPolicy(store.HeroPlacementHome), "replaced": pinnedPolicy(store.HeroPlacementHome, "film-2"), } { if heroRevisionAt(policy, "user-1", now) == before { t.Fatalf("hero revision did not move when a pin was %s", name) } } // Order is the operator's decision about which card leads, so reordering is a change // even though the same titles are pinned. reordered := pinnedPolicy(store.HeroPlacementHome, "film-2", "film-1") if heroRevisionAt(reordered, "user-1", now) == heroRevisionAt(pinnedPolicy(store.HeroPlacementHome, "film-1", "film-2"), "user-1", now) { t.Fatal("hero revision did not move when pins were reordered") } } func TestHeroRevisionSeparatesPlacements(t *testing.T) { now := time.Date(2026, 8, 20, 8, 0, 0, 0, heroRevisionZone) home := heroRevisionAt(pinnedPolicy(store.HeroPlacementHome, "film-1"), "user-1", now) movies := heroRevisionAt(pinnedPolicy(store.HeroPlacementMovies, "film-1"), "user-1", now) if home == movies { t.Fatal("pinning a title to Movies read as the same policy as pinning it to Home") } } func TestHeroRevisionIgnoresACosmeticSave(t *testing.T) { now := time.Date(2026, 8, 20, 8, 0, 0, 0, heroRevisionZone) saved := pinnedPolicy(store.HeroPlacementHome, "film-1") // UpdatedAt moves on every write. Hashing the document rather than what it resolves to // would repaint every launcher in the house for a save that changed nothing. resaved := pinnedPolicy(store.HeroPlacementHome, "film-1") resaved.UpdatedAt = now if heroRevisionAt(saved, "user-1", now) != heroRevisionAt(resaved, "user-1", now) { t.Fatal("hero revision moved for a save that changed nothing") } } func TestHeroRevisionMovesWhenAScheduleOpensAndCloses(t *testing.T) { policy := store.HeroPolicy{Schedules: []store.HeroSchedule{{ ID: "s1", ItemID: "film-9", Enabled: true, Frequency: "daily", StartTime: "20:00", EndTime: "22:00", Placements: []string{store.HeroPlacementHome}, }}} before := time.Date(2026, 8, 20, 19, 0, 0, 0, heroRevisionZone) during := time.Date(2026, 8, 20, 21, 0, 0, 0, heroRevisionZone) // Two readings within one rotation slot, so only the schedule can be the difference. if heroRotationSlot(before, heroRevisionZone) != heroRotationSlot(during, heroRevisionZone) { t.Fatal("test times straddle a rotation slot; the schedule is no longer the only variable") } if heroRevisionAt(policy, "user-1", before) == heroRevisionAt(policy, "user-1", during) { t.Fatal("hero revision did not move when a scheduled hero came into force") } empty := store.HeroPolicy{} if heroRevisionAt(policy, "user-1", before) != heroRevisionAt(empty, "user-1", before) { t.Fatal("a schedule that is not yet in force changed the revision; the launcher would repaint for nothing") } } func TestHeroRevisionIsPerViewerForAPersonalSchedule(t *testing.T) { now := time.Date(2026, 8, 20, 21, 0, 0, 0, heroRevisionZone) policy := store.HeroPolicy{Schedules: []store.HeroSchedule{{ ID: "s1", ItemID: "film-9", Enabled: true, UserID: "user-1", Frequency: "daily", StartTime: "20:00", EndTime: "22:00", Placements: []string{store.HeroPlacementHome}, }}} if heroRevisionAt(policy, "user-1", now) == heroRevisionAt(policy, "user-2", now) { t.Fatal("one viewer's scheduled hero moved another viewer's revision") } } func TestHeroRevisionMovesWithTheRotationSlot(t *testing.T) { policy := pinnedPolicy(store.HeroPlacementHome, "film-1") morning := time.Date(2026, 8, 20, 8, 0, 0, 0, heroRevisionZone) evening := time.Date(2026, 8, 20, 20, 0, 0, 0, heroRevisionZone) if heroRevisionAt(policy, "user-1", morning) == heroRevisionAt(policy, "user-1", evening) { t.Fatal("hero revision did not move across rotation slots; the day's draw would never be refetched") } }