mirror of https://github.com/Nezreka/SoulSync.git
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.
91 lines
3.3 KiB
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'}
|