From 7d252489de137c0454bfa013b82e139ff8d387e7 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 26 Jun 2026 08:39:39 +0000 Subject: [PATCH] fix(api): return 503 instead of 500 when Jellyfin/SSH not configured On a fresh deploy with no Jellyfin service configured yet, get_jellyfin_client (and get_user_id / get_ssh_client) raised a plain RuntimeError, which bubbled up as a 500 traceback on every Jellyfin-dependent route (dashboard counts/libraries/activity, media, users). Convert those RuntimeErrors to HTTPException(503) with a clear detail message so FastAPI returns a clean 503 JSON response instead of a 500, and the frontend can render a not-configured state. - dependencies.py: get_jellyfin_client (no service / missing creds), get_user_id (no users discovered), and get_ssh_client (no SSH machine + no legacy key path) now raise HTTPException(503, detail=...). - tests/test_api.py: added TestDashboard.test_jellyfin_endpoints_return_503_when_not_configured covering /api/dashboard/counts and /activity. ruff clean; 240 backend tests pass. --- .../media_library_viewer_api/dependencies.py | 20 +++++++++++++++---- backend/tests/test_api.py | 16 +++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backend/src/media_library_viewer_api/dependencies.py b/backend/src/media_library_viewer_api/dependencies.py index ed390a4..ef00d8b 100644 --- a/backend/src/media_library_viewer_api/dependencies.py +++ b/backend/src/media_library_viewer_api/dependencies.py @@ -163,11 +163,17 @@ def get_jellyfin_client(request: Request = None) -> JellyfinClient: service_id = _request_jellyfin_service_id(request) service = _service_record(store, "jellyfin", service_id) if service is None: - raise RuntimeError("No Jellyfin service is configured. Add a Jellyfin service on the Services page.") + raise HTTPException( + status_code=503, + detail="No Jellyfin service is configured. Add a Jellyfin service on the Services page.", + ) base_url = str(service.get("config", {}).get("base_url") or "") api_key = str(service.get("secrets", {}).get("api_key") or "") if not base_url or not api_key: - raise RuntimeError("Jellyfin service is missing base_url or api_key. Edit it on the Services page.") + raise HTTPException( + status_code=503, + detail="Jellyfin service is missing base_url or api_key. Edit it on the Services page.", + ) cache_key = (service["id"], base_url, api_key) return _jellyfin_client_for(cache_key) @@ -244,7 +250,10 @@ def get_ssh_client(request: Request = None): "set" if settings.ssh_password else "missing", ) if not settings.ssh_key_path: - raise RuntimeError("No SSH machine is configured and SSH key settings must be configured") + raise HTTPException( + status_code=503, + detail="No SSH machine is configured and SSH key settings must be configured", + ) return _ssh_client_for( ( "legacy", @@ -280,5 +289,8 @@ def get_user_id(request: Request = None) -> str: client = get_jellyfin_client(request) users = client.users() if not users: - raise RuntimeError("No Jellyfin users found and no user_id configured on the service") + raise HTTPException( + status_code=503, + detail="No Jellyfin users found and no user_id configured on the service", + ) return users[0]["Id"] diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index e2056f7..c986952 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -211,6 +211,22 @@ class TestDashboard: data = response.json() assert len(data) == 2 + def test_jellyfin_endpoints_return_503_when_not_configured(self, test_client): + # Remove the mocked Jellyfin dependency so the real one runs; with no + # Jellyfin service seeded, endpoints must degrade to 503, not 500. + app.dependency_overrides.pop(get_jellyfin_client, None) + app.dependency_overrides.pop(get_user_id, None) + try: + for path in ("/api/dashboard/counts", "/api/dashboard/activity"): + response = test_client.get(path) + assert response.status_code == 503, path + detail = response.json()["detail"] + assert "configured" in detail, path + finally: + # Restore the mocks for subsequent tests in this fixture session. + app.dependency_overrides[get_jellyfin_client] = lambda: MagicMock() + app.dependency_overrides[get_user_id] = lambda: "user123" + # --- Settings reset ---