98bf496a98
Strengthen test_secrets_not_logged (N-2): now sends real-looking secrets through prometheus + qbittorrent test_callables (mocked at network boundary), asserts no fragments leak into caplog, verified non-vacuous. Write apply-progress.md, tick all 29 tasks, add verify-report.md (21/21 PASS). Gates green: 362+ pytest, ruff clean, npm build+lint 0 errors, 158 vitest.
4.7 KiB
4.7 KiB
Apply Progress: Service Credential Tester
Change: service-credential-tester
Phase: apply-progress
Date: 2026-07-09
Status: complete — all 29 tasks done, all gates green, verified (see verify-report.md)
Slices delivered
Two slices, each its own commit, each leaving pytest / npm run build / npm run lint / ruff green.
Slice 1 — Backend test endpoint + per-type routines (commit 3391fbc)
integrations/base.py— added frozenTestResultdataclass (ok,detail,evidence) + optionaltest_callablefield onServiceDefinition(placed last for dataclass ordering). Signature:(store, config, secrets) -> TestResult(CT-101).- Shared error-translation helper
translate_connection_error— extracts thetest_machine_sshpatterns (ConnectionError/Timeout/SSL/HTTP 401-403/5xx → friendly strings) (CT-112). - Per-type
test_connectionroutines alongside eachDEFINITION:- qbittorrent —
QbittorrentClientlogin +maindata();"Fails."→ "Authentication failed (qBittorrent rejected credentials)" — resolves the #3 log-only pain at the API layer (CT-104). - prometheus —
POST {grafana_url}/api/ds/querywithgrafana_api_key+datasource_uid,expr "up"; evidence "Gateway reachable" (CT-105; gateway path pergrafana-metric-gateway). - alertmanager —
GET /api/v2/alerts(+ optional bearer); evidence cluster version (CT-106). - jellyfin —
JellyfinClient.users(); evidence " users" (CT-107). - authentik — directory endpoint GET; evidence slug/"connected" (CT-108).
- ssh_tasks — reuses
build_ssh_client(store, service)+connect(); banner/auth translation; no duplication oftest_machine_sshinternals (CT-109). - nextcloud —
GET /status.php(unauth); evidence version (CT-110). - backups —
test_callable=None→{ok: True, detail: "No test needed"}(CT-111).
- qbittorrent —
routers/services.py—POST /api/services/test: validation-first (422 on malformed config, no network call), dispatch, zero persistence (noupsert/update_setting), INFO log only type+ok (sanitized; no secrets) (CT-102, CT-103, CT-113).- Tests:
test_credential_tester.py(per-routine success + failure, dispatch, validation-before-test, no-persistence assertiontest_no_persistence_after_test, qBit "Fails." → auth message).
Slice 2 — Frontend Test button + gating (commit 9972514, amended)
types/index.ts—TestResultinterface (CT-113).api/services.ts+hooks/useServices.ts—testServiceInstance+useTestServiceInstancemutation (no cache invalidation; test is side-effect-free).- NEW
components/ServiceTestPanel.tsx— presentational shared component (cleaner than the design's stateful version — deviation N-6). Props:{ input, onResult, disabled }. Renders Test button +Testing…state + result pill (✓ green evidence / ✗ red detail) + Save-anyway checkbox (only on failure). Parent ownstestResult+saveAnywaystate; store-previous pattern clears on input change (CT-114, CT-115, CT-116, CT-117). - Wired into BOTH surfaces:
CreateServiceDialog(ServicesPage.tsx) ANDServiceConfigEditor(Settings.tsx— the correct edit surface per design source-finding, notServicePage.tsx). Create/Save confirm gated ontestPassed || saveAnyway(CT-118). - Tests: 7
ServiceTestPanel.test.tsxcases (button states, success/failure pills, checkbox toggle).
Deviations from tasks.md / design
- N-6 (intentional improvement):
ServiceTestPanelis presentational; the design's stateful version was simplified. Parent owns result + saveAnyway state. Cleaner; works identically in both surfaces. - Edit surface correction: spec CT-115 said
ServicePage.tsx; the actual edit dialog isSettings.tsx::ServiceConfigEditor(design source-finding). Tasks targeted the right file.
Final gate results
| Gate | Result |
|---|---|
backend && PYTHONPATH=src python3 -m pytest -q |
362 passed (+31 new), 2 warnings (pre-existing pythonjsonlogger) |
backend && PYTHONPATH=src python3 -m ruff check src tests |
All checks passed |
frontend && npm run build |
exit 0 (pre-existing chunk-size warning) |
frontend && npm run lint |
0 errors, 1 pre-existing warning (WidgetConfigDialog.tsx, untouched) |
frontend && npx vitest run |
158 passed (+7 ServiceTestPanel) |
Verification
See verify-report.md — adversarial fresh-context review: 21/21 PASS, all gates green. No blocking code findings. Archive blocker is doc-only (this file + ticked tasks clear it). Non-blocking notes: N-2 (no-secret-logs test sends empty secrets — weak coverage, not a defect), N-4 (edit flow requires re-typing secrets — inherent to no-persistence), N-5 (gating proven by source, not page-level test).