generated from coulomb/repo-seed
http service with health, extension listing, profile validation, run planning, async run jobs, job inspection, and report retrieval
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
import http.client
|
||||
import json
|
||||
import time
|
||||
import unittest
|
||||
from tempfile import TemporaryDirectory
|
||||
from pathlib import Path
|
||||
|
||||
from guide_board.discovery import discover_extensions
|
||||
from guide_board.execution import run_assessment
|
||||
from guide_board.gates import evaluate_trend_gates
|
||||
from guide_board.io import load_json
|
||||
from guide_board.planning import (
|
||||
build_run_plan,
|
||||
validate_assessment_profile,
|
||||
@@ -15,6 +18,7 @@ from guide_board.planning import (
|
||||
)
|
||||
from guide_board.retention import build_trend_summary, list_retained_runs
|
||||
from guide_board.schema import assert_valid
|
||||
from guide_board.service import ServiceHandle, start_service
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -35,6 +39,15 @@ class CoreArchitectureTests(unittest.TestCase):
|
||||
self.assertEqual(target["id"], "sample-repository")
|
||||
self.assertEqual(assessment["target_profile_ref"], "sample-repository")
|
||||
|
||||
def test_validates_evidence_request_template(self) -> None:
|
||||
request_set = load_json(ROOT / "extensions" / "_template" / "evidence-request-set.json")
|
||||
|
||||
assert_valid(request_set, "evidence-request-set")
|
||||
self.assertEqual(
|
||||
request_set["source_boundary"]["certification_boundary"],
|
||||
"This evidence request set supports assessment preparation only.",
|
||||
)
|
||||
|
||||
def test_builds_sample_run_plan(self) -> None:
|
||||
plan = build_run_plan(
|
||||
ROOT,
|
||||
@@ -164,6 +177,70 @@ class CoreArchitectureTests(unittest.TestCase):
|
||||
self.assertEqual(len(mappings), 1)
|
||||
self.assertEqual(mappings[0]["target_id"], "profile-readiness")
|
||||
|
||||
def test_serves_local_api_run_lifecycle(self) -> None:
|
||||
with TemporaryDirectory() as temporary_directory:
|
||||
service = start_service(ROOT, host="127.0.0.1", port=0)
|
||||
try:
|
||||
health = _request_json(service, "GET", "/health")
|
||||
self.assertEqual(health["status"], "ok")
|
||||
|
||||
extensions = _request_json(service, "GET", "/extensions")
|
||||
self.assertIn(
|
||||
"sample-noop",
|
||||
[extension["id"] for extension in extensions["extensions"]],
|
||||
)
|
||||
|
||||
target_validation = _request_json(
|
||||
service,
|
||||
"POST",
|
||||
"/profiles/validate",
|
||||
{
|
||||
"kind": "target",
|
||||
"path": "profiles/targets/sample-repository.json",
|
||||
},
|
||||
)
|
||||
self.assertEqual(target_validation["profile_id"], "sample-repository")
|
||||
|
||||
plan = _request_json(
|
||||
service,
|
||||
"POST",
|
||||
"/assessments/plan",
|
||||
{
|
||||
"target": "profiles/targets/sample-repository.json",
|
||||
"assessment": "profiles/assessments/sample-noop.json",
|
||||
},
|
||||
)
|
||||
self.assertEqual(plan["target_profile_snapshot"]["id"], "sample-repository")
|
||||
|
||||
job = _request_json(
|
||||
service,
|
||||
"POST",
|
||||
"/runs",
|
||||
{
|
||||
"target": "profiles/targets/sample-repository.json",
|
||||
"assessment": "profiles/assessments/sample-noop.json",
|
||||
"output_dir": str(Path(temporary_directory) / "api-run"),
|
||||
},
|
||||
expected_status=202,
|
||||
)
|
||||
status = _wait_for_job(service, job["job_id"])
|
||||
|
||||
self.assertEqual(status["status"], "succeeded")
|
||||
self.assertEqual(status["result"]["status"], "completed")
|
||||
|
||||
reports = _request_json(
|
||||
service,
|
||||
"GET",
|
||||
f"/runs/{job['job_id']}/reports",
|
||||
)
|
||||
self.assertIn("Guide Board Assessment Report", reports["report"]["markdown"])
|
||||
self.assertEqual(
|
||||
reports["assessment_package"]["json"]["run_id"],
|
||||
status["result"]["run_id"],
|
||||
)
|
||||
finally:
|
||||
service.stop()
|
||||
|
||||
def test_builds_retained_run_trends(self) -> None:
|
||||
with TemporaryDirectory() as temporary_directory:
|
||||
runs_dir = Path(temporary_directory)
|
||||
@@ -293,6 +370,42 @@ def _write_retention_summary(
|
||||
)
|
||||
|
||||
|
||||
def _request_json(
|
||||
service: ServiceHandle,
|
||||
method: str,
|
||||
path: str,
|
||||
payload: dict[str, object] | None = None,
|
||||
expected_status: int = 200,
|
||||
) -> dict[str, object]:
|
||||
connection = http.client.HTTPConnection(service.host, service.port, timeout=5)
|
||||
body = None
|
||||
headers = {}
|
||||
if payload is not None:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
headers["Content-Type"] = "application/json"
|
||||
try:
|
||||
connection.request(method, path, body=body, headers=headers)
|
||||
response = connection.getresponse()
|
||||
data = response.read().decode("utf-8")
|
||||
finally:
|
||||
connection.close()
|
||||
if response.status != expected_status:
|
||||
raise AssertionError(f"expected HTTP {expected_status}, got {response.status}: {data}")
|
||||
value = json.loads(data)
|
||||
if not isinstance(value, dict):
|
||||
raise AssertionError(f"expected JSON object response, got {type(value).__name__}")
|
||||
return value
|
||||
|
||||
|
||||
def _wait_for_job(service: ServiceHandle, job_id: str) -> dict[str, object]:
|
||||
for _ in range(50):
|
||||
status = _request_json(service, "GET", f"/runs/{job_id}")
|
||||
if status["status"] in {"succeeded", "failed"}:
|
||||
return status
|
||||
time.sleep(0.05)
|
||||
raise AssertionError(f"job did not finish: {job_id}")
|
||||
|
||||
|
||||
def _write_external_extension(extension_dir: Path) -> None:
|
||||
extension_dir.mkdir(parents=True, exist_ok=True)
|
||||
(extension_dir / "extension.json").write_text(
|
||||
|
||||
Reference in New Issue
Block a user