from __future__ import annotations import json import httpx import pytest from beefy_player import bandcamp TRACK_PAYLOAD = { "auto": { "results": [ { "type": "t", "name": "Rauschebart", "band_name": "Jens Buchert", "album_name": "Gravity", "item_url_root": "https://jensbuchert.bandcamp.com", "item_url_path": ( "https://jensbuchert.bandcamp.com/track/rauschebart?from=search" ), } ] } } ALBUM_PAYLOAD = { "auto": { "results": [ { "type": "a", "name": "Gravity", "band_name": "Jens Buchert", "item_url_root": "https://jensbuchert.bandcamp.com", "item_url_path": ( "https://jensbuchert.bandcamp.com/album/gravity?from=search" ), } ] } } ALBUM_PAGE_HTML = """
""" def test_parse_search_payload() -> None: results = bandcamp.parse_search_payload(TRACK_PAYLOAD) assert len(results) == 1 result = results[0] assert result.title == "Rauschebart" assert result.artist == "Jens Buchert" assert result.album == "Gravity" assert result.item_type == "track" # Tracking parameters are removed. assert result.url == "https://jensbuchert.bandcamp.com/track/rauschebart" def test_parse_search_payload_accepts_custom_domains_and_skips_other_types() -> None: payload = { "auto": { "results": [ { "type": "t", "name": "Rauschebart", "band_name": "Jens Buchert", "album_name": "Gravity", "item_url_path": "https://music.example.com/track/rauschebart", }, { "type": "b", "name": "Jens Buchert", "item_url_root": "https://jensbuchert.bandcamp.com", }, {"type": "t", "name": "", "item_url_path": "https://x.test/track/x"}, ] } } results = bandcamp.parse_search_payload(payload) assert [result.url for result in results] == [ "https://music.example.com/track/rauschebart" ] def test_parse_album_tracks_from_tralbum() -> None: results = bandcamp.parse_album_tracks( ALBUM_PAGE_HTML, "https://jensbuchert.bandcamp.com/album/gravity" ) assert [entry.title for entry in results] == ["Rauschebart", "Other Track"] assert results[0].url == "https://jensbuchert.bandcamp.com/track/rauschebart" def test_parse_album_tracks_from_json_ld_and_anchor_fallback() -> None: json_ld = """ """ results = bandcamp.parse_album_tracks( json_ld, "https://jensbuchert.bandcamp.com/album/gravity" ) assert results[0].url == "https://jensbuchert.bandcamp.com/track/rauschebart" anchors = """ Rauschebart Gravity """ results = bandcamp.parse_album_tracks( anchors, "https://jensbuchert.bandcamp.com/album/gravity" ) assert [entry.title for entry in results] == ["Rauschebart"] def test_pick_track_and_album_ignore_mismatches() -> None: results = bandcamp.parse_search_payload( { "auto": { "results": [ { "type": "a", "name": "Gravity", "band_name": "Jens Buchert", "item_url_path": "https://x.bandcamp.com/album/gravity", }, { "type": "t", "name": "Rauschebart", "band_name": "Someone Else", "album_name": "Other", "item_url_path": "https://other.bandcamp.com/track/rauschebart", }, ] } } ) assert bandcamp.pick_track(results, "Jens Buchert", "Gravity", "Rauschebart") is None assert bandcamp.pick_album(results, "Jens Buchert", "Gravity") == ( "https://x.bandcamp.com/album/gravity" ) assert bandcamp.pick_album(results, "Someone Else", "Gravity") is None def test_build_query_normalises_for_autocomplete() -> None: assert bandcamp.build_query( " Diskette Park ", "Dreams — [Extended Mix] (Live)!" ) == "diskette park dreams extended mix live!" assert bandcamp.build_query("Don't Stop", "AC/DC") == "dont stop acdc" def test_build_prefixes_grows_in_small_typing_groups() -> None: assert bandcamp.build_prefixes("diskette park dreams") == [ "dis", "disket", "diskette ", "diskette par", "diskette park d", "diskette park dreams", ] assert bandcamp.build_prefixes("abcde") == ["abcde"] assert bandcamp.build_prefixes("") == [] def test_matches_is_case_and_whitespace_insensitive() -> None: assert bandcamp._matches("Jens Buchert", "jens buchert") assert bandcamp._matches("Rauschebart", "RAUSCHEBART (Live)") assert not bandcamp._matches("Dreams", "Something Else") @pytest.mark.asyncio async def test_find_track_uses_track_search_first() -> None: requests: list[httpx.Request] = [] match_text = "jens buchert ra" def handler(request: httpx.Request) -> httpx.Response: requests.append(request) assert request.method == "POST" body = json.loads(request.content) assert body["search_filter"] == "t" assert body["full_page"] is True assert body["fan_id"] is None payload = TRACK_PAYLOAD if body["search_text"] == match_text else { "auto": {"results": []} } return httpx.Response(200, json=payload) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: url = await bandcamp.find_track(client, "Jens Buchert", "Gravity", "Rauschebart") assert url == "https://jensbuchert.bandcamp.com/track/rauschebart" assert [json.loads(request.content)["search_text"] for request in requests] == [ "jen", "jens b", "jens buch", "jens buchert", "jens buchert ra", ] @pytest.mark.asyncio async def test_find_track_falls_back_to_album_then_track_on_page() -> None: requests: list[httpx.Request] = [] def handler(request: httpx.Request) -> httpx.Response: requests.append(request) if request.url.path == "/api/bcsearch_public_api/1/autocomplete_elastic": body = json.loads(request.content) if body["search_filter"] == "t": return httpx.Response(200, json={"auto": {"results": []}}) if body["search_filter"] == "a": payload = ( ALBUM_PAYLOAD if body["search_text"] == "jens buchert" else {"auto": {"results": []}} ) return httpx.Response(200, json=payload) if request.url.path == "/album/gravity": return httpx.Response(200, text=ALBUM_PAGE_HTML) return httpx.Response(404) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: url = await bandcamp.find_track( client, "Jens Buchert", "Gravity", "Rauschebart" ) assert url == "https://jensbuchert.bandcamp.com/track/rauschebart" search_bodies = [ json.loads(request.content) for request in requests if request.method == "POST" ] assert [body["search_text"] for body in search_bodies] == [ "jen", "jens b", "jens buch", "jens buchert", "jens buchert ra", "jens buchert rausc", "jens buchert rauscheb", "jens buchert rauschebart", "jen", "jens b", "jens buch", "jens buchert", ] assert [body["search_filter"] for body in search_bodies] == ["t"] * 8 + [ "a" ] * 4 assert requests[-1].method == "GET" assert requests[-1].url.path == "/album/gravity" @pytest.mark.asyncio async def test_find_track_returns_none_for_challenge_or_no_results() -> None: requests: list[httpx.Request] = [] def challenge(request: httpx.Request) -> httpx.Response: requests.append(request) return httpx.Response(200, text="