"""Regression: ``GET /api/v1/auditor/coverage`` must NOT 500. The endpoint walks every auditor control's ``data_present`` check through ``_has_recent``, which looks up each model's freshest-row timestamp column from a static ``_data_present_timestamp_col`` registry. The registry was added in pre-launch hardening (2026-05-04) to replace ``getattr(..., None)`` fallback that silently masked a wrong-column-name bug — but ``AuditorSigninEvent`` was the one model the migration forgot to register, and the route calls ``_has_recent(AuditorSigninEvent)`` unconditionally, so EVERY ``/coverage`` request 500'd in production from the day the registry landed. User report 2026-05-05 (PAT / MCP / OAuth audit page): Banner showed ``无法读取控制项状态 — Request failed with status code 500``. The fix re-registers ``AuditorSigninEvent: AuditorSigninEvent.event_at`` in the registry. This test pins: 1. ``/coverage`` returns 200 on a fresh DB (no auditor rows anywhere). 2. The response carries every one of the eight ``control_key`` entries the frontend banner reads, with shape ``{providers: [...], data_present: bool}``. 3. ``data_present`` is False on a fresh DB for the data-driven controls — proving ``_has_recent`` runs through to the SELECT for each model rather than short-circuiting at a NameError. A future regression that drops a model from the registry without also removing the route's ``_has_recent`` call site will fail (3) loudly instead of silently 500-ing in production. """ from __future__ import annotations import pytest from httpx import AsyncClient @pytest.mark.asyncio async def test_coverage_returns_200_with_all_eight_controls( client: AsyncClient, admin_headers, ) -> None: resp = await client.get( "/api/v1/auditor/coverage", headers=admin_headers, ) assert resp.status_code == 200, resp.text body = resp.json() assert body["success"] is True controls = body["data"]["controls"] expected_keys = { "mfa", "privileged_review", "public_exposure", "secret_rotation", "signin_anomaly", "pat_audit", "cert_expiry", "risk_score", } assert set(controls.keys()) == expected_keys, ( f"Missing or extra control keys. Got: {set(controls.keys())} " f"Expected: {expected_keys}" ) for key, payload in controls.items(): # Each control must carry the two fields the banner reads. assert "providers" in payload, f"{key} missing providers" assert "data_present" in payload, f"{key} missing data_present" assert isinstance(payload["data_present"], bool), ( f"{key}.data_present is {type(payload['data_present'])!r}" ) @pytest.mark.asyncio async def test_coverage_signin_anomaly_data_present_runs_query( client: AsyncClient, admin_headers, ) -> None: """Pin the specific bug: ``signin_anomaly`` was the unregistered control. On a fresh DB the data_present check should return False (no AuditorSigninEvent rows in the past 7 days) — but it must actually RUN the SELECT, not raise a RuntimeError that gets masked into 500. """ resp = await client.get( "/api/v1/auditor/coverage", headers=admin_headers, ) assert resp.status_code == 200, resp.text payload = resp.json()["data"]["controls"]["signin_anomaly"] assert payload["data_present"] is False # The provider list shape is the contract the banner depends on # — pin it so a future refactor that flattens the structure # forces a UI test update. assert isinstance(payload["providers"], list) assert len(payload["providers"]) >= 1