From 2ca0f7a38598856debf8933b8df25e44faccf34c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 11 Jul 2026 12:12:17 +0200 Subject: [PATCH] feat: MbBrowser seam and FakeMbBrowser test double Co-Authored-By: Claude Opus 4.8 (1M context) --- worker/lyra_worker/adapters/fakes.py | 15 +++++++++++++++ worker/lyra_worker/browser.py | 28 ++++++++++++++++++++++++++++ worker/tests/test_browser.py | 14 ++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 worker/lyra_worker/browser.py create mode 100644 worker/tests/test_browser.py diff --git a/worker/lyra_worker/adapters/fakes.py b/worker/lyra_worker/adapters/fakes.py index be47239..feab4b0 100644 --- a/worker/lyra_worker/adapters/fakes.py +++ b/worker/lyra_worker/adapters/fakes.py @@ -1,5 +1,6 @@ from typing import Callable +from lyra_worker.browser import ArtistHit, ReleaseGroupInfo from lyra_worker.types import Candidate, DownloadResult, MBTarget, Quality _QUALITIES = { @@ -66,3 +67,17 @@ class FailingAdapter(_BaseFake): self, candidate: Candidate, dest: str, on_progress: Callable[[float], None] ) -> DownloadResult: return DownloadResult(ok=False, error="simulated download failure") + + +class FakeMbBrowser: + """In-memory MbBrowser for tests. `releases` maps artist mbid -> [ReleaseGroupInfo].""" + + def __init__(self, artists=None, releases=None): + self._artists = list(artists or []) + self._releases = dict(releases or {}) + + def search_artist(self, name: str) -> list[ArtistHit]: + return list(self._artists) + + def browse_release_groups(self, artist_mbid: str) -> list[ReleaseGroupInfo]: + return list(self._releases.get(artist_mbid, [])) diff --git a/worker/lyra_worker/browser.py b/worker/lyra_worker/browser.py new file mode 100644 index 0000000..84932a3 --- /dev/null +++ b/worker/lyra_worker/browser.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass, field +from typing import Protocol + + +@dataclass(frozen=True) +class ArtistHit: + mbid: str + name: str + disambiguation: str = "" + + +@dataclass(frozen=True) +class ReleaseGroupInfo: + rg_mbid: str + title: str + primary_type: str = "" + secondary_types: tuple[str, ...] = () + first_release_date: str = "" + + +class MbBrowser(Protocol): + def search_artist(self, name: str) -> list[ArtistHit]: + """Artist name search → ranked hits (best first).""" + ... + + def browse_release_groups(self, artist_mbid: str) -> list[ReleaseGroupInfo]: + """All official release-groups for an artist MBID.""" + ... diff --git a/worker/tests/test_browser.py b/worker/tests/test_browser.py new file mode 100644 index 0000000..0d191e3 --- /dev/null +++ b/worker/tests/test_browser.py @@ -0,0 +1,14 @@ +from lyra_worker.adapters.fakes import FakeMbBrowser +from lyra_worker.browser import ArtistHit, ReleaseGroupInfo + + +def test_fake_browser_returns_canned_artists_and_releases(): + rg = ReleaseGroupInfo(rg_mbid="rg-1", title="Continuum", primary_type="Album", + secondary_types=(), first_release_date="2006-09-12") + browser = FakeMbBrowser( + artists=[ArtistHit(mbid="a1", name="John Mayer", disambiguation="")], + releases={"a1": [rg]}, + ) + assert browser.search_artist("john")[0].name == "John Mayer" + assert browser.browse_release_groups("a1") == [rg] + assert browser.browse_release_groups("unknown") == []