import sqlite3 import sys import types import pytest 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 from core.metadata import album_tracks as metadata_album_tracks from core.metadata import registry as metadata_registry @pytest.fixture(autouse=True) def _clear_metadata_client_cache(): metadata_registry.clear_cached_metadata_clients() yield metadata_registry.clear_cached_metadata_clients() def _album(album_id="album-1", name="Album One", album_type="album"): return { "id": album_id, "name": name, "images": [{"url": f"https://img.example/{album_id}.jpg"}], "release_date": "2024-01-01", "album_type": album_type, "total_tracks": 1, } def _track(track_id="track-1", name="Track One"): return { "id": track_id, "name": name, "artists": [{"name": "Artist One"}], "duration_ms": 123456, "track_number": 1, "disc_number": 1, "explicit": "explicit", "preview_url": "https://preview.example/track-1", "external_urls": {"spotify": "https://example/track-1"}, "uri": f"spotify:track:{track_id}", } def test_get_artist_album_tracks_uses_primary_source_priority(monkeypatch): calls = [] monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"]) monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: object()) def fake_get_album_for_source(source, album_id): calls.append(("album", source, album_id)) return _album("album-1", "Album One") if source == "deezer" and album_id == "album-1" else None def fake_get_album_tracks_for_source(source, album_id): calls.append(("tracks", source, album_id)) return {"items": [_track()]} if source == "deezer" and album_id == "album-1" else None monkeypatch.setattr("core.metadata.album_tracks.get_album_for_source", fake_get_album_for_source) monkeypatch.setattr("core.metadata.album_tracks.get_album_tracks_for_source", fake_get_album_tracks_for_source) result = metadata_album_tracks.get_artist_album_tracks( "album-1", artist_name="Artist One", album_name="Album One", ) assert result["success"] is True assert result["source"] == "deezer" assert result["source_priority"] == ["deezer", "spotify", "itunes"] assert result["resolved_album_id"] == "album-1" assert result["album"]["image_url"] == "https://img.example/album-1.jpg" assert result["tracks"][0]["artists"] == ["Artist One"] assert result["tracks"][0]["explicit"] is True assert calls == [("album", "deezer", "album-1"), ("tracks", "deezer", "album-1")] def test_get_artist_album_tracks_resolves_database_album_reference(monkeypatch): calls = [] monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify", "itunes"]) monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: object()) def fake_get_album_for_source(source, album_id): calls.append(("album", source, album_id)) if source == "itunes" and album_id == "itunes-123": return _album("itunes-123", "Resolved Album") return None def fake_get_album_tracks_for_source(source, album_id): calls.append(("tracks", source, album_id)) if source == "itunes" and album_id == "itunes-123": return {"items": [_track("itunes-track-1", "Resolved Track")]} return None def fake_resolve_album_reference(album_id, preferred_source=None, album_name="", artist_name=""): assert album_id == "db-1" assert preferred_source == "itunes" return "itunes-123", "itunes" monkeypatch.setattr("core.metadata.album_tracks.get_album_for_source", fake_get_album_for_source) monkeypatch.setattr("core.metadata.album_tracks.get_album_tracks_for_source", fake_get_album_tracks_for_source) monkeypatch.setattr("core.metadata.album_tracks.resolve_album_reference", fake_resolve_album_reference) result = metadata_album_tracks.get_artist_album_tracks( "db-1", artist_name="Artist One", album_name="Album One", source_override="itunes", ) assert result["success"] is True assert result["source"] == "itunes" assert result["resolved_album_id"] == "itunes-123" assert result["tracks"][0]["name"] == "Resolved Track" assert ("album", "itunes", "itunes-123") in calls assert ("tracks", "itunes", "itunes-123") in calls def test_resolve_album_reference_prefers_stored_external_id(monkeypatch): conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("CREATE TABLE artists (id INTEGER PRIMARY KEY, name TEXT)") cursor.execute( """ CREATE TABLE albums ( id INTEGER PRIMARY KEY, title TEXT, artist_id INTEGER, spotify_album_id TEXT, itunes_album_id TEXT, deezer_id TEXT, deezer_album_id TEXT, discogs_id TEXT, soul_id TEXT, hydrabase_album_id TEXT ) """ ) cursor.execute("INSERT INTO artists (id, name) VALUES (1, 'Artist One')") cursor.execute( """ INSERT INTO albums (id, title, artist_id, deezer_id) VALUES (1, 'Album One', 1, 'deezer-abc') """ ) conn.commit() class _FakeDatabase: def _get_connection(self): return conn monkeypatch.setattr("database.music_database.get_database", lambda: _FakeDatabase()) monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify"]) resolved_id, resolved_source = metadata_album_tracks.resolve_album_reference("1", preferred_source="deezer") assert resolved_id == "deezer-abc" assert resolved_source == "deezer" def test_resolve_album_reference_searches_by_name_when_no_external_id_exists(monkeypatch): conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("CREATE TABLE artists (id INTEGER PRIMARY KEY, name TEXT)") cursor.execute( """ CREATE TABLE albums ( id INTEGER PRIMARY KEY, title TEXT, artist_id INTEGER, spotify_album_id TEXT, itunes_album_id TEXT, deezer_id TEXT, deezer_album_id TEXT, discogs_id TEXT, soul_id TEXT, hydrabase_album_id TEXT ) """ ) cursor.execute("INSERT INTO artists (id, name) VALUES (1, 'Artist One')") cursor.execute("INSERT INTO albums (id, title, artist_id) VALUES (1, 'Album One', 1)") conn.commit() class _FakeDatabase: def _get_connection(self): return conn class _FakeSearchClient: def __init__(self): self.calls = [] def search_albums(self, query, **kwargs): self.calls.append((query, dict(kwargs))) return [types.SimpleNamespace(id="searched-123", name="Album One")] fake_client = _FakeSearchClient() monkeypatch.setattr("database.music_database.get_database", lambda: _FakeDatabase()) monkeypatch.setattr(metadata_registry, "get_primary_source", lambda spotify_client_factory=None: "deezer") monkeypatch.setattr(metadata_registry, "get_source_priority", lambda primary: [primary, "spotify"]) monkeypatch.setattr(metadata_registry, "get_client_for_source", lambda source, **kwargs: fake_client if source == "deezer" else None) resolved_id, resolved_source = metadata_album_tracks.resolve_album_reference("1", preferred_source="deezer") assert resolved_id == "searched-123" assert resolved_source == "deezer" assert fake_client.calls == [("Artist One Album One", {"limit": 5})]