diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_musicbrainz.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_musicbrainz.py | 98 |
1 files changed, 96 insertions, 2 deletions
diff --git a/packages/meshbay-node/tests/test_musicbrainz.py b/packages/meshbay-node/tests/test_musicbrainz.py index 482395d..4d075d3 100644 --- a/packages/meshbay-node/tests/test_musicbrainz.py +++ b/packages/meshbay-node/tests/test_musicbrainz.py @@ -4,8 +4,7 @@ import time import httpx import pytest - -from meshbay_node.musicbrainz import MusicBrainzClient, _MIN_INTERVAL_SECS +from meshbay_node.musicbrainz import _MIN_INTERVAL_SECS, MusicBrainzClient, _escape_lucene class FakeRoster: @@ -55,6 +54,101 @@ async def test_no_results_returns_none_and_zero_confidence(): await client.close() +def test_escape_lucene_escapes_query_syntax_characters(): + # Verified live against musicbrainz.org before writing the fallback + # below: a literal "(" inside a quoted phrase is read as query syntax, + # not a character to match, and silently drops the phrase to zero + # results rather than raising. + assert _escape_lucene("Hebron Gate (2003)") == r"Hebron Gate \(2003\)" + assert _escape_lucene('Say "It" Loud') == r"Say \"It\" Loud" + assert _escape_lucene("Rock & Roll: Live") == r"Rock \& Roll\: Live" + assert _escape_lucene("no specials here") == "no specials here" + + +@pytest.mark.asyncio +async def test_strict_match_does_not_pay_for_a_second_request(): + """A tag that already matches MusicBrainz's own spelling should cost + exactly one request — the fallback below exists for the case that + doesn't, not for every call.""" + calls = [] + + def handle(request: httpx.Request) -> httpx.Response: + calls.append(str(request.url)) + return httpx.Response(200, json={ + "releases": [{"id": "abc-123", "title": "The Great Album", + "artist-credit": [{"name": "Some Artist"}]}], + }) + + client = MusicBrainzClient( + roster=FakeRoster(), + transport=httpx.MockTransport(handle), + ) + await client.search_release("Some Artist", "The Great Album") + + assert len(calls) == 1 + await client.close() + + +@pytest.mark.asyncio +async def test_falls_back_to_a_loose_query_when_the_strict_one_finds_nothing(): + """ + Regression for the real failure mode found against the live service: + `artist:"Groundation" AND release:"Hebron Gate"` finds the release + (score 100), but a local album tag/folder carrying a trailing year — + "Hebron Gate (2003)", a common rip-folder shape — drops the strict + exact-phrase query to zero hits, not a low-scored one. The loose, + unscoped query must be tried next instead of stopping at "no match". + """ + calls = [] + + def handle(request: httpx.Request) -> httpx.Response: + query = request.url.params.get("query", "") + calls.append(query) + if query.startswith("artist:"): + return httpx.Response(200, json={"releases": []}) + return httpx.Response(200, json={ + "releases": [{"id": "xyz-789", "title": "Hebron Gate", + "artist-credit": [{"name": "Groundation"}]}], + }) + + client = MusicBrainzClient( + roster=FakeRoster(), + transport=httpx.MockTransport(handle), + ) + result, ratio = await client.search_release("Groundation", "Hebron Gate (2003)") + + assert result is not None + assert result["id"] == "xyz-789" + assert ratio > 0.5 + assert len(calls) == 2, "must retry with a loose query after the strict one finds nothing" + await client.close() + + +@pytest.mark.asyncio +async def test_confidence_reflects_a_wrong_artist_on_a_same_titled_release(): + """ + The loose fallback query has no field scoping at all, so a same- + titled release by an unrelated artist must not read as confidently as + one that also matches on artist — otherwise the fallback trades + "never finds a mismatch" for "sometimes confirms a wrong one". + """ + def handle(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={ + "releases": [{"id": "wrong-1", "title": "Live", + "artist-credit": [{"name": "An Entirely Different Band"}]}], + }) + + client = MusicBrainzClient( + roster=FakeRoster(), + transport=httpx.MockTransport(handle), + ) + result, ratio = await client.search_release("Groundation", "Live") + + assert result is not None + assert ratio < 0.6, "a title-only match against the wrong artist must not read as confident" + await client.close() + + @pytest.mark.asyncio async def test_no_contact_configured_makes_no_request(monkeypatch): monkeypatch.delenv("MESHBAY_MUSICBRAINZ_CONTACT_DEFAULT", raising=False) |