package database import ( "database/sql" "testing" ) // insertTestRelease inserts a minimal external_releases row for use in tests that need FK satisfaction. func insertTestRelease(db *DB, rgid, artistID string) error { _, err := db.Conn().Exec( "INSERT OR IGNORE INTO external_releases (rgid, artist_id, title) VALUES (?, ?, ?)", rgid, artistID, "Test Release "+rgid, ) return err } // TestMarkNotificationSent_New verifies inserting a new notification record. func TestMarkNotificationSent_New(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist: %v", err) } if err := insertTestRelease(db, "rgid-1", "artist-1"); err != nil { t.Fatalf("insertTestRelease: %v", err) } if err := MarkNotificationSent(db, "rgid-1"); err != nil { t.Fatalf("MarkNotificationSent() error: %v", err) } sentAt, err := GetNotificationSentAt(db, "rgid-1") if err != nil { t.Fatalf("GetNotificationSentAt() error: %v", err) } if sentAt == "" { t.Error("expected sent_at to be non-empty") } } // TestMarkNotificationSent_DuplicateSecond verifies that inserting the same RGID twice // within the same second fails due to the composite primary key (rgid, sent_at). // In practice, notifications are sent at most once per day, so this is acceptable. func TestMarkNotificationSent_DuplicateSecond(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist: %v", err) } if err := insertTestRelease(db, "rgid-1", "artist-1"); err != nil { t.Fatalf("insertTestRelease: %v", err) } if err := MarkNotificationSent(db, "rgid-1"); err != nil { t.Fatalf("first MarkNotificationSent() error: %v", err) } // Second insert in the same second should fail with a UNIQUE constraint error. err = MarkNotificationSent(db, "rgid-1") if err == nil { t.Fatal("expected UNIQUE constraint error on duplicate insert, got nil") } // Should still have exactly one row. var count int err = db.Conn().QueryRow("SELECT COUNT(*) FROM notifications_sent WHERE rgid = ?", "rgid-1").Scan(&count) if err != nil { t.Fatalf("count query error: %v", err) } if count != 1 { t.Errorf("expected 1 notification row, got %d", count) } } // TestMarkNotificationSent_DifferentTime verifies that inserting the same RGID at a // different explicit sent_at time succeeds (composite PK allows multiple rows per RGID). func TestMarkNotificationSent_DifferentTime(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist: %v", err) } if err := insertTestRelease(db, "rgid-1", "artist-1"); err != nil { t.Fatalf("insertTestRelease: %v", err) } // Insert with explicit different timestamps. _, err = db.Conn().Exec("INSERT INTO notifications_sent (rgid, sent_at) VALUES (?, ?)", "rgid-1", "2024-01-01T00:00:00Z") if err != nil { t.Fatalf("first insert error: %v", err) } _, err = db.Conn().Exec("INSERT INTO notifications_sent (rgid, sent_at) VALUES (?, ?)", "rgid-1", "2024-06-01T00:00:00Z") if err != nil { t.Fatalf("second insert error: %v", err) } // IsNotificationSent should return true. sent, err := IsNotificationSent(db, "rgid-1") if err != nil { t.Fatalf("IsNotificationSent() error: %v", err) } if !sent { t.Error("expected IsNotificationSent to return true") } // Should have two rows. var count int err = db.Conn().QueryRow("SELECT COUNT(*) FROM notifications_sent WHERE rgid = ?", "rgid-1").Scan(&count) if err != nil { t.Fatalf("count query error: %v", err) } if count != 2 { t.Errorf("expected 2 notification rows, got %d", count) } } // TestIsNotificationSent_True verifies true for a sent notification. func TestIsNotificationSent_True(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist: %v", err) } if err := insertTestRelease(db, "rgid-1", "artist-1"); err != nil { t.Fatalf("insertTestRelease: %v", err) } if err := MarkNotificationSent(db, "rgid-1"); err != nil { t.Fatalf("MarkNotificationSent() error: %v", err) } sent, err := IsNotificationSent(db, "rgid-1") if err != nil { t.Fatalf("IsNotificationSent() error: %v", err) } if !sent { t.Error("expected IsNotificationSent to return true") } } // TestIsNotificationSent_False verifies false for an unsent RGID. func TestIsNotificationSent_False(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() sent, err := IsNotificationSent(db, "nonexistent") if err != nil { t.Fatalf("IsNotificationSent() error: %v", err) } if sent { t.Error("expected IsNotificationSent to return false for nonexistent RGID") } } // TestGetNotificationSentAt_NotFound verifies sql.ErrNoRows for unsent RGID. func TestGetNotificationSentAt_NotFound(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() _, err = GetNotificationSentAt(db, "nonexistent") if err != sql.ErrNoRows { t.Errorf("expected sql.ErrNoRows, got %v", err) } } // TestGetUnnotifiedReleases_AllUnnotified verifies all releases returned when no notifications sent. func TestGetUnnotifiedReleases_AllUnnotified(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() releases := []ExternalRelease{ {RGID: "rg1", ArtistID: "artist-1", Title: "Album 1", Type: "album", ReleaseDate: "2024-01-01"}, {RGID: "rg2", ArtistID: "artist-1", Title: "Album 2", Type: "album", ReleaseDate: "2024-06-01"}, {RGID: "rg3", ArtistID: "artist-2", Title: "Single 1", Type: "single", ReleaseDate: "2024-03-01"}, } if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist(artist-1): %v", err) } if err := insertTestArtist(db, "artist-2"); err != nil { t.Fatalf("insertTestArtist(artist-2): %v", err) } for _, r := range releases { if err := SaveExternalRelease(db, &r); err != nil { t.Fatalf("SaveExternalRelease(%s) error: %v", r.RGID, err) } } results, err := GetUnnotifiedReleases(db) if err != nil { t.Fatalf("GetUnnotifiedReleases() error: %v", err) } if len(results) != 3 { t.Fatalf("expected 3 unnotified releases, got %d", len(results)) } } // TestGetUnnotifiedReleases_SomeNotified verifies only unnotified releases are returned. func TestGetUnnotifiedReleases_SomeNotified(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() releases := []ExternalRelease{ {RGID: "rg1", ArtistID: "artist-1", Title: "Album 1", Type: "album", ReleaseDate: "2024-01-01"}, {RGID: "rg2", ArtistID: "artist-1", Title: "Album 2", Type: "album", ReleaseDate: "2024-06-01"}, {RGID: "rg3", ArtistID: "artist-2", Title: "Single 1", Type: "single", ReleaseDate: "2024-03-01"}, } if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist(artist-1): %v", err) } if err := insertTestArtist(db, "artist-2"); err != nil { t.Fatalf("insertTestArtist(artist-2): %v", err) } for _, r := range releases { if err := SaveExternalRelease(db, &r); err != nil { t.Fatalf("SaveExternalRelease(%s) error: %v", r.RGID, err) } } // Mark rg1 and rg3 as notified. if err := MarkNotificationSent(db, "rg1"); err != nil { t.Fatalf("MarkNotificationSent(rg1) error: %v", err) } if err := MarkNotificationSent(db, "rg3"); err != nil { t.Fatalf("MarkNotificationSent(rg3) error: %v", err) } results, err := GetUnnotifiedReleases(db) if err != nil { t.Fatalf("GetUnnotifiedReleases() error: %v", err) } if len(results) != 1 { t.Fatalf("expected 1 unnotified release, got %d", len(results)) } if results[0].RGID != "rg2" { t.Errorf("expected unnotified release rg2, got %s", results[0].RGID) } if results[0].Title != "Album 2" { t.Errorf("expected Title 'Album 2', got %q", results[0].Title) } } // TestGetUnnotifiedReleases_AllNotified verifies empty result when all releases are notified. func TestGetUnnotifiedReleases_AllNotified(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() releases := []ExternalRelease{ {RGID: "rg1", ArtistID: "artist-1", Title: "Album 1"}, {RGID: "rg2", ArtistID: "artist-1", Title: "Album 2"}, } if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist(artist-1): %v", err) } for _, r := range releases { if err := SaveExternalRelease(db, &r); err != nil { t.Fatalf("SaveExternalRelease(%s) error: %v", r.RGID, err) } } // Mark all as notified. for _, r := range releases { if err := MarkNotificationSent(db, r.RGID); err != nil { t.Fatalf("MarkNotificationSent(%s) error: %v", r.RGID, err) } } results, err := GetUnnotifiedReleases(db) if err != nil { t.Fatalf("GetUnnotifiedReleases() error: %v", err) } if len(results) != 0 { t.Errorf("expected 0 unnotified releases, got %d", len(results)) } } // TestGetUnnotifiedReleases_NoReleases verifies empty result when no releases exist. func TestGetUnnotifiedReleases_NoReleases(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() results, err := GetUnnotifiedReleases(db) if err != nil { t.Fatalf("GetUnnotifiedReleases() error: %v", err) } if len(results) != 0 { t.Errorf("expected 0 unnotified releases, got %d", len(results)) } } // TestMarkNotificationSent_MultipleReleases verifies marking multiple different RGIDs. func TestMarkNotificationSent_MultipleReleases(t *testing.T) { db, err := New(":memory:") if err != nil { t.Fatalf("New() error: %v", err) } defer db.Close() if err := insertTestArtist(db, "artist-1"); err != nil { t.Fatalf("insertTestArtist: %v", err) } for _, rgid := range []string{"rg1", "rg2", "rg3"} { if err := insertTestRelease(db, rgid, "artist-1"); err != nil { t.Fatalf("insertTestRelease(%s): %v", rgid, err) } } rgids := []string{"rg1", "rg2", "rg3"} for _, rgid := range rgids { if err := MarkNotificationSent(db, rgid); err != nil { t.Fatalf("MarkNotificationSent(%s) error: %v", rgid, err) } } for _, rgid := range rgids { sent, err := IsNotificationSent(db, rgid) if err != nil { t.Fatalf("IsNotificationSent(%s) error: %v", rgid, err) } if !sent { t.Errorf("expected IsNotificationSent(%s) to return true", rgid) } } // Verify count. var count int err = db.Conn().QueryRow("SELECT COUNT(*) FROM notifications_sent").Scan(&count) if err != nil { t.Fatalf("count query error: %v", err) } if count != 3 { t.Errorf("expected 3 notification rows, got %d", count) } }