soulsync/tests/media_server/test_conformance.py
Broque Thomas 49f7679eef MS Cin-1 + Cin-2: Explicit contract inheritance + generic accessors
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.
2026-05-05 16:59:05 -07:00

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"
)