126 lines
5.4 KiB
Python
Executable File
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())
|