from lyra_worker.adapters.fakes import FakeMbBrowser from lyra_worker.browser import ReleaseGroupInfo from lyra_worker.scan import build_worklist, scan_chunk, clear_worklist from lyra_worker.types import MBTarget from tests.test_scan import FakeProbe, FakeResolver, _album, _counts def _tree(tmp_path): """Three albums across two artists: Artist A/Alpha, Artist A/Beta, Artist B/Gamma.""" _album(tmp_path, "Artist A", "Alpha (2001)") _album(tmp_path, "Artist A", "Beta (2002)") _album(tmp_path, "Artist B", "Gamma (2003)") resolver = FakeResolver({ ("Artist A", "Alpha"): MBTarget(artist="Artist A", album="Alpha", year=2001, rg_mbid="rg-a1", artist_mbid="ma"), ("Artist A", "Beta"): MBTarget(artist="Artist A", album="Beta", year=2002, rg_mbid="rg-a2", artist_mbid="ma"), ("Artist B", "Gamma"): MBTarget(artist="Artist B", album="Gamma", year=2003, rg_mbid="rg-b1", artist_mbid="mb"), }) browser = FakeMbBrowser(releases={ "ma": [ReleaseGroupInfo("rg-a1", "Alpha", "Album", (), "2001"), ReleaseGroupInfo("rg-a2", "Beta", "Album", (), "2002")], "mb": [ReleaseGroupInfo("rg-b1", "Gamma", "Album", (), "2003")], }) return resolver, browser def test_build_worklist_inserts_one_row_per_album(conn, tmp_path): resolver, _ = _tree(tmp_path) n = build_worklist(conn, "scan1", str(tmp_path)) assert n == 3 with conn.cursor() as cur: cur.execute('SELECT count(*) FROM "ScanWorkItem" WHERE "scanId" = %s AND NOT done', ("scan1",)) assert cur.fetchone()[0] == 3 def test_build_worklist_is_idempotent(conn, tmp_path): resolver, _ = _tree(tmp_path) assert build_worklist(conn, "scan1", str(tmp_path)) == 3 assert build_worklist(conn, "scan1", str(tmp_path)) == 0 # ON CONFLICT DO NOTHING with conn.cursor() as cur: cur.execute('SELECT count(*) FROM "ScanWorkItem" WHERE "scanId" = %s', ("scan1",)) assert cur.fetchone()[0] == 3 def test_scan_chunk_respects_limit_and_marks_done(conn, tmp_path): resolver, browser = _tree(tmp_path) build_worklist(conn, "scan1", str(tmp_path)) imported, skipped, done = scan_chunk(conn, resolver, FakeProbe(quality_class=2), browser, "scan1", limit=2) assert (imported, skipped, done) == (2, 0, False) # 2 of 3 processed, more remain with conn.cursor() as cur: cur.execute('SELECT count(*) FROM "ScanWorkItem" WHERE "scanId" = %s AND done', ("scan1",)) assert cur.fetchone()[0] == 2 cur.execute('SELECT count(*) FROM "LibraryItem"') assert cur.fetchone()[0] == 2 def test_scan_chunk_resumes_and_finishes(conn, tmp_path): resolver, browser = _tree(tmp_path) build_worklist(conn, "scan1", str(tmp_path)) scan_chunk(conn, resolver, FakeProbe(quality_class=2), browser, "scan1", limit=2) imported, skipped, done = scan_chunk(conn, resolver, FakeProbe(quality_class=2), browser, "scan1", limit=25) assert (imported, skipped, done) == (1, 0, True) # last album, then drained with conn.cursor() as cur: cur.execute('SELECT album FROM "LibraryItem" ORDER BY album') assert cur.fetchall() == [("Alpha",), ("Beta",), ("Gamma",)] def test_scan_chunk_one_per_chunk_totals_correctly(conn, tmp_path): resolver, browser = _tree(tmp_path) build_worklist(conn, "scan1", str(tmp_path)) total_i = total_s = guard = 0 done = False while not done and guard < 10: i, s, done = scan_chunk(conn, resolver, FakeProbe(quality_class=2), browser, "scan1", limit=1) total_i += i total_s += s guard += 1 assert (total_i, total_s) == (3, 0) assert guard == 3 assert _counts(conn) == {"LibraryItem": 3, "MonitoredRelease": 3, "WatchedArtist": 2} def test_clear_worklist_removes_this_scans_rows(conn, tmp_path): resolver, _ = _tree(tmp_path) build_worklist(conn, "scan1", str(tmp_path)) clear_worklist(conn, "scan1") with conn.cursor() as cur: cur.execute('SELECT count(*) FROM "ScanWorkItem" WHERE "scanId" = %s', ("scan1",)) assert cur.fetchone()[0] == 0