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.
This commit is contained in:
Developer
2026-06-26 08:39:39 +00:00
parent 04319025de
commit 7d252489de
2 changed files with 32 additions and 4 deletions
@@ -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"]
+16
View File
@@ -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 ---