Apply the Cin-1 / Cin-2 pattern from the download refactor PR to the media server engine PR before review. Cin-1 — explicit inheritance: - PlexClient, JellyfinClient, NavidromeClient, SoulSyncClient now explicitly inherit MediaServerClient instead of relying on structural typing alone. Pre-change a reader of plex_client.py had no way to know the class was supposed to satisfy the contract. - Removed the engine + registry re-exports from core/media_server/__init__.py to break the circular import that the inheritance change introduced (importing the package now triggered a chain that loaded clients before their base class resolved). Submodules import directly: from core.media_server.engine import MediaServerEngine, etc. - Conformance test now also asserts isinstance() / issubclass() against MediaServerClient — drift in any class fails at the test boundary instead of at runtime. Cin-2 — generic accessors + singleton: - engine.configured_clients() — replaces the legacy per-server `if X and X.is_connected(): clients[name] = X` chains in web_server.py. - engine.reload_config(name=None) — generic dispatch, so callers pass the server name instead of reaching for plex_client.reload_config() directly. - get_media_server_engine() / set_media_server_engine() singleton factory matching the get_metadata_engine() / get_download_orchestrator() shape. web_server.py boots via set_media_server_engine(...) so factory + global handle share state. - 7 new tests pin the accessors + singleton behaviour.
72 lines
2.7 KiB
Python
72 lines
2.7 KiB
Python
"""Pin the structural conformance of every media server client to
|
|
``MediaServerClient``. Mirrors the download plugin conformance test
|
|
shape — class-level checks (not instance-level) so importing the
|
|
test doesn't drag every server's heavy auth init into the test
|
|
collection phase.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from core.media_server.contract import REQUIRED_METHODS
|
|
|
|
|
|
def _import_server_classes():
|
|
"""Import every server client class lazily inside tests so
|
|
auth-init-heavy modules (Plex, Jellyfin) aren't imported at test
|
|
collection."""
|
|
from core.jellyfin_client import JellyfinClient
|
|
from core.navidrome_client import NavidromeClient
|
|
from core.plex_client import PlexClient
|
|
from core.soulsync_client import SoulSyncClient
|
|
|
|
return {
|
|
'plex': PlexClient,
|
|
'jellyfin': JellyfinClient,
|
|
'navidrome': NavidromeClient,
|
|
'soulsync': SoulSyncClient,
|
|
}
|
|
|
|
|
|
def test_default_registry_registers_all_four_servers():
|
|
"""Smoke check that the foundation registry knows about every
|
|
server SoulSync historically dispatched to."""
|
|
from core.media_server.registry import build_default_registry
|
|
|
|
registry = build_default_registry()
|
|
expected = {'plex', 'jellyfin', 'navidrome', 'soulsync'}
|
|
assert set(registry.names()) == expected
|
|
|
|
|
|
@pytest.mark.parametrize('server_name', ['plex', 'jellyfin', 'navidrome', 'soulsync'])
|
|
def test_server_class_has_all_required_methods(server_name):
|
|
"""Every registered server class exposes every required protocol
|
|
method by name. Diagnostic-friendly: tells you WHICH method is
|
|
missing when a new server is added without all the required
|
|
methods."""
|
|
classes = _import_server_classes()
|
|
cls = classes[server_name]
|
|
|
|
missing = [m for m in REQUIRED_METHODS if not hasattr(cls, m)]
|
|
assert not missing, (
|
|
f"{server_name} ({cls.__name__}) missing required methods: {missing}"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize('server_name', ['plex', 'jellyfin', 'navidrome', 'soulsync'])
|
|
def test_server_class_explicitly_inherits_contract(server_name):
|
|
"""Per Cin's standard from the download refactor: clients must
|
|
explicitly inherit ``MediaServerClient`` so the contract conformance
|
|
is obvious from reading the class declaration. Structural
|
|
typing alone (which would still pass `hasattr` checks) leaves
|
|
the contract invisible to anyone reading the code — drift in a
|
|
future client class wouldn't fail at the contract boundary."""
|
|
from core.media_server.contract import MediaServerClient
|
|
|
|
classes = _import_server_classes()
|
|
cls = classes[server_name]
|
|
assert issubclass(cls, MediaServerClient), (
|
|
f"{cls.__name__} must explicitly inherit MediaServerClient — "
|
|
f"structural typing isn't enough"
|
|
)
|