`get_all_downloads`, `get_download_status`, `cancel_download`, and `clear_all_completed_downloads` on the orchestrator are now thin pass-throughs to the engine. The plugin-iteration logic lives in one place (the engine) instead of duplicated across orchestrator methods. Source-hint routing semantics preserved verbatim — engine.cancel treats streaming-source names as direct routes and unknown names as Soulseek peer usernames, exactly like the legacy orchestrator did. Per-plugin exceptions still get swallowed defensively. Test fixture `_build_orchestrator` now constructs an engine and registers every mock plugin so the helper-built orchestrators have the same wiring as production. Suite still green (2012 passed). Zero behavior change for users.
102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
from core.download_engine import DownloadEngine
|
|
from core.download_orchestrator import DownloadOrchestrator
|
|
from core.download_plugins.registry import DownloadPluginRegistry, PluginSpec
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, configured=True, clear_result=True):
|
|
self.configured = configured
|
|
self.clear_result = clear_result
|
|
self.clear_calls = 0
|
|
|
|
def is_configured(self):
|
|
return self.configured
|
|
|
|
async def clear_all_completed_downloads(self):
|
|
self.clear_calls += 1
|
|
return self.clear_result
|
|
|
|
|
|
def _build_orchestrator(**clients):
|
|
"""Build an orchestrator with mock clients via the registry.
|
|
|
|
The orchestrator iterates `self.registry.all_plugins()` to drive
|
|
every per-source operation, so the test must set up a real
|
|
registry with mock plugins (not just stuff attributes on the
|
|
orchestrator). Source slots not provided in `clients` are
|
|
skipped — registry only holds the ones the test cares about.
|
|
"""
|
|
registry = DownloadPluginRegistry()
|
|
name_to_display = {
|
|
'soulseek': 'Soulseek', 'youtube': 'YouTube', 'tidal': 'Tidal',
|
|
'qobuz': 'Qobuz', 'hifi': 'HiFi', 'deezer_dl': 'Deezer',
|
|
'lidarr': 'Lidarr', 'soundcloud': 'SoundCloud',
|
|
}
|
|
# 'deezer_dl' is the legacy attr name; canonical registry name is 'deezer'.
|
|
aliases_for = {'deezer_dl': ('deezer_dl',)}
|
|
canonical_for = {'deezer_dl': 'deezer'}
|
|
|
|
for slot, client in clients.items():
|
|
if client is None:
|
|
continue
|
|
canonical_name = canonical_for.get(slot, slot)
|
|
registry.register(PluginSpec(
|
|
name=canonical_name,
|
|
factory=lambda c=client: c,
|
|
display_name=name_to_display.get(slot, slot),
|
|
aliases=aliases_for.get(slot, ()),
|
|
))
|
|
registry.initialize()
|
|
|
|
orch = DownloadOrchestrator.__new__(DownloadOrchestrator)
|
|
orch.registry = registry
|
|
orch._init_failures = registry.init_failures
|
|
orch.soulseek = registry.get('soulseek')
|
|
orch.youtube = registry.get('youtube')
|
|
orch.tidal = registry.get('tidal')
|
|
orch.qobuz = registry.get('qobuz')
|
|
orch.hifi = registry.get('hifi')
|
|
orch.deezer_dl = registry.get('deezer')
|
|
orch.lidarr = registry.get('lidarr')
|
|
orch.soundcloud = registry.get('soundcloud')
|
|
# Engine — orchestrator delegates per-source query/cancel
|
|
# methods to it, so the test fixture must build one and
|
|
# register every mock plugin under its canonical name.
|
|
orch.engine = DownloadEngine()
|
|
for source_name, plugin in registry.all_plugins():
|
|
orch.engine.register_plugin(source_name, plugin)
|
|
return orch
|
|
|
|
|
|
def _run_async(coro):
|
|
import asyncio
|
|
|
|
loop = asyncio.new_event_loop()
|
|
try:
|
|
return loop.run_until_complete(coro)
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
def test_clear_all_completed_downloads_ignores_unconfigured_clients():
|
|
orch = _build_orchestrator(
|
|
soulseek=_FakeClient(configured=True, clear_result=True),
|
|
youtube=_FakeClient(configured=False, clear_result=False),
|
|
)
|
|
|
|
result = _run_async(orch.clear_all_completed_downloads())
|
|
|
|
assert result is True
|
|
assert orch.soulseek.clear_calls == 1
|
|
assert orch.youtube.clear_calls == 0
|
|
|
|
|
|
def test_clear_all_completed_downloads_propagates_configured_failures():
|
|
orch = _build_orchestrator(
|
|
soulseek=_FakeClient(configured=True, clear_result=False),
|
|
)
|
|
|
|
result = _run_async(orch.clear_all_completed_downloads())
|
|
|
|
assert result is False
|
|
assert orch.soulseek.clear_calls == 1
|