package musicbrainz import ( "testing" "time" "naviwatcher/internal/database" ) func insertTestArtistForCache(db *database.DB, id string) error { _, err := db.Conn().Exec( "INSERT OR IGNORE INTO artist_settings (id, name) VALUES (?, ?)", id, "Test Artist "+id, ) return err } func TestGetCachedReleases_CacheHit(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() artistID := "artist-cache-hit" if err := insertTestArtistForCache(db, artistID); err != nil { t.Fatalf("insertTestArtist: %v", err) } // Insert releases with recent cached_at timestamps now := time.Now().Format("2006-01-02 15:04:05") _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, type, cached_at) VALUES (?, ?, ?, ?, ?)", "rg-hit-1", artistID, "Cached Album 1", "album", now, ) if err != nil { t.Fatalf("insert rg-hit-1: %v", err) } _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, type, cached_at) VALUES (?, ?, ?, ?, ?)", "rg-hit-2", artistID, "Cached Album 2", "single", now, ) if err != nil { t.Fatalf("insert rg-hit-2: %v", err) } ttl := 24 * time.Hour stats, err := GetCachedReleases(db, artistID, ttl) if err != nil { t.Fatalf("GetCachedReleases() error: %v", err) } if stats.CacheHitCount != 2 { t.Errorf("CacheHitCount = %d, want 2", stats.CacheHitCount) } if !stats.IsCached("rg-hit-1") { t.Error("expected rg-hit-1 to be cached") } if !stats.IsCached("rg-hit-2") { t.Error("expected rg-hit-2 to be cached") } } func TestGetCachedReleases_CacheMiss_Expired(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() artistID := "artist-cache-miss" if err := insertTestArtistForCache(db, artistID); err != nil { t.Fatalf("insertTestArtist: %v", err) } // Insert a release with an expired cached_at (48 hours ago) expired := time.Now().Add(-48 * time.Hour).Format("2006-01-02 15:04:05") _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, type, cached_at) VALUES (?, ?, ?, ?, ?)", "rg-expired", artistID, "Expired Album", "album", expired, ) if err != nil { t.Fatalf("insert expired release: %v", err) } ttl := 24 * time.Hour stats, err := GetCachedReleases(db, artistID, ttl) if err != nil { t.Fatalf("GetCachedReleases() error: %v", err) } if stats.CacheHitCount != 0 { t.Errorf("CacheHitCount = %d, want 0 (expired entry should not be cached)", stats.CacheHitCount) } if stats.IsCached("rg-expired") { t.Error("expected rg-expired to NOT be cached") } } func TestGetCachedReleases_CacheMiss_NoCachedAt(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() artistID := "artist-no-cached" if err := insertTestArtistForCache(db, artistID); err != nil { t.Fatalf("insertTestArtist: %v", err) } // Insert a release WITHOUT cached_at (NULL) _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, type) VALUES (?, ?, ?, ?)", "rg-nocached", artistID, "Uncached Album", "album", ) if err != nil { t.Fatalf("insert uncached release: %v", err) } ttl := 24 * time.Hour stats, err := GetCachedReleases(db, artistID, ttl) if err != nil { t.Fatalf("GetCachedReleases() error: %v", err) } if stats.CacheHitCount != 0 { t.Errorf("CacheHitCount = %d, want 0 (NULL cached_at should not be cached)", stats.CacheHitCount) } } func TestGetCachedReleases_EmptyArtist(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() ttl := 24 * time.Hour stats, err := GetCachedReleases(db, "nonexistent-artist", ttl) if err != nil { t.Fatalf("GetCachedReleases() error: %v", err) } if stats.CacheHitCount != 0 { t.Errorf("CacheHitCount = %d, want 0 for nonexistent artist", stats.CacheHitCount) } } func TestIsArtistCacheValid_Valid(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() artistID := "artist-valid-cache" if err := insertTestArtistForCache(db, artistID); err != nil { t.Fatalf("insertTestArtist: %v", err) } now := time.Now().Format("2006-01-02 15:04:05") _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, cached_at) VALUES (?, ?, ?, ?)", "rg-valid", artistID, "Valid Album", now, ) if err != nil { t.Fatalf("insert: %v", err) } ttl := 24 * time.Hour valid, err := IsArtistCacheValid(db, artistID, ttl) if err != nil { t.Fatalf("IsArtistCacheValid() error: %v", err) } if !valid { t.Error("expected cache to be valid") } } func TestIsArtistCacheValid_Invalid(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() ttl := 24 * time.Hour // No releases at all valid, err := IsArtistCacheValid(db, "no-releases", ttl) if err != nil { t.Fatalf("IsArtistCacheValid() error: %v", err) } if valid { t.Error("expected cache to be invalid for artist with no releases") } } func TestCacheStats_IsCached_Empty(t *testing.T) { stats := &CacheStats{} if stats.IsCached("anything") { t.Error("expected IsCached to return false for empty stats") } } func TestGetCachedReleases_MixedExpiry(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() artistID := "artist-mixed" if err := insertTestArtistForCache(db, artistID); err != nil { t.Fatalf("insertTestArtist: %v", err) } now := time.Now().Format("2006-01-02 15:04:05") expired := time.Now().Add(-48 * time.Hour).Format("2006-01-02 15:04:05") // Mix of fresh and expired _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, cached_at) VALUES (?, ?, ?, ?)", "rg-fresh", artistID, "Fresh Album", now, ) if err != nil { t.Fatalf("insert fresh: %v", err) } _, err = db.Conn().Exec( "INSERT INTO external_releases (rgid, artist_id, title, cached_at) VALUES (?, ?, ?, ?)", "rg-old", artistID, "Old Album", expired, ) if err != nil { t.Fatalf("insert old: %v", err) } ttl := 24 * time.Hour stats, err := GetCachedReleases(db, artistID, ttl) if err != nil { t.Fatalf("GetCachedReleases() error: %v", err) } if stats.CacheHitCount != 1 { t.Errorf("CacheHitCount = %d, want 1 (only fresh entry)", stats.CacheHitCount) } if !stats.IsCached("rg-fresh") { t.Error("expected rg-fresh to be cached") } if stats.IsCached("rg-old") { t.Error("expected rg-old to NOT be cached (expired)") } }