from lyra_worker.adapters.youtube import YouTubeAdapter from lyra_worker.claim import claim_next from lyra_worker.pipeline import run_pipeline from lyra_worker.types import MBTarget from tests.conftest import insert_request from tests.test_youtube_adapter import FakeYtClient class FakeResolver: def __init__(self, target): self._target = target def resolve(self, artist, album): return self._target def _job_state(conn, job_id): with conn.cursor() as cur: cur.execute('SELECT state FROM "Job" WHERE id = %s', (job_id,)) return cur.fetchone()[0] def test_single_track_download_rejected_against_musicbrainz_count(conn): # MusicBrainz says Continuum has 12 tracks; a 1-track YouTube "full album" video must be rejected. resolver = FakeResolver(MBTarget(artist="John Mayer", album="Continuum", track_count=12)) yt = [{"source_ref": "yt1", "title": "Continuum", "artist": "John Mayer", "track_count": 1}] # FakeYtClient.download reports the same 1-track count for the single-video candidate: client = FakeYtClient(results=yt) client.download = lambda source_ref, dest, on_progress: (on_progress(1.0), {"track_count": 1, "path": dest})[1] job_id = insert_request(conn, artist="john mayer", album="continuum") claim_next(conn) run_pipeline(conn, job_id, [YouTubeAdapter(client)], resolver=resolver, dest_root="/tmp/lib") assert _job_state(conn, job_id) == "needs_attention" def test_complete_download_imports_with_musicbrainz_count(conn): resolver = FakeResolver(MBTarget(artist="John Mayer", album="Continuum", track_count=12)) yt = [{"source_ref": "yt1", "title": "Continuum", "artist": "John Mayer", "track_count": 12}] client = FakeYtClient(results=yt) client.download = lambda source_ref, dest, on_progress: (on_progress(1.0), {"track_count": 12, "path": dest})[1] job_id = insert_request(conn, artist="john mayer", album="continuum") claim_next(conn) run_pipeline(conn, job_id, [YouTubeAdapter(client)], resolver=resolver, dest_root="/tmp/lib") assert _job_state(conn, job_id) == "imported" with conn.cursor() as cur: # canonical artist/album from MusicBrainz are what get stored cur.execute('SELECT artist, album FROM "LibraryItem"') assert cur.fetchone() == ("John Mayer", "Continuum") def test_no_resolver_preserves_legacy_behavior(conn): # Without a resolver, target.track_count stays None and the candidate's own count is used. yt = [{"source_ref": "yt1", "title": "Continuum", "artist": "John Mayer", "track_count": 1}] client = FakeYtClient(results=yt) client.download = lambda source_ref, dest, on_progress: (on_progress(1.0), {"track_count": 1, "path": dest})[1] job_id = insert_request(conn, artist="John Mayer", album="Continuum") claim_next(conn) run_pipeline(conn, job_id, [YouTubeAdapter(client)], dest_root="/tmp/lib") # no resolver assert _job_state(conn, job_id) == "imported" # 1 == 1, passes as before def test_resolver_returning_none_falls_back_to_request(conn): resolver = FakeResolver(None) yt = [{"source_ref": "yt1", "title": "Continuum", "artist": "John Mayer", "track_count": 1}] client = FakeYtClient(results=yt) client.download = lambda source_ref, dest, on_progress: (on_progress(1.0), {"track_count": 1, "path": dest})[1] job_id = insert_request(conn, artist="John Mayer", album="Continuum") claim_next(conn) run_pipeline(conn, job_id, [YouTubeAdapter(client)], resolver=resolver, dest_root="/tmp/lib") assert _job_state(conn, job_id) == "imported" # falls back to raw target, 1==1