统一测试辅助函数到 conftest.py,消除代码重复
This commit is contained in:
+103
-14
@@ -16,6 +16,31 @@ TEST_ID = uuid.uuid4().hex
|
|||||||
TEST_DB = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}.sqlite3"
|
TEST_DB = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}.sqlite3"
|
||||||
TEST_UPLOADS = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}-uploads"
|
TEST_UPLOADS = Path(tempfile.gettempdir()) / f"opencloud-{TEST_ID}-uploads"
|
||||||
|
|
||||||
|
ADMIN_EMAIL = "admin@example.com"
|
||||||
|
ADMIN_PASSWORD = "admin-password-123"
|
||||||
|
|
||||||
|
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_EMAIL
|
||||||
|
os.environ["ADMIN_PASSWORD"] = ADMIN_PASSWORD
|
||||||
|
os.environ["ADMIN_USERNAME"] = "测试管理员"
|
||||||
|
|
||||||
|
from app.database import Base, engine # noqa: E402
|
||||||
|
from app.main import app # noqa: E402
|
||||||
|
from app.routers import auth as auth_router # noqa: E402
|
||||||
|
from app.seed import seed # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# shared helpers — usable from any test file
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def bearer(token: str) -> dict[str, str]:
|
def bearer(token: str) -> dict[str, str]:
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
@@ -26,21 +51,85 @@ def png_bytes() -> bytes:
|
|||||||
Image.new("RGB", (200, 200), color=(125, 196, 240)).save(output, format="PNG")
|
Image.new("RGB", (200, 200), color=(125, 196, 240)).save(output, format="PNG")
|
||||||
return output.getvalue()
|
return output.getvalue()
|
||||||
|
|
||||||
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
|
def capture_email(monkeypatch) -> dict[str, str]:
|
||||||
from app.main import app # noqa: E402
|
"""Patch send_confirmation_email and return a dict that will hold the token."""
|
||||||
from app.seed import seed # noqa: E402
|
captured: dict[str, str] = {}
|
||||||
|
|
||||||
|
async def capture(recipient: str, token: str) -> None:
|
||||||
|
captured["token"] = token
|
||||||
|
|
||||||
|
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
||||||
|
return captured
|
||||||
|
|
||||||
|
|
||||||
|
def register_user(client: TestClient, email: str, password: str, username: str):
|
||||||
|
return client.post(
|
||||||
|
"/api/v1/auth/register",
|
||||||
|
json={"email": email, "password": password, "username": username},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_email(client: TestClient, token: str):
|
||||||
|
return client.post("/api/v1/auth/confirm-email", json={"token": token})
|
||||||
|
|
||||||
|
|
||||||
|
def login_user(client: TestClient, email: str, password: str):
|
||||||
|
return client.post(
|
||||||
|
"/api/v1/auth/login",
|
||||||
|
json={"email": email, "password": password},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register_and_confirm(
|
||||||
|
client: TestClient,
|
||||||
|
monkeypatch,
|
||||||
|
email: str,
|
||||||
|
password: str,
|
||||||
|
username: str,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Create a fully registered + confirmed user. Returns token & user_id."""
|
||||||
|
captured = capture_email(monkeypatch)
|
||||||
|
r = register_user(client, email, password, username)
|
||||||
|
assert r.status_code == 201
|
||||||
|
token = captured.get("token")
|
||||||
|
assert token
|
||||||
|
cr = confirm_email(client, token)
|
||||||
|
assert cr.status_code == 200
|
||||||
|
lr = login_user(client, email, password)
|
||||||
|
assert lr.status_code == 200
|
||||||
|
body = lr.json()
|
||||||
|
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
||||||
|
|
||||||
|
|
||||||
|
def admin_token(client: TestClient) -> str:
|
||||||
|
r = login_user(client, ADMIN_EMAIL, ADMIN_PASSWORD)
|
||||||
|
return r.json()["access_token"]
|
||||||
|
|
||||||
|
|
||||||
|
def upload_cloud(client: TestClient, token: str, **overrides):
|
||||||
|
data = {"cloud_type_id": "1", "is_hidden": "false"}
|
||||||
|
data.update(overrides)
|
||||||
|
return client.post(
|
||||||
|
"/api/v1/clouds",
|
||||||
|
headers=bearer(token),
|
||||||
|
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def approve_cloud(client: TestClient, admin_tok: str, cloud_id: str) -> None:
|
||||||
|
r = client.patch(
|
||||||
|
"/api/v1/admin/clouds/status",
|
||||||
|
headers=bearer(admin_tok),
|
||||||
|
json={"ids": [cloud_id], "status": "approved"},
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# fixtures
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
async def _prepare_database() -> None:
|
async def _prepare_database() -> None:
|
||||||
|
|||||||
+37
-78
@@ -3,53 +3,13 @@ import uuid
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from tests.conftest import (
|
||||||
from tests.conftest import bearer, png_bytes
|
admin_token,
|
||||||
|
bearer,
|
||||||
ADMIN_EMAIL = "admin@example.com"
|
png_bytes,
|
||||||
ADMIN_PASSWORD = "admin-password-123"
|
register_and_confirm,
|
||||||
|
upload_cloud,
|
||||||
|
)
|
||||||
def _capture_email(monkeypatch) -> dict[str, str]:
|
|
||||||
captured: dict[str, str] = {}
|
|
||||||
|
|
||||||
async def capture(recipient: str, token: str) -> None:
|
|
||||||
captured["token"] = token
|
|
||||||
|
|
||||||
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
|
||||||
return captured
|
|
||||||
|
|
||||||
|
|
||||||
def _register_and_confirm(client, monkeypatch, email, password, username) -> dict[str, str]:
|
|
||||||
captured = _capture_email(monkeypatch)
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/register",
|
|
||||||
json={"email": email, "password": password, "username": username},
|
|
||||||
)
|
|
||||||
assert r.status_code == 201
|
|
||||||
client.post("/api/v1/auth/confirm-email", json={"token": captured["token"]})
|
|
||||||
lr = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
|
||||||
body = lr.json()
|
|
||||||
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
|
||||||
|
|
||||||
|
|
||||||
def _admin_token(client: TestClient) -> str:
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/login",
|
|
||||||
json={"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
|
|
||||||
)
|
|
||||||
return r.json()["access_token"]
|
|
||||||
|
|
||||||
|
|
||||||
def _upload_cloud(client: TestClient, token: str, **overrides):
|
|
||||||
data = {"cloud_type_id": "1", "is_hidden": "false"}
|
|
||||||
data.update(overrides)
|
|
||||||
return client.post(
|
|
||||||
"/api/v1/clouds",
|
|
||||||
headers=bearer(token),
|
|
||||||
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
|
||||||
data=data,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -58,7 +18,7 @@ def _upload_cloud(client: TestClient, token: str, **overrides):
|
|||||||
|
|
||||||
|
|
||||||
def test_stats(client: TestClient) -> None:
|
def test_stats(client: TestClient) -> None:
|
||||||
r = client.get("/api/v1/admin/stats", headers=bearer(_admin_token(client)))
|
r = client.get("/api/v1/admin/stats", headers=bearer(admin_token(client)))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
body = r.json()
|
body = r.json()
|
||||||
for key in ("users", "images", "today_uploads", "pending", "approved", "rejected", "hidden"):
|
for key in ("users", "images", "today_uploads", "pending", "approved", "rejected", "hidden"):
|
||||||
@@ -66,7 +26,7 @@ def test_stats(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stats_non_admin(client: TestClient, monkeypatch) -> None:
|
def test_stats_non_admin(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"notadmin-{uuid.uuid4().hex[:8]}@example.com",
|
f"notadmin-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"notadmin12", "非管理员",
|
"notadmin12", "非管理员",
|
||||||
@@ -87,7 +47,7 @@ def test_stats_unauthenticated(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_list_users(client: TestClient) -> None:
|
def test_list_users(client: TestClient) -> None:
|
||||||
r = client.get("/api/v1/admin/users", headers=bearer(_admin_token(client)))
|
r = client.get("/api/v1/admin/users", headers=bearer(admin_token(client)))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["total"] >= 1
|
assert r.json()["total"] >= 1
|
||||||
|
|
||||||
@@ -98,15 +58,14 @@ def test_list_users(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_user_role(client: TestClient, monkeypatch) -> None:
|
def test_update_user_role(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"promote-{uuid.uuid4().hex[:8]}@example.com",
|
f"promote-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"promote12", "升管理员",
|
"promote12", "升管理员",
|
||||||
)
|
)
|
||||||
admin = _admin_token(client)
|
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/admin/users/{info['user_id']}",
|
f"/api/v1/admin/users/{info['user_id']}",
|
||||||
headers=bearer(admin),
|
headers=bearer(admin_token(client)),
|
||||||
json={"role": "admin"},
|
json={"role": "admin"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -114,15 +73,15 @@ def test_update_user_role(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_user_disable(client: TestClient, monkeypatch) -> None:
|
def test_update_user_disable(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"disableme-{uuid.uuid4().hex[:8]}@example.com",
|
f"disableme-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"disableme", "被禁用",
|
"disableme", "被禁用",
|
||||||
)
|
)
|
||||||
admin = _admin_token(client)
|
adm = admin_token(client)
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/admin/users/{info['user_id']}",
|
f"/api/v1/admin/users/{info['user_id']}",
|
||||||
headers=bearer(admin),
|
headers=bearer(adm),
|
||||||
json={"is_disabled": True},
|
json={"is_disabled": True},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -130,18 +89,18 @@ def test_update_user_disable(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
client.patch(
|
client.patch(
|
||||||
f"/api/v1/admin/users/{info['user_id']}",
|
f"/api/v1/admin/users/{info['user_id']}",
|
||||||
headers=bearer(admin),
|
headers=bearer(adm),
|
||||||
json={"is_disabled": False},
|
json={"is_disabled": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_update_self_disable(client: TestClient) -> None:
|
def test_update_self_disable(client: TestClient) -> None:
|
||||||
admin_token = _admin_token(client)
|
adm = admin_token(client)
|
||||||
me = client.get("/api/v1/auth/me", headers=bearer(admin_token))
|
me = client.get("/api/v1/auth/me", headers=bearer(adm))
|
||||||
admin_id = me.json()["user"]["id"]
|
admin_id = me.json()["user"]["id"]
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/admin/users/{admin_id}",
|
f"/api/v1/admin/users/{admin_id}",
|
||||||
headers=bearer(admin_token),
|
headers=bearer(adm),
|
||||||
json={"is_disabled": True},
|
json={"is_disabled": True},
|
||||||
)
|
)
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
@@ -149,12 +108,12 @@ def test_update_self_disable(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_self_demote(client: TestClient) -> None:
|
def test_update_self_demote(client: TestClient) -> None:
|
||||||
admin_token = _admin_token(client)
|
adm = admin_token(client)
|
||||||
me = client.get("/api/v1/auth/me", headers=bearer(admin_token))
|
me = client.get("/api/v1/auth/me", headers=bearer(adm))
|
||||||
admin_id = me.json()["user"]["id"]
|
admin_id = me.json()["user"]["id"]
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/admin/users/{admin_id}",
|
f"/api/v1/admin/users/{admin_id}",
|
||||||
headers=bearer(admin_token),
|
headers=bearer(adm),
|
||||||
json={"role": "user"},
|
json={"role": "user"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
@@ -164,7 +123,7 @@ def test_update_self_demote(client: TestClient) -> None:
|
|||||||
def test_update_nonexistent_user(client: TestClient) -> None:
|
def test_update_nonexistent_user(client: TestClient) -> None:
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/admin/users/{uuid.uuid4()}",
|
f"/api/v1/admin/users/{uuid.uuid4()}",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"role": "admin"},
|
json={"role": "admin"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
@@ -176,7 +135,7 @@ def test_update_nonexistent_user(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_list_admin_clouds(client: TestClient) -> None:
|
def test_list_admin_clouds(client: TestClient) -> None:
|
||||||
r = client.get("/api/v1/admin/clouds", headers=bearer(_admin_token(client)))
|
r = client.get("/api/v1/admin/clouds", headers=bearer(admin_token(client)))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert "items" in r.json()
|
assert "items" in r.json()
|
||||||
|
|
||||||
@@ -184,7 +143,7 @@ def test_list_admin_clouds(client: TestClient) -> None:
|
|||||||
def test_list_admin_clouds_filter_status(client: TestClient) -> None:
|
def test_list_admin_clouds_filter_status(client: TestClient) -> None:
|
||||||
r = client.get(
|
r = client.get(
|
||||||
"/api/v1/admin/clouds",
|
"/api/v1/admin/clouds",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
params={"status": "pending"},
|
params={"status": "pending"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -195,7 +154,7 @@ def test_list_admin_clouds_filter_status(client: TestClient) -> None:
|
|||||||
def test_list_admin_clouds_filter_hidden(client: TestClient) -> None:
|
def test_list_admin_clouds_filter_hidden(client: TestClient) -> None:
|
||||||
r = client.get(
|
r = client.get(
|
||||||
"/api/v1/admin/clouds",
|
"/api/v1/admin/clouds",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
params={"is_hidden": "true"},
|
params={"is_hidden": "true"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -207,16 +166,16 @@ def test_list_admin_clouds_filter_hidden(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_cloud_status(client: TestClient, monkeypatch) -> None:
|
def test_update_cloud_status(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"admin-cld-{uuid.uuid4().hex[:8]}@example.com",
|
f"admin-cld-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"admincld", "管理员云审核",
|
"admincld", "管理员云审核",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
"/api/v1/admin/clouds/status",
|
"/api/v1/admin/clouds/status",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"ids": [cloud_id], "status": "rejected"},
|
json={"ids": [cloud_id], "status": "rejected"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -226,7 +185,7 @@ def test_update_cloud_status(client: TestClient, monkeypatch) -> None:
|
|||||||
def test_update_cloud_status_nonexistent(client: TestClient) -> None:
|
def test_update_cloud_status_nonexistent(client: TestClient) -> None:
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
"/api/v1/admin/clouds/status",
|
"/api/v1/admin/clouds/status",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"ids": [str(uuid.uuid4())], "status": "approved"},
|
json={"ids": [str(uuid.uuid4())], "status": "approved"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
@@ -238,16 +197,16 @@ def test_update_cloud_status_nonexistent(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_cloud_visibility(client: TestClient, monkeypatch) -> None:
|
def test_update_cloud_visibility(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"vis-{uuid.uuid4().hex[:8]}@example.com",
|
f"vis-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"vispass12", "可见性测试",
|
"vispass12", "可见性测试",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
"/api/v1/admin/clouds/visibility",
|
"/api/v1/admin/clouds/visibility",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"ids": [cloud_id], "is_hidden": True},
|
json={"ids": [cloud_id], "is_hidden": True},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -258,7 +217,7 @@ def test_update_cloud_visibility(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
client.patch(
|
client.patch(
|
||||||
"/api/v1/admin/clouds/visibility",
|
"/api/v1/admin/clouds/visibility",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"ids": [cloud_id], "is_hidden": False},
|
json={"ids": [cloud_id], "is_hidden": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,16 +228,16 @@ def test_update_cloud_visibility(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_admin_batch_delete_clouds(client: TestClient, monkeypatch) -> None:
|
def test_admin_batch_delete_clouds(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"adm-del-{uuid.uuid4().hex[:8]}@example.com",
|
f"adm-del-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"admdel12", "管理员删图",
|
"admdel12", "管理员删图",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/api/v1/admin/clouds/batch-delete",
|
"/api/v1/admin/clouds/batch-delete",
|
||||||
headers=bearer(_admin_token(client)),
|
headers=bearer(admin_token(client)),
|
||||||
json={"ids": [cloud_id]},
|
json={"ids": [cloud_id]},
|
||||||
)
|
)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|||||||
+11
-32
@@ -1,19 +1,6 @@
|
|||||||
import io
|
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from tests.conftest import bearer, capture_email, confirm_email, png_bytes
|
||||||
|
|
||||||
|
|
||||||
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:
|
def test_openapi_contains_all_feature_groups(client: TestClient) -> None:
|
||||||
@@ -34,12 +21,7 @@ def test_openapi_contains_all_feature_groups(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) -> None:
|
def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) -> None:
|
||||||
captured_tokens: dict[str, str] = {}
|
captured_tokens = capture_email(monkeypatch)
|
||||||
|
|
||||||
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")
|
health = client.get("/api/v1/health")
|
||||||
assert health.status_code == 200
|
assert health.status_code == 200
|
||||||
@@ -53,12 +35,9 @@ def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) ->
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert registered.status_code == 201
|
assert registered.status_code == 201
|
||||||
assert "confirmation" in captured_tokens
|
assert "token" in captured_tokens
|
||||||
|
|
||||||
confirmed = client.post(
|
confirmed = confirm_email(client, captured_tokens["token"])
|
||||||
"/api/v1/auth/confirm-email",
|
|
||||||
json={"token": captured_tokens["confirmation"]},
|
|
||||||
)
|
|
||||||
assert confirmed.status_code == 200
|
assert confirmed.status_code == 200
|
||||||
|
|
||||||
login = client.post(
|
login = client.post(
|
||||||
@@ -69,21 +48,21 @@ def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) ->
|
|||||||
user_token = login.json()["access_token"]
|
user_token = login.json()["access_token"]
|
||||||
user_id = login.json()["user"]["id"]
|
user_id = login.json()["user"]["id"]
|
||||||
|
|
||||||
me = client.get("/api/v1/auth/me", headers=_bearer(user_token))
|
me = client.get("/api/v1/auth/me", headers=bearer(user_token))
|
||||||
assert me.status_code == 200
|
assert me.status_code == 200
|
||||||
assert me.json()["profile"]["username"] == "观云者"
|
assert me.json()["profile"]["username"] == "观云者"
|
||||||
|
|
||||||
renamed = client.patch(
|
renamed = client.patch(
|
||||||
"/api/v1/profiles/me",
|
"/api/v1/profiles/me",
|
||||||
headers=_bearer(user_token),
|
headers=bearer(user_token),
|
||||||
json={"username": "天空观察员"},
|
json={"username": "天空观察员"},
|
||||||
)
|
)
|
||||||
assert renamed.status_code == 200
|
assert renamed.status_code == 200
|
||||||
|
|
||||||
upload = client.post(
|
upload = client.post(
|
||||||
"/api/v1/clouds",
|
"/api/v1/clouds",
|
||||||
headers=_bearer(user_token),
|
headers=bearer(user_token),
|
||||||
files={"image": ("cloud.png", _png_bytes(), "image/png")},
|
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
||||||
data={
|
data={
|
||||||
"cloud_type_id": "1",
|
"cloud_type_id": "1",
|
||||||
"latitude": "31.2345",
|
"latitude": "31.2345",
|
||||||
@@ -101,7 +80,7 @@ def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) ->
|
|||||||
assert cloud["latitude"] == 31.23
|
assert cloud["latitude"] == 31.23
|
||||||
assert upload.json()["unlocked_badge"]["cloud_type_id"] == 1
|
assert upload.json()["unlocked_badge"]["cloud_type_id"] == 1
|
||||||
|
|
||||||
collection = client.get("/api/v1/collections/me", headers=_bearer(user_token))
|
collection = client.get("/api/v1/collections/me", headers=bearer(user_token))
|
||||||
assert collection.status_code == 200
|
assert collection.status_code == 200
|
||||||
assert len(collection.json()) == 1
|
assert len(collection.json()) == 1
|
||||||
|
|
||||||
@@ -116,7 +95,7 @@ def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) ->
|
|||||||
admin_token = admin_login.json()["access_token"]
|
admin_token = admin_login.json()["access_token"]
|
||||||
approved = client.patch(
|
approved = client.patch(
|
||||||
"/api/v1/admin/clouds/status",
|
"/api/v1/admin/clouds/status",
|
||||||
headers=_bearer(admin_token),
|
headers=bearer(admin_token),
|
||||||
json={"ids": [cloud_id], "status": "approved"},
|
json={"ids": [cloud_id], "status": "approved"},
|
||||||
)
|
)
|
||||||
assert approved.status_code == 200
|
assert approved.status_code == 200
|
||||||
@@ -140,7 +119,7 @@ def test_complete_user_cloud_and_admin_flow(client: TestClient, monkeypatch) ->
|
|||||||
assert public_profile_clouds.status_code == 200
|
assert public_profile_clouds.status_code == 200
|
||||||
assert public_profile_clouds.json()["total"] == 1
|
assert public_profile_clouds.json()["total"] == 1
|
||||||
|
|
||||||
deleted = client.delete(f"/api/v1/clouds/{cloud_id}", headers=_bearer(user_token))
|
deleted = client.delete(f"/api/v1/clouds/{cloud_id}", headers=bearer(user_token))
|
||||||
assert deleted.status_code == 200
|
assert deleted.status_code == 200
|
||||||
assert deleted.json()["deleted"] == 1
|
assert deleted.json()["deleted"] == 1
|
||||||
|
|
||||||
|
|||||||
+44
-96
@@ -4,68 +4,14 @@ import pytest
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from app.routers import auth as auth_router
|
||||||
from tests.conftest import bearer
|
from tests.conftest import (
|
||||||
|
bearer,
|
||||||
ADMIN_EMAIL = "admin@example.com"
|
capture_email,
|
||||||
ADMIN_PASSWORD = "admin-password-123"
|
confirm_email,
|
||||||
|
login_user,
|
||||||
|
register_and_confirm,
|
||||||
# ---------------------------------------------------------------------------
|
register_user,
|
||||||
# helpers
|
)
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def _capture_email(monkeypatch) -> dict[str, str]:
|
|
||||||
captured: dict[str, str] = {}
|
|
||||||
|
|
||||||
async def capture(recipient: str, token: str) -> None:
|
|
||||||
captured["token"] = token
|
|
||||||
|
|
||||||
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
|
||||||
return captured
|
|
||||||
|
|
||||||
|
|
||||||
def _register(client: TestClient, email: str, password: str, username: str):
|
|
||||||
return client.post(
|
|
||||||
"/api/v1/auth/register",
|
|
||||||
json={"email": email, "password": password, "username": username},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _confirm(client: TestClient, token: str):
|
|
||||||
return client.post("/api/v1/auth/confirm-email", json={"token": token})
|
|
||||||
|
|
||||||
|
|
||||||
def _login(client: TestClient, email: str, password: str):
|
|
||||||
return client.post(
|
|
||||||
"/api/v1/auth/login",
|
|
||||||
json={"email": email, "password": password},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _register_and_confirm(
|
|
||||||
client: TestClient,
|
|
||||||
monkeypatch,
|
|
||||||
email: str,
|
|
||||||
password: str,
|
|
||||||
username: str,
|
|
||||||
) -> dict[str, str]:
|
|
||||||
captured = _capture_email(monkeypatch)
|
|
||||||
r = _register(client, email, password, username)
|
|
||||||
assert r.status_code == 201
|
|
||||||
token = captured.get("token")
|
|
||||||
assert token
|
|
||||||
cr = _confirm(client, token)
|
|
||||||
assert cr.status_code == 200
|
|
||||||
lr = _login(client, email, password)
|
|
||||||
assert lr.status_code == 200
|
|
||||||
body = lr.json()
|
|
||||||
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
|
||||||
|
|
||||||
|
|
||||||
def _login_as_user(client: TestClient, email: str, password: str) -> str:
|
|
||||||
r = _login(client, email, password)
|
|
||||||
return r.json()["access_token"]
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -75,8 +21,8 @@ def _login_as_user(client: TestClient, email: str, password: str) -> str:
|
|||||||
|
|
||||||
def test_register_success(client: TestClient, monkeypatch) -> None:
|
def test_register_success(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"reg-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"reg-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
captured = _capture_email(monkeypatch)
|
captured = capture_email(monkeypatch)
|
||||||
r = _register(client, email, "regpassword", "注册测试")
|
r = register_user(client, email, "regpassword", "注册测试")
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
assert r.json()["message"] == "注册成功,请查收确认邮件"
|
assert r.json()["message"] == "注册成功,请查收确认邮件"
|
||||||
assert "token" in captured
|
assert "token" in captured
|
||||||
@@ -84,8 +30,8 @@ def test_register_success(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
def test_register_duplicate_email(client: TestClient, monkeypatch) -> None:
|
def test_register_duplicate_email(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"dup-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"dup-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register(client, email, "firstpass", "第一个用户")
|
register_user(client, email, "firstpass", "第一个用户")
|
||||||
r = _register(client, email, "secondpass", "第二个用户")
|
r = register_user(client, email, "secondpass", "第二个用户")
|
||||||
assert r.status_code == 409
|
assert r.status_code == 409
|
||||||
assert "该邮箱已被注册" == r.json()["detail"]
|
assert "该邮箱已被注册" == r.json()["detail"]
|
||||||
|
|
||||||
@@ -93,24 +39,24 @@ def test_register_duplicate_email(client: TestClient, monkeypatch) -> None:
|
|||||||
def test_register_duplicate_username(client: TestClient, monkeypatch) -> None:
|
def test_register_duplicate_username(client: TestClient, monkeypatch) -> None:
|
||||||
email_a = f"dupname-a-{uuid.uuid4().hex[:8]}@example.com"
|
email_a = f"dupname-a-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
email_b = f"dupname-b-{uuid.uuid4().hex[:8]}@example.com"
|
email_b = f"dupname-b-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register(client, email_a, "passA123", "同名用户")
|
register_user(client, email_a, "passA123", "同名用户")
|
||||||
r = _register(client, email_b, "passB123", "同名用户")
|
r = register_user(client, email_b, "passB123", "同名用户")
|
||||||
assert r.status_code == 409
|
assert r.status_code == 409
|
||||||
assert "这个昵称已经被使用" == r.json()["detail"]
|
assert "这个昵称已经被使用" == r.json()["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_register_short_password(client: TestClient) -> None:
|
def test_register_short_password(client: TestClient) -> None:
|
||||||
r = _register(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "1234567", "短密码")
|
r = register_user(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "1234567", "短密码")
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_register_short_username(client: TestClient) -> None:
|
def test_register_short_username(client: TestClient) -> None:
|
||||||
r = _register(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "pass12345", "A")
|
r = register_user(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "pass12345", "A")
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_register_invalid_email(client: TestClient) -> None:
|
def test_register_invalid_email(client: TestClient) -> None:
|
||||||
r = _register(client, "not-an-email", "pass12345", "坏邮箱")
|
r = register_user(client, "not-an-email", "pass12345", "坏邮箱")
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
@@ -121,18 +67,18 @@ def test_register_invalid_email(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_confirm_email_valid(client: TestClient, monkeypatch) -> None:
|
def test_confirm_email_valid(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"confirm-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"confirm-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
captured = _capture_email(monkeypatch)
|
captured = capture_email(monkeypatch)
|
||||||
r = _register(client, email, "confirmpass", "确认测试")
|
r = register_user(client, email, "confirmpass", "确认测试")
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
token = captured.get("token")
|
token = captured.get("token")
|
||||||
assert token
|
assert token
|
||||||
cr = _confirm(client, token)
|
cr = confirm_email(client, token)
|
||||||
assert cr.status_code == 200
|
assert cr.status_code == 200
|
||||||
assert cr.json()["message"] == "邮箱确认成功"
|
assert cr.json()["message"] == "邮箱确认成功"
|
||||||
|
|
||||||
|
|
||||||
def test_confirm_email_invalid_token(client: TestClient) -> None:
|
def test_confirm_email_invalid_token(client: TestClient) -> None:
|
||||||
r = _confirm(client, "not-a-valid-token-at-all-never")
|
r = confirm_email(client, "not-a-valid-token-at-all-never")
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
assert "无效" in r.json()["detail"]
|
assert "无效" in r.json()["detail"]
|
||||||
|
|
||||||
@@ -144,49 +90,51 @@ def test_confirm_email_invalid_token(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_login_success(client: TestClient, monkeypatch) -> None:
|
def test_login_success(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"login-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"login-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
info = _register_and_confirm(client, monkeypatch, email, "loginpass", "登录测试")
|
info = register_and_confirm(client, monkeypatch, email, "loginpass", "登录测试")
|
||||||
assert info["token"]
|
assert info["token"]
|
||||||
assert info["user_id"]
|
assert info["user_id"]
|
||||||
|
|
||||||
|
|
||||||
def test_login_wrong_password(client: TestClient, monkeypatch) -> None:
|
def test_login_wrong_password(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"loginfail-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"loginfail-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register_and_confirm(client, monkeypatch, email, "rightpass", "登录失败")
|
register_and_confirm(client, monkeypatch, email, "rightpass", "登录失败")
|
||||||
r = _login(client, email, "wrongpass")
|
r = login_user(client, email, "wrongpass")
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
assert "邮箱或密码错误" == r.json()["detail"]
|
assert "邮箱或密码错误" == r.json()["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_login_nonexistent_email(client: TestClient) -> None:
|
def test_login_nonexistent_email(client: TestClient) -> None:
|
||||||
r = _login(client, "nobody@example.com", "whatever")
|
r = login_user(client, "nobody@example.com", "whatever")
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_login_unverified_email(client: TestClient, monkeypatch) -> None:
|
def test_login_unverified_email(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"unverified-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"unverified-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_capture_email(monkeypatch)
|
capture_email(monkeypatch)
|
||||||
_register(client, email, "unverified", "未确认")
|
register_user(client, email, "unverified", "未确认")
|
||||||
r = _login(client, email, "unverified")
|
r = login_user(client, email, "unverified")
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
assert "邮箱尚未确认" == r.json()["detail"]
|
assert "邮箱尚未确认" == r.json()["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_login_disabled_user(client: TestClient, monkeypatch) -> None:
|
def test_login_disabled_user(client: TestClient, monkeypatch) -> None:
|
||||||
|
from tests.conftest import ADMIN_EMAIL, ADMIN_PASSWORD
|
||||||
|
|
||||||
email = f"disabled-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"disabled-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
info = _register_and_confirm(client, monkeypatch, email, "disabled", "禁用测试")
|
info = register_and_confirm(client, monkeypatch, email, "disabled", "禁用测试")
|
||||||
admin_token = _login_as_user(client, ADMIN_EMAIL, ADMIN_PASSWORD)
|
admin_tok = login_user(client, ADMIN_EMAIL, ADMIN_PASSWORD).json()["access_token"]
|
||||||
client.patch(
|
client.patch(
|
||||||
f"/api/v1/admin/users/{info['user_id']}",
|
f"/api/v1/admin/users/{info['user_id']}",
|
||||||
headers=bearer(admin_token),
|
headers=bearer(admin_tok),
|
||||||
json={"is_disabled": True},
|
json={"is_disabled": True},
|
||||||
)
|
)
|
||||||
r = _login(client, email, "disabled")
|
r = login_user(client, email, "disabled")
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
assert "账号已被禁用" == r.json()["detail"]
|
assert "账号已被禁用" == r.json()["detail"]
|
||||||
|
|
||||||
client.patch(
|
client.patch(
|
||||||
f"/api/v1/admin/users/{info['user_id']}",
|
f"/api/v1/admin/users/{info['user_id']}",
|
||||||
headers=bearer(admin_token),
|
headers=bearer(admin_tok),
|
||||||
json={"is_disabled": False},
|
json={"is_disabled": False},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -198,7 +146,7 @@ def test_login_disabled_user(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
def test_refresh_success(client: TestClient, monkeypatch) -> None:
|
def test_refresh_success(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"refresh-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"refresh-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register_and_confirm(client, monkeypatch, email, "refreshpass", "刷新测试")
|
register_and_confirm(client, monkeypatch, email, "refreshpass", "刷新测试")
|
||||||
r = client.post("/api/v1/auth/refresh")
|
r = client.post("/api/v1/auth/refresh")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert "access_token" in r.json()
|
assert "access_token" in r.json()
|
||||||
@@ -237,7 +185,7 @@ def test_logout(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_forgot_password_existing_email(client: TestClient, monkeypatch) -> None:
|
def test_forgot_password_existing_email(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"forgot-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"forgot-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register_and_confirm(client, monkeypatch, email, "forgotpass", "忘记密码")
|
register_and_confirm(client, monkeypatch, email, "forgotpass", "忘记密码")
|
||||||
captured = {}
|
captured = {}
|
||||||
monkeypatch.setattr(auth_router, "send_password_reset_email", captured.__setitem__)
|
monkeypatch.setattr(auth_router, "send_password_reset_email", captured.__setitem__)
|
||||||
r = client.post("/api/v1/auth/forgot-password", json={"email": email})
|
r = client.post("/api/v1/auth/forgot-password", json={"email": email})
|
||||||
@@ -252,7 +200,7 @@ def test_forgot_password_nonexistent_email(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_reset_password_valid(client: TestClient, monkeypatch) -> None:
|
def test_reset_password_valid(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"reset-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"reset-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
_register_and_confirm(client, monkeypatch, email, "oldpass12", "重置密码")
|
register_and_confirm(client, monkeypatch, email, "oldpass12", "重置密码")
|
||||||
captured = {}
|
captured = {}
|
||||||
monkeypatch.setattr(auth_router, "send_password_reset_email", lambda r, t: captured.update(token=t))
|
monkeypatch.setattr(auth_router, "send_password_reset_email", lambda r, t: captured.update(token=t))
|
||||||
|
|
||||||
@@ -267,9 +215,9 @@ def test_reset_password_valid(client: TestClient, monkeypatch) -> None:
|
|||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["message"] == "密码已重置,请重新登录"
|
assert r.json()["message"] == "密码已重置,请重新登录"
|
||||||
|
|
||||||
r = _login(client, email, "oldpass12")
|
r = login_user(client, email, "oldpass12")
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
r = _login(client, email, "newpass12")
|
r = login_user(client, email, "newpass12")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@@ -289,7 +237,7 @@ def test_reset_password_invalid_token(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_change_password(client: TestClient, monkeypatch) -> None:
|
def test_change_password(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"chpw-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"chpw-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
info = _register_and_confirm(client, monkeypatch, email, "oldpass12", "改密")
|
info = register_and_confirm(client, monkeypatch, email, "oldpass12", "改密")
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
"/api/v1/auth/password",
|
"/api/v1/auth/password",
|
||||||
headers=bearer(info["token"]),
|
headers=bearer(info["token"]),
|
||||||
@@ -298,9 +246,9 @@ def test_change_password(client: TestClient, monkeypatch) -> None:
|
|||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert "access_token" in r.json()
|
assert "access_token" in r.json()
|
||||||
|
|
||||||
r = _login(client, email, "oldpass12")
|
r = login_user(client, email, "oldpass12")
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
r = _login(client, email, "updatedpass12")
|
r = login_user(client, email, "updatedpass12")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@@ -311,7 +259,7 @@ def test_change_password(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
def test_me(client: TestClient, monkeypatch) -> None:
|
def test_me(client: TestClient, monkeypatch) -> None:
|
||||||
email = f"me-{uuid.uuid4().hex[:8]}@example.com"
|
email = f"me-{uuid.uuid4().hex[:8]}@example.com"
|
||||||
info = _register_and_confirm(client, monkeypatch, email, "mepass12!", "我的信息")
|
info = register_and_confirm(client, monkeypatch, email, "mepass12!", "我的信息")
|
||||||
r = client.get("/api/v1/auth/me", headers=bearer(info["token"]))
|
r = client.get("/api/v1/auth/me", headers=bearer(info["token"]))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
body = r.json()
|
body = r.json()
|
||||||
|
|||||||
+51
-108
@@ -3,70 +3,14 @@ import uuid
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from tests.conftest import (
|
||||||
from tests.conftest import bearer, png_bytes
|
admin_token,
|
||||||
|
approve_cloud,
|
||||||
ADMIN_EMAIL = "admin@example.com"
|
bearer,
|
||||||
ADMIN_PASSWORD = "admin-password-123"
|
png_bytes,
|
||||||
|
register_and_confirm,
|
||||||
|
upload_cloud,
|
||||||
# ---------------------------------------------------------------------------
|
)
|
||||||
# helpers
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def _capture_email(monkeypatch) -> dict[str, str]:
|
|
||||||
captured: dict[str, str] = {}
|
|
||||||
|
|
||||||
async def capture(recipient: str, token: str) -> None:
|
|
||||||
captured["token"] = token
|
|
||||||
|
|
||||||
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
|
||||||
return captured
|
|
||||||
|
|
||||||
|
|
||||||
def _register_and_confirm(client, monkeypatch, email, password, username) -> dict[str, str]:
|
|
||||||
captured = _capture_email(monkeypatch)
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/register",
|
|
||||||
json={"email": email, "password": password, "username": username},
|
|
||||||
)
|
|
||||||
assert r.status_code == 201
|
|
||||||
client.post("/api/v1/auth/confirm-email", json={"token": captured["token"]})
|
|
||||||
lr = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
|
||||||
body = lr.json()
|
|
||||||
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
|
||||||
|
|
||||||
|
|
||||||
def _approve_cloud(client: TestClient, admin_token: str, cloud_id: str) -> None:
|
|
||||||
r = client.patch(
|
|
||||||
"/api/v1/admin/clouds/status",
|
|
||||||
headers=bearer(admin_token),
|
|
||||||
json={"ids": [cloud_id], "status": "approved"},
|
|
||||||
)
|
|
||||||
assert r.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def _admin_token(client: TestClient) -> str:
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/login",
|
|
||||||
json={"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
|
|
||||||
)
|
|
||||||
return r.json()["access_token"]
|
|
||||||
|
|
||||||
|
|
||||||
def _upload_cloud(client: TestClient, token: str, **overrides):
|
|
||||||
data = {
|
|
||||||
"cloud_type_id": "1",
|
|
||||||
"is_hidden": "false",
|
|
||||||
}
|
|
||||||
data.update(overrides)
|
|
||||||
return client.post(
|
|
||||||
"/api/v1/clouds",
|
|
||||||
headers=bearer(token),
|
|
||||||
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
|
||||||
data=data,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -75,12 +19,12 @@ def _upload_cloud(client: TestClient, token: str, **overrides):
|
|||||||
|
|
||||||
|
|
||||||
def test_create_cloud_with_type(client: TestClient, monkeypatch) -> None:
|
def test_create_cloud_with_type(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"cloud-create-{uuid.uuid4().hex[:8]}@example.com",
|
f"cloud-create-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"cloudpass12", "云图创建者",
|
"cloudpass12", "云图创建者",
|
||||||
)
|
)
|
||||||
r = _upload_cloud(client, info["token"], cloud_type_id="1")
|
r = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
cloud = r.json()["cloud"]
|
cloud = r.json()["cloud"]
|
||||||
assert cloud["status"] == "pending"
|
assert cloud["status"] == "pending"
|
||||||
@@ -91,12 +35,12 @@ def test_create_cloud_with_type(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_create_cloud_custom_type(client: TestClient, monkeypatch) -> None:
|
def test_create_cloud_custom_type(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"custom-{uuid.uuid4().hex[:8]}@example.com",
|
f"custom-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"custompass", "自定义类型",
|
"custompass", "自定义类型",
|
||||||
)
|
)
|
||||||
r = _upload_cloud(client, info["token"], cloud_type_id=None, custom_cloud_type="火烧云")
|
r = upload_cloud(client, info["token"], cloud_type_id=None, custom_cloud_type="火烧云")
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
cloud = r.json()["cloud"]
|
cloud = r.json()["cloud"]
|
||||||
assert cloud["cloud_type_id"] is None
|
assert cloud["cloud_type_id"] is None
|
||||||
@@ -105,43 +49,43 @@ def test_create_cloud_custom_type(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_create_cloud_missing_type(client: TestClient, monkeypatch) -> None:
|
def test_create_cloud_missing_type(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"notype-{uuid.uuid4().hex[:8]}@example.com",
|
f"notype-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"notypepass", "缺类型",
|
"notypepass", "缺类型",
|
||||||
)
|
)
|
||||||
r = _upload_cloud(client, info["token"], cloud_type_id=None, custom_cloud_type=None)
|
r = upload_cloud(client, info["token"], cloud_type_id=None, custom_cloud_type=None)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
assert "必须选择" in r.json()["detail"]
|
assert "必须选择" in r.json()["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_create_cloud_unauthenticated(client: TestClient) -> None:
|
def test_create_cloud_unauthenticated(client: TestClient) -> None:
|
||||||
r = _upload_cloud(client, "bad-token", cloud_type_id="1")
|
r = upload_cloud(client, "bad-token", cloud_type_id="1")
|
||||||
assert r.status_code == 401
|
assert r.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_create_cloud_lat_lon_mismatch(client: TestClient, monkeypatch) -> None:
|
def test_create_cloud_lat_lon_mismatch(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"latlon-{uuid.uuid4().hex[:8]}@example.com",
|
f"latlon-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"latlonpass", "坐标测试",
|
"latlonpass", "坐标测试",
|
||||||
)
|
)
|
||||||
r = _upload_cloud(client, info["token"], cloud_type_id="1", latitude="31.23", longitude=None)
|
r = upload_cloud(client, info["token"], cloud_type_id="1", latitude="31.23", longitude=None)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
assert "经纬度" in r.json()["detail"]
|
assert "经纬度" in r.json()["detail"]
|
||||||
|
|
||||||
|
|
||||||
def test_create_duplicate_collection_no_badge(client: TestClient, monkeypatch) -> None:
|
def test_create_duplicate_collection_no_badge(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"dupbadge-{uuid.uuid4().hex[:8]}@example.com",
|
f"dupbadge-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"dupbadge", "重复徽章",
|
"dupbadge", "重复徽章",
|
||||||
)
|
)
|
||||||
r1 = _upload_cloud(client, info["token"], cloud_type_id="1")
|
r1 = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
assert r1.status_code == 201
|
assert r1.status_code == 201
|
||||||
assert r1.json()["unlocked_badge"] is not None
|
assert r1.json()["unlocked_badge"] is not None
|
||||||
|
|
||||||
r2 = _upload_cloud(client, info["token"], cloud_type_id="1")
|
r2 = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
assert r2.status_code == 201
|
assert r2.status_code == 201
|
||||||
assert r2.json()["unlocked_badge"] is None
|
assert r2.json()["unlocked_badge"] is None
|
||||||
|
|
||||||
@@ -152,26 +96,25 @@ def test_create_duplicate_collection_no_badge(client: TestClient, monkeypatch) -
|
|||||||
|
|
||||||
|
|
||||||
def test_list_gallery_empty_while_no_approved(client: TestClient, monkeypatch) -> None:
|
def test_list_gallery_empty_while_no_approved(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"gallery-empty-{uuid.uuid4().hex[:8]}@example.com",
|
f"gallery-empty-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"gallerypass", "画廊空",
|
"gallerypass", "画廊空",
|
||||||
)
|
)
|
||||||
_upload_cloud(client, info["token"], cloud_type_id="1")
|
upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
r = client.get("/api/v1/clouds")
|
r = client.get("/api/v1/clouds")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["total"] == 0
|
assert r.json()["total"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_list_gallery_with_approved(client: TestClient, monkeypatch) -> None:
|
def test_list_gallery_with_approved(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"gallery-{uuid.uuid4().hex[:8]}@example.com",
|
f"gallery-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"gallerypass", "画廊有",
|
"gallerypass", "画廊有",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"])
|
||||||
_approve_cloud(client, _admin_token(client), cloud_id)
|
|
||||||
r = client.get("/api/v1/clouds")
|
r = client.get("/api/v1/clouds")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["total"] >= 1
|
assert r.json()["total"] >= 1
|
||||||
@@ -186,12 +129,12 @@ def test_list_gallery_search_by_cloud_name(client: TestClient) -> None:
|
|||||||
|
|
||||||
def test_list_gallery_search_by_username(client: TestClient, monkeypatch) -> None:
|
def test_list_gallery_search_by_username(client: TestClient, monkeypatch) -> None:
|
||||||
raw = uuid.uuid4().hex[:8]
|
raw = uuid.uuid4().hex[:8]
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"searchuser-{raw}@example.com", "searchuser", raw,
|
f"searchuser-{raw}@example.com", "searchuser", raw,
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
_approve_cloud(client, _admin_token(client), ur.json()["cloud"]["id"])
|
approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"])
|
||||||
r = client.get("/api/v1/clouds", params={"search": f"@{raw}"})
|
r = client.get("/api/v1/clouds", params={"search": f"@{raw}"})
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["total"] >= 1
|
assert r.json()["total"] >= 1
|
||||||
@@ -203,12 +146,12 @@ def test_list_gallery_search_by_username(client: TestClient, monkeypatch) -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_get_own_cloud(client: TestClient, monkeypatch) -> None:
|
def test_get_own_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"own-{uuid.uuid4().hex[:8]}@example.com",
|
f"own-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"ownpass12", "拥有者",
|
"ownpass12", "拥有者",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.get(f"/api/v1/clouds/{cloud_id}", headers=bearer(info["token"]))
|
r = client.get(f"/api/v1/clouds/{cloud_id}", headers=bearer(info["token"]))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -216,15 +159,15 @@ def test_get_own_cloud(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_get_others_pending_cloud(client: TestClient, monkeypatch) -> None:
|
def test_get_others_pending_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
owner_info = _register_and_confirm(
|
owner_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"owner-{uuid.uuid4().hex[:8]}@example.com",
|
f"owner-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"ownerpass1", "上传者",
|
"ownerpass1", "上传者",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
|
|
||||||
viewer_info = _register_and_confirm(
|
viewer_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"viewer-{uuid.uuid4().hex[:8]}@example.com",
|
f"viewer-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"viewerpass", "路人",
|
"viewerpass", "路人",
|
||||||
@@ -244,12 +187,12 @@ def test_get_nonexistent_cloud(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_own_cloud(client: TestClient, monkeypatch) -> None:
|
def test_update_own_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"upd-{uuid.uuid4().hex[:8]}@example.com",
|
f"upd-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"updpass12", "更新者",
|
"updpass12", "更新者",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/api/v1/clouds/{cloud_id}",
|
f"/api/v1/clouds/{cloud_id}",
|
||||||
@@ -261,15 +204,15 @@ def test_update_own_cloud(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_others_cloud(client: TestClient, monkeypatch) -> None:
|
def test_update_others_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
owner_info = _register_and_confirm(
|
owner_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"upd-owner-{uuid.uuid4().hex[:8]}@example.com",
|
f"upd-owner-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"ownerpass", "原主",
|
"ownerpass", "原主",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
|
|
||||||
other_info = _register_and_confirm(
|
other_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"upd-other-{uuid.uuid4().hex[:8]}@example.com",
|
f"upd-other-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"otherpass", "别人",
|
"otherpass", "别人",
|
||||||
@@ -288,12 +231,12 @@ def test_update_others_cloud(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_delete_own_cloud(client: TestClient, monkeypatch) -> None:
|
def test_delete_own_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"del-{uuid.uuid4().hex[:8]}@example.com",
|
f"del-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"delpass12", "删除者",
|
"delpass12", "删除者",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
r = client.delete(f"/api/v1/clouds/{cloud_id}", headers=bearer(info["token"]))
|
r = client.delete(f"/api/v1/clouds/{cloud_id}", headers=bearer(info["token"]))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@@ -303,15 +246,15 @@ def test_delete_own_cloud(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_delete_others_cloud(client: TestClient, monkeypatch) -> None:
|
def test_delete_others_cloud(client: TestClient, monkeypatch) -> None:
|
||||||
owner_info = _register_and_confirm(
|
owner_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"del-own-{uuid.uuid4().hex[:8]}@example.com",
|
f"del-own-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"ownerpass", "物主",
|
"ownerpass", "物主",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
ur = upload_cloud(client, owner_info["token"], cloud_type_id="1")
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
cloud_id = ur.json()["cloud"]["id"]
|
||||||
|
|
||||||
other_info = _register_and_confirm(
|
other_info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"del-oth-{uuid.uuid4().hex[:8]}@example.com",
|
f"del-oth-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"otherpass", "外人",
|
"otherpass", "外人",
|
||||||
@@ -326,13 +269,13 @@ def test_delete_others_cloud(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_batch_delete(client: TestClient, monkeypatch) -> None:
|
def test_batch_delete(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"batch-{uuid.uuid4().hex[:8]}@example.com",
|
f"batch-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"batchpass", "批量删除",
|
"batchpass", "批量删除",
|
||||||
)
|
)
|
||||||
u1 = _upload_cloud(client, info["token"], cloud_type_id="1")
|
u1 = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
u2 = _upload_cloud(client, info["token"], cloud_type_id="2")
|
u2 = upload_cloud(client, info["token"], cloud_type_id="2")
|
||||||
ids = [u1.json()["cloud"]["id"], u2.json()["cloud"]["id"]]
|
ids = [u1.json()["cloud"]["id"], u2.json()["cloud"]["id"]]
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/api/v1/clouds/batch-delete",
|
"/api/v1/clouds/batch-delete",
|
||||||
@@ -344,12 +287,12 @@ def test_batch_delete(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_batch_delete_partial_invalid(client: TestClient, monkeypatch) -> None:
|
def test_batch_delete_partial_invalid(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"batch2-{uuid.uuid4().hex[:8]}@example.com",
|
f"batch2-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"batch2pass", "批量部分",
|
"batch2pass", "批量部分",
|
||||||
)
|
)
|
||||||
u1 = _upload_cloud(client, info["token"], cloud_type_id="1")
|
u1 = upload_cloud(client, info["token"], cloud_type_id="1")
|
||||||
ids = [u1.json()["cloud"]["id"], str(uuid.uuid4())]
|
ids = [u1.json()["cloud"]["id"], str(uuid.uuid4())]
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/api/v1/clouds/batch-delete",
|
"/api/v1/clouds/batch-delete",
|
||||||
@@ -365,17 +308,17 @@ def test_batch_delete_partial_invalid(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_map_clouds(client: TestClient, monkeypatch) -> None:
|
def test_map_clouds(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"map-{uuid.uuid4().hex[:8]}@example.com",
|
f"map-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"mappass12", "地图用户",
|
"mappass12", "地图用户",
|
||||||
)
|
)
|
||||||
ur = _upload_cloud(
|
ur = upload_cloud(
|
||||||
client, info["token"],
|
client, info["token"],
|
||||||
cloud_type_id="1", latitude="31.23", longitude="121.47",
|
cloud_type_id="1", latitude="31.23", longitude="121.47",
|
||||||
captured_at="2025-01-01T00:00:00",
|
captured_at="2025-01-01T00:00:00",
|
||||||
)
|
)
|
||||||
_approve_cloud(client, _admin_token(client), ur.json()["cloud"]["id"])
|
approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"])
|
||||||
r = client.get("/api/v1/clouds/map", params={
|
r = client.get("/api/v1/clouds/map", params={
|
||||||
"start": "2024-12-31T00:00:00",
|
"start": "2024-12-31T00:00:00",
|
||||||
"end": "2026-01-01T00:00:00",
|
"end": "2026-01-01T00:00:00",
|
||||||
|
|||||||
@@ -1,42 +1,12 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import pytest
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from tests.conftest import bearer, png_bytes, register_and_confirm
|
||||||
from tests.conftest import bearer, png_bytes
|
|
||||||
|
|
||||||
|
|
||||||
def _capture_email(monkeypatch) -> dict[str, str]:
|
|
||||||
captured: dict[str, str] = {}
|
|
||||||
|
|
||||||
async def capture(recipient: str, token: str) -> None:
|
|
||||||
captured["token"] = token
|
|
||||||
|
|
||||||
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
|
||||||
return captured
|
|
||||||
|
|
||||||
|
|
||||||
def _register_and_confirm(client, monkeypatch, email, password, username) -> dict[str, str]:
|
|
||||||
captured = _capture_email(monkeypatch)
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/register",
|
|
||||||
json={"email": email, "password": password, "username": username},
|
|
||||||
)
|
|
||||||
assert r.status_code == 201
|
|
||||||
client.post("/api/v1/auth/confirm-email", json={"token": captured["token"]})
|
|
||||||
lr = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
|
||||||
body = lr.json()
|
|
||||||
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# my collections
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_my_collections_after_upload(client: TestClient, monkeypatch) -> None:
|
def test_my_collections_after_upload(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"coll-{uuid.uuid4().hex[:8]}@example.com",
|
f"coll-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"collpass1", "收藏用户",
|
"collpass1", "收藏用户",
|
||||||
|
|||||||
+28
-61
@@ -3,58 +3,13 @@ import uuid
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from app.routers import auth as auth_router
|
from tests.conftest import (
|
||||||
from tests.conftest import bearer, png_bytes
|
admin_token,
|
||||||
|
approve_cloud,
|
||||||
ADMIN_EMAIL = "admin@example.com"
|
bearer,
|
||||||
ADMIN_PASSWORD = "admin-password-123"
|
png_bytes,
|
||||||
|
register_and_confirm,
|
||||||
|
)
|
||||||
def _capture_email(monkeypatch) -> dict[str, str]:
|
|
||||||
captured: dict[str, str] = {}
|
|
||||||
|
|
||||||
async def capture(recipient: str, token: str) -> None:
|
|
||||||
captured["token"] = token
|
|
||||||
|
|
||||||
monkeypatch.setattr(auth_router, "send_confirmation_email", capture)
|
|
||||||
return captured
|
|
||||||
|
|
||||||
|
|
||||||
def _register_and_confirm(client, monkeypatch, email, password, username) -> dict[str, str]:
|
|
||||||
captured = _capture_email(monkeypatch)
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/register",
|
|
||||||
json={"email": email, "password": password, "username": username},
|
|
||||||
)
|
|
||||||
assert r.status_code == 201
|
|
||||||
client.post("/api/v1/auth/confirm-email", json={"token": captured["token"]})
|
|
||||||
lr = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
|
||||||
body = lr.json()
|
|
||||||
return {"token": body["access_token"], "user_id": body["user"]["id"]}
|
|
||||||
|
|
||||||
|
|
||||||
def _admin_token(client: TestClient) -> str:
|
|
||||||
r = client.post(
|
|
||||||
"/api/v1/auth/login",
|
|
||||||
json={"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
|
|
||||||
)
|
|
||||||
return r.json()["access_token"]
|
|
||||||
|
|
||||||
|
|
||||||
def _upload_and_approve(client: TestClient, token: str) -> str:
|
|
||||||
ur = client.post(
|
|
||||||
"/api/v1/clouds",
|
|
||||||
headers=bearer(token),
|
|
||||||
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
|
||||||
data={"cloud_type_id": "1", "is_hidden": "false"},
|
|
||||||
)
|
|
||||||
cloud_id = ur.json()["cloud"]["id"]
|
|
||||||
client.patch(
|
|
||||||
"/api/v1/admin/clouds/status",
|
|
||||||
headers=bearer(_admin_token(client)),
|
|
||||||
json={"ids": [cloud_id], "status": "approved"},
|
|
||||||
)
|
|
||||||
return cloud_id
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -63,7 +18,7 @@ def _upload_and_approve(client: TestClient, token: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def test_get_profile(client: TestClient, monkeypatch) -> None:
|
def test_get_profile(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"prof-{uuid.uuid4().hex[:8]}@example.com",
|
f"prof-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"profpass1", "资料展示",
|
"profpass1", "资料展示",
|
||||||
@@ -84,7 +39,7 @@ def test_get_nonexistent_profile(client: TestClient) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_me(client: TestClient, monkeypatch) -> None:
|
def test_update_me(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"updme-{uuid.uuid4().hex[:8]}@example.com",
|
f"updme-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"updmepass", "原昵称",
|
"updmepass", "原昵称",
|
||||||
@@ -99,7 +54,7 @@ def test_update_me(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_me_short_username(client: TestClient, monkeypatch) -> None:
|
def test_update_me_short_username(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"shortname-{uuid.uuid4().hex[:8]}@example.com",
|
f"shortname-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"shortname", "短名字测试",
|
"shortname", "短名字测试",
|
||||||
@@ -113,12 +68,12 @@ def test_update_me_short_username(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_update_me_duplicate_username(client: TestClient, monkeypatch) -> None:
|
def test_update_me_duplicate_username(client: TestClient, monkeypatch) -> None:
|
||||||
_register_and_confirm(
|
register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"dupname-u1-{uuid.uuid4().hex[:8]}@example.com",
|
f"dupname-u1-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"pass11112", "已占用的名字",
|
"pass11112", "已占用的名字",
|
||||||
)
|
)
|
||||||
info2 = _register_and_confirm(
|
info2 = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"dupname-u2-{uuid.uuid4().hex[:8]}@example.com",
|
f"dupname-u2-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"pass22223", "第二人",
|
"pass22223", "第二人",
|
||||||
@@ -138,12 +93,18 @@ def test_update_me_duplicate_username(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_get_user_clouds_public(client: TestClient, monkeypatch) -> None:
|
def test_get_user_clouds_public(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"pubcloud-{uuid.uuid4().hex[:8]}@example.com",
|
f"pubcloud-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"pubcloud1", "公开云主",
|
"pubcloud1", "公开云主",
|
||||||
)
|
)
|
||||||
_upload_and_approve(client, info["token"])
|
ur = client.post(
|
||||||
|
"/api/v1/clouds",
|
||||||
|
headers=bearer(info["token"]),
|
||||||
|
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
||||||
|
data={"cloud_type_id": "1", "is_hidden": "false"},
|
||||||
|
)
|
||||||
|
approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"])
|
||||||
r = client.get(f"/api/v1/profiles/{info['user_id']}/clouds")
|
r = client.get(f"/api/v1/profiles/{info['user_id']}/clouds")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["total"] >= 1
|
assert r.json()["total"] >= 1
|
||||||
@@ -152,12 +113,18 @@ def test_get_user_clouds_public(client: TestClient, monkeypatch) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_get_user_clouds_as_owner(client: TestClient, monkeypatch) -> None:
|
def test_get_user_clouds_as_owner(client: TestClient, monkeypatch) -> None:
|
||||||
info = _register_and_confirm(
|
info = register_and_confirm(
|
||||||
client, monkeypatch,
|
client, monkeypatch,
|
||||||
f"owncloud-{uuid.uuid4().hex[:8]}@example.com",
|
f"owncloud-{uuid.uuid4().hex[:8]}@example.com",
|
||||||
"owncloud1", "私有云主",
|
"owncloud1", "私有云主",
|
||||||
)
|
)
|
||||||
cloud_id = _upload_and_approve(client, info["token"])
|
ur = client.post(
|
||||||
|
"/api/v1/clouds",
|
||||||
|
headers=bearer(info["token"]),
|
||||||
|
files={"image": ("cloud.png", png_bytes(), "image/png")},
|
||||||
|
data={"cloud_type_id": "1", "is_hidden": "false"},
|
||||||
|
)
|
||||||
|
approve_cloud(client, admin_token(client), ur.json()["cloud"]["id"])
|
||||||
r = client.get(
|
r = client.get(
|
||||||
f"/api/v1/profiles/{info['user_id']}/clouds",
|
f"/api/v1/profiles/{info['user_id']}/clouds",
|
||||||
headers=bearer(info["token"]),
|
headers=bearer(info["token"]),
|
||||||
|
|||||||
Reference in New Issue
Block a user