import sys import types if "spotipy" not in sys.modules: spotipy = types.ModuleType("spotipy") class _DummySpotify: def __init__(self, *args, **kwargs): pass oauth2 = types.ModuleType("spotipy.oauth2") class _DummyOAuth: def __init__(self, *args, **kwargs): pass spotipy.Spotify = _DummySpotify oauth2.SpotifyOAuth = _DummyOAuth oauth2.SpotifyClientCredentials = _DummyOAuth spotipy.oauth2 = oauth2 sys.modules["spotipy"] = spotipy sys.modules["spotipy.oauth2"] = oauth2 if "config.settings" not in sys.modules: config_pkg = types.ModuleType("config") settings_mod = types.ModuleType("config.settings") class _DummyConfigManager: def get(self, key, default=None): return default def get_active_media_server(self): return "primary" settings_mod.config_manager = _DummyConfigManager() config_pkg.settings = settings_mod sys.modules["config"] = config_pkg sys.modules["config.settings"] = settings_mod import types as pytypes from core.metadata import registry as metadata_registry from core.metadata import similar_artists as metadata_similar_artists class _FakeMusicMapResponse: def __init__(self, text): self.text = text def raise_for_status(self): return None class _FakeSourceClient: def __init__(self, results_by_query): self.results_by_query = results_by_query self.search_calls = [] def search_artists(self, query, **kwargs): self.search_calls.append((query, dict(kwargs))) return list(self.results_by_query.get(query, [])) def test_iter_musicmap_similar_artist_events_uses_source_priority(monkeypatch): html = """
""" deezer = _FakeSourceClient({ "Artist One": [pytypes.SimpleNamespace(id="dz-seed", name="Artist One")], "Similar Artist": [], }) itunes = _FakeSourceClient({ "Artist One": [pytypes.SimpleNamespace(id="it-seed", name="Artist One")], "Similar Artist": [ pytypes.SimpleNamespace( id="it-match", name="iTunes Canonical", image_url="https://itunes.example/it-match.jpg", genres=["indie", "alt"], popularity=77, ) ], }) spotify = _FakeSourceClient({}) monkeypatch.setattr(metadata_similar_artists.requests, "get", lambda *args, **kwargs: _FakeMusicMapResponse(html)) monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "itunes", "spotify"]) monkeypatch.setattr( metadata_registry, "get_client_for_source", lambda source, **kwargs: {"deezer": deezer, "itunes": itunes, "spotify": spotify}.get(source), ) events = list(metadata_similar_artists.iter_musicmap_similar_artist_events("Artist One", limit=5)) assert events[0]["type"] == "start" assert events[0]["source_priority"] == ["deezer", "itunes", "spotify"] assert events[1]["type"] == "artist" assert events[1]["artist"]["id"] == "it-match" assert events[1]["artist"]["name"] == "iTunes Canonical" assert events[1]["artist"]["image_url"] == "https://itunes.example/it-match.jpg" assert events[1]["artist"]["genres"] == ["indie", "alt"] assert events[1]["artist"]["popularity"] == 77 assert events[1]["artist"]["source"] == "itunes" assert events[-1]["type"] == "complete" assert events[-1]["complete"] is True assert events[-1]["total"] == 1 assert events[-1]["total_found"] == 1 assert [call[0] for call in deezer.search_calls] == ["Artist One", "Similar Artist"] assert [call[0] for call in itunes.search_calls] == ["Artist One", "Similar Artist"] assert spotify.search_calls == [("Artist One", {"limit": 1, "allow_fallback": False})] def test_iter_musicmap_similar_artist_events_enriches_itunes_images(monkeypatch): html = """ """ class _ItunesClient(_FakeSourceClient): def __init__(self): super().__init__({ "Artist One": [pytypes.SimpleNamespace(id="it-seed", name="Artist One")], "Similar Artist": [pytypes.SimpleNamespace(id="it-match", name="iTunes Canonical")], }) self.get_artist_calls = [] def get_artist(self, artist_id): self.get_artist_calls.append(artist_id) return { "id": artist_id, "name": "iTunes Canonical", "images": [{"url": "https://itunes.example/full-art.jpg"}], "genres": ["indie"], "popularity": 0, } itunes = _ItunesClient() monkeypatch.setattr(metadata_similar_artists.requests, "get", lambda *args, **kwargs: _FakeMusicMapResponse(html)) monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "itunes") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary]) monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: itunes if source == "itunes" else None) events = list(metadata_similar_artists.iter_musicmap_similar_artist_events("Artist One", limit=5)) artist_events = [event for event in events if event.get("type") == "artist"] assert len(artist_events) == 1 assert artist_events[0]["artist"]["image_url"] == "https://itunes.example/full-art.jpg" assert itunes.get_artist_calls == ["it-match"] def test_iter_musicmap_similar_artist_events_falls_back_to_itunes_album_art(monkeypatch): html = """ """ class _ItunesClient(_FakeSourceClient): def __init__(self): super().__init__({ "Artist One": [pytypes.SimpleNamespace(id="it-seed", name="Artist One")], "Similar Artist": [pytypes.SimpleNamespace(id="it-match", name="iTunes Canonical")], }) self.get_artist_calls = [] self.album_art_calls = [] def get_artist(self, artist_id): self.get_artist_calls.append(artist_id) return { "id": artist_id, "name": "iTunes Canonical", "images": [], "genres": ["indie"], "popularity": 0, } def _get_artist_image_from_albums(self, artist_id): self.album_art_calls.append(artist_id) return "https://itunes.example/album-art.jpg" itunes = _ItunesClient() monkeypatch.setattr(metadata_similar_artists.requests, "get", lambda *args, **kwargs: _FakeMusicMapResponse(html)) monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "itunes") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary]) monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: itunes if source == "itunes" else None) events = list(metadata_similar_artists.iter_musicmap_similar_artist_events("Artist One", limit=5)) artist_events = [event for event in events if event.get("type") == "artist"] assert len(artist_events) == 1 assert artist_events[0]["artist"]["image_url"] == "https://itunes.example/album-art.jpg" assert itunes.get_artist_calls == ["it-match"] assert itunes.album_art_calls == ["it-match"] def test_get_musicmap_similar_artists_returns_not_found_when_musicmap_missing(monkeypatch): html = """