test_acceptance.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. """P2-M5 acceptance test - verify all backend API endpoints.
  2. Run while backend is running on http://127.0.0.1:8000.
  3. All source is ASCII.
  4. """
  5. import json
  6. import urllib.request
  7. BASE = "http://127.0.0.1:8000"
  8. passed = 0
  9. failed = 0
  10. def api_get(path):
  11. with urllib.request.urlopen(f"{BASE}{path}", timeout=10) as r:
  12. return json.loads(r.read())
  13. def test(name, condition, detail=""):
  14. global passed, failed
  15. if condition:
  16. passed += 1
  17. print(f" [PASS] {name}")
  18. else:
  19. failed += 1
  20. print(f" [FAIL] {name} {detail}")
  21. print("=" * 60)
  22. print("P2-M5 Acceptance Test - Backend API Verification")
  23. print("=" * 60)
  24. # 1. Health
  25. print("\n[1] Health & Core")
  26. r = api_get("/api/health")
  27. test("health status ok", r.get("status") == "ok")
  28. # 2. Projects
  29. print("\n[2] Projects API")
  30. r = api_get("/api/projects?limit=5")
  31. test("projects list returns total", "total" in r, str(r)[:100])
  32. test("projects list has items", "items" in r)
  33. # 3. Plans
  34. print("\n[3] Plans API")
  35. r = api_get("/api/plans?limit=5")
  36. test("plans list returns total", "total" in r)
  37. test("plans list has items", "items" in r)
  38. # 4. Experience CRUD
  39. print("\n[4] Experience Library API")
  40. r = api_get("/api/experience?limit=5")
  41. test("experience list returns total", "total" in r)
  42. test("experience list has items", "items" in r)
  43. # Create a test case
  44. print(" -- Create test case --")
  45. test_data = {
  46. "topology": "SSSR",
  47. "source_plan_id": "ACCEPTANCE-TEST",
  48. "params": {"Airgap": 1.0, "RMSCurrent": 20},
  49. "metrics": {"tavg_nm": 2.5, "efficiency_pct": 85.0, "ripple_pct": 3.0},
  50. "conclusion": "Acceptance test case",
  51. "tags": ["acceptance-test"],
  52. "rating": 3
  53. }
  54. req = urllib.request.Request(
  55. f"{BASE}/api/experience",
  56. data=json.dumps(test_data).encode(),
  57. headers={"Content-Type": "application/json"},
  58. method="POST"
  59. )
  60. with urllib.request.urlopen(req, timeout=10) as r:
  61. created = json.loads(r.read())
  62. test("create experience returns 201", created.get("id") is not None)
  63. case_id = created.get("id")
  64. # Get single case
  65. r = api_get(f"/api/experience/{case_id}")
  66. test("get single case", r.get("id") == case_id)
  67. test("case has params", bool(r.get("params")))
  68. test("case has metrics", bool(r.get("metrics")))
  69. # Update case
  70. print(" -- Update test case --")
  71. update_data = {"conclusion": "Updated by acceptance test", "rating": 5, "tags": ["acceptance-test", "updated"]}
  72. req = urllib.request.Request(
  73. f"{BASE}/api/experience/{case_id}",
  74. data=json.dumps(update_data).encode(),
  75. headers={"Content-Type": "application/json"},
  76. method="PUT"
  77. )
  78. with urllib.request.urlopen(req, timeout=10) as r:
  79. updated = json.loads(r.read())
  80. test("update case returns updated", updated.get("conclusion") == "Updated by acceptance test")
  81. test("update rating", updated.get("rating") == 5)
  82. # Delete test case
  83. print(" -- Delete test case --")
  84. req = urllib.request.Request(
  85. f"{BASE}/api/experience/{case_id}",
  86. method="DELETE"
  87. )
  88. with urllib.request.urlopen(req, timeout=10) as r:
  89. test("delete case returns 204", r.status == 204)
  90. # 5. Analytics
  91. print("\n[5] Analytics API")
  92. r = api_get("/api/analytics/metrics")
  93. test("metrics list", len(r.get("metrics", [])) >= 10)
  94. r = api_get("/api/analytics/experience/stats")
  95. test("experience stats has total", "total" in r)
  96. test("experience stats has topology_distribution", "topology_distribution" in r)
  97. test("experience stats has metric_ranges", "metric_ranges" in r)
  98. # Similar search
  99. print(" -- Similar search --")
  100. req = urllib.request.Request(
  101. f"{BASE}/api/analytics/experience/similar?top_k=3&tolerance=0.5",
  102. data=json.dumps({"params": {"Airgap": 1.0, "RMSCurrent": 20}}).encode(),
  103. headers={"Content-Type": "application/json"},
  104. method="POST"
  105. )
  106. with urllib.request.urlopen(req, timeout=10) as r:
  107. similar = json.loads(r.read())
  108. test("similar search returns items", "items" in similar)
  109. # 6. Scan parameters / Generation
  110. print("\n[6] Generation / Rule Engine API")
  111. r = api_get("/api/scan-parameters")
  112. test("scan parameters list", len(r.get("parameters", [])) >= 5, str(r)[:100])
  113. # 7. API docs
  114. print("\n[7] API Documentation")
  115. try:
  116. with urllib.request.urlopen(f"{BASE}/docs", timeout=5) as r:
  117. test("Swagger docs accessible", r.status == 200)
  118. except Exception as e:
  119. test("Swagger docs accessible", False, str(e))
  120. try:
  121. with urllib.request.urlopen(f"{BASE}/openapi.json", timeout=5) as r:
  122. spec = json.loads(r.read())
  123. test("OpenAPI spec has paths", len(spec.get("paths", {})) >= 10)
  124. test("OpenAPI spec paths count", len(spec.get("paths", {})) >= 15, f"count={len(spec.get('paths',{}))}")
  125. except Exception as e:
  126. test("OpenAPI spec accessible", False, str(e))
  127. # Summary
  128. print("\n" + "=" * 60)
  129. print(f"ACCEPTANCE TEST SUMMARY: {passed} passed, {failed} failed")
  130. print("=" * 60)
  131. if failed > 0:
  132. print("\nSome tests failed. Please review.")
  133. exit(1)
  134. else:
  135. print("\nAll backend API acceptance tests passed!")
  136. exit(0)