test(e2e): 运营登录只用 seed 账号;补幂等 E2E 与两处机械守卫
1) 修复一处从未暴露的 CI 缺陷 库中 operator 账号没有任何迁移种子,DemoDataSeeder 也不创建运营账号 (只统计数量),全新库唯一的账号来源是 seed_e2e_operators.sql。 而 conftest 的 admin_token 写死 username="admin" —— 那是某台开发机上 手工创建的遗留账号:本机有它所以本地一直跑绿,CI 全新库没有它 → 该 fixture 必然 401 → e2e job 从未真正通过过。 同一文件里「外加迁移 seed 的 admin」这句注释也是纯粹的虚构。 改用 seed 创建的 e2e_admin(已绑定 super_admin,语义等价)。 2) fixture_account_guard_test.py(2 用例) 机械拦住「fixture 依赖本机手工数据」:从 seed SQL 解析账号清单, 扫描 conftest 的所有登录用户名,任何一个不在 seed 内即变红; 另一条锁住推理前提(迁移里确实没有 operator 种子)。 两条都带「解析失败即失败」断言 —— 守卫自身静默失效是本项目反复踩过的坑。 3) idempotency_test.py(7 用例) 同键重放响应逐字节相同、同键重复提交只产生一条记录、body 字段幂等未回归、 不同键相互独立、同键作用域按 actor 隔离、5 线程并发同键业务恰好落库一次、 无键重复 Like 不得返回 500。 4) conftest.ApiClient.call 支持 headers 覆盖,供幂等用例携带自定义键。
This commit is contained in:
+29
-7
@@ -49,12 +49,16 @@ class ApiClient:
|
||||
token: Optional[str] = None,
|
||||
json: Optional[Dict[str, Any]] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> requests.Response:
|
||||
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
||||
merged = {"Authorization": f"Bearer {token}"} if token else {}
|
||||
if headers:
|
||||
# 显式传入的头覆盖默认头(幂等键等按用例定制)
|
||||
merged.update(headers)
|
||||
return self.session.request(
|
||||
method,
|
||||
f"{self.base_url}{path}",
|
||||
headers=headers,
|
||||
headers=merged,
|
||||
json=json,
|
||||
params=params,
|
||||
timeout=self.timeout,
|
||||
@@ -112,15 +116,29 @@ def operator_password() -> str:
|
||||
return pwd
|
||||
|
||||
|
||||
ADMIN_USERNAME = "e2e_admin"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def admin_token(api: ApiClient, operator_password: str) -> str:
|
||||
"""超级管理员 token。
|
||||
|
||||
必须使用 **seed 脚本创建的账号**,而不是 `admin`。
|
||||
|
||||
原因(本轮发现):库中 operator 账号**没有任何迁移种子**,`DemoDataSeeder`
|
||||
也不创建运营账号(只统计),运营账号只能由运行时服务或本 seed 脚本产生。
|
||||
此前该 fixture 写的是 `username="admin"` —— 那是某台开发机上手工创建的遗留
|
||||
账号,全新库(CI)里根本不存在 → 该 fixture 在 CI 上必然 401,
|
||||
**整个 E2E job 从未真正通过过**,只是本机因为有遗留账号而看不出来。
|
||||
`e2e_admin` 由 seed 绑定 super_admin 角色,语义等价且可自举。
|
||||
"""
|
||||
data = api.ok(
|
||||
"POST",
|
||||
"/auth/operator/login",
|
||||
json={"username": "admin", "password": operator_password},
|
||||
json={"username": ADMIN_USERNAME, "password": operator_password},
|
||||
)
|
||||
token = data.get("accessToken")
|
||||
assert token, "admin 登录未返回 accessToken"
|
||||
assert token, f"{ADMIN_USERNAME} 登录未返回 accessToken"
|
||||
return token
|
||||
|
||||
|
||||
@@ -130,9 +148,13 @@ def operator_token(
|
||||
) -> Callable[[str], str]:
|
||||
"""按用户名取运营 token(会话内缓存)。
|
||||
|
||||
可用账号见 tests/e2e/sql/seed_e2e_operators.sql:
|
||||
e2e_admin / e2e_platform / e2e_counselor / e2e_station / e2e_matchmaker / e2e_content,
|
||||
外加迁移 seed 的 admin。用法:`operator_token("e2e_counselor")`。
|
||||
可用账号**仅限** tests/e2e/sql/seed_e2e_operators.sql 中创建的 6 个:
|
||||
e2e_admin / e2e_platform / e2e_counselor / e2e_station / e2e_matchmaker / e2e_content。
|
||||
迁移里**没有任何 operator 种子**(原文"外加迁移 seed 的 admin"是错的,
|
||||
已纠正——全新库里没有 admin 这个账号)。用法:`operator_token("e2e_counselor")`。
|
||||
|
||||
传入 seed 之外的用户名等于依赖某台机器上的手工数据,CI 上必然 401;
|
||||
`fixture_account_guard_test.py` 会机械拦住这类写法。
|
||||
"""
|
||||
cache: Dict[str, str] = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user