diff --git a/tests/media_server/test_jellyfin_pinning.py b/tests/media_server/test_jellyfin_pinning.py new file mode 100644 index 00000000..d05f407f --- /dev/null +++ b/tests/media_server/test_jellyfin_pinning.py @@ -0,0 +1,104 @@ +"""Phase A pinning tests for JellyfinClient. + +Pin the OBSERVABLE BEHAVIOR the engine will dispatch through after +Phase B/C. Jellyfin's connection-readiness check is stricter than +Plex (requires base_url + api_key + user_id + music_library_id all +present), and get_all_artists has a side effect of pre-populating +caches. +""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +import pytest + +from core.jellyfin_client import JellyfinClient + + +@pytest.fixture +def jellyfin_client(): + """A bare JellyfinClient with no real connection.""" + client = JellyfinClient.__new__(JellyfinClient) + client.base_url = None + client.api_key = None + client.user_id = None + client.music_library_id = None + client._connection_attempted = False + client._is_connecting = False + # Initialize the various caches the real __init__ sets up + client._album_cache = {} + client._track_cache = {} + client._artist_cache = {} + client._cache_populated = False + client._metadata_only_mode = False + return client + + +# --------------------------------------------------------------------------- +# is_connected +# --------------------------------------------------------------------------- + + +def test_is_connected_returns_false_when_no_credentials(jellyfin_client): + """Pinning: any of base_url / api_key / user_id / music_library_id + being None → False. All four must be set.""" + with patch.object(jellyfin_client, 'ensure_connection', return_value=False): + assert jellyfin_client.is_connected() is False + + +def test_is_connected_returns_true_only_when_all_four_set(jellyfin_client): + """Pinning: requires base_url AND api_key AND user_id AND + music_library_id. Setting only some → still False.""" + jellyfin_client._connection_attempted = True + + # Only some — still False + jellyfin_client.base_url = 'http://j' + jellyfin_client.api_key = 'k' + jellyfin_client.user_id = 'u' + jellyfin_client.music_library_id = None + assert jellyfin_client.is_connected() is False + + # All four — True + jellyfin_client.music_library_id = 'lib' + assert jellyfin_client.is_connected() is True + + +# --------------------------------------------------------------------------- +# get_all_artists / get_all_album_ids +# --------------------------------------------------------------------------- + + +def test_get_all_artists_returns_empty_when_not_connected(jellyfin_client): + with patch.object(jellyfin_client, 'ensure_connection', return_value=False): + assert jellyfin_client.get_all_artists() == [] + + +def test_get_all_artists_returns_empty_when_no_music_library_id(jellyfin_client): + """Pinning: even with ensure_connection True, no music_library_id + selected → empty (don't crash).""" + jellyfin_client.music_library_id = None + with patch.object(jellyfin_client, 'ensure_connection', return_value=True): + assert jellyfin_client.get_all_artists() == [] + + +def test_get_all_album_ids_returns_set(jellyfin_client): + """Pinning: returns a set of string Jellyfin GUID ids. Same shape + semantic as Plex (set of strings) — engine extraction depends on + uniform set type.""" + jellyfin_client._connection_attempted = True + jellyfin_client.base_url = 'http://j' + jellyfin_client.api_key = 'k' + jellyfin_client.user_id = 'u' + jellyfin_client.music_library_id = 'lib' + + fake_response_pages = [ + {'Items': [{'Id': 'guid-1'}, {'Id': 'guid-2'}], 'TotalRecordCount': 2}, + ] + + with patch.object(jellyfin_client, 'ensure_connection', return_value=True), \ + patch.object(jellyfin_client, '_make_request', side_effect=fake_response_pages): + result = jellyfin_client.get_all_album_ids() + + assert isinstance(result, set) + assert result == {'guid-1', 'guid-2'}