Checkpoint React frontend migration
This commit is contained in:
346
backend/test_all_routes.py
Normal file
346
backend/test_all_routes.py
Normal file
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test all routes in the IRT Bank Soal application.
|
||||
Tests each endpoint and checks for Internal Server Errors.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# All routes from OpenAPI spec
|
||||
API_ROUTES = [
|
||||
# Root endpoints
|
||||
("GET", "/"),
|
||||
("GET", "/health"),
|
||||
# Session endpoints
|
||||
("POST", "/api/v1/session/"),
|
||||
("GET", "/api/v1/session/{session_id}"),
|
||||
("POST", "/api/v1/session/{session_id}/complete"),
|
||||
("GET", "/api/v1/session/{session_id}/next_item"),
|
||||
("POST", "/api/v1/session/{session_id}/submit_answer"),
|
||||
# Tryout endpoints
|
||||
("GET", "/api/v1/tryout/"),
|
||||
("GET", "/api/v1/tryout/{tryout_id}/config"),
|
||||
("PUT", "/api/v1/tryout/{tryout_id}/normalization"),
|
||||
("GET", "/api/v1/tryout/{tryout_id}/calibration-status"),
|
||||
("POST", "/api/v1/tryout/{tryout_id}/calibrate"),
|
||||
("POST", "/api/v1/tryout/{tryout_id}/calibrate/{item_id}"),
|
||||
# WordPress endpoints
|
||||
("POST", "/api/v1/wordpress/sync_users"),
|
||||
("POST", "/api/v1/wordpress/verify_session"),
|
||||
("GET", "/api/v1/wordpress/website/{website_id}/users"),
|
||||
("GET", "/api/v1/wordpress/website/{website_id}/user/{wp_user_id}"),
|
||||
# Reports endpoints
|
||||
("POST", "/api/v1/reports/schedule"),
|
||||
("GET", "/api/v1/reports/schedule/{schedule_id}"),
|
||||
("GET", "/api/v1/reports/schedules"),
|
||||
("DELETE", "/api/v1/reports/schedule/{schedule_id}"),
|
||||
("POST", "/api/v1/reports/schedule/{schedule_id}/export"),
|
||||
("GET", "/api/v1/reports/student/performance"),
|
||||
("GET", "/api/v1/reports/student/performance/export/{format}"),
|
||||
("GET", "/api/v1/reports/items/analysis"),
|
||||
("GET", "/api/v1/reports/items/analysis/export/{format}"),
|
||||
("GET", "/api/v1/reports/calibration/status"),
|
||||
("GET", "/api/v1/reports/calibration/status/export/{format}"),
|
||||
("GET", "/api/v1/reports/tryout/comparison"),
|
||||
("GET", "/api/v1/reports/tryout/comparison/export/{format}"),
|
||||
("GET", "/api/v1/reports/export/{schedule_id}/{format}"),
|
||||
# Import/Export endpoints
|
||||
("POST", "/api/v1/import-export/preview"),
|
||||
("POST", "/api/v1/import-export/questions"),
|
||||
("GET", "/api/v1/import-export/export/questions"),
|
||||
("POST", "/api/v1/import-export/tryout-json/preview"),
|
||||
("POST", "/api/v1/import-export/tryout-json"),
|
||||
# Admin AI endpoints
|
||||
("POST", "/api/v1/admin/ai/generate-preview"),
|
||||
("POST", "/api/v1/admin/ai/generate-save"),
|
||||
("GET", "/api/v1/admin/ai/stats"),
|
||||
("GET", "/api/v1/admin/ai/models"),
|
||||
# Admin endpoints
|
||||
("POST", "/api/v1/admin/{tryout_id}/calibrate"),
|
||||
("POST", "/api/v1/admin/{tryout_id}/toggle-ai-generation"),
|
||||
("POST", "/api/v1/admin/{tryout_id}/reset-normalization"),
|
||||
# Admin CAT endpoints
|
||||
("POST", "/api/v1/admin/cat/test"),
|
||||
("GET", "/api/v1/admin/session/{session_id}/status"),
|
||||
# Admin web routes (HTML pages)
|
||||
("GET", "/admin"),
|
||||
("GET", "/admin/login"),
|
||||
("POST", "/admin/login"),
|
||||
("POST", "/admin/logout"),
|
||||
("GET", "/admin/password"),
|
||||
("POST", "/admin/password"),
|
||||
("GET", "/admin/dashboard"),
|
||||
("GET", "/admin/questions"),
|
||||
("GET", "/admin/questions/{item_id}"),
|
||||
("GET", "/admin/questions/{item_id}/quality"),
|
||||
("GET", "/admin/exams"),
|
||||
("GET", "/admin/exams/{tryout_id}"),
|
||||
("GET", "/admin/reports"),
|
||||
("GET", "/admin/settings"),
|
||||
("GET", "/admin/hierarchy"),
|
||||
("GET", "/admin/websites"),
|
||||
("POST", "/admin/websites"),
|
||||
("GET", "/admin/websites/new"),
|
||||
("GET", "/admin/websites/{website_id}"),
|
||||
("POST", "/admin/websites/{website_id}"),
|
||||
("POST", "/admin/websites/{website_id}/delete"),
|
||||
("GET", "/admin/tryout-import"),
|
||||
("GET", "/admin/tryout-import/preview"),
|
||||
("POST", "/admin/tryout-import"),
|
||||
("GET", "/admin/snapshot-questions"),
|
||||
("POST", "/admin/snapshot-questions/promote-bulk"),
|
||||
("GET", "/admin/calibration-status"),
|
||||
("GET", "/admin/item-statistics"),
|
||||
("GET", "/admin/sessions"),
|
||||
("GET", "/admin/basis-items"),
|
||||
("GET", "/admin/basis-items/{item_id}"),
|
||||
("POST", "/admin/basis-items/{item_id}/generate"),
|
||||
("POST", "/admin/basis-items/{item_id}/generate/review-bulk"),
|
||||
("GET", "/admin/basis-items/{item_id}/generate/variants/{variant_id}"),
|
||||
]
|
||||
|
||||
# Placeholder values for path parameters
|
||||
PLACEHOLDERS = {
|
||||
"{session_id}": "test-session-123",
|
||||
"{tryout_id}": "test-tryout-123",
|
||||
"{item_id}": "1",
|
||||
"{website_id}": "1",
|
||||
"{wp_user_id}": "123",
|
||||
"{schedule_id}": "test-schedule-123",
|
||||
"{format}": "xlsx",
|
||||
"{variant_id}": "test-variant-123",
|
||||
}
|
||||
|
||||
# Minimal request bodies for POST endpoints
|
||||
REQUEST_BODIES = {
|
||||
"/api/v1/session/": {
|
||||
"session_id": "test",
|
||||
"tryout_id": "test",
|
||||
"wp_user_id": "123",
|
||||
"website_id": 1,
|
||||
"scoring_mode": "ctt",
|
||||
},
|
||||
"/api/v1/session/{session_id}/complete": {
|
||||
"end_time": "2024-01-01T00:00:00Z",
|
||||
"user_answers": [],
|
||||
},
|
||||
"/api/v1/session/{session_id}/submit_answer": {
|
||||
"item_id": 1,
|
||||
"response": "A",
|
||||
"time_spent": 10,
|
||||
},
|
||||
"/api/v1/tryout/{tryout_id}/normalization": {
|
||||
"normalization_mode": "static",
|
||||
"static_rataan": 500,
|
||||
"static_sb": 100,
|
||||
},
|
||||
"/api/v1/wordpress/sync_users": {}, # Requires proper auth header
|
||||
"/api/v1/wordpress/verify_session": {
|
||||
"website_id": 1,
|
||||
"wp_user_id": "123",
|
||||
"token": "test",
|
||||
},
|
||||
"/api/v1/reports/schedule": {
|
||||
"tryout_id": "test",
|
||||
"report_type": "student_performance",
|
||||
},
|
||||
"/api/v1/admin/ai/generate-preview": {
|
||||
"basis_item_id": 1,
|
||||
"target_level": "sulit",
|
||||
"ai_model": "qwen/qwen2.5-32b-instruct",
|
||||
},
|
||||
"/api/v1/admin/ai/generate-save": {
|
||||
"stem": "Test?",
|
||||
"options": {"A": "a", "B": "b", "C": "c", "D": "d"},
|
||||
"correct": "A",
|
||||
"tryout_id": "test",
|
||||
"website_id": 1,
|
||||
"basis_item_id": 1,
|
||||
"slot": 1,
|
||||
"level": "sulit",
|
||||
"ai_model": "qwen/qwen2.5-32b-instruct",
|
||||
},
|
||||
"/api/v1/admin/cat/test": {"tryout_id": "test", "website_id": 1},
|
||||
"/api/v1/admin/{tryout_id}/calibrate": {},
|
||||
"/api/v1/admin/{tryout_id}/toggle-ai-generation": {},
|
||||
"/api/v1/admin/{tryout_id}/reset-normalization": {},
|
||||
"/api/v1/import-export/preview": None, # Requires file upload
|
||||
"/api/v1/import-export/questions": None, # Requires file upload
|
||||
"/api/v1/import-export/tryout-json/preview": None, # Requires file upload
|
||||
"/api/v1/import-export/tryout-json": None, # Requires file upload
|
||||
}
|
||||
|
||||
|
||||
def expand_route(method: str, route: str) -> list:
|
||||
"""Expand route with placeholders."""
|
||||
expanded = []
|
||||
test_route = route
|
||||
for placeholder, value in PLACEHOLDERS.items():
|
||||
if placeholder in test_route:
|
||||
test_route = test_route.replace(placeholder, value)
|
||||
expanded.append((method, test_route))
|
||||
return expanded
|
||||
|
||||
|
||||
def test_route(client: httpx.Client, method: str, route: str) -> dict:
|
||||
"""Test a single route."""
|
||||
# Expand placeholders
|
||||
expanded = expand_route(method, route)
|
||||
if not expanded:
|
||||
return {
|
||||
"route": route,
|
||||
"method": method,
|
||||
"error": "Could not expand route",
|
||||
"status_code": None,
|
||||
}
|
||||
|
||||
method, test_route = expanded[0]
|
||||
|
||||
# Determine request body
|
||||
body = None
|
||||
request_body = REQUEST_BODIES.get(route, REQUEST_BODIES.get(test_route, {}))
|
||||
if request_body is not None:
|
||||
body = request_body
|
||||
|
||||
# Determine query params
|
||||
params = {}
|
||||
if "export/questions" in route:
|
||||
params = {"tryout_id": "test"}
|
||||
|
||||
headers = {"X-Website-ID": "1"}
|
||||
|
||||
try:
|
||||
response = client.request(
|
||||
method=method,
|
||||
url=BASE_URL + test_route,
|
||||
json=body if body and method in ["POST", "PUT", "PATCH"] else None,
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=10.0,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
is_500 = response.status_code == 500
|
||||
is_ise = "Internal Server Error" in response.text
|
||||
|
||||
return {
|
||||
"route": route,
|
||||
"method": method,
|
||||
"expanded_route": test_route,
|
||||
"status_code": response.status_code,
|
||||
"has_500": is_500,
|
||||
"has_ise": is_ise,
|
||||
"response_preview": response.text[:200] if response.text else "",
|
||||
"error": None,
|
||||
}
|
||||
except httpx.TimeoutException:
|
||||
return {
|
||||
"route": route,
|
||||
"method": method,
|
||||
"expanded_route": test_route,
|
||||
"status_code": None,
|
||||
"has_500": False,
|
||||
"has_ise": False,
|
||||
"response_preview": "",
|
||||
"error": "Timeout",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"route": route,
|
||||
"method": method,
|
||||
"expanded_route": test_route,
|
||||
"status_code": None,
|
||||
"has_500": False,
|
||||
"has_ise": False,
|
||||
"response_preview": "",
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 80)
|
||||
print("Testing all IRT Bank Soal routes for Internal Server Errors")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
results = []
|
||||
has_errors = False
|
||||
|
||||
with httpx.Client(timeout=30.0) as client:
|
||||
for method, route in API_ROUTES:
|
||||
result = test_route(client, method, route)
|
||||
results.append(result)
|
||||
|
||||
status = result["status_code"]
|
||||
error_marker = ""
|
||||
|
||||
if result["error"]:
|
||||
error_marker = f" [ERROR: {result['error']}]"
|
||||
has_errors = True
|
||||
elif status and status >= 500:
|
||||
error_marker = f" [INTERNAL SERVER ERROR!]"
|
||||
has_errors = True
|
||||
elif status and status == 500:
|
||||
error_marker = f" [500 - INTERNAL SERVER ERROR!]"
|
||||
has_errors = True
|
||||
elif "Internal Server Error" in str(result.get("response_preview", "")):
|
||||
error_marker = " [500 - INTERNAL SERVER ERROR!]"
|
||||
has_errors = True
|
||||
|
||||
status_str = str(status) if status else "N/A"
|
||||
print(f"{method:6} {route:<60} -> {status_str}{error_marker}")
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
total = len(results)
|
||||
successful = sum(1 for r in results if r["status_code"] and r["status_code"] < 500)
|
||||
client_errors = sum(
|
||||
1 for r in results if r["status_code"] and 400 <= r["status_code"] < 500
|
||||
)
|
||||
server_errors = sum(
|
||||
1 for r in results if r["status_code"] and r["status_code"] >= 500
|
||||
)
|
||||
timeouts = sum(1 for r in results if r["error"] == "Timeout")
|
||||
exceptions = sum(1 for r in results if r["error"] and r["error"] != "Timeout")
|
||||
ise_errors = sum(1 for r in results if r.get("has_ise") or r.get("has_500"))
|
||||
|
||||
print(f"Total routes tested: {total}")
|
||||
print(f"Successful (2xx): {successful}")
|
||||
print(f"Client errors (4xx): {client_errors}")
|
||||
print(f"Server errors (5xx): {server_errors}")
|
||||
print(f"Timeouts: {timeouts}")
|
||||
print(f"Exceptions: {exceptions}")
|
||||
print(f"Internal Server Errors: {ise_errors}")
|
||||
print()
|
||||
|
||||
if has_errors:
|
||||
print("Routes with issues:")
|
||||
for r in results:
|
||||
if r["status_code"] and r["status_code"] >= 500:
|
||||
print(f" - {r['method']} {r['route']} -> {r['status_code']}")
|
||||
elif r["error"]:
|
||||
print(f" - {r['method']} {r['route']} -> ERROR: {r['error']}")
|
||||
elif r.get("has_ise"):
|
||||
print(f" - {r['method']} {r['route']} -> Internal Server Error")
|
||||
|
||||
print()
|
||||
if ise_errors == 0 and exceptions == 0:
|
||||
print("✅ All routes passed! No Internal Server Errors detected.")
|
||||
return 0
|
||||
else:
|
||||
print("❌ Some routes have issues. Please review the output above.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user