soulsync/tests/media_server/test_conformance.py
Broque Thomas f702196dca Phase 0: Add MediaServerClient contract + registry
`core/media_server/` package with the Protocol contract that
every media server client (Plex, Jellyfin, Navidrome, SoulSync
standalone) satisfies, plus the registry that holds them.

Required methods conservatively limited to the four every server
truly implements today: is_connected, ensure_connection,
get_all_artists, get_all_album_ids. Other generic methods
(search_tracks, trigger_library_scan, get_recently_added_albums,
etc.) are listed as OPTIONAL — present on most servers but not
all (SoulSync has no library-scan API since it walks the filesystem
directly; Jellyfin uses a different search shape). Phase B's
engine adapters route around the gaps with per-server fallback
instead of forcing every client to declare a no-op stub.

Same registry shape as the download plugin registry — single
source of truth for which servers exist + name resolution. Adding
a 5th server (Subsonic, Emby, etc.) becomes one register call
plus the new client class.

5 conformance tests pin every server class implements every
required method. Plan doc at docs/media-server-engine-refactor-plan.md.

Pure additive — no consumer routes through the contract or
registry yet. Suite still green (1921 passed).
2026-05-04 20:49:25 -07:00

54 lines
1.9 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 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}"
)