跑通迁移与测试后修掉的真问题: - 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:
+14
-13
@@ -39,16 +39,13 @@ SessionFactory = async_sessionmaker(engine, expire_on_commit=False, autoflush=Fa
|
||||
|
||||
async def get_session() -> AsyncIterator[AsyncSession]:
|
||||
"""一个请求 = 一个事务。异常即整体回滚。"""
|
||||
async with SessionFactory() as session:
|
||||
async with session.begin():
|
||||
yield session
|
||||
async with SessionFactory() as session, session.begin():
|
||||
yield session
|
||||
|
||||
|
||||
async def bind_tenant(session: AsyncSession, ws_id: uuid.UUID, user_id: uuid.UUID | None) -> None:
|
||||
"""把租户上下文写进当前事务。必须在任何业务查询之前调用。"""
|
||||
await session.execute(
|
||||
text("SELECT set_config('app.ws_id', :ws, true)"), {"ws": str(ws_id)}
|
||||
)
|
||||
await session.execute(text("SELECT set_config('app.ws_id', :ws, true)"), {"ws": str(ws_id)})
|
||||
if user_id is not None:
|
||||
await session.execute(
|
||||
text("SELECT set_config('app.user_id', :uid, true)"), {"uid": str(user_id)}
|
||||
@@ -65,15 +62,19 @@ async def bind_user(session: AsyncSession, user_id: uuid.UUID) -> None:
|
||||
async def current_app_role(session: AsyncSession) -> dict[str, Any]:
|
||||
"""自检用:确认应用连接的不是特权角色。"""
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
"SELECT current_user AS role, "
|
||||
"pg_has_role(current_user, 'pg_read_all_data', 'member') AS read_all, "
|
||||
"(SELECT rolsuper FROM pg_roles WHERE rolname = current_user) AS is_super, "
|
||||
"(SELECT rolbypassrls FROM pg_roles WHERE rolname = current_user) AS bypass_rls"
|
||||
(
|
||||
await session.execute(
|
||||
text(
|
||||
"SELECT current_user AS role, "
|
||||
"pg_has_role(current_user, 'pg_read_all_data', 'member') AS read_all, "
|
||||
"(SELECT rolsuper FROM pg_roles WHERE rolname = current_user) AS is_super, "
|
||||
"(SELECT rolbypassrls FROM pg_roles WHERE rolname = current_user) AS bypass_rls"
|
||||
)
|
||||
)
|
||||
)
|
||||
).mappings().one()
|
||||
.mappings()
|
||||
.one()
|
||||
)
|
||||
return dict(row)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user