"""Tests for the Jellyseerr stats provider + generic stat widget source.""" from __future__ import annotations import asyncio from unittest.mock import MagicMock, patch from media_library_viewer_api.widgets.jellyseerr_stats import JellyseerrStatsProvider from media_library_viewer_api.widgets.sources import ServiceRecord, StatsWidgetSource from media_library_viewer_api.widgets.stats_provider import ( StatValue, get_stats_provider, ) def _service( *, jellyseerr_url: str = "https://js.example.com", jellyseerr_api_key: str = "key", ) -> ServiceRecord: return ServiceRecord( id="svc-jf", service_type="jellyfin", name="Jellyfin", config={"jellyseerr_url": jellyseerr_url, "jellyseerr_api_key": jellyseerr_api_key}, secrets={}, enabled=True, ) _COUNTS = {"total": 5, "pending": 2, "approved": 1, "declined": 0, "processing": 1, "available": 1} _RECENT = [{"id": 1, "name": "Inception", "status": "pending", "media_status": "available"}] def test_provider_registered_for_jellyfin(): assert isinstance(get_stats_provider("jellyfin"), JellyseerrStatsProvider) def test_provider_returns_normalized_stats_and_recent(): provider = JellyseerrStatsProvider(ttl=0) fake = MagicMock() fake.request_count.return_value = _COUNTS fake.recent_requests.return_value = _RECENT with patch("media_library_viewer_api.widgets.jellyseerr_stats._jellyseer_client", return_value=fake): result = provider.fetch_stats(_service()) assert [s.key for s in result.stats] == [ "total", "pending", "approved", "declined", "processing", "available", ] by_key = {s.key: s.value for s in result.stats} assert by_key["pending"] == 2 and by_key["total"] == 5 assert result.recent == _RECENT def test_provider_not_configured_returns_detail(): provider = JellyseerrStatsProvider() result = provider.fetch_stats(_service(jellyseerr_url="", jellyseerr_api_key="")) assert result.stats == [] assert "not configured" in (result.detail or "").lower() def test_provider_caches_within_ttl(): provider = JellyseerrStatsProvider(ttl=10) fake = MagicMock() fake.request_count.return_value = _COUNTS fake.recent_requests.return_value = _RECENT with patch("media_library_viewer_api.widgets.jellyseerr_stats._jellyseer_client", return_value=fake): provider.fetch_stats(_service()) provider.fetch_stats(_service()) # served from cache assert fake.request_count.call_count == 1 def test_stat_widget_returns_selected_value(): src = StatsWidgetSource() result = MagicMock() result.detail = None result.stats = [StatValue("total", "Total", 5), StatValue("pending", "Pending", 2)] result.recent = [] provider = MagicMock() provider.fetch_stats.return_value = result with patch("media_library_viewer_api.widgets.sources.get_stats_provider", return_value=provider): data = asyncio.run(src.fetch(_service(), "stat", {"stat": "pending"})) assert data == {"key": "pending", "label": "Pending", "value": 2} def test_stats_overview_widget_returns_all_stats_and_recent(): src = StatsWidgetSource() result = MagicMock() result.detail = None result.stats = [StatValue("total", "Total", 5), StatValue("pending", "Pending", 2)] result.recent = _RECENT provider = MagicMock() provider.fetch_stats.return_value = result with patch("media_library_viewer_api.widgets.sources.get_stats_provider", return_value=provider): data = asyncio.run(src.fetch(_service(), "stats_overview", {})) assert data["stats"] == [ {"key": "total", "label": "Total", "value": 5}, {"key": "pending", "label": "Pending", "value": 2}, ] assert data["recent"] == _RECENT def test_stat_widget_unknown_stat_returns_error(): src = StatsWidgetSource() result = MagicMock() result.detail = None result.stats = [StatValue("total", "Total", 5)] result.recent = [] provider = MagicMock() provider.fetch_stats.return_value = result with patch("media_library_viewer_api.widgets.sources.get_stats_provider", return_value=provider): data = asyncio.run(src.fetch(_service(), "stat", {"stat": "nope"})) assert "error" in data def test_jellyseer_client_requests_maps_fields(): """requests() paginates /request and maps type/status/media_status enums.""" from media_library_viewer_api.clients.jellyseerr import JellyseerrClient c = JellyseerrClient("https://js.example.com", "key") c.session = MagicMock() resp = MagicMock() resp.raise_for_status.return_value = None resp.status_code = 200 resp.text = "" resp.json.return_value = { "results": [ { "id": 7, "type": 1, "title": "Inception", "status": 1, "media": {"status": 5}, "createdAt": 1_700_000_000, } ] } c.session.get.return_value = resp out = c.requests(500) assert len(out) == 1 r = out[0] assert r["id"] == 7 assert r["type"] == "movie" # 1 -> movie assert r["name"] == "Inception" assert r["status"] == "pending" # 1 -> pending assert r["media_status"] == "available" # 5 -> available assert r["created_at"] == 1_700_000_000 # Single short page -> no second fetch. assert c.session.get.call_count == 1 def test_fetch_jellyseer_requests_not_configured_returns_empty(): """No jellyseerr config -> empty list (tab shows 'No requests').""" from media_library_viewer_api.widgets.jellyseerr_stats import fetch_jellyseer_requests service = ServiceRecord(id="s", service_type="jellyfin", name="JF", config={}, secrets={}) assert fetch_jellyseer_requests(service) == []