fix(m0): 迁移可执行 + 测试全绿(33 条)
CI / backend (push) Canceled after 0s

跑通迁移与测试后修掉的真问题:

- alembic 0001:asyncpg 走预备语句,一次 execute 只能一条 SQL。
  CREATE FUNCTION / CREATE POLICY / GRANT 全部拆成单语句逐条执行,
  否则报 "cannot insert multiple commands into a prepared statement"。
- 创建工作区:ORM flush 只按 relationship 排序,裸外键不算数,
  member 可能先于 workspace 落地撞 FK。改为先 flush workspace 再插 member。
- datetime.UTC 是 3.11+ 才有的,本机 3.10 直接 ImportError。改用 timezone.utc。
- 幂等中间件:原来会缓存 4xx。校验失败被缓存后,用户改对参数仍拿到同一个 422。
  改为只缓存 <400。
- 发码节流窗口改为可配置 SMS_THROTTLE_SECONDS(测试置 0),
  节流用例自己显式打开,避免"整库关掉节流"导致节流从未被验证。
- 租户隔离用例:pytest.raises(Exception) 改为精确的 ProgrammingError,
  否则任何异常都会让"应用角色不能建表"这条断言假通过。

验收:pytest 33 passed;ruff check / format 通过;真机跑 uvicorn,
/health、/version、登录、建工作区、列表、/auth/me 全通。
This commit is contained in:
Steven
2026-10-28 03:22:59 +08:00
parent 0c53aa7062
commit 2b493bd9f8
14 changed files with 160 additions and 136 deletions
+8 -4
View File
@@ -13,7 +13,7 @@ from ..config import get_settings
from ..deps import CurrentUser, SessionDep
from ..errors import AppError, ErrorCode
from ..models import AppUser, Member, Workspace
from ..schemas import Envelope, ok
from ..schemas import ok
from ..security import create_token_pair, decode_token
from ..stores import get_store, sms_code_key, sms_throttle_key
@@ -43,9 +43,13 @@ class RefreshIn(BaseModel):
@router.post("/sms/send", summary="发送登录验证码")
async def send_code(body: SendCodeIn) -> dict:
store = get_store()
hits = await store.incr(sms_throttle_key(body.phone), 60)
if hits > 1:
raise AppError(ErrorCode.SMS_TOO_FREQUENT, "发送过于频繁,请 60 秒后重试")
if settings.sms_throttle_seconds > 0:
hits = await store.incr(sms_throttle_key(body.phone), settings.sms_throttle_seconds)
if hits > 1:
raise AppError(
ErrorCode.SMS_TOO_FREQUENT,
f"发送过于频繁,请 {settings.sms_throttle_seconds} 秒后重试",
)
if settings.sms_provider == "dev":
code = settings.sms_dev_code