import io from fastapi.testclient import TestClient from PIL import Image from app.routers import auth as auth_router def _png_bytes() -> bytes: output = io.BytesIO() Image.new("RGB", (1200, 800), color=(125, 196, 240)).save(output, format="PNG") return output.getvalue() def _bearer(token: str) -> dict[str, str]: return {"Authorization": f"Bearer {token}"} def test_openapi_contains_all_feature_groups(client: TestClient) -> None: response = client.get("/openapi.json") assert response.status_code == 200 paths = response.json()["paths"] expected = { "/api/v1/auth/register", "/api/v1/auth/login", "/api/v1/cloud-types", "/api/v1/clouds", "/api/v1/clouds/map", "/api/v1/collections/me", "/api/v1/profiles/{user_id}/clouds", "/api/v1/admin/stats", } assert expected <= set(paths) def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) -> None: captured_tokens: dict[str, str] = {} async def capture_confirmation(_: str, token: str) -> None: captured_tokens["confirmation"] = token monkeypatch.setattr(auth_router, "send_confirmation_email", capture_confirmation) health = client.get("/api/v1/health") assert health.status_code == 200 registered = client.post( "/api/v1/auth/register", json={ "email": "observer@example.com", "password": "observer-password-123", "username": "观云者", }, ) assert registered.status_code == 201 assert "confirmation" in captured_tokens confirmed = client.post( "/api/v1/auth/confirm-email", json={"token": captured_tokens["confirmation"]}, ) assert confirmed.status_code == 200 login = client.post( "/api/v1/auth/login", json={"email": "observer@example.com", "password": "observer-password-123"}, ) assert login.status_code == 200 user_token = login.json()["access_token"] user_id = login.json()["user"]["id"] me = client.get("/api/v1/auth/me", headers=_bearer(user_token)) assert me.status_code == 200 assert me.json()["profile"]["username"] == "观云者" renamed = client.patch( "/api/v1/profiles/me", headers=_bearer(user_token), json={"username": "天空观察员"}, ) assert renamed.status_code == 200 upload = client.post( "/api/v1/clouds", headers=_bearer(user_token), files={"image": ("cloud.png", _png_bytes(), "image/png")}, data={ "cloud_type_id": "1", "latitude": "31.2345", "longitude": "121.4737", "location_name": "上海", "description": "测试云图", "captured_at": "2026-07-18T10:00:00+08:00", "is_hidden": "false", }, ) assert upload.status_code == 201, upload.text cloud = upload.json()["cloud"] cloud_id = cloud["id"] assert cloud["status"] == "pending" assert cloud["latitude"] == 31.23 assert upload.json()["unlocked_badge"]["cloud_type_id"] == 1 collection = client.get("/api/v1/collections/me", headers=_bearer(user_token)) assert collection.status_code == 200 assert len(collection.json()) == 1 hidden_from_gallery = client.get("/api/v1/clouds") assert hidden_from_gallery.json()["total"] == 0 admin_login = client.post( "/api/v1/auth/login", json={"email": "admin@example.com", "password": "admin-password-123"}, ) assert admin_login.status_code == 200 admin_token = admin_login.json()["access_token"] approved = client.patch( "/api/v1/admin/clouds/status", headers=_bearer(admin_token), json={"ids": [cloud_id], "status": "approved"}, ) assert approved.status_code == 200 assert approved.json()["updated"] == 1 gallery = client.get("/api/v1/clouds", params={"search": "积云"}) assert gallery.status_code == 200 assert gallery.json()["total"] == 1 map_clouds = client.get( "/api/v1/clouds/map", params={ "start": "2026-07-18T00:00:00+08:00", "end": "2026-07-19T00:00:00+08:00", }, ) assert map_clouds.status_code == 200 assert len(map_clouds.json()) == 1 public_profile_clouds = client.get(f"/api/v1/profiles/{user_id}/clouds") assert public_profile_clouds.status_code == 200 assert public_profile_clouds.json()["total"] == 1 deleted = client.delete(f"/api/v1/clouds/{cloud_id}", headers=_bearer(user_token)) assert deleted.status_code == 200 assert deleted.json()["deleted"] == 1 logout = client.post("/api/v1/auth/logout") assert logout.status_code == 200