from __future__ import annotations import unittest from types import SimpleNamespace import httpx from app.clients.spotify import SpotifyClient class RecordingHttpClient: def __init__(self, responses: list[httpx.Response]) -> None: self._responses = list(responses) self.calls: list[dict] = [] async def request(self, method: str, url: str, **kwargs): self.calls.append( { "method": method, "url": url, "headers": kwargs.get("headers"), "params": kwargs.get("params"), "json": kwargs.get("json"), } ) if not self._responses: raise AssertionError("No queued response for request") return self._responses.pop(0) def make_response(method: str, url: str, status_code: int, *, json_body=None, text_body: str | None = None) -> httpx.Response: request = httpx.Request(method, url) if json_body is not None: return httpx.Response(status_code, request=request, json=json_body) return httpx.Response(status_code, request=request, text=text_body or "") def make_client(http_client: RecordingHttpClient) -> SpotifyClient: settings = SimpleNamespace( spotify_client_id="id", spotify_client_secret="secret", spotify_redirect_uri="https://example.test/callback", ) return SpotifyClient(settings, http_client) # type: ignore[arg-type] class SpotifyClientPlaylistTests(unittest.IsolatedAsyncioTestCase): async def test_create_playlist_uses_me_endpoint(self) -> None: http_client = RecordingHttpClient( [ make_response( "POST", "https://api.spotify.com/v1/me/playlists", 201, json_body={ "id": "pl1", "name": "Test Playlist", "external_urls": {"spotify": "https://open.spotify.com/playlist/pl1"}, }, ) ] ) client = make_client(http_client) payload = await client.create_playlist( "token", user_id="user123", name="Test Playlist", description="desc", public=False, ) self.assertEqual(payload["id"], "pl1") self.assertEqual(len(http_client.calls), 1) call = http_client.calls[0] self.assertEqual(call["method"], "POST") self.assertEqual(call["url"], "https://api.spotify.com/v1/me/playlists") self.assertEqual(call["json"], {"name": "Test Playlist", "description": "desc", "public": False}) self.assertEqual(call["headers"]["Authorization"], "Bearer token") async def test_delete_playlist_calls_followers_delete(self) -> None: http_client = RecordingHttpClient( [ make_response( "DELETE", "https://api.spotify.com/v1/playlists/pl123/followers", 200, text_body="", ) ] ) client = make_client(http_client) await client.delete_playlist("token", "pl123") self.assertEqual(len(http_client.calls), 1) call = http_client.calls[0] self.assertEqual(call["method"], "DELETE") self.assertEqual(call["url"], "https://api.spotify.com/v1/playlists/pl123/followers") self.assertEqual(call["headers"]["Authorization"], "Bearer token") self.assertIsNone(call["json"]) async def test_add_playlist_items_uses_items_endpoint_and_chunks(self) -> None: uris = [f"spotify:track:{i:02d}" for i in range(101)] http_client = RecordingHttpClient( [ make_response( "POST", "https://api.spotify.com/v1/playlists/pl999/items", 201, json_body={"snapshot_id": "snap1"}, ), make_response( "POST", "https://api.spotify.com/v1/playlists/pl999/items", 201, json_body={"snapshot_id": "snap2"}, ), ] ) client = make_client(http_client) await client.add_playlist_items("token", "pl999", uris) self.assertEqual(len(http_client.calls), 2) first, second = http_client.calls self.assertEqual(first["method"], "POST") self.assertEqual(first["url"], "https://api.spotify.com/v1/playlists/pl999/items") self.assertEqual(second["url"], "https://api.spotify.com/v1/playlists/pl999/items") self.assertEqual(len(first["json"]["uris"]), 100) self.assertEqual(len(second["json"]["uris"]), 1) self.assertEqual(first["headers"]["Authorization"], "Bearer token") if __name__ == "__main__": unittest.main()