Add admin panel, WebSocket support, and API versioning
Introduce an admin portal (React + Nginx), WebSocket routing, and API versioning middleware with `/api/v1/` prefix deprecation. Add master API key authentication, new Prometheus metrics for AI token consumption and active WebSockets, and extend S3 config with a public endpoint URL. Update test paths and fixtures to align with the new routing structure.
This commit is contained in:
+24
-16
@@ -46,12 +46,20 @@ async def _upload_test_image(
|
||||
)
|
||||
|
||||
# Le pipeline ARQ est mocké globalement dans conftest.py
|
||||
response = await async_client.post(
|
||||
"/images/upload",
|
||||
files={"file": (filename, io.BytesIO(jpeg_bytes), "image/jpeg")},
|
||||
headers=headers,
|
||||
)
|
||||
return response
|
||||
import httpx
|
||||
print(f"Uploading {filename}...")
|
||||
try:
|
||||
response = await async_client.post(
|
||||
"/api/v1/images/upload",
|
||||
files={"file": (filename, io.BytesIO(jpeg_bytes), "image/jpeg")},
|
||||
headers=headers,
|
||||
timeout=httpx.Timeout(5.0)
|
||||
)
|
||||
print(f"Uploaded {filename}: {response.status_code}")
|
||||
return response
|
||||
except Exception as e:
|
||||
print(f"Error uploading {filename}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
@@ -71,14 +79,14 @@ async def test_client_a_image_invisible_to_client_b(
|
||||
assert upload_resp.status_code == 201
|
||||
|
||||
# Liste pour A → contient l'image
|
||||
response = await async_client.get("/images", headers=auth_headers_a)
|
||||
response = await async_client.get("/api/v1/images", headers=auth_headers_a)
|
||||
assert response.status_code == 200
|
||||
data_a = response.json()
|
||||
assert data_a["total"] == 1
|
||||
assert data_a["items"][0]["original_name"] == "photo_a.jpg"
|
||||
|
||||
# Liste pour B → vide
|
||||
response = await async_client.get("/images", headers=auth_headers_b)
|
||||
response = await async_client.get("/api/v1/images", headers=auth_headers_b)
|
||||
assert response.status_code == 200
|
||||
data_b = response.json()
|
||||
assert data_b["total"] == 0
|
||||
@@ -103,7 +111,7 @@ async def test_client_b_cannot_read_client_a_image(
|
||||
image_id = upload_resp.json()["id"]
|
||||
|
||||
# B essaie de lire l'image de A → 404
|
||||
response = await async_client.get(f"/images/{image_id}", headers=auth_headers_b)
|
||||
response = await async_client.get(f"/api/v1/images/{image_id}", headers=auth_headers_b)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@@ -125,11 +133,11 @@ async def test_client_b_cannot_delete_client_a_image(
|
||||
image_id = upload_resp.json()["id"]
|
||||
|
||||
# B essaie de supprimer → 404
|
||||
response = await async_client.delete(f"/images/{image_id}", headers=auth_headers_b)
|
||||
response = await async_client.delete(f"/api/v1/images/{image_id}", headers=auth_headers_b)
|
||||
assert response.status_code == 404
|
||||
|
||||
# L'image existe toujours pour A
|
||||
response = await async_client.get(f"/images/{image_id}", headers=auth_headers_a)
|
||||
response = await async_client.get(f"/api/v1/images/{image_id}", headers=auth_headers_a)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@@ -153,11 +161,11 @@ async def test_listing_returns_only_own_images(
|
||||
await _upload_test_image(async_client, auth_headers_b, "b1.jpg")
|
||||
|
||||
# A voit 2 images
|
||||
resp_a = await async_client.get("/images", headers=auth_headers_a)
|
||||
resp_a = await async_client.get("/api/v1/images", headers=auth_headers_a)
|
||||
assert resp_a.json()["total"] == 2
|
||||
|
||||
# B voit 1 image
|
||||
resp_b = await async_client.get("/images", headers=auth_headers_b)
|
||||
resp_b = await async_client.get("/api/v1/images", headers=auth_headers_b)
|
||||
assert resp_b.json()["total"] == 1
|
||||
assert resp_b.json()["items"][0]["original_name"] == "b1.jpg"
|
||||
|
||||
@@ -181,7 +189,7 @@ async def test_reprocess_other_client_image_returns_404(
|
||||
|
||||
# B essaie de reprocess → 404
|
||||
response = await async_client.post(
|
||||
f"/images/{image_id}/reprocess",
|
||||
f"/api/v1/images/{image_id}/reprocess",
|
||||
headers=auth_headers_b,
|
||||
)
|
||||
assert response.status_code == 404
|
||||
@@ -203,7 +211,7 @@ async def test_sub_endpoints_other_client_returns_404(
|
||||
assert upload_resp.status_code == 201
|
||||
image_id = upload_resp.json()["id"]
|
||||
|
||||
for endpoint in [f"/images/{image_id}/status", f"/images/{image_id}/exif",
|
||||
f"/images/{image_id}/ocr", f"/images/{image_id}/ai"]:
|
||||
for endpoint in [f"/api/v1/images/{image_id}/status", f"/api/v1/images/{image_id}/exif",
|
||||
f"/api/v1/images/{image_id}/ocr", f"/api/v1/images/{image_id}/ai"]:
|
||||
response = await async_client.get(endpoint, headers=auth_headers_b)
|
||||
assert response.status_code == 404, f"Endpoint {endpoint} should return 404 for other client"
|
||||
|
||||
Reference in New Issue
Block a user