feat: MusicBrainz-aware intake and completeness check against canonical track count

This commit is contained in:
Jonathan
2026-07-10 23:17:06 +02:00
parent cf5632aa5f
commit c16e9f1035
4 changed files with 96 additions and 2 deletions
+78
View File
@@ -0,0 +1,78 @@
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