325 lines
11 KiB
Python
325 lines
11 KiB
Python
import uuid
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.routers import auth as auth_router
|
|
from tests.conftest import bearer
|
|
|
|
ADMIN_EMAIL = "admin@example.com"
|
|
ADMIN_PASSWORD = "admin-password-123"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# register
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_register_success(client: TestClient, monkeypatch) -> None:
|
|
email = f"reg-{uuid.uuid4().hex[:8]}@example.com"
|
|
captured = _capture_email(monkeypatch)
|
|
r = _register(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(client, email, "firstpass", "第一个用户")
|
|
r = _register(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(client, email_a, "passA123", "同名用户")
|
|
r = _register(client, email_b, "passB123", "同名用户")
|
|
assert r.status_code == 409
|
|
assert "这个昵称已经被使用" == r.json()["detail"]
|
|
|
|
|
|
def test_register_short_password(client: TestClient) -> None:
|
|
r = _register(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(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(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(client, email, "confirmpass", "确认测试")
|
|
assert r.status_code == 201
|
|
token = captured.get("token")
|
|
assert token
|
|
cr = _confirm(client, token)
|
|
assert cr.status_code == 200
|
|
assert cr.json()["message"] == "邮箱确认成功"
|
|
|
|
|
|
def test_confirm_email_invalid_token(client: TestClient) -> None:
|
|
r = _confirm(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(client, email, "wrongpass")
|
|
assert r.status_code == 401
|
|
assert "邮箱或密码错误" == r.json()["detail"]
|
|
|
|
|
|
def test_login_nonexistent_email(client: TestClient) -> None:
|
|
r = _login(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(client, email, "unverified", "未确认")
|
|
r = _login(client, email, "unverified")
|
|
assert r.status_code == 403
|
|
assert "邮箱尚未确认" == r.json()["detail"]
|
|
|
|
|
|
def test_login_disabled_user(client: TestClient, monkeypatch) -> None:
|
|
email = f"disabled-{uuid.uuid4().hex[:8]}@example.com"
|
|
info = _register_and_confirm(client, monkeypatch, email, "disabled", "禁用测试")
|
|
admin_token = _login_as_user(client, ADMIN_EMAIL, ADMIN_PASSWORD)
|
|
client.patch(
|
|
f"/api/v1/admin/users/{info['user_id']}",
|
|
headers=bearer(admin_token),
|
|
json={"is_disabled": True},
|
|
)
|
|
r = _login(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_token),
|
|
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(client, email, "oldpass12")
|
|
assert r.status_code == 401
|
|
r = _login(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(client, email, "oldpass12")
|
|
assert r.status_code == 401
|
|
r = _login(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["profile"]["username"] == "我的信息"
|
|
|
|
|
|
def test_me_unauthenticated(client: TestClient) -> None:
|
|
r = client.get("/api/v1/auth/me")
|
|
assert r.status_code == 401
|