Ashh: the manual-match modal fuzzy-searches a service and shows the top 8. When the right release isn't in those 8 (common title — their example was "Idols", which returns 8 unrelated releases and not Yungblud's), there was no way through. But the user usually already knows the exact MBID. Now the modal's search box doubles as a direct-ID box. Paste a MusicBrainz MBID (bare UUID or a musicbrainz.org URL) and SoulSync looks that exact entity up and shows it as the single result to confirm + Match — no fighting the search ranking. - core/library/direct_id.py: pure detector, returns the canonical ID only when the text unambiguously IS one (whole-query UUID, or a UUID inside a musicbrainz.org URL). "Idols", "Yungblud Idols", a UUID buried in free text → None, so normal search is never hijacked. - _search_service: direct-ID fast path before the fuzzy search — get_release (→ get_release_group fallback for albums) / get_artist / get_recording. A pasted-but-unresolvable ID falls THROUGH to fuzzy search, so a typo can't dead-end the modal. - UI: MusicBrainz placeholder now says "…or paste a MusicBrainz ID/URL". Detector is service-keyed so Spotify/iTunes/etc. direct IDs can be added later; today only MusicBrainz has a confirmable direct lookup, matching the reporter's ask + screenshot. 9 tests: detector truth table (bare/URL/plain/ buried/other-service) + dispatch (confirmed release, release-group fallback, unresolvable→fuzzy, plain query skips direct lookup).
113 lines
4.5 KiB
Python
113 lines
4.5 KiB
Python
"""Direct-ID manual matching (Ashh: 'just slap MB ID in that search').
|
|
|
|
When the right release isn't in the top-8 fuzzy results, the user pastes the
|
|
exact ID. extract_direct_id detects it (pure); _search_service confirms it
|
|
via a direct lookup and returns just that entity, falling back to fuzzy
|
|
search if the paste only looks ID-ish.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
from core.library.direct_id import extract_direct_id
|
|
|
|
MBID = "1af02ea7-3f00-40ca-804b-41e2dca7e4a9"
|
|
|
|
|
|
# ── pure detector ────────────────────────────────────────────────────────────
|
|
|
|
def test_bare_mbid_detected():
|
|
assert extract_direct_id("musicbrainz", "album", MBID) == MBID
|
|
assert extract_direct_id("musicbrainz", "artist", MBID.upper()) == MBID # normalized
|
|
|
|
|
|
def test_mbid_in_url_detected():
|
|
for url in (
|
|
f"https://musicbrainz.org/release/{MBID}",
|
|
f"https://musicbrainz.org/release/{MBID}/cover-art",
|
|
f" https://beta.musicbrainz.org/artist/{MBID} ",
|
|
):
|
|
assert extract_direct_id("musicbrainz", "album", url) == MBID
|
|
|
|
|
|
def test_plain_text_query_is_not_an_id():
|
|
assert extract_direct_id("musicbrainz", "album", "Idols") is None
|
|
assert extract_direct_id("musicbrainz", "album", "Yungblud Idols") is None
|
|
assert extract_direct_id("musicbrainz", "album", "") is None
|
|
assert extract_direct_id("musicbrainz", "album", " ") is None
|
|
|
|
|
|
def test_loose_uuid_without_url_context_is_rejected():
|
|
# A UUID embedded in free text (not the whole query, no MB URL) is NOT
|
|
# treated as a direct ID — avoids hijacking a genuine search.
|
|
assert extract_direct_id("musicbrainz", "album", f"album {MBID} deluxe") is None
|
|
|
|
|
|
def test_non_musicbrainz_services_have_no_direct_id_yet():
|
|
assert extract_direct_id("spotify", "album", MBID) is None
|
|
assert extract_direct_id("deezer", "track", "12345") is None
|
|
|
|
|
|
# ── _search_service direct dispatch ──────────────────────────────────────────
|
|
|
|
def _wire_mb(monkeypatch, **methods):
|
|
import core.library.service_search as ss
|
|
mb_client = MagicMock(**methods)
|
|
worker = SimpleNamespace(mb_service=SimpleNamespace(mb_client=mb_client))
|
|
monkeypatch.setattr(ss, "mb_worker", worker)
|
|
return ss, mb_client
|
|
|
|
|
|
def test_pasted_mbid_returns_single_confirmed_release(monkeypatch):
|
|
ss, mb_client = _wire_mb(monkeypatch)
|
|
mb_client.get_release.return_value = {
|
|
"id": MBID, "title": "Idols", "date": "2025-06-20",
|
|
"artist-credit": [{"name": "Yungblud"}],
|
|
}
|
|
results = ss._search_service("musicbrainz", "album", MBID)
|
|
|
|
assert len(results) == 1
|
|
assert results[0]["id"] == MBID
|
|
assert results[0]["name"] == "Idols"
|
|
assert "Direct ID match" in results[0]["extra"]
|
|
assert "Yungblud" in results[0]["extra"]
|
|
mb_client.get_release.assert_called_once_with(MBID)
|
|
mb_client.search_release.assert_not_called() # never fuzzy-searched
|
|
|
|
|
|
def test_album_falls_back_to_release_group(monkeypatch):
|
|
ss, mb_client = _wire_mb(
|
|
monkeypatch,
|
|
get_release=lambda mbid: None,
|
|
get_release_group=lambda mbid: {"id": MBID, "title": "Idols", "artist-credit": []},
|
|
)
|
|
results = ss._search_service("musicbrainz", "album", MBID)
|
|
assert len(results) == 1 and results[0]["name"] == "Idols"
|
|
|
|
|
|
def test_unresolvable_mbid_falls_through_to_fuzzy(monkeypatch):
|
|
# ID-shaped but doesn't resolve → don't dead-end; run the normal search.
|
|
ss, mb_client = _wire_mb(
|
|
monkeypatch,
|
|
get_release=lambda mbid: None,
|
|
get_release_group=lambda mbid: None,
|
|
search_release=lambda q, limit=8, strict=False: [
|
|
{"id": "other", "title": "Idols (fuzzy)", "artist-credit": [], "date": "", "score": 90},
|
|
],
|
|
)
|
|
results = ss._search_service("musicbrainz", "album", MBID)
|
|
assert len(results) == 1 and results[0]["id"] == "other" # fuzzy result
|
|
|
|
|
|
def test_plain_query_skips_direct_lookup(monkeypatch):
|
|
ss, mb_client = _wire_mb(
|
|
monkeypatch,
|
|
search_release=lambda q, limit=8, strict=False: [
|
|
{"id": "r1", "title": "Idols", "artist-credit": [], "date": "", "score": 100},
|
|
],
|
|
)
|
|
results = ss._search_service("musicbrainz", "album", "Idols")
|
|
assert results[0]["id"] == "r1"
|
|
mb_client.get_release.assert_not_called() # no wasted direct lookup
|