Files
opencloud-backend/tests/test_auth.py
T
Mplan e75833ee4b 重构表结构:合并 profiles→users,展平 API 响应,新增计数器字段
- 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
2026-07-28 23:48:57 +08:00

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