a43d6a6206
Users router removed: - Delete routers/users.py + users_impl.py (Jellyfin-backed user directory, Jellyfin-email message compose, Jellyseerr enrichment). - Drop orphaned get_jellyseerr_client dep from dependencies.py (get_user_id stays; used by dashboard/media/media_index_worker). - clients/jellyseerr.py stays (still imported by widgets/sources.py). - test_api.py TestUsers block + mock_jellyseerr fixture removed. Backups service attribution: - backup_jobs gains a nullable service_id column (PRAGMA migration). - _resolve_backup_service_id helper: explicit service_id wins, else first-wins an enabled backups instance, else empty (backward-compat). - Both report endpoints accept ?service_id= and persist it on the job. - Dashboard summary + poller aggregate across all jobs unchanged. Named dashboards backend: - named_dashboards table (id, label, slug UNIQUE, sort_order, payload_json, timestamps) with full CRUD methods + _slugify/_unique_slug helpers. - models/dashboards.py (NamedDashboardInput/NamedDashboard). - routers/dashboards.py: GET/POST/PUT/DELETE /api/dashboards. - Router registered in main.py. Tests: test_dashboards.py (CRUD, slug collision, explicit slug, 404); test_api.py trimmed. 271 backend tests pass (was 268; +6 dashboards -3 users); ruff clean. Refs openspec/changes/services-as-hub-ia/ (spec R5/R6.1, tasks slice 3).
98 lines
3.1 KiB
Python
98 lines
3.1 KiB
Python
"""Tests for named-dashboards CRUD + slug uniqueness."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from media_library_viewer_api.dependencies import get_settings_store
|
|
from media_library_viewer_api.main import app
|
|
from media_library_viewer_api.services.settings_store import SettingsStore
|
|
|
|
|
|
def _client(tmp_path: Path) -> TestClient:
|
|
store = SettingsStore(tmp_path / "settings.sqlite")
|
|
app.dependency_overrides[get_settings_store] = lambda: store
|
|
client = TestClient(app)
|
|
client.store = store # type: ignore[attr-defined]
|
|
return client
|
|
|
|
|
|
def test_create_and_list_dashboards(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
resp = client.post(
|
|
"/api/dashboards",
|
|
json={"label": "Storage Overview", "payload": {"widgets": []}},
|
|
)
|
|
assert resp.status_code == 200
|
|
created = resp.json()
|
|
assert created["label"] == "Storage Overview"
|
|
assert created["slug"] == "storage-overview"
|
|
assert created["payload"] == {"widgets": []}
|
|
|
|
listed = client.get("/api/dashboards").json()
|
|
assert len(listed) == 1
|
|
assert listed[0]["id"] == created["id"]
|
|
finally:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_update_dashboard(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
created = client.post("/api/dashboards", json={"label": "First"}).json()
|
|
updated = client.put(
|
|
f"/api/dashboards/{created['id']}",
|
|
json={"label": "Renamed", "payload": {"widgets": ["w1"]}},
|
|
).json()
|
|
assert updated["label"] == "Renamed"
|
|
assert updated["payload"] == {"widgets": ["w1"]}
|
|
assert updated["slug"] == "renamed"
|
|
finally:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_delete_dashboard(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
created = client.post("/api/dashboards", json={"label": "Temp"}).json()
|
|
resp = client.delete(f"/api/dashboards/{created['id']}")
|
|
assert resp.status_code == 200
|
|
assert client.get("/api/dashboards").json() == []
|
|
finally:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_slug_collision_appends_suffix(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
first = client.post("/api/dashboards", json={"label": "Overview"}).json()
|
|
second = client.post("/api/dashboards", json={"label": "Overview"}).json()
|
|
assert first["slug"] == "overview"
|
|
assert second["slug"] == "overview-2"
|
|
finally:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_explicit_slug_respected(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
created = client.post(
|
|
"/api/dashboards",
|
|
json={"label": "My Dashboard", "slug": "custom-slug"},
|
|
).json()
|
|
assert created["slug"] == "custom-slug"
|
|
finally:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_update_nonexistent_returns_404(tmp_path: Path):
|
|
client = _client(tmp_path)
|
|
try:
|
|
resp = client.put("/api/dashboards/nope", json={"label": "X"})
|
|
assert resp.status_code == 404
|
|
finally:
|
|
app.dependency_overrides.clear()
|