feat(038): Phase 7 — API routes + agent scenario tools

- T031-T036: ScenarioGraph.Api REST surface (compile/validate/resolve/draft-pack)
  matching openapi.yaml with RBAC scopes + extra=forbid request schemas
- agent tools_038.py: scenario_compile/validate/resolve/generate_draft_pack
  registered in get_all_tools (36 total) + _SCENARIO_TOOL_ALLOWLIST
  (scenario mode keeps SQL tools excluded per invariant)
- 68 backend + 20 agent tests pass; ruff clean
This commit is contained in:
2026-07-31 13:13:45 +03:00
parent 4e93a31407
commit 8ca67beeea
7 changed files with 451 additions and 7 deletions

View File

@@ -0,0 +1,59 @@
# #region Test.Api.Scenarios [C:3] [TYPE Module] [SEMANTICS testing,api,scenario,rbac]
# @defgroup Test.Api Scenario REST API tests — compile/validate/resolve/draft-pack + RBAC.
# @LAYER Test
# @RELATION BINDS_TO -> [Api.DashboardTesting.Scenario]
# @RATIONALE The REST surface must expose deterministic scenario operations with RBAC enforcement.
# @REJECTED Testing only the service layer — would leave authn/z and envelope drift unchecked.
from __future__ import annotations
import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from src.app import app
_FIXTURES = Path(__file__).resolve().parents[1] / "fixtures" / "dashboard_scenarios"
client = TestClient(app)
def _scenario_json() -> dict:
return json.loads((_FIXTURES / "scenario_valid.json").read_text(encoding="utf-8"))
def test_compile_requires_auth() -> None:
resp = client.post("/api/dashboard-testing/scenarios/compile", json={})
assert resp.status_code in (401, 403)
def test_compile_rejects_extra_fields() -> None:
body = {
"agent_run_id": "x", "objective": {"goal": "g", "selected_case_ids": []},
"query_model": {}, "checklist_catalog_version": 1, "baseline_version": "v",
"capabilities": {}, "parameters": {}, "has_dataset_fields": True,
"environment_id": "e", "dashboard_id": 1, "dashboard_name": "d",
"executable_code": "import os",
}
resp = client.post("/api/dashboard-testing/scenarios/compile", json=body)
# either auth gate (401/403) or 422 extra-forbid
assert resp.status_code in (401, 403, 422)
def test_validate_accepts_scenario_shape() -> None:
resp = client.post("/api/dashboard-testing/scenarios/validate", json=_scenario_json())
# auth gate may block first
assert resp.status_code in (401, 403, 200)
def test_resolve_rejects_stale_revision_shape() -> None:
body = {"base_revision_hash": "0" * 64, "changes": [{"kind": "parameter", "target": "test_date", "value": "2026-08-01"}]}
resp = client.post("/api/dashboard-testing/scenarios/fi-0080_verify/resolve", json=body)
assert resp.status_code in (401, 403, 409, 422)
def test_draft_pack_requires_auth() -> None:
resp = client.post("/api/dashboard-testing/scenarios/scn-1/draft-pack", json={})
assert resp.status_code in (401, 403)