Files
DadaoTask/backend/tests/test_auth.py
T
Steven 2c338490e5
CI / backend (push) Canceled after 0s
fix(auth): 令牌到期自动续上,不再用半小时就被踢回登录页
服务端一直签 access(30 分钟)和 refresh(14 天)两个令牌,但两个客户端拿到 401 都是直接清
令牌跳登录页,refresh 从来没被用过——"14 天有效"只是纸面参数,真实寿命就是 30 分钟。

- web/client.ts:请求前剩不到 90 秒先换;撞 401 先换再重放一次原请求;并发共用一次刷新;
  上传附件同一条路
- android/Api.kt:call 拆 call/callOnce,2002 时用 Mutex 串行刷新后重放
- 后端 /auth/refresh 看一眼账号状态:停用的账号不能靠 refresh 一直续(2012)
- 新增 session-refresh.mjs:假 access + 真 refresh 要能留住人,两个都假才回登录页
2026-11-03 22:53:43 +08:00

100 lines
3.9 KiB
Python

"""认证相关。"""
from __future__ import annotations
from httpx import AsyncClient
from .conftest import DEV_CODE, auth, login, unwrap
async def test_login_creates_user_and_me_works(client: AsyncClient) -> None:
user = await login(client, "13800000001")
me = unwrap(await client.get("/api/v1/auth/me", headers=user["headers"]))
assert me["user"]["phone"] == "138****0001" # 手机号对外必须打码
assert me["workspaces"] == []
async def test_login_is_idempotent_for_same_phone(client: AsyncClient) -> None:
first = await login(client, "13800000002")
second = await login(client, "13800000002")
assert first["user_id"] == second["user_id"]
async def test_wrong_code_rejected(client: AsyncClient) -> None:
await client.post("/api/v1/auth/sms/send", json={"phone": "13800000003"})
resp = await client.post("/api/v1/auth/login", json={"phone": "13800000003", "code": "000000"})
assert resp.status_code == 400
assert resp.json()["code"] == 2010
async def test_code_can_only_be_used_once(client: AsyncClient) -> None:
await client.post("/api/v1/auth/sms/send", json={"phone": "13800000004"})
ok = await client.post("/api/v1/auth/login", json={"phone": "13800000004", "code": DEV_CODE})
assert ok.status_code == 200
again = await client.post("/api/v1/auth/login", json={"phone": "13800000004", "code": DEV_CODE})
assert again.json()["code"] == 2010
async def test_sms_throttled(client: AsyncClient, monkeypatch) -> None:
# 全局测试默认关掉了节流(SMS_THROTTLE_SECONDS=0),这里显式打开
from app.config import get_settings
monkeypatch.setattr(get_settings(), "sms_throttle_seconds", 60)
payload = {"phone": "13800000005"}
assert (await client.post("/api/v1/auth/sms/send", json=payload)).status_code == 200
second = await client.post("/api/v1/auth/sms/send", json=payload)
assert second.json()["code"] == 2011
async def test_refresh_token_cannot_be_used_as_access_token(client: AsyncClient) -> None:
user = await login(client)
resp = await client.get("/api/v1/auth/me", headers=auth(user["refresh_token"]))
assert resp.status_code == 401
assert resp.json()["code"] == 2003
async def test_access_token_cannot_be_used_to_refresh(client: AsyncClient) -> None:
user = await login(client)
resp = await client.post("/api/v1/auth/refresh", json={"refresh_token": user["token"]})
assert resp.status_code == 401
assert resp.json()["code"] == 2003
async def test_refresh_returns_working_token_pair(client: AsyncClient) -> None:
user = await login(client)
data = unwrap(
await client.post("/api/v1/auth/refresh", json={"refresh_token": user["refresh_token"]})
)
me = await client.get("/api/v1/auth/me", headers=auth(data["access_token"]))
assert me.status_code == 200
async def test_disabled_account_cannot_refresh(client: AsyncClient) -> None:
# 停用的账号不能靠 refresh 一直续下去:令牌在有效期内也要挡住
user = await login(client, "13800000009")
from app.db import SessionFactory
from app.models import AppUser
async with SessionFactory() as session, session.begin():
row = await session.get(AppUser, user["user_id"])
assert row is not None
row.status = "disabled"
resp = await client.post("/api/v1/auth/refresh", json={"refresh_token": user["refresh_token"]})
assert resp.status_code == 403
assert resp.json()["code"] == 2012
async def test_requires_token(client: AsyncClient) -> None:
resp = await client.get("/api/v1/auth/me")
assert resp.status_code == 401
assert resp.json()["code"] == 2001
assert resp.json()["request_id"] != "-"
async def test_unknown_path_returns_envelope(client: AsyncClient) -> None:
resp = await client.get("/api/v1/nope")
assert resp.status_code == 404
assert set(resp.json()) == {"code", "message", "data", "request_id"}