feat(service-credential-tester): slice 1 — backend test endpoint + per-type routines

TestResult dataclass + translate_connection_error shared helper in base.py.
test_callable field on ServiceDefinition (default None). 7 per-type
test_connection routines (qbittorrent, prometheus via Grafana gateway,
alertmanager, jellyfin, authentik, ssh_tasks via build_ssh_client, nextcloud).
POST /api/services/test endpoint: validation-first (422 on malformed config),
dispatch, no-persistence, no-secret-logs. backups has test_callable=None.
qBit 'Fails.' → specific auth message (resolves #3 at API layer).
Backend: 362 pytest pass (+31 new), ruff clean. Frontend: build green.
This commit is contained in:
Developer
2026-07-09 22:41:06 +00:00
parent c4f68b4938
commit 3391fbc85d
11 changed files with 697 additions and 3 deletions
@@ -6,13 +6,39 @@ dashboard widgets yet; its service page holds connection config only.
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import requests
from media_library_viewer_api.integrations.base import (
SecretField,
ServiceBaseUrl,
ServiceConfigBase,
ServiceDefinition,
TestResult,
translate_connection_error,
)
if TYPE_CHECKING:
from media_library_viewer_api.services.settings_store import SettingsStore
def test_connection(
config: dict[str, Any],
secrets: dict[str, str],
store: SettingsStore,
) -> TestResult:
"""GET {base_url}/status.php (unauthenticated server probe)."""
try:
base_url = str(config.get("base_url") or "").rstrip("/")
resp = requests.get(f"{base_url}/status.php", timeout=10)
resp.raise_for_status()
payload = resp.json()
version = str(payload.get("version", "") or "connected")
return TestResult(ok=True, detail="Connected to Nextcloud.", evidence=version)
except Exception as exc:
return translate_connection_error(exc, context="Nextcloud")
class NextcloudConfig(ServiceConfigBase):
"""Non-secret Nextcloud connection config."""
@@ -30,4 +56,5 @@ DEFINITION = ServiceDefinition(
SecretField(key="app_password", label="App password", required=True),
],
widget_kinds=[],
test_callable=test_connection,
)