You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SoulSync/tests/media_server/test_soulsync_pinning.py

91 lines
3.3 KiB

"""Phase A pinning tests for SoulSyncClient (standalone library mode).
Pin the OBSERVABLE BEHAVIOR the engine will dispatch through.
SoulSync standalone is the structurally-different one — no auth /
no API / no library scan. is_connected just checks `transfer_path`
is a directory. Filesystem walk happens via _get_cached_scan().
"""
from __future__ import annotations
import os
from unittest.mock import patch
import pytest
from core.soulsync_client import SoulSyncClient
@pytest.fixture
def ss_client(tmp_path):
"""A bare SoulSyncClient pointed at a temp directory."""
client = SoulSyncClient.__new__(SoulSyncClient)
client._transfer_path = str(tmp_path)
client._progress_callback = None
client._cache = None
client._cache_time = 0
client._cache_ttl = 300
client._last_scan_time = None
return client
# ---------------------------------------------------------------------------
# is_connected / ensure_connection
# ---------------------------------------------------------------------------
def test_is_connected_true_when_transfer_path_exists(ss_client, tmp_path):
"""Pinning: filesystem-based connectivity. is_connected just
checks os.path.isdir(transfer_path)."""
assert ss_client.is_connected() is True # tmp_path exists
def test_is_connected_false_when_transfer_path_missing(ss_client):
"""Pinning: missing transfer_path → False. The standalone case
where the user hasn't configured a folder yet."""
ss_client._transfer_path = '/nonexistent/path/that/does/not/exist'
assert ss_client.is_connected() is False
def test_ensure_connection_reloads_config_then_checks_path(ss_client, tmp_path):
"""Pinning: ensure_connection re-reads config (so the user
changing the transfer_path takes effect without a process
restart) and then checks the path. Returns True iff the path
is a directory."""
with patch.object(ss_client, '_reload_config') as fake_reload:
result = ss_client.ensure_connection()
fake_reload.assert_called_once()
assert result is True
# ---------------------------------------------------------------------------
# get_all_artists / get_all_album_ids
# ---------------------------------------------------------------------------
def test_get_all_artists_uses_cached_scan(ss_client):
"""Pinning: get_all_artists delegates to _get_cached_scan which
enforces the 5-min TTL. Engine extraction must preserve."""
fake_artists = [object(), object(), object()]
with patch.object(ss_client, '_get_cached_scan', return_value=fake_artists):
result = ss_client.get_all_artists()
assert result == fake_artists
def test_get_all_album_ids_returns_set(ss_client):
"""Pinning: returns a set of MD5-hashed string ids. Same uniform
set semantics as Plex/Jellyfin/Navidrome."""
fake_album = type('FakeAlbum', (), {'ratingKey': 'hash-1'})()
fake_album2 = type('FakeAlbum', (), {'ratingKey': 'hash-2'})()
# Real SoulSyncArtist exposes albums() method (not _albums attr).
fake_artist = type('FakeArtist', (), {
'albums': lambda self: [fake_album, fake_album2],
})()
with patch.object(ss_client, '_get_cached_scan', return_value=[fake_artist]):
result = ss_client.get_all_album_ids()
assert isinstance(result, set)
assert result == {'hash-1', 'hash-2'}