refactor: engineering improvements -- API versioning, structured logging, Alembic, error standardization, test coverage

- API versioning: all REST endpoints prefixed with /api/v1/
- Structured logging: replaced stdlib logging with structlog (console/JSON modes)
- Alembic migrations: versioned DB schema with initial migration
- Error standardization: global exception handlers for consistent envelope format
- Interrupt cleanup: asyncio background task for expired interrupt removal
- Integration tests: +30 tests (analytics, replay, openapi, error, session APIs)
- Frontend tests: +57 tests (all components, pages, useWebSocket hook)
- Backend: 557 tests, 89.75% coverage | Frontend: 80 tests, 16 test files
This commit is contained in:
Yaojia Wang
2026-04-06 23:19:29 +02:00
parent af53111928
commit f0699436c5
59 changed files with 2846 additions and 149 deletions

View File

@@ -62,7 +62,7 @@ class TestFlow5OpenAPIImport:
with TestClient(app) as client:
# Step 1: Start import job
resp = client.post(
"/api/openapi/import",
"/api/v1/openapi/import",
json={"url": "https://api.example.com/openapi.json"},
)
assert resp.status_code == 202
@@ -71,7 +71,7 @@ class TestFlow5OpenAPIImport:
job_id = body["job_id"]
# Step 2: Check job status (still pending since background task hasn't run)
resp = client.get(f"/api/openapi/jobs/{job_id}")
resp = client.get(f"/api/v1/openapi/jobs/{job_id}")
assert resp.status_code == 200
assert resp.json()["job_id"] == job_id
@@ -99,7 +99,7 @@ class TestFlow5OpenAPIImport:
with TestClient(app) as client:
# Step 1: Get classifications
resp = client.get(f"/api/openapi/jobs/{job_id}/classifications")
resp = client.get(f"/api/v1/openapi/jobs/{job_id}/classifications")
assert resp.status_code == 200
classifications = resp.json()
assert len(classifications) == 2
@@ -118,7 +118,7 @@ class TestFlow5OpenAPIImport:
# Step 2: Update a classification
resp = client.put(
f"/api/openapi/jobs/{job_id}/classifications/0",
f"/api/v1/openapi/jobs/{job_id}/classifications/0",
json={
"access_type": "write",
"needs_interrupt": True,
@@ -132,7 +132,7 @@ class TestFlow5OpenAPIImport:
assert updated["agent_group"] == "order_actions"
# Step 3: Approve the job
resp = client.post(f"/api/openapi/jobs/{job_id}/approve")
resp = client.post(f"/api/v1/openapi/jobs/{job_id}/approve")
assert resp.status_code == 200
assert resp.json()["status"] == "approved"
@@ -140,14 +140,14 @@ class TestFlow5OpenAPIImport:
app = create_e2e_app()
with TestClient(app) as client:
resp = client.get("/api/openapi/jobs/nonexistent")
resp = client.get("/api/v1/openapi/jobs/nonexistent")
assert resp.status_code == 404
def test_import_invalid_url_returns_422(self) -> None:
app = create_e2e_app()
with TestClient(app) as client:
resp = client.post("/api/openapi/import", json={"url": "not-a-url"})
resp = client.post("/api/v1/openapi/import", json={"url": "not-a-url"})
assert resp.status_code == 422
def test_classification_index_out_of_range(self) -> None:
@@ -166,7 +166,7 @@ class TestFlow5OpenAPIImport:
with TestClient(app) as client:
resp = client.put(
f"/api/openapi/jobs/{job_id}/classifications/99",
f"/api/v1/openapi/jobs/{job_id}/classifications/99",
json={
"access_type": "read",
"needs_interrupt": False,
@@ -191,7 +191,7 @@ class TestFlow5OpenAPIImport:
with TestClient(app) as client:
resp = client.put(
f"/api/openapi/jobs/{job_id}/classifications/0",
f"/api/v1/openapi/jobs/{job_id}/classifications/0",
json={
"access_type": "read",
"needs_interrupt": False,