soulsync/tests/quality/test_engine_fallback.py
dev 8934c6418c fix(quality): correct ranking behaviour flagged in PR #896 (#3, #4)
#3 priority mode is quality-agnostic again: search_with_fallback (the
priority/hybrid path — best_quality has its own search_all_sources) returned
the first source whose results met a target, so an mp3 with bitrate=None (slskd
omits it often) was deemed unsatisfied and deprioritised, changing which source
wins for users who never opted in. Restore "first source with tracks wins",
byte-for-byte; cross-source quality pooling stays in best_quality mode.

#4 metadata-less FLAC no longer over-claims a hi-res target: matches_target let
a FLAC with no sample_rate/bit_depth satisfy a 24-bit/192k target while a real
16/44 FLAC failed it, so unknown-spec files outranked and discarded genuine CD
FLAC under audiophile/hi-res profiles. An unconfirmable spec now fails the strict
tier and falls to the plain-flac bucket.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:25:36 +02:00

127 lines
4.2 KiB
Python

"""Engine ``search_with_fallback`` is the PRIORITY-mode search path and is
deliberately quality-agnostic: the first source in the chain that returns any
tracks wins (source order is king), byte-for-byte like the pre-quality-system
hybrid loop (#896 review #3). It skips unconfigured/unavailable sources,
swallows per-source exceptions, and returns RAW tracks (match-filtering +
final quality ranking happen later). Cross-source quality pooling lives in
best_quality mode (``search_all_sources``), not here.
"""
import asyncio
import pytest
from core.download_engine.engine import DownloadEngine
from core.quality.model import AudioQuality
class _Cand:
def __init__(self, aq, name):
self.audio_quality = aq
self.name = name
class _FakePlugin:
def __init__(self, tracks, configured=True, raises=False):
self._tracks = tracks
self._configured = configured
self._raises = raises
self.searched = False
def is_configured(self):
return self._configured
async def search(self, query, timeout=None, progress_callback=None):
self.searched = True
if self._raises:
raise RuntimeError("boom")
return (self._tracks, [])
FLAC = AudioQuality('flac', sample_rate=44100, bit_depth=16)
MP3 = AudioQuality('mp3', bitrate=320)
MP3_NO_BITRATE = AudioQuality('mp3') # slskd often omits bitrate
def _engine_with(plugins):
eng = object.__new__(DownloadEngine)
eng._plugins = plugins
return eng
def test_returns_first_source_with_tracks_source_priority_king():
first = _FakePlugin([_Cand(MP3, 'a-mp3')])
second = _FakePlugin([_Cand(FLAC, 'b-flac')])
eng = _engine_with({'first': first, 'second': second})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert [t.name for t in tracks] == ['a-mp3'] # first source wins regardless of quality
assert second.searched is False # never queried — priority is king
def test_metadata_less_mp3_still_wins_in_priority_mode():
"""An mp3 whose bitrate slskd omitted must NOT be deprioritised in priority
mode — the whole point of #896 review #3."""
first = _FakePlugin([_Cand(MP3_NO_BITRATE, 'a-mp3')])
second = _FakePlugin([_Cand(FLAC, 'b-flac')])
eng = _engine_with({'first': first, 'second': second})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert [t.name for t in tracks] == ['a-mp3']
assert second.searched is False
def test_skips_to_next_source_when_first_empty():
first = _FakePlugin([])
second = _FakePlugin([_Cand(FLAC, 'b-flac')])
eng = _engine_with({'first': first, 'second': second})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert [t.name for t in tracks] == ['b-flac']
assert second.searched is True
def test_returns_raw_tracks_not_pruned():
# All of the winning source's tracks come back so the orchestrator's match
# filter can pick the correct one.
first = _FakePlugin([_Cand(MP3, 'one'), _Cand(FLAC, 'two')])
eng = _engine_with({'first': first})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first']))
assert {t.name for t in tracks} == {'one', 'two'}
def test_skips_unconfigured_source():
first = _FakePlugin([_Cand(FLAC, 'a')], configured=False)
second = _FakePlugin([_Cand(MP3, 'b')])
eng = _engine_with({'first': first, 'second': second})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert [t.name for t in tracks] == ['b']
assert first.searched is False
def test_swallows_per_source_exception_and_continues():
first = _FakePlugin([], raises=True)
second = _FakePlugin([_Cand(MP3, 'b')])
eng = _engine_with({'first': first, 'second': second})
tracks, _ = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert [t.name for t in tracks] == ['b']
def test_returns_empty_when_all_sources_empty():
first = _FakePlugin([])
second = _FakePlugin([])
eng = _engine_with({'first': first, 'second': second})
tracks, albums = asyncio.run(eng.search_with_fallback('q', ['first', 'second']))
assert tracks == []
assert albums == []