|
@@ -0,0 +1,161 @@
|
|
|
|
|
+"""P2-M5 acceptance test - verify all backend API endpoints.
|
|
|
|
|
+
|
|
|
|
|
+Run while backend is running on http://127.0.0.1:8000.
|
|
|
|
|
+All source is ASCII.
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+import json
|
|
|
|
|
+import urllib.request
|
|
|
|
|
+
|
|
|
|
|
+BASE = "http://127.0.0.1:8000"
|
|
|
|
|
+passed = 0
|
|
|
|
|
+failed = 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def api_get(path):
|
|
|
|
|
+ with urllib.request.urlopen(f"{BASE}{path}", timeout=10) as r:
|
|
|
|
|
+ return json.loads(r.read())
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test(name, condition, detail=""):
|
|
|
|
|
+ global passed, failed
|
|
|
|
|
+ if condition:
|
|
|
|
|
+ passed += 1
|
|
|
|
|
+ print(f" [PASS] {name}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ failed += 1
|
|
|
|
|
+ print(f" [FAIL] {name} {detail}")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+print("=" * 60)
|
|
|
|
|
+print("P2-M5 Acceptance Test - Backend API Verification")
|
|
|
|
|
+print("=" * 60)
|
|
|
|
|
+
|
|
|
|
|
+# 1. Health
|
|
|
|
|
+print("\n[1] Health & Core")
|
|
|
|
|
+r = api_get("/api/health")
|
|
|
|
|
+test("health status ok", r.get("status") == "ok")
|
|
|
|
|
+
|
|
|
|
|
+# 2. Projects
|
|
|
|
|
+print("\n[2] Projects API")
|
|
|
|
|
+r = api_get("/api/projects?limit=5")
|
|
|
|
|
+test("projects list returns total", "total" in r, str(r)[:100])
|
|
|
|
|
+test("projects list has items", "items" in r)
|
|
|
|
|
+
|
|
|
|
|
+# 3. Plans
|
|
|
|
|
+print("\n[3] Plans API")
|
|
|
|
|
+r = api_get("/api/plans?limit=5")
|
|
|
|
|
+test("plans list returns total", "total" in r)
|
|
|
|
|
+test("plans list has items", "items" in r)
|
|
|
|
|
+
|
|
|
|
|
+# 4. Experience CRUD
|
|
|
|
|
+print("\n[4] Experience Library API")
|
|
|
|
|
+r = api_get("/api/experience?limit=5")
|
|
|
|
|
+test("experience list returns total", "total" in r)
|
|
|
|
|
+test("experience list has items", "items" in r)
|
|
|
|
|
+
|
|
|
|
|
+# Create a test case
|
|
|
|
|
+print(" -- Create test case --")
|
|
|
|
|
+test_data = {
|
|
|
|
|
+ "topology": "SSSR",
|
|
|
|
|
+ "source_plan_id": "ACCEPTANCE-TEST",
|
|
|
|
|
+ "params": {"Airgap": 1.0, "RMSCurrent": 20},
|
|
|
|
|
+ "metrics": {"tavg_nm": 2.5, "efficiency_pct": 85.0, "ripple_pct": 3.0},
|
|
|
|
|
+ "conclusion": "Acceptance test case",
|
|
|
|
|
+ "tags": ["acceptance-test"],
|
|
|
|
|
+ "rating": 3
|
|
|
|
|
+}
|
|
|
|
|
+req = urllib.request.Request(
|
|
|
|
|
+ f"{BASE}/api/experience",
|
|
|
|
|
+ data=json.dumps(test_data).encode(),
|
|
|
|
|
+ headers={"Content-Type": "application/json"},
|
|
|
|
|
+ method="POST"
|
|
|
|
|
+)
|
|
|
|
|
+with urllib.request.urlopen(req, timeout=10) as r:
|
|
|
|
|
+ created = json.loads(r.read())
|
|
|
|
|
+test("create experience returns 201", created.get("id") is not None)
|
|
|
|
|
+case_id = created.get("id")
|
|
|
|
|
+
|
|
|
|
|
+# Get single case
|
|
|
|
|
+r = api_get(f"/api/experience/{case_id}")
|
|
|
|
|
+test("get single case", r.get("id") == case_id)
|
|
|
|
|
+test("case has params", bool(r.get("params")))
|
|
|
|
|
+test("case has metrics", bool(r.get("metrics")))
|
|
|
|
|
+
|
|
|
|
|
+# Update case
|
|
|
|
|
+print(" -- Update test case --")
|
|
|
|
|
+update_data = {"conclusion": "Updated by acceptance test", "rating": 5, "tags": ["acceptance-test", "updated"]}
|
|
|
|
|
+req = urllib.request.Request(
|
|
|
|
|
+ f"{BASE}/api/experience/{case_id}",
|
|
|
|
|
+ data=json.dumps(update_data).encode(),
|
|
|
|
|
+ headers={"Content-Type": "application/json"},
|
|
|
|
|
+ method="PUT"
|
|
|
|
|
+)
|
|
|
|
|
+with urllib.request.urlopen(req, timeout=10) as r:
|
|
|
|
|
+ updated = json.loads(r.read())
|
|
|
|
|
+test("update case returns updated", updated.get("conclusion") == "Updated by acceptance test")
|
|
|
|
|
+test("update rating", updated.get("rating") == 5)
|
|
|
|
|
+
|
|
|
|
|
+# Delete test case
|
|
|
|
|
+print(" -- Delete test case --")
|
|
|
|
|
+req = urllib.request.Request(
|
|
|
|
|
+ f"{BASE}/api/experience/{case_id}",
|
|
|
|
|
+ method="DELETE"
|
|
|
|
|
+)
|
|
|
|
|
+with urllib.request.urlopen(req, timeout=10) as r:
|
|
|
|
|
+ test("delete case returns 204", r.status == 204)
|
|
|
|
|
+
|
|
|
|
|
+# 5. Analytics
|
|
|
|
|
+print("\n[5] Analytics API")
|
|
|
|
|
+r = api_get("/api/analytics/metrics")
|
|
|
|
|
+test("metrics list", len(r.get("metrics", [])) >= 10)
|
|
|
|
|
+
|
|
|
|
|
+r = api_get("/api/analytics/experience/stats")
|
|
|
|
|
+test("experience stats has total", "total" in r)
|
|
|
|
|
+test("experience stats has topology_distribution", "topology_distribution" in r)
|
|
|
|
|
+test("experience stats has metric_ranges", "metric_ranges" in r)
|
|
|
|
|
+
|
|
|
|
|
+# Similar search
|
|
|
|
|
+print(" -- Similar search --")
|
|
|
|
|
+req = urllib.request.Request(
|
|
|
|
|
+ f"{BASE}/api/analytics/experience/similar?top_k=3&tolerance=0.5",
|
|
|
|
|
+ data=json.dumps({"params": {"Airgap": 1.0, "RMSCurrent": 20}}).encode(),
|
|
|
|
|
+ headers={"Content-Type": "application/json"},
|
|
|
|
|
+ method="POST"
|
|
|
|
|
+)
|
|
|
|
|
+with urllib.request.urlopen(req, timeout=10) as r:
|
|
|
|
|
+ similar = json.loads(r.read())
|
|
|
|
|
+test("similar search returns items", "items" in similar)
|
|
|
|
|
+
|
|
|
|
|
+# 6. Scan parameters / Generation
|
|
|
|
|
+print("\n[6] Generation / Rule Engine API")
|
|
|
|
|
+r = api_get("/api/scan-parameters")
|
|
|
|
|
+test("scan parameters list", len(r.get("parameters", [])) >= 5, str(r)[:100])
|
|
|
|
|
+
|
|
|
|
|
+# 7. API docs
|
|
|
|
|
+print("\n[7] API Documentation")
|
|
|
|
|
+try:
|
|
|
|
|
+ with urllib.request.urlopen(f"{BASE}/docs", timeout=5) as r:
|
|
|
|
|
+ test("Swagger docs accessible", r.status == 200)
|
|
|
|
|
+except Exception as e:
|
|
|
|
|
+ test("Swagger docs accessible", False, str(e))
|
|
|
|
|
+
|
|
|
|
|
+try:
|
|
|
|
|
+ with urllib.request.urlopen(f"{BASE}/openapi.json", timeout=5) as r:
|
|
|
|
|
+ spec = json.loads(r.read())
|
|
|
|
|
+ test("OpenAPI spec has paths", len(spec.get("paths", {})) >= 10)
|
|
|
|
|
+ test("OpenAPI spec paths count", len(spec.get("paths", {})) >= 15, f"count={len(spec.get('paths',{}))}")
|
|
|
|
|
+except Exception as e:
|
|
|
|
|
+ test("OpenAPI spec accessible", False, str(e))
|
|
|
|
|
+
|
|
|
|
|
+# Summary
|
|
|
|
|
+print("\n" + "=" * 60)
|
|
|
|
|
+print(f"ACCEPTANCE TEST SUMMARY: {passed} passed, {failed} failed")
|
|
|
|
|
+print("=" * 60)
|
|
|
|
|
+
|
|
|
|
|
+if failed > 0:
|
|
|
|
|
+ print("\nSome tests failed. Please review.")
|
|
|
|
|
+ exit(1)
|
|
|
|
|
+else:
|
|
|
|
|
+ print("\nAll backend API acceptance tests passed!")
|
|
|
|
|
+ exit(0)
|