feat(phase-4): Task 2 — SEC-08 CloudConnectionOut, SEC-09 delete-user cleanup, admin audit writes

- Add CloudConnectionOut Pydantic model (SEC-08): credentials_enc deliberately excluded
- Implement DELETE /api/admin/users/{id} (SEC-09): collects user docs, deletes MinIO
  objects best-effort before DB delete; audit log written within same transaction
- Add write_audit_log calls to: create_user (admin.user_created), update_user_status
  (admin.user_deactivated/admin.user_activated), update_user_quota (admin.quota_changed),
  update_ai_config (admin.ai_provider_assigned), delete_user (admin.user_deleted)
- Add Request param to all admin state-changing handlers for IP extraction
- Fix test_admin_impersonation_not_found: accept 405 in addition to 404/422
  (expected: DELETE /users/{id} exists now, so GET returns 405 — no impersonation
  route still satisfied, just a different HTTP status for non-existent method)
This commit is contained in:
curo1305
2026-05-25 21:51:34 +02:00
parent e451b16f8f
commit 8e6005cb73
2 changed files with 140 additions and 5 deletions
+9 -3
View File
@@ -320,13 +320,19 @@ async def test_update_ai_config(admin_client):
@pytest.mark.asyncio
async def test_admin_impersonation_not_found(async_client: AsyncClient):
"""GET /api/admin/users/impersonate → 404 or 422 (route does not exist)."""
"""GET /api/admin/users/impersonate → 404, 422, or 405 (no GET impersonation route).
Note: 405 is acceptable when DELETE /api/admin/users/{id} exists (Plan 04-07, SEC-09)
— the DELETE route is the user-delete endpoint, NOT impersonation. A 405 means
GET is not allowed, which satisfies the invariant that no impersonation GET endpoint
exists (ADMIN-07).
"""
# No admin override — just verifying the route doesn't exist at all.
resp = await async_client.get("/api/admin/users/impersonate")
# 404 = no route; 422 = id parse failed (impersonate is not a UUID) → acceptable
# 405 = Method Not Allowed (DELETE /users/{id} exists but no GET handler) → also acceptable
# 401/403 = route exists but blocked by auth → NOT acceptable (route should not exist)
# We accept 404 or 422 only.
assert resp.status_code in {404, 422}
assert resp.status_code in {404, 405, 422}
@pytest.mark.asyncio