package wanted import ( "context" "fmt" "testing" "time" "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" "git.fabledsword.com/bvandeusen/minstrel/internal/release" ) func TestUpgradeFor(t *testing.T) { low := release.Held{Tier: release.TierLossyLow} flac := release.Held{Tier: release.TierLossless} high := release.Held{Tier: release.TierLossyHigh} cases := []struct { name string held release.Held judged bool rips int until release.Tier want string }{ {"below the cutoff", low, true, 0, release.TierLossyHigh, UpgradeQuality}, {"at the cutoff", high, true, 0, release.TierLossyHigh, ""}, {"above the cutoff", flac, true, 0, release.TierLossyHigh, ""}, {"upgrades off", low, true, 0, "", ""}, {"a rip, upgrades off", flac, true, 1, "", UpgradeRip}, {"nothing to judge", release.Held{}, false, 0, release.TierLossless, ""}, } for _, c := range cases { if got := UpgradeFor(c.held, c.judged, c.rips, c.until); got != c.want { t.Errorf("%s: %q, want %q", c.name, got, c.want) } } } // seedMP3s puts Blur's The Ballad of Darren in the library as MP3s at about // kbps, two tracks, the first held back as a video rip when rip is set. func seedMP3s(t *testing.T, pool *pgxpool.Pool, kbps int64, rip bool) dbq.Album { t.Helper() ctx := context.Background() q := dbq.New(pool) mbid, group, rel := blur, theBallad, "r-ballad-mp3" artist, err := q.UpsertArtist(ctx, dbq.UpsertArtistParams{Name: "Blur", SortName: "Blur", Mbid: &mbid}) if err != nil { t.Fatal(err) } album, err := q.UpsertAlbum(ctx, dbq.UpsertAlbumParams{Title: "The Ballad of Darren", SortTitle: "Ballad of Darren", ArtistID: artist.ID, Mbid: &rel, ReleaseGroupMbid: &group}) if err != nil { t.Fatal(err) } for i := range 2 { num := int32(i + 1) tr, err := q.UpsertTrack(ctx, dbq.UpsertTrackParams{ Title: fmt.Sprintf("Track %d", i+1), AlbumID: album.ID, ArtistID: artist.ID, TrackNumber: &num, DurationMs: 200000, FilePath: fmt.Sprintf("/music/ballad/%02d.mp3", i+1), FileSize: kbps * 200000 / 8, FileFormat: "mp3", }) if err != nil { t.Fatal(err) } if rip && i == 0 { if _, err := pool.Exec(ctx, `UPDATE tracks SET source_verdict = 'suspect' WHERE id = $1`, tr.ID); err != nil { t.Fatal(err) } } } return album } func TestCheckUpgrades_AHeldAlbumBelowTheCutoffIsWantedAgain_Integration(t *testing.T) { pool := newPool(t) q := dbq.New(pool) ctx := context.Background() s := newService(t, pool, newFakeMB(t)) if _, err := WantWith(ctx, q, WantParams{ GroupMBID: theBallad, ArtistMBID: blur, ArtistName: "Blur", Title: "The Ballad of Darren", Reason: ReasonRequest, }); err != nil { t.Fatal(err) } seedMP3s(t, pool, 192, false) res, err := s.Pass(ctx) if err != nil || res.Held != 1 || res.Upgrades != 1 { t.Fatalf("pass: %+v, %v", res, err) } w, _ := q.GetWantByReleaseGroup(ctx, theBallad) if w.Upgrade != UpgradeQuality || w.HeldTier != string(release.TierLossyLow) || w.HeldQuality != "MP3, about 192 kbps" { t.Fatalf("want = upgrade %q, tier %q, %q", w.Upgrade, w.HeldTier, w.HeldQuality) } due, err := q.ListWantsDueForSearch(ctx, dbq.ListWantsDueForSearchParams{Hours: 24, MaxRows: 10}) if err != nil || len(due) != 1 || due[0].Upgrade != UpgradeQuality { t.Fatalf("due for search: %+v, %v", due, err) } // Upgrades switched off: no longer wanted again. if _, err := pool.Exec(ctx, `UPDATE acquisition_settings SET upgrade_until = ''`); err != nil { t.Fatal(err) } s.upgradesChecked = time.Time{} if _, err := s.Pass(ctx); err != nil { t.Fatal(err) } w, _ = q.GetWantByReleaseGroup(ctx, theBallad) if w.Upgrade != "" { t.Errorf("upgrades off: upgrade %q", w.Upgrade) } } func TestCheckUpgrades_ARipIsReplacedWhateverTheSetting_Integration(t *testing.T) { pool := newPool(t) q := dbq.New(pool) ctx := context.Background() s := newService(t, pool, newFakeMB(t)) if _, err := WantWith(ctx, q, WantParams{ GroupMBID: theBallad, ArtistMBID: blur, ArtistName: "Blur", Title: "The Ballad of Darren", Reason: ReasonRequest, }); err != nil { t.Fatal(err) } seedMP3s(t, pool, 320, true) if _, err := pool.Exec(ctx, `UPDATE acquisition_settings SET upgrade_until = ''`); err != nil { t.Fatal(err) } if _, err := s.Pass(ctx); err != nil { t.Fatal(err) } w, _ := q.GetWantByReleaseGroup(ctx, theBallad) if w.Upgrade != UpgradeRip { t.Errorf("a rip: upgrade %q", w.Upgrade) } } // A missing track with no disc number is on disc 1, so a present 1/2 stands in // for a missing "2". Air's Love 2 stayed wanted over one such row and was // downloaded again for nothing (2026-10-20). A missing track no present track // covers still counts. func TestWantHoldings_AMissingTrackWithNoDiscIsOnDiscOne_Integration(t *testing.T) { pool := newPool(t) ctx := context.Background() album := seedMP3s(t, pool, 320, false) if _, err := pool.Exec(ctx, `UPDATE tracks SET disc_number = 1 WHERE album_id = $1`, album.ID); err != nil { t.Fatal(err) } q := dbq.New(pool) for _, n := range []int32{2, 3} { num := n tr, err := q.UpsertTrack(ctx, dbq.UpsertTrackParams{ Title: fmt.Sprintf("Stale %d", n), AlbumID: album.ID, ArtistID: album.ArtistID, TrackNumber: &num, DurationMs: 200000, FilePath: fmt.Sprintf("/music/ballad/stale-%d.mp3", n), FileSize: 1, FileFormat: "mp3", }) if err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, `UPDATE tracks SET missing_since = now() - interval '2 days' WHERE id = $1`, tr.ID); err != nil { t.Fatal(err) } } rows, err := q.WantHoldings(ctx, dbq.WantHoldingsParams{ Since: pgtype.Timestamptz{Time: time.Now(), Valid: true}, GroupMbid: theBallad, AlbumID: album.ID, }) if err != nil { t.Fatal(err) } if len(rows) != 1 || rows[0].UncoveredMissing != 1 { t.Fatalf("holdings = %+v, want one album with 1 uncovered missing track (track 3 only)", rows) } }