"""Soulseek search fallback: the Soulseek server drops searches containing blocklisted terms (major-label artists / album titles) → 0 responses. search_album retries with album-only then artist-only, keeping only path-matching results so it never grabs a same-titled foreign album.""" import json import lyra_worker.adapters._slskd as slskd_mod from lyra_worker.adapters._slskd import ( SlskdClient, _keep_matching, _norm, _path_matches, ) # --- pure helpers ----------------------------------------------------------------------------- def test_norm_strips_diacritics_and_punctuation(): assert _norm("Beyoncé") == "beyonce" assert _norm("I Am… Sasha Fierce") == "i am sasha fierce" assert _norm("AC/DC") == "ac dc" def test_path_matches_is_accent_and_punctuation_tolerant(): p = r"@@bckfj\All\Beyonce\I Am... Sasha Fierce CD1\03 Halo.flac" assert _path_matches("Beyoncé", p) # accent folded assert _path_matches("I Am… Sasha Fierce", p) # ellipsis / punctuation ignored assert not _path_matches("Rihanna", p) def test_path_matches_short_needle_requires_whole_string(): assert _path_matches("U2", r"x\U2 - Achtung Baby\01.flac") assert not _path_matches("U2", r"x\Blur - 13\01.flac") def test_keep_matching_drops_foreign_and_empty_responses(): responses = [ {"username": "a", "files": [{"filename": r"x\Rihanna - Unapologetic\01.flac"}]}, {"username": "b", "files": [{"filename": r"x\Someone Else - Unapologetic\01.flac"}]}, ] kept = _keep_matching(responses, "Rihanna") assert [r["username"] for r in kept] == ["a"] # --- fallback orchestration (HTTP faked) ------------------------------------------------------ class _Resp: def __init__(self, payload): self._p = payload def raise_for_status(self): pass def json(self): return self._p class _FakeSearch: """Routes slskd search HTTP by URL: POST returns an id, GET on the search returns a completed state, GET on /responses returns the canned responses for that query. Records queries sent.""" def __init__(self, by_query): self.by_query = by_query # {searchText: [responses]} self.queries = [] # searchText values POSTed, in order self._id_query = {} self._n = 0 def post(self, url, **kw): text = kw["json"]["searchText"] self.queries.append(text) self._n += 1 sid = f"s{self._n}" self._id_query[sid] = text return _Resp({"id": sid}) def get(self, url, **kw): if url.endswith("/responses"): sid = url.rsplit("/", 2)[-2] return _Resp(self.by_query.get(self._id_query[sid], [])) return _Resp({"state": "Completed, TimedOut"}) def _client(): return SlskdClient({"slskd.url": "http://x", "slskd.api_key": "k", "slskd.downloadsDir": "/x"}) def _install(monkeypatch, fake): monkeypatch.setattr(slskd_mod, "requests", fake) monkeypatch.setattr(slskd_mod.time, "sleep", lambda _s: None) def test_primary_hit_does_not_trigger_fallback(monkeypatch): fake = _FakeSearch({ "Rihanna Unapologetic": [ {"username": "a", "hasFreeUploadSlot": True, "uploadSpeed": 1, "queueLength": 0, "files": [{"filename": r"x\Rihanna - Unapologetic\01.flac", "size": 5}]}, ], }) _install(monkeypatch, fake) out = _client().search_album("Rihanna", "Unapologetic") assert len(out) == 1 assert fake.queries == ["Rihanna Unapologetic"] # no fallback searches def test_album_only_fallback_recovers_blocklisted_artist(monkeypatch): # Primary (artist blocked) → 0; album-only returns the artist's folder → recovered. fake = _FakeSearch({ "Beyoncé I Am… Sasha Fierce": [], # blocked "i am sasha fierce": [ {"username": "a", "hasFreeUploadSlot": True, "uploadSpeed": 1, "queueLength": 0, "files": [{"filename": r"x\Beyonce - I Am... Sasha Fierce\01.flac", "size": 5}]}, {"username": "b", "hasFreeUploadSlot": True, "uploadSpeed": 1, "queueLength": 0, "files": [{"filename": r"x\Other Artist - I Am Sasha Fierce\01.flac", "size": 5}]}, ], }) _install(monkeypatch, fake) out = _client().search_album("Beyoncé", "I Am… Sasha Fierce") assert fake.queries == ["Beyoncé I Am… Sasha Fierce", "i am sasha fierce"] peers = [json.loads(c["source_ref"])["username"] for c in out] assert peers == ["a"] # foreign same-titled album dropped def test_artist_only_fallback_when_album_title_blocked(monkeypatch): # Album-only also blocked/empty → fall through to artist-only, filtered by album in path. fake = _FakeSearch({ "Foo Lemonade": [], "lemonade": [], # album title itself blocked "foo": [ {"username": "a", "hasFreeUploadSlot": True, "uploadSpeed": 1, "queueLength": 0, "files": [{"filename": r"x\Foo - Lemonade\01.flac", "size": 5}]}, ], }) _install(monkeypatch, fake) out = _client().search_album("Foo", "Lemonade") assert fake.queries == ["Foo Lemonade", "lemonade", "foo"] assert len(out) == 1 def test_fallback_yields_nothing_when_title_too_generic(monkeypatch): # 'Adele 21': artist blocked, album title '21' returns lots of non-Adele content → filtered # to nothing rather than grabbing a wrong album. fake = _FakeSearch({ "Adele 21": [], "21": [ {"username": "z", "hasFreeUploadSlot": True, "uploadSpeed": 1, "queueLength": 0, "files": [{"filename": r"x\21 Savage - Savage Mode\01.flac", "size": 5}]}, ], "adele": [], # artist blocked }) _install(monkeypatch, fake) out = _client().search_album("Adele", "21") assert out == []