- models: 删除 Profile 模型,字段合并到 User;User 新增云朵/公开/收藏计数器;Cloud 新增 favorite_count - schemas/serializers: 展平 UserOut/AuthOut/MeOut/AdminUserOut,移除嵌套 profile - deps: user.profile.role→user.role,移除 selectinload profile - auth/register: 注册时直接设置 user 字段,不再创建 Profile - clouds: 创建/删除云朵时同步 user.cloud_count 计数器,状态变化时同步 public_cloud_count - favorites: 点赞/取消时同步 cloud.favorite_count 计数器 - admin: 审批/隐藏/批量删时同步 public_cloud_count 计数器 - profiles/stats: 用计数器替代实时 COUNT 查询 - alembic: 新增 migration 合并 profiles 到 users,初始化 counters
273 lines
9.6 KiB
Python
273 lines
9.6 KiB
Python
import uuid
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.routers import auth as auth_router
|
|
from tests.conftest import (
|
|
bearer,
|
|
capture_email,
|
|
confirm_email,
|
|
login_user,
|
|
register_and_confirm,
|
|
register_user,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# register
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_register_success(client: TestClient, monkeypatch) -> None:
|
|
email = f"reg-{uuid.uuid4().hex[:8]}@example.com"
|
|
captured = capture_email(monkeypatch)
|
|
r = register_user(client, email, "regpassword", "注册测试")
|
|
assert r.status_code == 201
|
|
assert r.json()["message"] == "注册成功,请查收确认邮件"
|
|
assert "token" in captured
|
|
|
|
|
|
def test_register_duplicate_email(client: TestClient, monkeypatch) -> None:
|
|
email = f"dup-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_user(client, email, "firstpass", "第一个用户")
|
|
r = register_user(client, email, "secondpass", "第二个用户")
|
|
assert r.status_code == 409
|
|
assert "该邮箱已被注册" == r.json()["detail"]
|
|
|
|
|
|
def test_register_duplicate_username(client: TestClient, monkeypatch) -> None:
|
|
email_a = f"dupname-a-{uuid.uuid4().hex[:8]}@example.com"
|
|
email_b = f"dupname-b-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_user(client, email_a, "passA123", "同名用户")
|
|
r = register_user(client, email_b, "passB123", "同名用户")
|
|
assert r.status_code == 409
|
|
assert "这个昵称已经被使用" == r.json()["detail"]
|
|
|
|
|
|
def test_register_short_password(client: TestClient) -> None:
|
|
r = register_user(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "1234567", "短密码")
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_register_short_username(client: TestClient) -> None:
|
|
r = register_user(client, f"s-{uuid.uuid4().hex[:8]}@e.com", "pass12345", "A")
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_register_invalid_email(client: TestClient) -> None:
|
|
r = register_user(client, "not-an-email", "pass12345", "坏邮箱")
|
|
assert r.status_code == 422
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# confirm-email
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_confirm_email_valid(client: TestClient, monkeypatch) -> None:
|
|
email = f"confirm-{uuid.uuid4().hex[:8]}@example.com"
|
|
captured = capture_email(monkeypatch)
|
|
r = register_user(client, email, "confirmpass", "确认测试")
|
|
assert r.status_code == 201
|
|
token = captured.get("token")
|
|
assert token
|
|
cr = confirm_email(client, token)
|
|
assert cr.status_code == 200
|
|
assert cr.json()["message"] == "邮箱确认成功"
|
|
|
|
|
|
def test_confirm_email_invalid_token(client: TestClient) -> None:
|
|
r = confirm_email(client, "not-a-valid-token-at-all-never")
|
|
assert r.status_code == 400
|
|
assert "无效" in r.json()["detail"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# login
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_login_success(client: TestClient, monkeypatch) -> None:
|
|
email = f"login-{uuid.uuid4().hex[:8]}@example.com"
|
|
info = register_and_confirm(client, monkeypatch, email, "loginpass", "登录测试")
|
|
assert info["token"]
|
|
assert info["user_id"]
|
|
|
|
|
|
def test_login_wrong_password(client: TestClient, monkeypatch) -> None:
|
|
email = f"loginfail-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_and_confirm(client, monkeypatch, email, "rightpass", "登录失败")
|
|
r = login_user(client, email, "wrongpass")
|
|
assert r.status_code == 401
|
|
assert "邮箱或密码错误" == r.json()["detail"]
|
|
|
|
|
|
def test_login_nonexistent_email(client: TestClient) -> None:
|
|
r = login_user(client, "nobody@example.com", "whatever")
|
|
assert r.status_code == 401
|
|
|
|
|
|
def test_login_unverified_email(client: TestClient, monkeypatch) -> None:
|
|
email = f"unverified-{uuid.uuid4().hex[:8]}@example.com"
|
|
capture_email(monkeypatch)
|
|
register_user(client, email, "unverified", "未确认")
|
|
r = login_user(client, email, "unverified")
|
|
assert r.status_code == 403
|
|
assert "邮箱尚未确认" == r.json()["detail"]
|
|
|
|
|
|
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"
|
|
info = register_and_confirm(client, monkeypatch, email, "disabled", "禁用测试")
|
|
admin_tok = login_user(client, ADMIN_EMAIL, ADMIN_PASSWORD).json()["access_token"]
|
|
client.patch(
|
|
f"/api/v1/admin/users/{info['user_id']}",
|
|
headers=bearer(admin_tok),
|
|
json={"is_disabled": True},
|
|
)
|
|
r = login_user(client, email, "disabled")
|
|
assert r.status_code == 403
|
|
assert "账号已被禁用" == r.json()["detail"]
|
|
|
|
client.patch(
|
|
f"/api/v1/admin/users/{info['user_id']}",
|
|
headers=bearer(admin_tok),
|
|
json={"is_disabled": False},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# refresh
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_refresh_success(client: TestClient, monkeypatch) -> None:
|
|
email = f"refresh-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_and_confirm(client, monkeypatch, email, "refreshpass", "刷新测试")
|
|
r = client.post("/api/v1/auth/refresh")
|
|
assert r.status_code == 200
|
|
assert "access_token" in r.json()
|
|
|
|
|
|
def test_refresh_no_cookie(client: TestClient) -> None:
|
|
session_client = TestClient(client.app)
|
|
r = session_client.post("/api/v1/auth/refresh")
|
|
assert r.status_code == 401
|
|
assert "缺少刷新令牌" == r.json()["detail"]
|
|
|
|
|
|
def test_refresh_invalid_cookie(client: TestClient) -> None:
|
|
r = client.post(
|
|
"/api/v1/auth/refresh",
|
|
cookies={"opencloud_refresh": "not-a-valid-refresh-token-at-all"},
|
|
)
|
|
assert r.status_code == 401
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# logout
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_logout(client: TestClient) -> None:
|
|
r = client.post("/api/v1/auth/logout")
|
|
assert r.status_code == 200
|
|
assert r.json()["message"] == "已退出登录"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# forgot-password / reset-password
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_forgot_password_existing_email(client: TestClient, monkeypatch) -> None:
|
|
email = f"forgot-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_and_confirm(client, monkeypatch, email, "forgotpass", "忘记密码")
|
|
captured = {}
|
|
monkeypatch.setattr(auth_router, "send_password_reset_email", captured.__setitem__)
|
|
r = client.post("/api/v1/auth/forgot-password", json={"email": email})
|
|
assert r.status_code == 202
|
|
assert "邮件" in r.json()["message"]
|
|
|
|
|
|
def test_forgot_password_nonexistent_email(client: TestClient) -> None:
|
|
r = client.post("/api/v1/auth/forgot-password", json={"email": "ghost@example.com"})
|
|
assert r.status_code == 202
|
|
|
|
|
|
def test_reset_password_valid(client: TestClient, monkeypatch) -> None:
|
|
email = f"reset-{uuid.uuid4().hex[:8]}@example.com"
|
|
register_and_confirm(client, monkeypatch, email, "oldpass12", "重置密码")
|
|
captured = {}
|
|
monkeypatch.setattr(auth_router, "send_password_reset_email", lambda r, t: captured.update(token=t))
|
|
|
|
client.post("/api/v1/auth/forgot-password", json={"email": email})
|
|
token = captured.get("token")
|
|
assert token
|
|
|
|
r = client.post(
|
|
"/api/v1/auth/reset-password",
|
|
json={"token": token, "password": "newpass12"},
|
|
)
|
|
assert r.status_code == 200
|
|
assert r.json()["message"] == "密码已重置,请重新登录"
|
|
|
|
r = login_user(client, email, "oldpass12")
|
|
assert r.status_code == 401
|
|
r = login_user(client, email, "newpass12")
|
|
assert r.status_code == 200
|
|
|
|
|
|
def test_reset_password_invalid_token(client: TestClient) -> None:
|
|
r = client.post(
|
|
"/api/v1/auth/reset-password",
|
|
json={"token": "not-a-real-reset-token-ever", "password": "newpass12"},
|
|
)
|
|
assert r.status_code == 400
|
|
assert "无效" in r.json()["detail"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# change-password
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_change_password(client: TestClient, monkeypatch) -> None:
|
|
email = f"chpw-{uuid.uuid4().hex[:8]}@example.com"
|
|
info = register_and_confirm(client, monkeypatch, email, "oldpass12", "改密")
|
|
r = client.patch(
|
|
"/api/v1/auth/password",
|
|
headers=bearer(info["token"]),
|
|
json={"password": "updatedpass12"},
|
|
)
|
|
assert r.status_code == 200
|
|
assert "access_token" in r.json()
|
|
|
|
r = login_user(client, email, "oldpass12")
|
|
assert r.status_code == 401
|
|
r = login_user(client, email, "updatedpass12")
|
|
assert r.status_code == 200
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# me
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_me(client: TestClient, monkeypatch) -> None:
|
|
email = f"me-{uuid.uuid4().hex[:8]}@example.com"
|
|
info = register_and_confirm(client, monkeypatch, email, "mepass12!", "我的信息")
|
|
r = client.get("/api/v1/auth/me", headers=bearer(info["token"]))
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["user"]["email"] == email
|
|
assert body["user"]["username"] == "我的信息"
|
|
|
|
|
|
def test_me_unauthenticated(client: TestClient) -> None:
|
|
r = client.get("/api/v1/auth/me")
|
|
assert r.status_code == 401
|