diff --git a/worker/lyra_worker/adapters/fakes.py b/worker/lyra_worker/adapters/fakes.py index 83ab6fe..a97072b 100644 --- a/worker/lyra_worker/adapters/fakes.py +++ b/worker/lyra_worker/adapters/fakes.py @@ -2,6 +2,7 @@ from typing import Callable from lyra_worker.browser import ArtistHit, ReleaseGroupInfo from lyra_worker.probe import ProbeResult +from lyra_worker.similarity.base import SimilarArtist from lyra_worker.types import Candidate, DownloadResult, MBTarget, Quality _QUALITIES = { @@ -90,3 +91,19 @@ class FakeAudioProbe: self._r = ProbeResult(artist=artist, album=album, fmt=fmt, quality_class=quality_class) def probe(self, path: str) -> ProbeResult: return self._r + + +class FakeSimilaritySource: + """In-memory SimilaritySource for tests. `similar` maps seed mbid -> [SimilarArtist].""" + + name = "fake" + + def __init__(self, similar=None, healthy=True): + self._similar = dict(similar or {}) + self._healthy = healthy + + def health(self) -> bool: + return self._healthy + + def similar_artists(self, mbid: str) -> list[SimilarArtist]: + return list(self._similar.get(mbid, [])) diff --git a/worker/lyra_worker/similarity/__init__.py b/worker/lyra_worker/similarity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/worker/lyra_worker/similarity/base.py b/worker/lyra_worker/similarity/base.py new file mode 100644 index 0000000..fef5675 --- /dev/null +++ b/worker/lyra_worker/similarity/base.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from typing import Protocol, runtime_checkable + + +@dataclass(frozen=True) +class SimilarArtist: + mbid: str + name: str + score: float + + +@runtime_checkable +class SimilaritySource(Protocol): + name: str + + def health(self) -> bool: + """Is this source reachable right now?""" + ... + + def similar_artists(self, mbid: str) -> list[SimilarArtist]: + """Artists similar to the given artist MBID (MBID-native).""" + ... diff --git a/worker/tests/test_similarity_base.py b/worker/tests/test_similarity_base.py new file mode 100644 index 0000000..805d626 --- /dev/null +++ b/worker/tests/test_similarity_base.py @@ -0,0 +1,14 @@ +from lyra_worker.adapters.fakes import FakeSimilaritySource +from lyra_worker.similarity.base import SimilarArtist, SimilaritySource + + +def test_fake_conforms_to_protocol_and_returns_similar(): + src = FakeSimilaritySource(similar={"seed": [SimilarArtist("c1", "Cand", 0.9)]}) + assert isinstance(src, SimilaritySource) + assert src.health() is True + assert src.similar_artists("seed") == [SimilarArtist("c1", "Cand", 0.9)] + assert src.similar_artists("unknown") == [] + + +def test_fake_can_report_unhealthy(): + assert FakeSimilaritySource(healthy=False).health() is False