import re from datetime import datetime, timezone import pytest from ntr_fetcher.soundcloud import SoundCloudClient FAKE_HTML = """
""" FAKE_HTML_EXPIRING = """ """ @pytest.mark.asyncio async def test_extract_client_id(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) client = SoundCloudClient() client_id = await client._extract_client_id() assert client_id == "test_client_id_abc123" @pytest.mark.asyncio async def test_extract_client_id_caches(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) client = SoundCloudClient() id1 = await client._extract_client_id() id2 = await client._extract_client_id() assert id1 == id2 assert len(httpx_mock.get_requests()) == 1 @pytest.mark.asyncio async def test_extract_client_id_bad_html(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text="no hydration here") client = SoundCloudClient() with pytest.raises(ValueError, match="client_id"): await client._extract_client_id() FAKE_RESOLVE_RESPONSE = {"id": 206979918, "kind": "user", "username": "NICKtheRAT"} FAKE_LIKES_RESPONSE = { "collection": [ { "created_at": "2026-03-09T02:25:43Z", "kind": "like", "track": { "id": 12345, "title": "Test Track", "permalink_url": "https://soundcloud.com/artist/test-track", "duration": 180000, "full_duration": 180000, "genre": "Electronic", "tag_list": "", "created_at": "2026-03-01T00:00:00Z", "description": "", "artwork_url": "https://i1.sndcdn.com/artworks-abc-large.jpg", "license": "cc-by", "user": { "id": 999, "username": "TestArtist", "permalink_url": "https://soundcloud.com/testartist", }, "media": {"transcodings": []}, }, } ], "next_href": None, } @pytest.mark.asyncio async def test_resolve_user(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) httpx_mock.add_response( url=re.compile(r"https://api-v2\.soundcloud\.com/resolve.*"), json=FAKE_RESOLVE_RESPONSE, ) client = SoundCloudClient() user_id = await client.resolve_user("nicktherat") assert user_id == 206979918 @pytest.mark.asyncio async def test_fetch_likes(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) httpx_mock.add_response( url=re.compile(r"https://api-v2\.soundcloud\.com/users/206979918/likes.*"), json=FAKE_LIKES_RESPONSE, ) client = SoundCloudClient() tracks = await client.fetch_likes( user_id=206979918, since=datetime(2026, 3, 1, 0, 0, 0, tzinfo=timezone.utc), until=datetime(2026, 3, 10, 0, 0, 0, tzinfo=timezone.utc), ) assert len(tracks) == 1 assert tracks[0].title == "Test Track" assert tracks[0].artist == "TestArtist" assert tracks[0].id == 12345 @pytest.mark.asyncio async def test_fetch_likes_filters_outside_range(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) httpx_mock.add_response( url=re.compile(r"https://api-v2\.soundcloud\.com/users/206979918/likes.*"), json=FAKE_LIKES_RESPONSE, ) client = SoundCloudClient() tracks = await client.fetch_likes( user_id=206979918, since=datetime(2026, 3, 10, 0, 0, 0, tzinfo=timezone.utc), until=datetime(2026, 3, 12, 0, 0, 0, tzinfo=timezone.utc), ) assert len(tracks) == 0 @pytest.mark.asyncio async def test_fetch_likes_retries_on_401(httpx_mock): httpx_mock.add_response(url="https://soundcloud.com", text=FAKE_HTML) httpx_mock.add_response( url=re.compile(r"https://api-v2\.soundcloud\.com/users/206979918/likes.*"), status_code=401, ) httpx_mock.add_response( url="https://soundcloud.com", text=FAKE_HTML.replace("test_client_id_abc123", "new_client_id_456"), ) httpx_mock.add_response( url=re.compile(r"https://api-v2\.soundcloud\.com/users/206979918/likes.*"), json=FAKE_LIKES_RESPONSE, ) client = SoundCloudClient() tracks = await client.fetch_likes( user_id=206979918, since=datetime(2026, 3, 1, 0, 0, 0, tzinfo=timezone.utc), until=datetime(2026, 3, 10, 0, 0, 0, tzinfo=timezone.utc), ) assert len(tracks) == 1