first commit

This commit is contained in:
2026-07-18 20:32:51 +08:00
commit 77bf076ad0
34 changed files with 4068 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
import asyncio
import os
import shutil
import tempfile
import uuid
from collections.abc import Iterator
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
TEST_ID = uuid.uuid4().hex
TEST_DB = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}.sqlite3"
TEST_UPLOADS = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}-uploads"
os.environ["ENVIRONMENT"] = "test"
os.environ["DATABASE_URL"] = f"sqlite+aiosqlite:///{TEST_DB}"
os.environ["SECRET_KEY"] = "test-secret-key-that-is-long-enough-for-opencloud"
os.environ["PUBLIC_BASE_URL"] = "http://testserver"
os.environ["FRONTEND_URL"] = "http://testserver"
os.environ["CORS_ORIGINS"] = "http://testserver"
os.environ["UPLOAD_DIR"] = str(TEST_UPLOADS)
os.environ["EMAIL_DELIVERY_MODE"] = "console"
os.environ["ADMIN_EMAIL"] = "admin@example.com"
os.environ["ADMIN_PASSWORD"] = "admin-password-123"
os.environ["ADMIN_USERNAME"] = "测试管理员"
from app.database import Base, engine # noqa: E402
from app.main import app # noqa: E402
from app.seed import seed # noqa: E402
async def _prepare_database() -> None:
async with engine.begin() as connection:
await connection.run_sync(Base.metadata.create_all)
await seed()
async def _close_database() -> None:
await engine.dispose()
@pytest.fixture(scope="session")
def client() -> Iterator[TestClient]:
asyncio.run(_prepare_database())
with TestClient(app) as test_client:
yield test_client
asyncio.run(_close_database())
TEST_DB.unlink(missing_ok=True)
shutil.rmtree(TEST_UPLOADS, ignore_errors=True)
+148
View File
@@ -0,0 +1,148 @@
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