# Fairness 认证夹具隔离修复(A6 后独立纯测试提交) 日期:2026-09-17 分支:`refactor/r01-structure-performance` A6 提交:`c6db74a1a8300e1aab8a452fb67909f86861fa43` 本轮性质:**纯测试修复** —— 生产代码 diff 为 0,Flyway 为 0,OpenAPI 为 0 --- ## 0. 结论 `FairnessMonitoringControllerTest` 已不再依赖种子数据、测试执行顺序或共享表清空, **单独运行稳定通过**;并新增反证测试锁定该隔离性。 --- ## 1. 缺陷(A6 独立复验定位) | 环节 | 事实 | |---|---| | 冲突 | 该测试用 `token(1L, "ADMIN")` / `token(2L, "STUDENT")`,而 `V1_1__seed_data.sql` 的种子账号占用 `users.id = 1/2/3` | | 被禁用 | `V43__disable_legacy_seed_accounts.sql` 把这三行置为 `is_active = 0` | | 夹具缺陷 | `TestAuthTokens.writeSession()` 仅在 `SELECT COUNT(*) FROM users WHERE id = ?` 为 0 时 INSERT,**不会激活已存在的禁用行** | | 后果 | `AuthSessionService.isSessionActive()` 的用户检查短路 → 全部 Token 请求 401 | | 被掩盖 | 全量顺序下 `com.mydreamjob.controller.AuthIntegrationTest` 的 `@BeforeEach` 执行 `userRepository.deleteAll()`,偶然清掉种子行,夹具随后才补建 active 用户 | 单跑实测(修复前):`Tests run: 7, Failures: 6`,全部 `Status expected:<200|403|400> but was:<401>`。 详见 `docs/r01-a6-independent-verification-20260917.md` §9。 --- ## 2. 修复方案 **选择:让 `FairnessMonitoringControllerTest` 使用独占用户,而不是修改公共 `TestAuthTokens`。** 理由:公共 helper 若"自动激活已存在的 inactive 用户",会静默复活本应被禁用的账号 (包括生产语义上被 V43 禁用的种子账号),风险远大于收益。 ### 2.1 改动内容 | 项 | 修复前 | 修复后 | |---|---|---| | 夹具身份 | `userId = 1` / `2`(与种子账号冲突) | **独占** `ADMIN_USER_ID = 9_310_001L`、`STUDENT_USER_ID = 9_310_002L` | | 手机号 | 由 `TestAuthTokens` 隐式生成 `"t"+id` | 独占 `a6fair-admin` / `a6fair-student` | | 用户来源 | 依赖 `TestAuthTokens` 的"不存在才插入" | `@BeforeEach` **显式插入 `is_active = 1`** 的独占用户 | | 清理 | 无 | `@AfterEach` 按**精确 ID**、按外键顺序删除:`auth_tokens` → `user_roles` → `users` | `@BeforeEach` 先执行与 `@AfterEach` 相同的精确清理再插入,因此对"上轮异常中断残留"具备幂等性, 且**从不把任何已存在的 inactive 用户直接激活**(是删除后重建自有行)。 ### 2.2 未使用的手段(明确排除) 未改 `V43`;未改生产认证逻辑;未使用 `deleteAll()` 清空共享表;未使用 `@DirtiesContext` 掩盖污染; 未捕获并忽略约束异常;未依赖测试执行顺序;未放宽任何既有 Fairness 断言;未修改公共 `TestAuthTokens`。 --- ## 3. 反证测试 `FairnessMonitoringFixtureIsolationTest`(7 例) | # | 用例 | 覆盖点 | |---|---|---| | 1 | `fixtureIdsAreOutsideSeedAccountRange` | 夹具 ID 不在种子号段 1/2/3;夹具用户确为 active;未占用种子手机号 | | 2 | `seedRowsInactiveOrAbsent_stillAuthenticates` | 种子行 **inactive(V43 状态)** 与 **完全缺失** 两种前置下端点均返回 200 | | 3 | `repeatedProvisioningAndRequests_doNotConflict` | 连续 3 轮"装配 + 请求"均成功,且不产生重复用户行 | | 4 | `fixtureCycleLeavesSeedRowsUntouched` | 完整周期前后种子行逐字段不变(顺序无关的根本保证) | | 5 | `cleanupRemovesOnlyOwnFixtureRows` | 清理只删本类行;探针用户及其会话不受波及 | | 6 | `unrelatedUsersAndSessionsUnchanged` | 非夹具用户与会话逐字段不变 | | 7 | `publicTokenFixtureDoesNotSilentlyReviveInactiveUser` | `TestAuthTokens` 对已存在 inactive 用户**不激活**,该 Token 请求为 401 且 Service 零调用 | 用例 2 会临时快照并按主键**原样恢复**种子行(`try` 语义由 `@AfterEach` 兜底),不新增、不批量重建。 --- ## 4. 验证矩阵 命令均在 `mydreamjob-server/` 下执行;日志位于 `docs/acceptance/r01-d4-2-20260916/ff-*.log`。 | # | 命令 | 结果 | 日志 | |---|---|---|---| | 1 | `mvn -Dtest="FairnessMonitoringControllerTest" test` | **7 / 7**,F=0 E=0 S=0 ✔ | `ff-1-standalone.log` | | 2 | `mvn -Dtest="FairnessMonitoringFixtureIsolationTest,FairnessMonitoringControllerTest" test` | **14 / 14**,F=0 E=0 S=0 ✔ | `ff-2-isolation.log` | | 3 | Fairness **先**跑 ×3(`-Dsurefire.runOrder=reversealphabetical`) | **13 / 13** ×3 ✔ | `ff-3-fairnessfirst-{1,2,3}.log` | | 4 | Auth **先**跑 ×3(`-Dsurefire.runOrder=alphabetical`) | **13 / 13** ×3 ✔ | `ff-4-authfirst-{1,2,3}.log` | | 5 | `mvn clean verify` | 见 §4.1 | `ff-5-clean-verify.log` | | 6 | `python ..\docs\verify_openapi.py` | **176 paths / 214 operations**,All checks passed ✔ | — | > **顺序说明**:surefire 默认 `runOrder=filesystem` 会忽略 `-Dtest` 的书写顺序, > 因此额外用 `-Dsurefire.runOrder=reversealphabetical`(Fairness 先)与 `=alphabetical`(Auth 先) > 强制两个方向,并在日志中逐次核对实际执行顺序(`reversealphabetical` 下 > `scoringmonitoring.FairnessMonitoringControllerTest` 确在 `controller.AuthIntegrationTest` 之前)。 > `runOrder=testorder` 在本仓库使用的 surefire 3.5.6 中不可用(已实测报错)。 ### 4.1 全量结果 | 项 | 数值 | |---|---| | 全量测试总数 | 2278 → **2285**(+7:新增反证测试类) | | Failures / Errors / Skipped | **0 / 0 / 0** ✔ | | 生产代码 diff | **0** ✔ | | V1–V45 | **未改动** ✔ | | `docs/openapi-rc.json` | **未修改** ✔ | --- ## 5. 修改文件 ``` mydreamjob-server/src/test/java/com/mydreamjob/scoringmonitoring/FairnessMonitoringControllerTest.java (修改:夹具独占化) mydreamjob-server/src/test/java/com/mydreamjob/scoringmonitoring/FairnessMonitoringFixtureIsolationTest.java(新增:反证测试) docs/r01-a6-fairness-fixture-isolation-fix-20260917.md (新增:本报告) docs/backend-development-roadmap.md (夹具问题关闭记录) ``` **无生产代码、无 Flyway、无 ErrorCode、无 DTO、无接口、无 OpenAPI、无 Filter/Accessor/SecurityConfig 改动。** --- ## 6. 明确声明 - 未修改任何生产代码,未修改 `V43`,未修改公共 `TestAuthTokens` 夹具。 - 未删除、未放宽任何既有断言;`FairnessMonitoringControllerTest` 的业务断言逐字保留。 - 未使用 `deleteAll()`、`@DirtiesContext`、顺序依赖或异常吞没等掩盖手段。 - 未与 R01-B 混合。 --- > **FairnessMonitoringControllerTest 单跑夹具缺陷已通过独立纯测试提交修复; > 该测试不再依赖种子数据状态与测试执行顺序。**