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:
WorkBuddy
2026-09-18 16:37:33 +08:00
parent 8dee67040c
commit af5412646b
3 changed files with 321 additions and 7 deletions
+73
View File
@@ -0,0 +1,73 @@
"""E2E 自检守卫:fixture 只能使用 seed 脚本真正创建的运营账号。
为什么需要它(本轮发现的真实缺陷):
库中 operator 账号**没有任何迁移种子**,`DemoDataSeeder` 也不创建运营账号
(只统计数量)。因此全新库(CI)里唯一的运营账号来源是
`tests/e2e/sql/seed_e2e_operators.sql`。
而 `conftest.py` 的 `admin_token` 曾写死 `username="admin"` —— 那是某台
开发机上手工创建的遗留账号。本机有它,所以本地跑绿;CI 全新库没有它,
该 fixture 必然 401 → **整个 E2E job 从未真正通过过**。
这类"依赖本机手工数据"的写法没有任何运行期提示,只能靠机械检查拦住。
"""
from __future__ import annotations
import re
from pathlib import Path
E2E_DIR = Path(__file__).resolve().parent
CONFTEST = E2E_DIR / "conftest.py"
SEED_SQL = E2E_DIR / "sql" / "seed_e2e_operators.sql"
MIGRATION_DIR = E2E_DIR.parent.parent / "backend" / "src" / "main" / "resources" / "db" / "migration"
def _seeded_usernames() -> set[str]:
sql = SEED_SQL.read_text(encoding="utf-8")
# seed 用的是 VALUES 行:('uuid', '名称', 'username', crypt(...), 'active')
return set(re.findall(r"'([A-Za-z0-9_]+)',\s+crypt\(", sql))
def _usernames_used_in_conftest() -> set[str]:
src = CONFTEST.read_text(encoding="utf-8")
used = set(re.findall(r'"username"\s*:\s*"([A-Za-z0-9_]+)"', src))
used |= set(re.findall(r'ADMIN_USERNAME\s*=\s*"([A-Za-z0-9_]+)"', src))
return used
def test_conftest_only_logs_in_with_seeded_accounts() -> None:
seeded = _seeded_usernames()
assert seeded, f"未能从 {SEED_SQL.name} 解析出任何账号,守卫已失效"
used = _usernames_used_in_conftest()
assert used, f"未能从 {CONFTEST.name} 解析出任何登录用户名,守卫已失效"
unknown = sorted(used - seeded)
assert not unknown, (
"conftest 使用了 seed 脚本未创建的运营账号:"
f"{unknown}(seed 现有:{sorted(seeded)})。\n"
"全新库(CI)里这些账号不存在,对应 fixture 会 401,E2E 必然失败。"
"请改用 seed 中的账号,或在 seed 脚本中补上该账号。"
)
def test_no_operator_account_is_seeded_by_migrations() -> None:
"""锁住上面那条推理的前提:迁移确实没有 operator 种子。
若将来有人在迁移里加了 admin 种子,本用例会变红——那时应重新评估
`admin_token` 的选择(迁移种子同样是自举的,可作为合法来源),
而不是继续凭本机的遗留账号。
"""
hits: list[str] = []
for sql_file in MIGRATION_DIR.glob("*.sql"):
text = sql_file.read_text(encoding="utf-8", errors="replace")
if re.search(r"INSERT\s+INTO\s+operators\b", text, re.IGNORECASE):
hits.append(sql_file.name)
assert not hits, (
f"迁移中出现了 operator 种子:{hits}。"
"本次 E2E fixture 的选型前提('运营账号只能来自 seed 脚本')已改变,"
"请重新评估 conftest 的 ADMIN_USERNAME 与本文档说明。"
)