Files
asf/scripts/seed-traceability.py
T
2026-09-08 07:21:34 -04:00

126 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Seed tenant t-demo-003 with 5 artifacts + 5 relationships for a rich Traceability Graph demo.
Endpoint testati via curl:
- POST /api/v1/artifacts
- POST /api/v1/relationships
- GET /api/v1/traceability/graph
"""
from __future__ import annotations
import json
import subprocess
import sys
from typing import Any
BASE = "http://127.0.0.1:8000/api/v1"
H = [
"-H", "X-ASF-Tenant-Id: t-demo-003",
"-H", "X-ASF-User-Id: u-mario",
"-H", "Content-Type: application/json",
]
def curl(method: str, path: str, body: dict[str, Any] | None = None) -> tuple[int, dict[str, Any] | list | str]:
cmd = ["curl", "-s", "-m", "15", "-o", "-", "-w", "\n%{http_code}", "-X", method] + H
if body is not None:
cmd += ["--data", json.dumps(body, ensure_ascii=False)]
cmd.append(f"{BASE}{path}")
r = subprocess.run(cmd, capture_output=True, text=True)
out_raw = r.stdout.strip() or "{}"
# Last line is the http code we injected with -w
if "\n" in out_raw:
json_part, code_part = out_raw.rsplit("\n", 1)
else:
json_part, code_part = "", out_raw
try:
code = int(code_part)
except ValueError:
code = 0
try:
data = json.loads(json_part or "null")
except Exception as exc: # pragma: no cover
data = f"JSON_ERR {exc} RAW={json_part[:200]!r}"
print(f" [{method:6s} {path:24s}] HTTP={code:3d}",
f"type={data.get('type','?') if isinstance(data,dict) else '?'}\tid={str(data.get('id',''))[:10] if isinstance(data,dict) else '?'}",
file=sys.stderr)
return code, data
def main() -> int:
print("# Step 1: list existing artifacts + project_id", file=sys.stderr)
_, arts = curl("GET", "/artifacts?limit=50")
assert isinstance(arts, list), f"artifacts list not a list: {arts!r}"
req_103 = None
project_id = None
for a in arts:
if isinstance(a, dict) and a.get("external_id") == "REQ-103":
req_103 = a
project_id = a.get("project_id")
break
if not req_103:
# fallback: first artifact
req_103 = arts[0] if arts else None
project_id = req_103.get("project_id") if isinstance(req_103, dict) else None
print(f" req_103 id={str(req_103['id'])[:10] if isinstance(req_103,dict) else None} project_id={str(project_id)[:10] if project_id else None}",
file=sys.stderr)
def make(type_: str, ext: str, title: str, content: str) -> dict[str, Any]:
code, data = curl(
"POST", "/artifacts",
{
"type": type_,
"external_id": ext,
"title": title,
"content": content,
"status": "done",
"project_id": project_id,
},
)
assert code == 201 and isinstance(data, dict) and "id" in data, f"make {type_} FAILED HTTP={code} {data}"
return data
print(file=sys.stderr); print("# Step 2: create 5 extra artifacts", file=sys.stderr)
task = make("task", "TASK-101", "Implementa tenant trust header coerce", "FastAPI correlation middleware: UUID UUID5 per stringhe t-demo-xxx, no 400 invalid")
pr = make("pull_request", "PR#88", "feat(rbac): tenant trust + policies v1", "feat(rbac): add trust header middleware + policy service helpers")
test = make("test", "TEST-22", "cross-tenant reads/writes blocked", "Pytest suite proving cross-tenant artifact operations return 404/403")
build = make("build", "BUILD-910","asf-core build #910 (main)", "GitHub Actions build main branch, all checks green.")
deploy = make("deployment", "DEP-44", "asf-core v0.5.0 → prod-west-1", "ArgoCD sync asf-core to cluster prod-west-1 eu-west")
print(file=sys.stderr); print("# Step 3: create 5 relationships (chain: DEP → BUILD → PR → TASK → REQ, TEST → PR)", file=sys.stderr)
rels_pairs = [
(task, req_103, "implements"),
(pr, task, "generated_from"),
(build, pr, "generated_from"),
(test, pr, "verifies"),
(deploy, build, "deployed_as"),
]
for src, tgt, kind in rels_pairs:
code, rel = curl(
"POST", "/relationships",
{
"source_artifact_id": src["id"],
"target_artifact_id": tgt["id"],
"relationship_type": kind,
"meta": {"seed": "traceability_demo"},
},
)
assert code < 300 and isinstance(rel, dict) and "id" in rel, f"REL FAIL {kind} -> {rel}"
print(file=sys.stderr); print("# Step 4: GET traceability graph → counts", file=sys.stderr)
code, graph = curl("GET", "/traceability/graph?limit=200")
assert code == 200 and isinstance(graph, dict), f"GRAPH FAIL HTTP={code} {graph}"
nodes = graph.get("nodes", [])
edges = graph.get("edges", [])
print(f"\n=== SEED SUCCESS ===", file=sys.stderr)
print(f" graph.nodes = {len(nodes)} graph.edges = {len(edges)}", file=sys.stderr)
# print to stdout for bash pipeline capture (JSON):
print(json.dumps({"nodes": len(nodes), "edges": len(edges),
"node_ids": [{"id":str(n["id"])[:10],"type":n.get("type"),"label":n.get("label")} for n in nodes],
"edge_labels": [{"s":e["source"][:8],"t":e["target"][:8],"lbl":e.get("label")} for e in edges]}))
return 0
if __name__ == "__main__":
raise SystemExit(main())