feat(demo): plain-language integration status, richer audit, reset integrity

Integration status badges across Dashboard/Automation now show honest
plain-language labels instead of raw backend state strings (and fix a
few states that had no matching CSS colour class at all). Audit trail
gets a "view related events" action reusing the existing correlation_id
filter. About page gains scope/architecture/security/testing sections
and a guided-demo entry point. POST /api/v1/demo/reset now runs and
records a server-side scenario-integrity check. Also fixes a second real
race condition (caught by the return-review e2e test): the odometer
scenario pre-fill now resolves before ReturnForm mounts instead of
patching its value in after the fact.
This commit is contained in:
NuklearRabbit
2026-08-03 15:00:11 +02:00
parent cf9a889547
commit 5fa4fe0811
13 changed files with 254 additions and 48 deletions
+4 -1
View File
@@ -14,7 +14,7 @@ from app.models.user import User
from app.schemas import CurrentUser, DemoLoginRequest, DemoManifestOut
from app.seed_loader import reset_and_seed
from app.services.audit import record_audit_event
from app.services.demo_manifest import build_demo_manifest
from app.services.demo_manifest import build_demo_manifest, scenario_integrity_report
router = APIRouter(prefix="/api/v1/demo", tags=["demo"])
settings = get_settings()
@@ -107,6 +107,7 @@ def demo_reset(
detail="Demo reset is disabled on this deployment.",
)
result = reset_and_seed(db)
integrity = scenario_integrity_report(db)
record_audit_event(
db,
actor_type="user",
@@ -116,6 +117,7 @@ def demo_reset(
metadata={
"counts": result.counts,
"anchor_date": result.anchor_date.isoformat(),
"scenario_integrity": integrity,
},
)
db.commit()
@@ -125,4 +127,5 @@ def demo_reset(
"counts": result.counts,
"anchor_date": result.anchor_date.isoformat(),
"seeded_at": result.seeded_at.isoformat(),
"scenario_integrity": integrity,
}
+14
View File
@@ -223,6 +223,20 @@ def _integrations(db: Session) -> list[DemoIntegrationSummaryOut]:
]
def scenario_integrity_report(db: Session) -> dict:
"""Server-side scenario-integrity check run after every reset (section 15): confirms
each of the 5 named scenarios is actually present and ready, rather than trusting the
seed loader silently. Reuses the same readiness derivation the manifest/scenario
overview already use, so this can never drift from what a visitor actually sees."""
scenarios = _scenarios(db)
not_ready = [
{"id": s.id, "title": s.title, "reason": s.blocked_reason}
for s in scenarios
if not s.ready
]
return {"all_ready": len(not_ready) == 0, "not_ready": not_ready}
def build_demo_manifest(db: Session) -> DemoManifestOut:
last_reset_at, anchor_date = _last_reset(db)
return DemoManifestOut(
+6 -3
View File
@@ -17,9 +17,12 @@ def test_rental_employee_cannot_reset_demo(employee_client):
def test_operations_manager_can_reset_demo(ops_client):
response = ops_client.post("/api/v1/demo/reset")
assert response.status_code == 200
assert response.json()["counts"]["vehicles"] == 50
assert response.json()["anchor_date"]
assert response.json()["seeded_at"]
body = response.json()
assert body["counts"]["vehicles"] == 50
assert body["anchor_date"]
assert body["seeded_at"]
assert body["scenario_integrity"]["all_ready"] is True
assert body["scenario_integrity"]["not_ready"] == []
def test_reset_is_rejected_when_demo_allow_reset_is_disabled(ops_client, monkeypatch):