feat(bandcamp): add Bandcamp track lookup
This commit is contained in:
1 parent
b2eab64d12
commit
8e1c0a64eb
2 files changed
+587
No files matched your search
@@ -0,0 +1,219 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from somafm_tui 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 = """
|
||||
<html><body>
|
||||
<div data-tralbum='{"trackinfo": [
|
||||
{"title": "Rauschebart", "title_link": "/track/rauschebart?from=album", "track_id": 1},
|
||||
{"title": "Other Track", "title_link": "/track/other-track", "track_id": 2}
|
||||
]}'></div>
|
||||
</body></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 = """
|
||||
<script type="application/ld+json">
|
||||
{"@type": "MusicAlbum", "track": [
|
||||
{"@type": "MusicRecording", "name": "Rauschebart",
|
||||
"url": "https://jensbuchert.bandcamp.com/track/rauschebart"}
|
||||
]}
|
||||
</script>
|
||||
"""
|
||||
results = bandcamp.parse_album_tracks(
|
||||
json_ld, "https://jensbuchert.bandcamp.com/album/gravity"
|
||||
)
|
||||
assert results[0].url == "https://jensbuchert.bandcamp.com/track/rauschebart"
|
||||
|
||||
anchors = """
|
||||
<html><body>
|
||||
<a href="/track/rauschebart">Rauschebart</a>
|
||||
<a href="/album/gravity">Gravity</a>
|
||||
</body></html>
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_find_track_uses_track_search_first() -> None:
|
||||
requests: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
requests.append(request)
|
||||
assert request.method == "POST"
|
||||
assert json.loads(request.content) == {
|
||||
"search_text": "Jens Buchert Rauschebart",
|
||||
"search_filter": "t",
|
||||
"full_page": True,
|
||||
"fan_id": None,
|
||||
}
|
||||
return httpx.Response(200, json=TRACK_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 len(requests) == 1
|
||||
|
||||
|
||||
@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":
|
||||
return httpx.Response(200, json=ALBUM_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"
|
||||
assert len(requests) == 3
|
||||
search_bodies = [json.loads(request.content) for request in requests[:2]]
|
||||
assert [body["search_text"] for body in search_bodies] == [
|
||||
"Jens Buchert Rauschebart",
|
||||
"Jens Buchert Gravity",
|
||||
]
|
||||
assert [body["search_filter"] for body in search_bodies] == ["t", "a"]
|
||||
assert requests[2].method == "GET"
|
||||
assert requests[2].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="<html><title>Client Challenge</title></html>")
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(challenge)) as client:
|
||||
assert await bandcamp.find_track(client, "A", "B", "C") is None
|
||||
assert len(requests) == 2
|
||||
Reference in new issue
Block a user