"""E2E tests for API Key authentication flow. Tests dual auth support (JWT Bearer + X-API-Key header), API Key CRUD, scope enforcement, and role-based access control. """ import asyncio import os import httpx BASE_URL = os.environ.get("PIP_BASE_URL", "http://localhost:8000") ADMIN_CREDS = {"username": os.environ.get("PIP_ADMIN_USER", "synctest"), "password": os.environ.get("PIP_ADMIN_PASS", "Test123!")} async def get_admin_token(client: httpx.AsyncClient) -> str: r = await client.post("/api/v1/auth/token", json=ADMIN_CREDS) assert r.status_code == 200, f"Login failed: {r.status_code} {r.text[:200]}" return r.json()["access_token"] def bearer_headers(token: str) -> dict: return {"Authorization": f"Bearer {token}"} async def test_api_key_crud(): async with httpx.AsyncClient(base_url=BASE_URL) as c: token = await get_admin_token(c) h = bearer_headers(token) clients = await c.get("/api/v1/api-clients", headers=h) assert clients.status_code == 200 client_id = next((ci["id"] for ci in clients.json().get("items", []) if ci["name"] == "Test Client"), None) if not client_id: resp = await c.post("/api/v1/api-clients", headers=h, json={ "name": "Test Client", "description": "E2E test client", "scopes": ["pharmacies:read", "medications:read", "inventory:read"], }) assert resp.status_code == 201 client_id = resp.json()["id"] key_resp = await c.post(f"/api/v1/api-clients/{client_id}/keys", headers=h, json={"name": "E2E CRUD Key"}) assert key_resp.status_code == 201, f"Key create failed: {key_resp.status_code} {key_resp.text[:200]}" raw_key = key_resp.json()["key"] key_prefix = key_resp.json()["key_prefix"] assert len(raw_key) > 20 assert len(key_prefix) == 8 keys = await c.get(f"/api/v1/api-clients/{client_id}/keys", headers=h) assert keys.status_code == 200 assert len(keys.json()) >= 1 print(" PASS: test_api_key_crud") async def test_api_key_read_access(): async with httpx.AsyncClient(base_url=BASE_URL) as c: token = await get_admin_token(c) h = bearer_headers(token) clients = await c.get("/api/v1/api-clients", headers=h) client_id = next((ci["id"] for ci in clients.json().get("items", []) if ci["name"] == "Test Client"), None) kr = await c.post(f"/api/v1/api-clients/{client_id}/keys", headers=h, json={"name": "Read Access Key"}) raw_key = kr.json()["key"] api_h = {"X-API-Key": raw_key} r = await c.get("/api/v1/pharmacies", headers=api_h) assert r.status_code == 200 assert r.json()["total"] >= 0 r2 = await c.get("/api/v1/medications", headers=api_h) assert r2.status_code == 200 r3 = await c.get("/api/v1/inventory", headers=api_h) assert r3.status_code == 200 print(" PASS: test_api_key_read_access") async def test_api_key_write_blocked(): async with httpx.AsyncClient(base_url=BASE_URL) as c: token = await get_admin_token(c) h = bearer_headers(token) clients = await c.get("/api/v1/api-clients", headers=h) client_id = next((ci["id"] for ci in clients.json().get("items", []) if ci["name"] == "Test Client"), None) kr = await c.post(f"/api/v1/api-clients/{client_id}/keys", headers=h, json={"name": "Write Block Key"}) raw_key = kr.json()["key"] api_h = {"X-API-Key": raw_key} r = await c.post("/api/v1/pharmacies", headers=api_h, json={"code": "BLOCKED", "name": "Should Fail"}) assert r.status_code == 403, f"Expected 403, got {r.status_code}" print(" PASS: test_api_key_write_blocked") async def test_invalid_api_key(): async with httpx.AsyncClient(base_url=BASE_URL) as c: r = await c.get("/api/v1/pharmacies", headers={"X-API-Key": "invalidkey12345"}) assert r.status_code == 401 print(" PASS: test_invalid_api_key") async def test_no_auth_rejected(): async with httpx.AsyncClient(base_url=BASE_URL) as c: r = await c.get("/api/v1/pharmacies") assert r.status_code == 401 print(" PASS: test_no_auth_rejected") async def test_dual_auth_bearer_priority(): async with httpx.AsyncClient(base_url=BASE_URL) as c: token = await get_admin_token(c) h = bearer_headers(token) clients = await c.get("/api/v1/api-clients", headers=h) client_id = next((ci["id"] for ci in clients.json().get("items", []) if ci["name"] == "Test Client"), None) kr = await c.post(f"/api/v1/api-clients/{client_id}/keys", headers=h, json={"name": "Dual Auth Key"}) raw_key = kr.json()["key"] dual_h = {"Authorization": f"Bearer {token}", "X-API-Key": raw_key} r = await c.get("/api/v1/pharmacies", headers=dual_h) assert r.status_code == 200 print(" PASS: test_dual_auth_bearer_priority") async def run_all(): print("=== API Key Auth E2E Tests ===") await test_api_key_crud() await test_api_key_read_access() await test_api_key_write_blocked() await test_invalid_api_key() await test_no_auth_rejected() await test_dual_auth_bearer_priority() print("=== All API Key tests passed ===") if __name__ == "__main__": asyncio.run(run_all())