from lyra_worker.adapters.fakes import FakeMbBrowser from lyra_worker.browser import ReleaseGroupInfo from lyra_worker.scan import scan_chunk from lyra_worker.types import MBTarget from tests.test_scan import FakeProbe, FakeResolver, _album, _counts def _tree(tmp_path): """Three albums across two artists, in nested-sorted order: 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_scan_chunk_respects_limit_and_returns_cursor(conn, tmp_path): resolver, browser = _tree(tmp_path) cursor, imported, skipped, done = scan_chunk( conn, resolver, FakeProbe(quality_class=2), browser, str(tmp_path), cursor="", limit=2) assert (imported, skipped, done) == (2, 0, False) # processed 2 of 3, more remain assert cursor == "Artist A/Beta (2002)" # last processed key with conn.cursor() as cur: cur.execute('SELECT count(*) FROM "LibraryItem"') assert cur.fetchone()[0] == 2 def test_scan_chunk_resumes_from_cursor(conn, tmp_path): resolver, browser = _tree(tmp_path) cursor, imported, skipped, done = scan_chunk( conn, resolver, FakeProbe(quality_class=2), browser, str(tmp_path), cursor="Artist A/Beta (2002)", limit=25) assert (imported, skipped, done) == (1, 0, True) # only Gamma remains assert cursor == "Artist B/Gamma (2003)" with conn.cursor() as cur: cur.execute('SELECT album FROM "LibraryItem"') assert cur.fetchall() == [("Gamma",)] # only Gamma imported def test_scan_chunk_cursor_past_end_is_noop(conn, tmp_path): resolver, browser = _tree(tmp_path) cursor, imported, skipped, done = scan_chunk( conn, resolver, FakeProbe(), browser, str(tmp_path), cursor="zzzz/zzzz", limit=10) assert (imported, skipped, done) == (0, 0, True) assert cursor == "zzzz/zzzz" # unchanged assert _counts(conn)["LibraryItem"] == 0 def test_scan_chunk_splits_artist_across_chunks_idempotently(conn, tmp_path): # limit=1 forces Artist A's two albums into separate chunks, re-browsing its # discography — which must stay idempotent (ON CONFLICT) and total the same as one scan. resolver, browser = _tree(tmp_path) cursor, done, total_i, total_s, guard = "", False, 0, 0, 0 while not done and guard < 10: cursor, i, s, done = scan_chunk( conn, resolver, FakeProbe(quality_class=2), browser, str(tmp_path), cursor=cursor, limit=1) total_i += i total_s += s guard += 1 assert (total_i, total_s) == (3, 0) assert guard == 3 # three albums, one per chunk, then done assert _counts(conn) == {"LibraryItem": 3, "MonitoredRelease": 3, "WatchedArtist": 2} def test_scan_chunk_unbounded_matches_full_walk(conn, tmp_path): resolver, browser = _tree(tmp_path) cursor, imported, skipped, done = scan_chunk( conn, resolver, FakeProbe(quality_class=2), browser, str(tmp_path), cursor="", limit=None) assert (imported, skipped, done) == (3, 0, True) assert cursor == "Artist B/Gamma (2003)" assert _counts(conn) == {"LibraryItem": 3, "MonitoredRelease": 3, "WatchedArtist": 2}