Files
Lyra/worker/tests/test_lastfm_source.py
T
Jonathan 146bf32f23 feat(worker): Last.fm artist.getSimilar as a second discovery SimilaritySource
Adds LastfmSource, resolving Last.fm's often-empty artist mbid via the
injected MB browser (name->mbid cached) so every emitted SimilarArtist.mbid
is a real MusicBrainz artist MBID. Registered in build_similarity_sources,
gated on lastfm.api_key so the ListenBrainz-only path is unchanged when
unset.
2026-07-13 19:23:18 +02:00

109 lines
4.3 KiB
Python

from unittest.mock import MagicMock, patch
import requests
from lyra_worker.browser import ArtistHit
from lyra_worker.similarity._lastfm import LastfmSource
from lyra_worker.similarity.base import SimilarArtist
def _resp(payload, status=200):
r = MagicMock()
r.status_code = status
r.json.return_value = payload
return r
class _FakeBrowser:
"""Records search_artist calls; resolves via a name -> [ArtistHit] map."""
def __init__(self, hits=None):
self._hits = dict(hits or {})
self.calls = []
def search_artist(self, name):
self.calls.append(name)
return list(self._hits.get(name, []))
def test_similar_artists_parses_and_uses_supplied_mbid():
payload = {"similarartists": {"artist": [
{"name": "Cand One", "mbid": "c1", "match": "0.9"},
]}}
browser = _FakeBrowser()
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == [SimilarArtist("c1", "Cand One", 0.9)]
assert browser.calls == [] # mbid supplied, no resolution needed
def test_similar_artists_resolves_missing_mbid_and_caches():
payload = {"similarartists": {"artist": [
{"name": "Cand Two", "mbid": "", "match": "0.5"},
{"name": "Cand Two", "mbid": "", "match": "0.4"},
]}}
browser = _FakeBrowser(hits={"Cand Two": [ArtistHit(mbid="c2", name="Cand Two")]})
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == [
SimilarArtist("c2", "Cand Two", 0.5),
SimilarArtist("c2", "Cand Two", 0.4),
]
assert browser.calls == ["Cand Two"] # cached after first lookup
def test_similar_artists_skips_unresolvable_row():
payload = {"similarartists": {"artist": [
{"name": "Unknown Artist", "mbid": "", "match": "0.5"},
{"name": "Cand One", "mbid": "c1", "match": "0.9"},
]}}
browser = _FakeBrowser(hits={}) # no hits for "Unknown Artist"
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == [SimilarArtist("c1", "Cand One", 0.9)]
def test_similar_artists_skips_self():
payload = {"similarartists": {"artist": [
{"name": "Seed", "mbid": "seed", "match": "1.0"},
{"name": "Cand One", "mbid": "c1", "match": "0.9"},
]}}
browser = _FakeBrowser()
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == [SimilarArtist("c1", "Cand One", 0.9)]
def test_similar_artists_coerces_single_object():
payload = {"similarartists": {"artist": {"name": "Cand One", "mbid": "c1", "match": "0.9"}}}
browser = _FakeBrowser()
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == [SimilarArtist("c1", "Cand One", 0.9)]
def test_similar_artists_error_envelope_returns_empty_list():
payload = {"error": 6, "message": "no artist"}
browser = _FakeBrowser()
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
out = LastfmSource(api_key="k", browser=browser).similar_artists("seed")
assert out == []
def test_health_true_on_normal_response():
payload = {"similarartists": {"artist": [{"name": "Radiohead", "mbid": "x", "match": "1.0"}]}}
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
assert LastfmSource(api_key="k", browser=_FakeBrowser()).health() is True
def test_health_false_on_error_envelope():
payload = {"error": 10, "message": "invalid api key"}
with patch("lyra_worker.similarity._lastfm.requests.get", return_value=_resp(payload)):
assert LastfmSource(api_key="k", browser=_FakeBrowser()).health() is False
def test_health_false_on_request_exception():
with patch("lyra_worker.similarity._lastfm.requests.get",
side_effect=requests.ConnectionError("down")):
assert LastfmSource(api_key="k", browser=_FakeBrowser()).health() is False