249 lines
7.1 KiB
Go
249 lines
7.1 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"naviwatcher/internal/database"
|
|
)
|
|
|
|
// newTestDB creates an in-memory SQLite database with all migrations applied.
|
|
func newTestDB(t *testing.T) *database.DB {
|
|
t.Helper()
|
|
db, err := database.New(":memory:")
|
|
if err != nil {
|
|
t.Fatalf("database.New() error: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// seedArtist inserts a minimal artist_settings row so FK constraints pass.
|
|
func seedArtist(t *testing.T, db *database.DB, id, name string) {
|
|
t.Helper()
|
|
if err := database.SaveArtistSettings(db, &database.ArtistSettings{
|
|
ID: id,
|
|
Name: name,
|
|
Monitored: true,
|
|
}); err != nil {
|
|
t.Fatalf("seedArtist(%s) error: %v", id, err)
|
|
}
|
|
}
|
|
|
|
// seedLocalAlbum inserts a local_albums row for an artist.
|
|
func seedLocalAlbum(t *testing.T, db *database.DB, id, artistID, title string) {
|
|
t.Helper()
|
|
if err := database.SaveLocalAlbum(db, &database.LocalAlbum{
|
|
ID: id,
|
|
ArtistID: artistID,
|
|
Title: title,
|
|
}); err != nil {
|
|
t.Fatalf("seedLocalAlbum(%s) error: %v", id, err)
|
|
}
|
|
}
|
|
|
|
// seedExternalRelease inserts an external_releases row for an artist.
|
|
func seedExternalRelease(t *testing.T, db *database.DB, rgid, artistID, title string, ignored bool) {
|
|
t.Helper()
|
|
if err := database.SaveExternalRelease(db, &database.ExternalRelease{
|
|
RGID: rgid,
|
|
ArtistID: artistID,
|
|
Title: title,
|
|
IsIgnored: ignored,
|
|
}); err != nil {
|
|
t.Fatalf("seedExternalRelease(%s) error: %v", rgid, err)
|
|
}
|
|
}
|
|
|
|
func TestScanArtist(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
|
|
// Local collection has "The Wall" but not "Animals".
|
|
seedLocalAlbum(t, db, "l1", "artist-1", "The Wall")
|
|
seedExternalRelease(t, db, "rg1", "artist-1", "The Wall", false)
|
|
seedExternalRelease(t, db, "rg2", "artist-1", "Animals", false)
|
|
|
|
missing, err := ScanArtist(context.Background(), db, "artist-1", 0)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist() error: %v", err)
|
|
}
|
|
|
|
rgids := map[string]bool{}
|
|
for _, m := range missing {
|
|
rgids[m.RGID] = true
|
|
}
|
|
if !rgids["rg2"] {
|
|
t.Errorf("expected rg2 (Animals) to be missing, got %v", rgids)
|
|
}
|
|
if rgids["rg1"] {
|
|
t.Errorf("did not expect rg1 (The Wall) to be missing, got %v", rgids)
|
|
}
|
|
}
|
|
|
|
func TestScanArtist_ZeroThresholdUsesDefault(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
seedLocalAlbum(t, db, "l1", "artist-1", "The Wall")
|
|
seedExternalRelease(t, db, "rg1", "artist-1", "The Wall", false)
|
|
seedExternalRelease(t, db, "rg2", "artist-1", "Animals", false)
|
|
|
|
// Pass 0 (zero value / unset) and the explicit default; results must match.
|
|
zero, err := ScanArtist(context.Background(), db, "artist-1", 0)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist(0) error: %v", err)
|
|
}
|
|
explicit, err := ScanArtist(context.Background(), db, "artist-1", DefaultThreshold)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist(%v) error: %v", DefaultThreshold, err)
|
|
}
|
|
if len(zero) != len(explicit) {
|
|
t.Errorf("ScanArtist(0) returned %d missing, ScanArtist(%v) returned %d; must match",
|
|
len(zero), DefaultThreshold, len(explicit))
|
|
}
|
|
}
|
|
|
|
func TestScanArtist_IgnoredNotReported(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
seedExternalRelease(t, db, "rg1", "artist-1", "Animals", true)
|
|
|
|
missing, err := ScanArtist(context.Background(), db, "artist-1", 0.85)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist() error: %v", err)
|
|
}
|
|
if len(missing) != 0 {
|
|
t.Errorf("ignored release should not be reported, got %v", missing)
|
|
}
|
|
}
|
|
|
|
func TestScanArtist_RemasteredVariantNotMissing(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
seedLocalAlbum(t, db, "l1", "artist-1", "The Wall (Remastered)")
|
|
seedExternalRelease(t, db, "rg1", "artist-1", "The Wall", false)
|
|
|
|
missing, err := ScanArtist(context.Background(), db, "artist-1", 0)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist() error: %v", err)
|
|
}
|
|
if len(missing) != 0 {
|
|
t.Errorf("remastered local should match external, got %v", missing)
|
|
}
|
|
}
|
|
|
|
func TestScanArtist_YearTitledAlbumReissueReportedMissing(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
// Rush "2112" is a bare year-titled album; "2112 (Remastered)" is a
|
|
// distinct release group. Owning the standard 2112 must NOT count as owning
|
|
// the remastered reissue — the reissue should be reported missing.
|
|
seedArtist(t, db, "artist-1", "Rush")
|
|
seedLocalAlbum(t, db, "l1", "artist-1", "2112")
|
|
seedExternalRelease(t, db, "rg-standard", "artist-1", "2112", false)
|
|
seedExternalRelease(t, db, "rg-remaster", "artist-1", "2112 (Remastered)", false)
|
|
|
|
missing, err := ScanArtist(context.Background(), db, "artist-1", 0)
|
|
if err != nil {
|
|
t.Fatalf("ScanArtist() error: %v", err)
|
|
}
|
|
|
|
rgids := map[string]bool{}
|
|
for _, m := range missing {
|
|
rgids[m.RGID] = true
|
|
}
|
|
if rgids["rg-standard"] {
|
|
t.Errorf("standard 2112 should match local copy, not be missing")
|
|
}
|
|
if !rgids["rg-remaster"] {
|
|
t.Errorf("2112 (Remastered) reissue should be reported missing, got %v", rgids)
|
|
}
|
|
}
|
|
|
|
func TestScanArtist_CtxCancelled(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
if _, err := ScanArtist(ctx, db, "artist-1", 0.85); err == nil {
|
|
t.Fatal("expected error from cancelled context, got nil")
|
|
}
|
|
}
|
|
|
|
func TestScanAll(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
// Monitored artist with one missing release.
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
seedLocalAlbum(t, db, "l1", "artist-1", "The Wall")
|
|
seedExternalRelease(t, db, "rg1", "artist-1", "The Wall", false)
|
|
seedExternalRelease(t, db, "rg2", "artist-1", "Animals", false)
|
|
|
|
// Unmonitored artist — must be skipped entirely.
|
|
seedArtistUnmonitored(t, db, "artist-2", "Other")
|
|
seedExternalRelease(t, db, "rg3", "artist-2", "Some Album", false)
|
|
|
|
missing, err := ScanAll(context.Background(), db, 0)
|
|
if err != nil {
|
|
t.Fatalf("ScanAll() error: %v", err)
|
|
}
|
|
|
|
rgids := map[string]bool{}
|
|
for _, m := range missing {
|
|
rgids[m.RGID] = true
|
|
}
|
|
if !rgids["rg2"] {
|
|
t.Errorf("expected rg2 (Animals) missing, got %v", rgids)
|
|
}
|
|
if rgids["rg1"] {
|
|
t.Errorf("did not expect rg1 (The Wall) missing, got %v", rgids)
|
|
}
|
|
if rgids["rg3"] {
|
|
t.Errorf("unmonitored artist's release must not be scanned, got %v", rgids)
|
|
}
|
|
}
|
|
|
|
func TestScanAll_CtxCancelledMidIteration(t *testing.T) {
|
|
db := newTestDB(t)
|
|
defer db.Close()
|
|
|
|
seedArtist(t, db, "artist-1", "Pink Floyd")
|
|
seedArtist(t, db, "artist-2", "Other")
|
|
|
|
// Cancel before scanning starts.
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
missing, err := ScanAll(ctx, db, 0.85)
|
|
if err == nil {
|
|
t.Fatal("expected error from cancelled context, got nil")
|
|
}
|
|
if missing != nil {
|
|
t.Errorf("expected nil results on early cancellation, got %v", missing)
|
|
}
|
|
}
|
|
|
|
// seedArtistUnmonitored inserts an artist_settings row with Monitored=false.
|
|
func seedArtistUnmonitored(t *testing.T, db *database.DB, id, name string) {
|
|
t.Helper()
|
|
if err := database.SaveArtistSettings(db, &database.ArtistSettings{
|
|
ID: id,
|
|
Name: name,
|
|
Monitored: false,
|
|
}); err != nil {
|
|
t.Fatalf("seedArtistUnmonitored(%s) error: %v", id, err)
|
|
}
|
|
}
|