from typing import Any import pytest from pydantic import SecretStr from pytest_mock import MockerFixture from resonance_engine.adapters.deezer import DeezerClient from resonance_engine.dsp.backends.deezer import DeezerDSPBackend from resonance_engine.dsp.enums import DSPId, DSPResource def _noisy() -> list[dict[str, Any]]: return [ { "id": 1, "name": "Item", "picture": "p", "picture_big": "pb", "link": "l", "tracklist": "tl", "artist": {"id": 2, "name": "Artist", "picture_medium": "pm", "share": "s"}, } ] def _clean() -> list[dict[str, Any]]: return [ { "id": 1, "name": "Item", "artist": {"id": 2, "name": "Artist"}, } ] TOKEN = SecretStr("access-token") class TestDeezerDSPBackend: @pytest.fixture def client_mock(self, mocker: MockerFixture) -> Any: return mocker.create_autospec(DeezerClient, instance=True) @pytest.fixture def backend(self, client_mock: Any) -> DeezerDSPBackend: return DeezerDSPBackend(client_mock) def test_dsp_id(self, backend: DeezerDSPBackend) -> None: assert backend.dsp_id == DSPId.deezer def test_required_scope_is_permissive(self, backend: DeezerDSPBackend) -> None: # Deezer authenticates per-fan with no OAuth scopes — nothing is pre-gated. assert backend.required_scope(DSPResource.top_artists) is None assert backend.has_required_scope(DSPResource.playlists, "anything") def test_close_closes_client( self, backend: DeezerDSPBackend, client_mock: Any ) -> None: backend.close() client_mock.close.assert_called_once_with() def test_refresh_token_returns_access_token( self, backend: DeezerDSPBackend, client_mock: Any ) -> None: client_mock.refresh_token.return_value = {"access_token": SecretStr("at")} result = backend.refresh_token(SecretStr("acc")) assert result == {"access_token": SecretStr("at")} client_mock.refresh_token.assert_called_once_with(SecretStr("acc")) def test_get_profile_strips_noise( self, backend: DeezerDSPBackend, client_mock: Any ) -> None: client_mock.get_current_user_profile.return_value = _noisy()[0] assert backend.get_profile(TOKEN) == _clean()[0] client_mock.get_current_user_profile.assert_called_once_with(TOKEN) @pytest.mark.parametrize( ("backend_method", "client_method", "max_items"), [ ("get_top_artists", "get_current_user_top_artists", 50), ("get_top_tracks", "get_current_user_top_tracks", 50), ("get_playlists", "get_current_user_playlists", 100), ("get_saved_albums", "get_current_user_albums", 100), ("get_saved_tracks", "get_current_user_tracks", 100), ("get_followed_artists", "get_current_user_artists", 100), ], ) def test_strips_noise_keys_and_passes_max_items( self, backend: DeezerDSPBackend, client_mock: Any, backend_method: str, client_method: str, max_items: int, ) -> None: getattr(client_mock, client_method).return_value = _noisy() result = getattr(backend, backend_method)(TOKEN) assert result == _clean() getattr(client_mock, client_method).assert_called_once_with( TOKEN, max_items=max_items ) def test_recently_played_ignores_after_and_strips( self, backend: DeezerDSPBackend, client_mock: Any ) -> None: # Deezer history has no after cursor — the argument is accepted but unused. client_mock.get_current_user_history.return_value = _noisy() result = backend.get_recently_played(TOKEN, after=1700000000) assert result == _clean() client_mock.get_current_user_history.assert_called_once_with( TOKEN, max_items=50 )