test(phase23): KEEP-VERIFY — remove 44 pass shells + fake INV tests, add shell guard

Phase 23 "可信系统测试重建 (REFACTOR)" re-audit. STATUS claimed "10 E2E
files / ~80+ cases" and "INV-011~020 ✅"; measurement showed the opposite.

D-23-A (Critical): V11InvariantsTest's 14 cases asserted only Constants
strings. INV-016 "Banner target types" actually asserted ARTICLE_STATUS_*;
INV-017 "home 8 entries" declared local arrays then asserted
firstRow.length == 4 (tautology, never called the home service);
INV-018/019/020 used appointment/article/account status constants as
stand-ins for permission and data-scope checks. Rewritten to 12 cases:
4 real service-behavior guards (INV-013 via real CounselingService.getDetail
— Matchmaker sees null userRequestSummary, with an assigned-Counselor
counter-proof so global sanitization cannot fake a pass; INV-016 reads the
production ALLOWED_TARGET_TYPES whitelist and asserts no external/http/url/
web entry; INV-017 calls real getHome and pins all 8 codes + order;
INV-019 asserts content_editor reads null), plus assertBehaviorTestExists
turning "行为验证见 XxxTest" pointers into executable Class.forName contracts.

D-23-B (High): 44 `pass` shells across 6 files in tests/e2e (4 files were
100% shells). Gate forbids skip/pass shells. Rewritten in place as honest
placeholders with the full scenario lists preserved.

D-23-C (High, residual): the pytest E2E suite is not executable at all —
no conftest.py anywhere, ~15 fixtures (api_client/verified_user/
banner_factory/...) do not exist. Only run_v11_tests.py really runs
(16 cases, needs a live stack + E2E_PASSWORD).

D-23-D (High, residual, NOT fixed): CI does not chain E2E as the plan
requires. Needs an infrastructure decision on how CI provisions a live
stack; not implemented rather than written unverified.

D-23-E (Medium): the CI migration job claimed to "验证 migration SQL 语法"
but only checked file non-emptiness, while the real syntax/baseline checks
live in MigrationValidationTest (already run by the backend job). Replaced
with a real sequence check (naming / non-empty / unique versions / no
gaps); verified by extracting the exact inline script and running it.

New gate: scripts/check-no-empty-tests.py (AST scan, blocks pass-only and
unconditionally skipped tests) + a CI test-shell-guard job. Verified both
ways: 0 findings on the real tree, 2 precise findings on injected shells.

Negative validation: 3 injected production defects (external_url in the
banner whitelist, swapped home entry order, isMatchmaker returning the full
record) turned exactly inv016/inv017/inv013 red, 9 others green, zero
unrelated failures; md5sum -c confirmed byte-identical restore.

Full regression: 655 tests / 652 passed / 0 failures / 0 errors / 3 skipped
(baseline 657; -2 because 14 vacuous cases were merged into 12). The 3
skips carry explicit reasons (2x @EnabledIfEnvironmentVariable(CI=true),
1x @Disabled "需要完整数据库连接") — none is a shell.

Phase 23 is PARTIALLY closed: fake coverage is gone, but the UAT gate
requires the end-to-end chain to actually execute, so "失败为零可进入 UAT"
is NOT claimed.
This commit is contained in:
WorkBuddy
2026-09-18 11:49:18 +08:00
parent 050d005c0a
commit ae1bd2b734
12 changed files with 798 additions and 415 deletions
+2 -1
View File
@@ -179,7 +179,8 @@
| Phase 19 统一运营后台 + 用户端公益服务页面复验 | 已完成(2026-09-17) | 见 `Phase-19-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **25 处真实缺陷**(**D20 服务站公告能力整条不可达:Service + V042 表都在、全仓零端点**;**D21 服务站 `create/update` 越权写:类级 `service_location.view` 兜底,`service_location.manage` 是从未使用的死权限**;D22 该控制器未纳入权限覆盖守卫;**D2/D3 Banner 校验 500 + 软删 Banner 仍上首页**;**D5/D6 Content 发布死路 + 分类停用不可恢复**;D10~D14 ServiceProgram 审批死路/草稿泄露/成员数恒 0/退出未实现/撞唯一键;D9/D23 测试库缺 `content_categories` 与 `service_location_announcements` → 零覆盖;D18 预约 IDOR;D24 迁移基线未跟进 V042);补 V042 公告表 + 运营端五端点 + 用户端只读端点 + 后台公告管理 UI + 小程序 U50 公告区块;新增 3 组守卫(24+5+5 用例)并把服务站运营端收入权限覆盖扫描;负向验证 3 注入 → 4 用例精准变红;后端 **602 tests 全绿**;admin-web `vue-tsc`/`vite build` 双绿;小程序 `tsc` EXIT=0;ui-tests 14/14;**Phase 20 尚未获准** |
| Phase 20 Analytics 数据统计与管理驾驶舱复验 | 已完成(2026-09-17) | 见 `Phase-20-V11-Recovery-Completion-Report-2026-09-17.md`;修复 **20 处真实缺陷**(**D1 实名人数用 `profile_status != 'rejected'` 冒充 → 把"资料还在填"算成已实名**;**D3 Chat Started 直接数会话行数 → 系统通知计入**;**D4 Match 不分来源 → 10 对互选被当 10 对成功引用**;**D5 活动漏斗全部硬编码 0 + 一行把"用户数"当"活动数"的死代码**;**D6 Banner 曝光/点击硬编码 0**;**D12 系统性软删缺陷:`BaseEntity.deletedAt` 无 `@TableLogic` + 全局无逻辑删除配置 → `selectCount` 把软删数据全部计入且无任何报错**,修复 45 处统计调用(含 2 轮复验补扫出的 5 处次级路径);**D15 三套独立实现** → 抽 `AnalyticsMetricsCalculator` 统一口径;**D13/权限 授予脱节:`data_viewer` 空壳角色 + `platform_admin` 被 `id >= '20000000-…'` 条件漏掉 V1.0 老权限段 → 整个模块除 super_admin 外全员 403**;**D14 `AnalyticsServiceTest` = 5×`assertTrue(true)` 假绿 → 删除**;D20 `getDailySummary` 无边界校验;前端 `AnalyticsView.vue` 未接 `/v11-dashboard`);建立指标字典 `MetricCatalog`(36 指标 × 12 字段)+ `docs/ANALYTICS_METRICS.md`;新增 V043 授权(**刻意排除 `organization_admin`** —— 相关表无组织维度,无范围全量数据即越权);新增守卫 `MetricCatalogGuardTest`(11) / `AnalyticsMetricsPhase20IntegrationTest`(21) / `AnalyticsSoftDeleteGuardTest`(4,结构性) / `AnalyticsPermissionGrantTest`(6);**跨阶段修复 X1~X3**(**注销写入 `user_identities` 状态域的 `'failed'` → 违反 V017 `chk_elig_verify_status` → 真实 PG 上账号注销 100% 不可用**;`checkCanWithdraw` 资格拦截死代码;测试库补两条 CHECK 后暴露 `ActivityPhase14Test` 夹具违规)并新增 `LocalEligibilityStatusVocabularyGuardTest`(9);负向验证两轮 7 注入 → 7 用例精准变红并还原(`md5sum` 逐字节一致);后端 **648 tests 全绿**;admin-web `vue-tsc` EXIT=0;**Phase 21 已复验关闭(2026-09-18,650 tests 全绿)** |
| Phase 21 账户生命周期增量复验 | 已完成(2026-09-18) | 见 `docs/Phase-21-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **6 处真实缺陷 + 假绿测试**:① `AuthService.checkAccountStatus` 不拒 `CLOSED` → 注销后仍可登录(Gate ❌→✅);② `notifyActiveMatches` 为 TODO 桩 → 注销未关闭活跃 Match/会话(Gate ❌→✅,已实现关闭 Match + `chatService.closeConversationForMatch`);③ `cancelPendingActivityRegistrations` 为 TODO 桩 → 未来活动报名未关闭(Gate ❌→✅);④ `anonymizeServiceHistory` 已实现却**从未被 `closeAccount` 调用(死代码)** → 个人/咨询正文暴露(Gate ❌→✅,已接线 + 脱敏身份证占位符 + `counseling_followups` 正文);⑤ `closeAccount` 在 blockers 处硬抛异常使 `cancelXxx` 永远不可达(死代码矛盾 → 信息性日志 + 继续清理);⑥ 删除 7×`assertTrue(true)` 假绿 → 替换为 11 用例真实 DB 集成测试 + 3 用例登录守卫;跨模块防线(推荐引擎/匹配资格/Capability 已拒非 NORMAL)确认有效;后端 **650 tests 全绿**(起点 648,净 +2);**Phase 22 已复验关闭(2026-09-18,657 tests 全绿)** |
| Phase 22 部署运维配置扩展(Feature Flag)复验 | 已完成(2026-09-18) | 见 `docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **2 处真实代码缺陷 + 2 处文档夸大**:① D-22-A(High)`FeatureFlagController.updateFlag` 仅支持布尔 flag(反射 `getMethod("set"+cap, boolean.class)`)→ 7 个非布尔 flag 经 API 更新必失败,已改为按字段类型分派 boolean/int/String;② D-22-B(Medium)`body.getOrDefault("value", false)` 缺 value 静默置 false + 非布尔转型崩,已改为缺 value/类型错→400 且不改原值;T01–T05 模块开关(`isCounselingEnabled` 等)全仓无业务调用点 → 死配置(暴露未 gate,遗留交各模块 Phase);T06 上传策略实测已在 `LocalEligibilityService`+`PhotoService` 真强制 ✅;T07 私有桶零引用 → 死配置(遗留);yml 无 `features:` 块、默认值实为 Java 字段默认(文档纠正);新增 `FeatureFlagControllerTest`(7 用例)+ 负向验证(仅布尔缺陷版恰 2 用例变红);后端 **657 tests 全绿**(起点 650,净 +7);**Phase 23 尚未获准** |
| Phase 22 部署运维配置扩展(Feature Flag)复验 | 已完成(2026-09-18) | 见 `docs/Phase-22-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **2 处真实代码缺陷 + 2 处文档夸大**:① D-22-A(High)`FeatureFlagController.updateFlag` 仅支持布尔 flag(反射 `getMethod("set"+cap, boolean.class)`)→ 7 个非布尔 flag 经 API 更新必失败,已改为按字段类型分派 boolean/int/String;② D-22-B(Medium)`body.getOrDefault("value", false)` 缺 value 静默置 false + 非布尔转型崩,已改为缺 value/类型错→400 且不改原值;T01–T05 模块开关(`isCounselingEnabled` 等)全仓无业务调用点 → 死配置(暴露未 gate,遗留交各模块 Phase);T06 上传策略实测已在 `LocalEligibilityService`+`PhotoService` 真强制 ✅;T07 私有桶零引用 → 死配置(遗留);yml 无 `features:` 块、默认值实为 Java 字段默认(文档纠正);新增 `FeatureFlagControllerTest`(7 用例)+ 负向验证(仅布尔缺陷版恰 2 用例变红);后端 **657 tests 全绿**(起点 650,净 +7);**Phase 23 已复验(2026-09-18,部分关闭)** |
| Phase 23 可信系统测试重建(REFACTOR)复验 | ⚠️ 部分关闭(2026-09-18) | 见 `docs/Phase-23-V11-Recovery-Completion-Report-2026-09-18.md`;KEEP-VERIFY 复验发现 STATUS 标✅ 但含 **5 处真实缺陷**:① **D-23-A(Critical)** `V11InvariantsTest` 原 14 个用例全为常量字符串断言 —— INV-016「Banner 目标类型」实际断言**文章状态**常量(跑错域)、INV-017「首页 8 入口」是**对自己刚声明的字面量数组做 `length==4` 的重言式**(从未调用首页服务)、INV-018/019/020 用预约/文章/账号状态常量冒充权限与数据范围验证;已重写为 12 个用例,含 **4 个真实服务行为守卫**(INV-013 走真实 `CounselingService.getDetail` 断言 Matchmaker 读到 `userRequestSummary==null` 并以 assigned Counselor 能读正文作反证;INV-016 反射读生产 `ALLOWED_TARGET_TYPES` 断言不含外链;INV-017 真实 `getHome` 断言 8 入口编码与顺序固定;INV-019 断言 `content_editor` 读到 null)+ `assertBehaviorTestExists` 用 `Class.forName` 把「行为验证见 XxxTest」变成可执行契约;② **D-23-B(High)** `tests/e2e/` **44 个 `pass` 空壳**(4 个文件 100% 为空壳,另 2 个混合),违反 Gate「不允许 skip/pass 空壳」→ 已移除并改为诚实占位(保留完整场景清单);③ **D-23-C(High)** pytest E2E 套件**整体不可执行**:全仓无 `conftest.py`,约 15 个 fixture(`api_client`/`verified_user`/`banner_factory`…)不存在,唯一可执行的 `run_v11_tests.py`(16 用例)需活栈 + `E2E_PASSWORD`;④ **D-23-D(High,遗留未修)** CI 未串联 E2E(计划明确要求)→ 需先决定 CI 活栈供给方式,未实施;⑤ **D-23-E(Medium)** CI `migration` job 注释称「验证 SQL 语法」实为只查文件非空(真实语法校验在 `MigrationValidationTest`,随 backend job 跑)→ 改为真实序列校验(命名/非空/版本号唯一/无缺号,43 个文件 1~43 连续唯一)。**新增闸门**:`scripts/check-no-empty-tests.py`(AST 扫描阻断 `pass` 空壳与无条件 `skip`,已双向验证)+ CI `test-shell-guard` job。**证据**:负向注入 3 处生产缺陷 → 恰好 `inv013`/`inv016`/`inv017` 3 个用例精准变红、其余 9 绿、零无关失败,`md5sum -c` 还原一致;`655 tests / 通过 652 / 失败 0 / 错误 0 / 跳过 3`(起点 657,净 −2 因 14 空转用例合并为 12;3 个跳过均有显式原因,非空壳)。**遗留**:E2E 入 CI 待基础设施决策、pytest fixture 体系缺失、INV-011 缺显式守卫。**结论:不宣布「失败为零可进入 UAT」**;**Phase 24 尚未获准** |
| UI-CLEANUP-20260913 | 已完成 | 废弃 UI 代码、冲突规范、旧计划和无效验收证据已清理;`ui-document-baseline-contracts` 锁定唯一视觉入口 |
| UI-ADMIN-20260913 | 已完成(待桌面端目视复验) | 管理端全局框架与工作台已按规格交付;18 modules + 22 routes,vue-tsc 与 vite build 全绿 |
| UI-MINIAPP-20260913 | 已完成 | 小程序 11 个核心页面已重构;11/11 页 TS-only,2026-09-16 通过用户验收 |
@@ -0,0 +1,164 @@
# Phase 23 — 可信系统测试重建(REFACTOR)KEEP-VERIFY 复验报告
- **日期**:2026-09-18
- **执行方式**:KEEP-VERIFY(历史 ✅ 不可信,逐条核对运行时路径与真实约束)
- **结果**:**部分关闭(PARTIAL CLOSED)** —— 核心假覆盖已清除并补真实守卫;CI 串联 E2E 一项因需基础设施决策,列为遗留
- **回归**:**655 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**(起点 657)
---
## 一、Gate 要求的测试报告
计划 Phase 23 Gate:「**不允许 skip/pass 空壳;报告列出总数、通过、失败、跳过及原因,失败为零才进入 UAT。**」
| 项 | 数值 |
|---|---|
| 总数(后端 `mvn -o test`) | **655** |
| 通过 | **652** |
| 失败 | **0** |
| 错误 | **0** |
| 跳过 | **3** |
### 跳过明细及原因(均有显式声明,非空壳)
| 测试类 | 跳过数 | 原因(代码内原文) |
|---|---|---|
| `concurrent.ConcurrentTests` | 2 | `@EnabledIfEnvironmentVariable(named = "CI", matches = "true", disabledReason = "本地并发测试跳过")` —— 仅在 CI 环境执行的并发竞态用例 |
| `core.CoreE2ETest` | 1 | `@Disabled("需要完整数据库连接,仅验证 Spring Context 可加载")` |
> 数量说明:起点 657 → 655,净 **−2**。
> 原因:`V11InvariantsTest` 原有 **14 个**空转用例被重写合并为 **12 个**(INV-014 状态机 / INV-014 意向 / INV-015 三合一)。
> 合并后 10 个不变量(INV-011~020)**全部仍有对应用例**,其中 **4 个为真实服务行为验证**。
---
## 二、真实缺陷清单
### D-23-A(Critical):`V11InvariantsTest` 全部 14 个用例只断言常量字符串,且不变量归属错误
计划 Gate 明文禁止:「INV-001~020 必须测试真实服务行为,不能只断言常量字符串存在」。
实测原文件问题:
| 用例 | 声称验证 | 实际断言 | 判定 |
|---|---|---|---|
| INV-016「Banner 目标类型验证」 | Banner `target_type` 白名单 | `ARTICLE_STATUS_PUBLISHED/DRAFT`(**文章状态**) | ❌ 完全跑错域 |
| INV-017「首页 8 核心入口编码固定」 | 首页入口顺序 | 声明局部数组后 `assertEquals(4, firstRow.length)` | ❌ 对自己刚写的字面量做断言(重言式),**从未调用首页服务** |
| INV-018「服务站数据范围」 | 服务站数据范围 | `APPOINTMENT_STATUS_*`(预约状态) | ❌ 无权限/范围判定 |
| INV-019「内容编辑员零用户数据」 | 零用户数据权限 | `ARTICLE_STATUS_*`(文章状态) | ❌ 无权限判定 |
| INV-020「组织关系不授权」 | 组织关系权限 | `ACCOUNT_STATUS_*`(账号状态) | ❌ 无权限判定 |
| 其余 9 个 | 各自不变量 | 仅 `assertEquals(常量, 字面量)` / `assertNotNull(常量)` | ❌ 不触碰任何服务行为 |
**修复**:重写为 12 个用例 ——
- **真实服务行为(4 个)**
- `inv013MatchmakerCannotReadCounselingBody`:真实 `CounselingService.getDetail`;断言 Matchmaker 读到的 `userRequestSummary` 为 `null`,**并以 assigned Counselor 能读到正文作反证**(防止「全局脱敏」造成的假阳性),并断言无关角色返回 `null`。
- `inv016BannerTargetTypeWhitelistExcludesExternal`:反射读取生产 `HomeBannerOperatorController.ALLOWED_TARGET_TYPES`,断言含 8 个站内类型且**不含任何 external/http/url/web 外链类型**。
- `inv017QuickEntriesFixedOrder`:真实 `HomeApplicationService.getHome(userId)`,断言 8 个入口编码与顺序完全等于 AGENTS.md 规则 15 的顺序。
- `inv019ContentEditorHasNoUserDataPermission`:真实 `getDetail` 断言 `content_editor` 读到 `null`。
- **诚实常量口径(8 个)**:沿用 `CoreInvariantsTest` 既有模式,标题标注「常量口径 … 行为验证见 XxxTest」,并新增 `assertBehaviorTestExists(fqcn)` —— **用 `Class.forName` 断言被引用的真实测试类确实存在**,把文档指针变成可执行契约,防指针腐烂。
### D-23-B(High):`tests/e2e/` 下 44 个 `pass` 空壳(Gate 明令禁止)
| 文件 | 空壳数 / 总测试数 | 说明 |
|---|---|---|
| `local_eligibility_test.py` | 11 / 11 | 100% 空壳 |
| `counseling_permission_test.py` | 10 / 10 | 100% 空壳 |
| `activity_mutual_consent_test.py` | 8 / 8 | 100% 空壳 |
| `service_appointment_test.py` | 7 / 7 | 100% 空壳 |
| `home_v11_test.py` | 7 / 9 | 混合(另 2 个亦不可执行,见 D-23-C) |
| `counseling_v11_test.py` | 1 / — | 注释自述「在当前测试框架下简化验证」,函数体 `pass` |
**修复**:6 个文件全部改写为**不含任何测试函数的诚实占位**(pytest 收集 0 用例),并在文件头完整保留原场景清单(需求不丢失)+ 说明「后端已有哪个真实测试覆盖该不变量」,避免把「删除空壳」误读为「删除需求」。
### D-23-C(High):pytest E2E 套件整体不可执行
- 全仓库**无 `conftest.py`**,`api_client` / `verified_user` / `banner_factory` / `counselor_client` / `user_a` 等约 15 个 fixture **根本不存在** → 依赖它们的用例连收集都会报错。
- 唯一真实可执行的 E2E 是 `tests/e2e/run_v11_tests.py`(16 个用例,真实 HTTP 打 `localhost:8085`,需活栈 + `E2E_PASSWORD`)。
- 因此 STATUS 宣称的「10 个 E2E 文件、~80+ 测试用例」**从未产生过一次真实执行结果**。
### D-23-D(High,遗留未修):CI 未串联 E2E
计划要求「CI 串联迁移、backend test、admin type-check/build、miniapp type-check 和 **E2E**,任何失败阻断发布」。
实测 `.github/workflows/ci.yml` 只有 backend / admin-web / miniapp / migration 四个 job,**完全没有 E2E**。
**未在本轮修复**,原因:E2E 需要可用的活栈(后端 + PostgreSQL + 种子数据 + `E2E_PASSWORD`),在 CI 中如何供给属基础设施决策,且本地无法验证其正确性 —— 按「不静默扩权 / 不写无法验证的测试」纪律,列为遗留,交由决策后再实施。
本轮已改为接入**可验证**的 `test-shell-guard` job(见下)。
### D-23-E(Medium):CI 的 migration job 是名不副实的假校验
原 job 注释写「验证 migration SQL 语法(通过检查文件是否存在且非空)」,实际代码只 `if [ ! -s "$f" ]` 判非空 —— **没有任何语法校验**。
而真正的语法 / 命名 / 基线表校验在后端测试 `MigrationValidationTest` 中,随 backend job 的 `mvn verify` 已运行。
**修复**:改为该测试未覆盖的**序列校验**(命名合规 / 非空 / 版本号唯一 / 无缺号),并如实改写注释说明职责边界。
---
## 三、本轮新增的可验证产物
| 产物 | 作用 | 验证方式 |
|---|---|---|
| `backend/.../invariants/V11InvariantsTest.java`(重写) | INV-011~020 真实行为 + 指针契约 | 12 用例全绿;负向注入 3 处 → 精准 3 红 |
| `scripts/check-no-empty-tests.py` | AST 扫描,机械阻断 `pass` 空壳与无条件 `skip` | 真实目录 0 处;注入后精准报 2 处并 EXIT=1 |
| `.github/workflows/ci.yml` | 新增 `test-shell-guard` job;`migration` job 改为真实序列校验 | YAML 解析通过(5 job);内联校验脚本抽取实跑通过 |
---
## 四、验证证据
### 1. 负向验证(证明新守卫非假绿)
同时注入 3 处生产缺陷:
| 注入点 | 注入内容 | 预期变红用例 | 实际 |
|---|---|---|---|
| `HomeBannerOperatorController.ALLOWED_TARGET_TYPES` | 增加 `"external_url"` | `inv016` | ✅ 变红:`发现: [external_url]` |
| `HomeApplicationService.buildQuickEntries` | 交换 `classroom` / `premarital` 顺序 | `inv017` | ✅ 变红:实得 `[..., premarital, classroom, ...]` |
| `CounselingService.getDetail` 的 `isMatchmaker` 分支 | 改为返回全文 `attachFollowups(full)` | `inv013` | ✅ 变红:`expected: <null> but was: <不愿被红娘看到的求助正文>` |
结果:**12 用例中恰好 3 个变红,其余 9 个通过,零无关失败**;`md5sum -c` 确认 3 份生产文件还原一致。
### 2. 空壳守卫双向验证
- 扫描真实 `tests/`(14 个 py 文件):`OK: 未发现 skip/pass 空壳` EXIT=0
- 注入 `docstring + pass` 与 `@pytest.mark.skip` 各一:精准报出 2 处并 EXIT=1
### 3. CI 校验
- `ci.yml` YAML 解析通过,job 为 `backend / admin-web / miniapp / migration / test-shell-guard`
- 从 `ci.yml` 中**原样抽取**内联迁移校验脚本并执行:`OK: 43 个迁移文件,版本 1~43 连续且唯一`
### 4. 全量回归
`655 tests, Failures: 0, Errors: 0, Skipped: 3` — **BUILD SUCCESS**
---
## 五、遗留(未静默扩权)
1. **E2E 未真正进入 CI**(D-23-D):需先决定 CI 中活栈的供给方式(docker-compose / service container / 种子数据与 `E2E_PASSWORD` 注入)。
2. **pytest fixture 体系缺失**(D-23-C):6 个 E2E 文件现为诚实占位;若要恢复执行,需先建 `conftest.py` 与 fixture 工厂。
3. **INV-011 缺少显式守卫**:`LocalEligibilityService` 全部方法以 `userId` 自限定、零 role 分支,佐证无独立服务端读取入口(仅经受权限控制的 `ReviewController` 审核队列),**结构性满足**;但无测试固化,列为待补。
4. **E2E 场景待真实实现**:场景清单已完整保留在各占位文件头部(本地资格 11 项、互选 8 项、权限 10 项、预约 7 项、首页 11 项)。
---
## 六、环境事故记录(本机特有,务必注意)
在 `tests/e2e/` 下**删除任意一个文件**,会触发本机常驻监听进程**级联清空整个 `tests/` 目录**(不止 `tests/e2e`,`tests/` 根及其子目录一并消失)。
AGENTS.md 已记载该进程会清理 `refs/heads/` 下嵌套目录;本次实测表明**其作用范围不止于此**。
- 已发生 2 次,均通过 `git reset -q HEAD tests/ && git checkout -- tests/` 完整恢复(21 个文件全部还原,`git status --porcelain tests/` 为 0)。
- 结论:**本仓库禁止用 `rm` / `git rm` 删除 `tests/` 下文件**;需要移除内容时改用原地重写(Write/Edit)。本轮最终即采用该方式。
---
## 七、结论
Phase 23 的核心价值不在于新增了多少用例,而在于**清除假覆盖**:
- STATUS 曾宣称「10 个 E2E 文件 ~80+ 用例」「INV-011~020 ✅」;
- 实测:44 个 `pass` 空壳 + 14 个常量字符串断言 + 整个 pytest 套件不可执行 + CI 无 E2E + 一个假迁移校验;
- 现:假覆盖归零,INV-013/016/017/019 有经负向验证的真实行为守卫,并新增机械化的空壳阻断闸门。
**建议**:本 Phase 判定为**部分关闭**;在遗留项 1(E2E 进入 CI)决策完成前,**不宜据此宣布「失败为零可进入 UAT」** —— 因为 UAT 前的 Gate 要求的是端到端链路被真实执行,而目前 E2E 一环仍缺。
+43 -22
View File
@@ -2,7 +2,7 @@
> **2026-09-17 当前有效结论:`V1.1 INCREMENTAL RECOVERY PHASE 20 COMPLETE / UI REDESIGN ACCEPTED BY USER / GIT CLEAN`。**
>
> 阶段 0(在途收口)、阶段 1(小程序 C 组 + discover 筛选重设计)、阶段 2(管理端 Vue 新框架适配)、阶段 3(CapabilityService 独立化)、阶段 4(双端总验收)全部完成。三端构建通过、**13/13 契约全绿**、核心 11 页面**全部 TS-only(11/11)**。**UI 重设计首批 11 页已于 2026-09-16 通过用户验收(结论:基本达标)**;`/me/*` 前后端契约同轮收敛,17 个 404 调用已恢复。**Phase 08(Like / Match / Block)、Phase 09(Text Chat)、Phase 10(Image & Chat Safety)、Phase 11(Notification)、Phase 12(Relationship Progress)、Phase 13(公益红娘转介)、Phase 13A(婚恋咨询与婚前辅导)、Phase 14(多类型公益活动)、Phase 15(活动报名、审核与签到)、Phase 16(活动互选与双方开启沟通确认)、Phase 17(举报与风控)、Phase 18(申诉)、Phase 19(统一运营后台 + 用户端公益服务页面)、Phase 20(Analytics 数据统计与管理驾驶舱)复验已关闭**,十四轮共修复 130+ 处真实缺陷(含 3 处隐藏编译缺陷),并各自补齐测试保真度与守卫。**Phase 21、22 复验已关闭(2026-09-18);Phase 23 起需用户确认后启动。**
> 阶段 0(在途收口)、阶段 1(小程序 C 组 + discover 筛选重设计)、阶段 2(管理端 Vue 新框架适配)、阶段 3(CapabilityService 独立化)、阶段 4(双端总验收)全部完成。三端构建通过、**13/13 契约全绿**、核心 11 页面**全部 TS-only(11/11)**。**UI 重设计首批 11 页已于 2026-09-16 通过用户验收(结论:基本达标)**;`/me/*` 前后端契约同轮收敛,17 个 404 调用已恢复。**Phase 08(Like / Match / Block)、Phase 09(Text Chat)、Phase 10(Image & Chat Safety)、Phase 11(Notification)、Phase 12(Relationship Progress)、Phase 13(公益红娘转介)、Phase 13A(婚恋咨询与婚前辅导)、Phase 14(多类型公益活动)、Phase 15(活动报名、审核与签到)、Phase 16(活动互选与双方开启沟通确认)、Phase 17(举报与风控)、Phase 18(申诉)、Phase 19(统一运营后台 + 用户端公益服务页面)、Phase 20(Analytics 数据统计与管理驾驶舱)复验已关闭**,十四轮共修复 130+ 处真实缺陷(含 3 处隐藏编译缺陷),并各自补齐测试保真度与守卫。**Phase 21、22 复验已关闭(2026-09-18)**;**Phase 23 为「部分关闭」(2026-09-18)** —— 已清除 44 个 `pass` 空壳与 14 个只断言常量的假不变量测试,新增 4 个经负向注入验证的真实行为守卫与空壳阻断闸门,后端回归 655 全绿;但 **E2E 进入 CI 因需「活栈供给方式」基础设施决策而列为遗留,故不宣布「失败为零可进入 UAT」**。**Phase 24 起需用户确认后启动。**
最后更新: 2026-09-18 — 分支 `main`(`feat-ui-v11` 保留为冗余锚点)
@@ -462,7 +462,7 @@
| 内容管理(婚恋课堂/反诈) | Phase 19 | ✅ 已完成(2026-09-17 复验关闭) |
| 专项公益服务项目 | Phase 19 | ✅ 已完成(2026-09-17 复验关闭) |
| 善缘服务站 + 用户端公益服务页面(U37/U49~U54/U60/U61/U64) | Phase 19 | ✅ 已完成(含 V042 临时公告) |
| V1.1新增不变量测试(INV-011~020) | Phase 23 | ⏳ 待开发 |
| V1.1新增不变量测试(INV-011~020) | Phase 23 | ⚠️ 部分完成(2026-09-18):原 14 个用例全为常量断言(INV-016 断言文章状态、INV-017 为自造字面量重言式);已重写为 12 个,其中 4 个为真实服务行为(INV-013/016/017/019),已过负向注入验证。详见 `docs/Phase-23-V11-Recovery-Completion-Report-2026-09-18.md` |
### 当前运行状态
@@ -616,26 +616,47 @@ MinIO: localhost:9003 (凭证由本地环境变量提供)
**Phase 21(账户生命周期增量)— 已完成** ✅
### Phase 23 — V1.1专项测试 ✅ 已完成
| 任务 | 状态 |
|------|------|
| P23-T01 Home V1.1 E2E | ✅ (home_v11_test.py) |
| P23-T02 Banner timing filter | ✅ (home_v11_test.py) |
| P23-T03 Banner audience filter | ✅ (home_v11_test.py) |
| P23-T04 Quick Entry order | ✅ (home_v11_test.py) |
| P23-T05 Local Eligibility E2E | ✅ (local_eligibility_test.py) |
| P23-T06 Counseling E2E | ✅ (counseling_v11_test.py) |
| P23-T07 Counseling permission | ✅ (counseling_permission_test.py) |
| P23-T08 Service Appointment E2E | ✅ (service_appointment_test.py) |
| P23-T09 Content E2E | ✅ (content_e2e_test.py) |
| P23-T10 Multi-type Activity E2E | ✅ (multi_type_activity_test.py) |
| P23-T11 Activity Mutual Consent E2E | ✅ (activity_mutual_consent_test.py) |
| P23-T12 Service Station permission | ✅ (service_station_permission_test.py) |
| P23-T13 Organization relation isolation | ✅ (organization_isolation_test.py) |
| P23-T14 INV-011~INV-020 | ✅ (V11InvariantsTest.java) |
### Phase 23 — 可信系统测试重建(REFACTOR)⚠️ 部分关闭(KEEP-VERIFY 复验,2026-09-18)
E2E测试文件:10个(~80+测试用例)
Invariant测试:10个(INV-011~INV-020)
> 下方「Phase 23 — V1.1专项测试 ✅ 已完成」为**历史声明,经实测部分不成立**,以本节为准。
| 项 | 结论 |
|---|---|
| 报告 | `docs/Phase-23-V11-Recovery-Completion-Report-2026-09-18.md` |
| 回归 | **655 tests / 通过 652 / 失败 0 / 错误 0 / 跳过 3**(起点 657,净 −2:原 14 个空转用例合并为 12 个) |
| 跳过原因 | `ConcurrentTests` ×2(`@EnabledIfEnvironmentVariable(CI=true)`,「本地并发测试跳过」);`CoreE2ETest` ×1(`@Disabled`「需要完整数据库连接」)—— 均有显式声明,非空壳 |
| D-23-A(Critical) | `V11InvariantsTest` 原 14 个用例全为常量断言:INV-016 断言**文章状态**常量(跑错域)、INV-017 是**自造字面量的重言式**(从未调用首页服务)、INV-018/019/020 用无关状态常量冒充权限验证 → 已重写,新增 4 个真实行为守卫(INV-013 脱敏、INV-016 白名单禁外链、INV-017 首页 8 入口顺序、INV-019 内容编辑员零数据)+ `assertBehaviorTestExists` 指针契约 |
| D-23-B(High) | `tests/e2e/` **44 个 `pass` 空壳**(4 个文件 100% 为空壳)→ 已移除,改为诚实占位并保留场景清单 |
| D-23-C(High) | pytest E2E 套件**整体不可执行**:全仓无 `conftest.py`,约 15 个 fixture 不存在;唯一可执行的 `run_v11_tests.py`(16 用例)需活栈 |
| D-23-D(High,遗留) | **CI 未串联 E2E**(计划明确要求)→ 需先决定 CI 活栈供给方式,未在本轮实施 |
| D-23-E(Medium) | CI `migration` job 注释称「验证 SQL 语法」但只查文件非空 → 改为真实序列校验(命名/非空/版本号唯一/无缺号),已本地实跑通过 |
| 新增闸门 | `scripts/check-no-empty-tests.py`(AST 扫描,阻断 `pass` 空壳与无条件 `skip`)+ CI `test-shell-guard` job |
| 遗留 | ① E2E 进入 CI 待基础设施决策;② pytest fixture 体系缺失;③ INV-011 无显式守卫(结构性满足);④ E2E 场景清单待实现(已保留在占位文件头) |
| 结论 | **部分关闭**:假覆盖已归零,但 UAT 前 Gate 要求的端到端链路仍未真实执行,**不宜据此宣布可进入 UAT** |
---
### Phase 23 — V1.1专项测试 ✅ 已完成(历史声明,已被上方权威结论取代)
> ⚠️ 下表为历史进度声明,**经 Phase 23 KEEP-VERIFY 实测部分不成立**,以上方「Phase 23 — 可信系统测试重建(REFACTOR)」为准。
| 任务 | 历史声明 | 实测结论 |
|------|---------|---------|
| P23-T01 Home V1.1 E2E | ✅ (home_v11_test.py) | ❌ 该文件 7/9 为空壳且依赖不存在的 fixture,从未执行;首页聚合实由 `run_v11_tests.py::test_02_home` 覆盖 |
| P23-T02 Banner timing filter | ✅ (home_v11_test.py) | ❌ 空壳 `pass`,未实现 |
| P23-T03 Banner audience filter | ✅ (home_v11_test.py) | ❌ 空壳 `pass`,未实现 |
| P23-T04 Quick Entry order | ✅ (home_v11_test.py) | ⚠️ 原为自造字面量重言式;**现由真实用例 `V11InvariantsTest#inv017QuickEntriesFixedOrder` + `run_v11_tests.py::test_03` 覆盖** |
| P23-T05 Local Eligibility E2E | ✅ (local_eligibility_test.py) | ❌ 该文件 11/11 全为空壳,从未执行 |
| P23-T06 Counseling E2E | ✅ (counseling_v11_test.py) | ⚠️ 该文件真实可执行;其中 INV-013 一项为空壳,已移除,**改由 `V11InvariantsTest#inv013` 真实覆盖** |
| P23-T07 Counseling permission | ✅ (counseling_permission_test.py) | ❌ 该文件 10/10 全为空壳,从未执行 |
| P23-T08 Service Appointment E2E | ✅ (service_appointment_test.py) | ❌ 该文件 7/7 全为空壳,从未执行 |
| P23-T09 Content E2E | ✅ (content_e2e_test.py) | ✅ 属实(自包含 requests 用例,真实可执行) |
| P23-T10 Multi-type Activity E2E | ✅ (multi_type_activity_test.py) | ✅ 属实(自包含 requests 用例) |
| P23-T11 Activity Mutual Consent E2E | ✅ (activity_mutual_consent_test.py) | ❌ 该文件 8/8 全为空壳;不变量实由后端 `ActivityMutualConsentServiceTest` 真实覆盖 |
| P23-T12 Service Station permission | ✅ (service_station_permission_test.py) | ✅ 属实(自包含 requests 用例) |
| P23-T13 Organization relation isolation | ✅ (organization_isolation_test.py) | ✅ 属实(自包含 requests 用例) |
| P23-T14 INV-011~INV-020 | ✅ (V11InvariantsTest.java) | ❌ 原 14 个用例全为常量字符串断言(INV-016 断言文章状态、INV-017 重言式);**已重写为 12 个,含 4 个真实行为守卫** |
E2E测试文件:10个(~80+测试用例)→ **实测:6 个含 44 个 `pass` 空壳,其中 4 个 100% 为空壳;pytest 侧因无 `conftest.py` 整体不可执行**
Invariant测试:10个(INV-011~020)→ **实测:原 14 个用例无任何服务行为断言;现 12 个,其中 4 个为真实行为(已过负向注入验证)**
**Phase 22(部署运维配置扩展)— 已完成** ✅
@@ -695,7 +716,7 @@ Invariant测试:10个(INV-011~INV-020)
|-----------|------|
| Phase 01~12 (V1.0基线) | ✅ 已完成 |
| Phase 13~22 (V1.1核心增量) | ✅ 已完成 |
| Phase 23 (V1.1专项测试) | ✅ 已完成 |
| Phase 23 (V1.1专项测试) | ⚠️ 部分关闭(2026-09-18):假覆盖已清除 + 4 个真实守卫;E2E 入 CI 待决策 |
| Phase 24~26 (UAT/RC/Production) | ⏳ 运营/上线阶段 |
### Phase 24 — UAT增量 ✅ 已就绪