Просмотр исходного кода

test(tdd): 测试负责人 R97 执行汇报 2026-11-09 0610 — 后端首次全量实测 2802例/94.75%(143红=预期RED94+装置37+产品12) + 251 派发 pytest 43/33/10 达成(G10首次) + 浏览器 28/28 零skip(B-76/B-59闭环) + B-65定性修正(依赖版本侥幸) + B-69定位PUBLIC_PATHS + 新增B-84/85/86/87

E2E Test Bot 4 дней назад
Родитель
Сommit
20b1d39986

+ 203 - 0
docs/tdd/orchestration/report-test-2026-11-09-0610.md

@@ -0,0 +1,203 @@
+# 测试负责人汇报 · R97(2026-11-09 06:10,本自动化闭环)
+
+> 执行明细与证据见同目录台账;本文件随 `docs/tdd/test/automation-run-2026-11-09-R97.md`、`blockers.md` 一并 push 至 `origin/cfclub` 作为研发通知载体(Agent Mail 仍未开通 `not_bound`,无企微通道)。
+> 基线:`cfclub` @ 跑测起点 `f985f9274`;跑测期间 HEAD 前进至 `1abfc7348`(7 个提交,**全部 docs/版本号,零源码变更**)→ 本轮结果对两个 SHA 均有效。
+
+---
+
+## 一、执行总览(R97 vs R96)
+
+| 维度 | R97 结果 | R96 基线 | 变化 |
+|---|---|---|---|
+| **后端全量(JUnit)** | **333 类 / 2802 例 / 2655 过 / 132F+11E+4S = 94.75%**(剔 skip 94.89%),34 红类 / 143 红例,耗时 1:00h | 承袭 R95 2753/115 红/95.68%(**未实测**) | **★ 首次实测**(推翻 R96「本节点无 Maven」) |
+| cfc-frontend jest | **423 / 371 / 49 / 3 skip = 87.71%** | 423 / 371 / 49 / 3 | **零漂移** |
+| cfc-web jest | **116 / 116 / 0** | 116 / 116 | 零漂移 |
+| tests/ 根级 jest | **23 / 7 / 16** | 23 / 7 / 16 | 零漂移(B-36 环境依赖) |
+| **langgraph @251 实派 pytest** | **43 / 33 / 10**(183.67s,`--ignore=tests/test_chat.py`) | 本机 44 / 32 / 12 | **★ 派发 251 达成**(G10 首次通过) |
+| 浏览器 live(admin+frontend+shop) | **15 / 15 全绿 · 0 skip** | 15/15(1 例曾 skip) | **★ B-76/B-59 装置修复,零跳过** |
+| 浏览器 health-contract | **13 / 13 全绿** | 13/13 | 零漂移 |
+| **合计(可执行全量)** | **3435 例 / 3210 过 = 93.45%** | — | 首份可复算全量口径 |
+
+---
+
+## 二、后端 143 红例三层归因(关键,非全为回归)
+
+| 层 | 例数 | 类数 | 代表证据 | 性质 |
+|---|---|---|---|---|
+| **预期 RED(TDD 契约待实现)** | **94** | 14 | `Guhe*ContractTest` 13 类 85 例,失败消息直接内嵌需求编号(如「契约方法 `AttributionComplianceGate#tagEvidence` 未实现(G-3 证据码隔离,T11)」);`DietFoodHealthAdjustContractTest` 9 例(§3 A1/A3/A5) | 研发 TDD 红态,**非回归** |
+| **A 类(测试侧缺陷,测试可自处置)** | **37** | 14 | `CfCommissionServiceSettleTest` 8(档位查询未 stub → `Wanted but not invoked`);`ArticleAutoTagServiceTest` 4(verify 目标 `taskExecutor` 与注入不一致);`DimensionScoreServiceImplTest` 4(`No value present`);`ArticleWorkflowTest` 3(源码文案已改「仅已驳回或已撤回」,断言仍旧文案);`doNothing()` 误用 2(`DailyCheckinStreakFlowTest` / `PlannerBindInviteFlowTest`) | 夹具/桩/断言过期 |
+| **B 类(产品缺陷,需研发修)** | **12** | 4 | `MindFortuneControllerTest` 1(401 vs **400**,B-69 连续第 5 轮);`EnergySystemFlowTest` 6(500);`PlannerBindInviteFlowTest` 4(403);`GuideModuleIntegrationTest` 1(`Data truncation: platform_fee` 列越界) | 真缺陷 |
+
+> 口径说明:`@Test 2792 + 参数化展开 26 − @Disabled 4 − 早退截断 12 = 2802`,误差 **0**;333/333 XML 无静默丢失;`testCompile 339 源 0 错`。
+
+---
+
+## 三、本轮四项结论修正(推翻前轮定性)
+
+### 3.1 推翻「本节点无 Maven」(R96)
+`which mvn` 为空但 **IDEA 自带 Maven 3.9.9 + `JAVA_HOME=D:\jdk1.8.0_25`** 可用 → 后端全量 1:00h 跑完。**R96 后端 95.68% 与 115 红为承袭值、未实测**,本轮为 R94 之后首次真实全量。
+
+### 3.2 **B-65(P0)证据升级:线上 200 / 251 pytest 500 —— 部署与依赖版本不一致**
+- 251 **live HTTP**:`POST /api/v1/analyze {report_id:1,user_id:1,focus:"general"}` → **200**,返回完整分析 JSON(15.77s,trace_id 齐全);`POST /api/v1/chat {query,...}` → **200**(28.44s,B-77 确认已闭环)。
+- 251 **pytest 实派**:`test_analyze.py` 2 例 **500**,异常 `ValueError: Checkpointer requires ... thread_id`。
+- 本机源码同因复现:`analysis_graph.py:96 MemorySaver()` + `analyze.py:35 ainvoke` 未传 config;本机 venv(langgraph **0.6.11**)直接 `ValueError`。
+- **判定**:不是「研发已修」,而是**运行实例依赖版本低于仓库锁版本**——线上侥幸可用,**一旦依赖升级即 100% 500**。P0 维持,修法不变(去 `MemorySaver()` 或 `ainvoke` 传 `thread_id`)。
+
+### 3.3 **B-69 证据升级:4 个 `/api/mind/**` 在「公开白名单」内**
+源码 `JwtInterceptor.java:79-82` 将 `/api/mind/alert/active`、`/api/mind/alert/checkin-alerts`、`/api/mind/traditional/mirror`、`/api/mind/traditional/compatibility` **列入 PUBLIC_PATHS(匿名白名单)**,线上实测匿名可达业务层:
+
+| 端点 | 匿名响应 |
+|---|---|
+| `POST /api/mind/alert/active` | `{"code":500,"message":"参数缺失:memberId"}` |
+| `POST /api/mind/traditional/mirror` | `{"code":200,"message":"success","data":{}}` |
+| 对照 `POST /api/health/plan/list` | `401` ✅ |
+
+→ 应把这 4 条从 PUBLIC_PATHS **移入 `LOGIN_REQUIRED_PATHS`**;后端 `MindFortuneControllerTest` 断言 `401` 仍红(实际 400),与线上同源。
+
+### 3.4 **B-76 / B-59 闭环(测试侧自处置)**
+`TC-A-U04` 首跑红(`登录身份`未自动带出),独立复跑 2/2 通过、`account-roles` 接口 5/5 均 60-110ms → 判为**装置时序竞态**而非产品回归。根因:全量串行时 `goto` 仅到 `domcontentloaded`,Vue 未挂载时 `fill()` 的 `@input` 无监听器 → 500ms 防抖查询永不发出。修复:
+1. 新增 `waitLoginFormReady()`(等表单可见 + 可编辑 + 登录按钮渲染);
+2. 新增 `fillPhoneAndAwaitRoles()`:**等 `account-roles` XHR 实际返回**并断言 200(禁止固定 sleep);
+3. `TC-A-U05` 原 `test.skip(登录未通过)`(吞掉前置失败、门禁显示"无失败")→ 改**硬断言**(B-59)。
+
+修复后 admin **5/5**、全套件 **15/15 零 skip**。
+
+---
+
+## 四、SRS → 用例更新闭环(本轮无新增 SRS)
+
+窗口内 SRS 变更仅 3 份,**均已有对应用例**,无需补写:
+
+| SRS | 能力键 / 任务 | 对应用例 | 状态 |
+|---|---|---|---|
+| `2026-11-09-diet-recommendation-health-questionnaire-adjust.md` | S-cfc-97 | `recommendation.healthAdjustUi.test.js` 8 RED + `DietFoodHealthAdjustContractTest` 9 RED | 待实现 |
+| `2026-11-09-signature-canvas2d-rewrite.md` | S-cfc-116 | `signature.canvas2d.test.js` 9 RED / 2 GREEN | 待实现(源码 `enterLandscape` 仍 4 处命中、`canvasToTempFilePath` 0 命中) |
+| `2026-11-08-diet-recommendation-polish.md` | S-cfc-79 / S-cfc-80 | `recommendation.uiRework` 25 RED + `participantSelect` 8 RED | 待实现(`currentTab/pagedFoods/loadMore` 命中 0) |
+| `2026-11-08-signature-landscape-fix.md` | B-75 | 已被 v2 整份取代,旧用例归档 `.superseded.js` | 已作废 |
+
+---
+
+## 五、覆盖度量(本轮统一口径,可复算)
+
+| 指标 | 值 | 口径 |
+|---|---|---|
+| 场景 SC | **245** | `docs/**/*.md` 去重 `SC-*` |
+| SC 被测试资产引用 | **137(55.9%)** | `cfc-frontend/__tests__`、`cfc-web/src/__tests__`、`cfc-backend/src/test`、`tests/e2e*` |
+| TC 台账条数 | **824** | `tests/test-cases/*.md` 去重 |
+| 其中异常流(-E/-P) | **496(60.2%)** | 同上 |
+| **TC 可执行锚点** | **95(11.5%)** | 排除 `tests/e2e/api/generated/cases.json`(生成物),见 B-86 |
+| 测试文件 / `@Test` | 333 / 2792 | 后端 |
+| 控制器 / `@GetMapping` 违规 | 249 / **8** | 仍违 AGENTS.md |
+
+> ⚠️ **B-86(新增)**:`tests/e2e/api/generated/cases.json` 内含 797 个 TC 编号,会让「锚点覆盖率」虚高到 96.7%;该文件是**描述性生成物**、非可执行资产,必须先剔除再统计(与 B-30/B-32 同族陷阱)。
+
+---
+
+## 六、域结论表
+
+| 审查维度 | 负责专家 | 结论 | 关键证据 |
+|---|---|---|---|
+| 单元·组件 | 苏雅 | **不通过** | 后端 34 红类,其中 A 类桩/夹具缺陷 37 例;无覆盖率/变异工具(未实测) |
+| 集成·接口 | 柯砚 | **不通过** | `EnergySystemFlowTest` 6×500、`PlannerBindInviteFlowTest` 4×403、`GuideModule` `platform_fee` 越界 |
+| 系统·验收 | 白樾 | **有条件通过** | 浏览器 15/15 + health 13/13 全绿;UAT/BDD 无资产 |
+| 测试保障 | 闻朔 | **不通过** | CI 为 blocking/observed 两级,observed 白名单仍含 33 类;冒烟无独立门禁 |
+| 性能·可靠性 | 骆骋 | **未实测** | 无压测工具/未授权;仅 `ActivityControllerTest` 222s、`GuidePackageServiceTest` 436s 作劣化信号 |
+| 安全 | 关屿 | **不通过** | B-69 四个 `/api/mind/**` 匿名可达业务层;health 契约 13/13 其余端点 401 正常;SAST/SCA 无工具 |
+| 兼容·体验 | 阮清 | **有条件通过** | 仅 admin-desktop + H5 mobile 两档;无障碍/多浏览器未测 |
+| 流程·度量·代码 | 何序 | **不通过** | `@GetMapping` 8 处违规;CI 无后端全量硬门禁;TC 锚点 11.5% |
+
+---
+
+## 七、五要素问题清单(P-1 ~ P-6)
+
+### P-1(P0)langgraph `/api/v1/analyze` 源码级 checkpointer 缺 thread_id
+1. **问题描述**:251 live 返回 200,但 251 pytest 与本地 venv 均 500 `ValueError: Checkpointer requires ... thread_id`。
+2. **测试 bug 点**:`cfc-langgraph/tests/test_analyze.py::test_analyze_endpoint`、`::test_analyze_missing_report`(`assert 500 == 200`);生产帧 `app/graphs/analysis_graph.py:96`、`app/api/analyze.py:35`。
+3. **最优修改方案**:`analysis_graph.create_analysis_graph()` 删除 `checkpointer=MemorySaver()`(或改 `app/checkpointer.py` 的共享 saver),并在 `analyze.py` 的 `ainvoke` 传 `config={"configurable":{"thread_id": f"analysis-{req.report_id}-{req.user_id}"}}`。
+4. **修改目的**:消除「依赖版本决定成败」的隐性 P0,保证依赖升级后 analyze 不 100% 失败。
+5. **闭环方式**:251 重派 `pytest tests/test_analyze.py -q` → 2 例 200 转绿;并 `curl /api/v1/analyze` 连跑 3 次 200 且耗时有界。
+
+### P-2(P1)B-69:4 个 `/api/mind/**` 位于公开白名单
+1. **问题描述**:匿名请求可进入业务层(`alert/active` 500「参数缺失:memberId」、`traditional/mirror` 200 空数据),未在网关层拒绝。
+2. **测试 bug 点**:`cfc-backend/src/test/java/com/etotem/cfc/controller/mind/MindFortuneControllerTest.java::testGetTodayFortuneWithoutAuth`(`expected:<401> but was:<400>`);生产帧 `config/JwtInterceptor.java:79-82`。
+3. **最优修改方案**:把这 4 条从 `PUBLIC_PATHS` 移入 `LOGIN_REQUIRED_PATHS`;若确需匿名,改为显式 `userId=-1` 只读分支并在 controller 首行返回 401。
+4. **修改目的**:匿名不得触达用户私有数据读写路径。
+5. **闭环方式**:`MindFortuneControllerTest` 转绿 + 线上 4 端点匿名 curl 全 401。
+
+### P-3(P1)A 类测试侧 37 例(桩/夹具/断言过期)
+1. **问题描述**:`CfCommissionServiceSettleTest` 8、`ArticleAutoTagServiceTest` 4、`DimensionScoreServiceImplTest` 4、`ArticleWorkflowTest` 3、`doNothing()` 误用 2 等共 37 例红,失败信息指向测试装置而非产品。
+2. **测试 bug 点**:`ArticleWorkflowTest.reDraft_notRejected_*`(期望「仅已驳回的文章可重新编辑」,实际「仅已驳回或已撤回…」);`DailyCheckinStreakFlowTest.childDeletesCheckin_Success` 与 `PlannerBindInviteFlowTest.parentUnbindsGuide_Success`(`doNothing()` 作用于非 void 方法)。
+3. **最优修改方案**:按类逐条对齐——文案断言改为当前产品文案;`doNothing()` 改 `when(...).thenReturn(...)` 或删;`CfCommission` 补档位查询 stub;`ArticleAutoTag` 统一 mock 字段名与注入名。
+4. **修改目的**:把 37 例「装置红」清出,使红例集合只剩真缺陷与预期 RED,门禁信号可信。
+5. **闭环方式**:定向 `mvn -Dtest=A,B,C -DfailIfNoTests=false test`(须用逗号、须核对 `Tests run:` 行),37 例转绿且其余类零转红。
+
+### P-4(P1)B 类产品缺陷 12 例
+1. **问题描述**:`EnergySystemFlowTest` 6×500、`PlannerBindInviteFlowTest` 4×403、`GuideModuleIntegrationTest` `platform_fee` 数据截断、`MindFortune` 401/400。
+2. **测试 bug 点**:`com.etotem.cfc.integration.EnergySystemFlowTest`(`expected:<200> but was:<500>` ×4、`expected:<500> but was:<5001>`)、`PlannerBindInviteFlowTest.parentConfirmsBind_Success`(403)、`GuideModuleIntegrationTest.testGuidePackageCreationFlow`(`MysqlDataTruncation: Out of range value for column 'platform_fee'`)。
+3. **最优修改方案**:EnergySystem 查 500 的 `Caused by` 最内层;PlannerBind 核对绑定态鉴权(403 疑角色闸门);`platform_fee` 列宽 `ALTER` 或写入前按分转元裁剪。
+4. **修改目的**:消除集成链路上的 5xx / 越权 / 数据截断。
+5. **闭环方式**:对应 4 类定向复跑转绿 + 线上同路径 curl 非 5xx。
+
+### P-5(P1)langgraph 套件非终止(B-84 新增)
+1. **问题描述**:`tests/test_chat.py` 走真实 LLM(意图分类→主生成→任务提取→记忆 embedding),本机运行 >35 分钟未结束、251 >10 分钟未结束,整套件**无法收敛**。
+2. **测试 bug 点**:`cfc-langgraph/tests/test_chat.py::test_chat_endpoint`;无 `pytest-timeout`、无 LLM 桩。
+3. **最优修改方案**:对该用例加 `@pytest.mark.timeout(120)` 或 `monkeypatch` 掉 LLM/embedding(仅断言编排链路);并在 `pytest.ini` 设 `timeout = 180`。
+4. **修改目的**:让 langgraph 套件可在 CI 内有界完成,否则 251 派发与 CI 都会挂死。
+5. **闭环方式**:全量 `pytest tests --ignore=tests/qna` 在 ≤5 分钟内自然结束并给出汇总行。
+
+### P-6(P1)预期 RED 契约 94 例待实现
+1. **问题描述**:`Guhe*ContractTest` 13 类 85 例 + `DietFoodHealthAdjustContractTest` 9 例,失败消息均为「方法/常量/字段未实现」。
+2. **测试 bug 点**:`com.etotem.cfc.unit.GuheEvidenceIsolateContractTest`(11)、`GuheNoPathMapContractTest`(10)、`GuheKbVersionSenseContractTest`(9)、`GuheLowSideRuleLoadContractTest`(9)、`DietFoodHealthAdjustContractTest`(9)等。
+3. **最优修改方案**:按契约逐项实现(`AttributionComplianceGate#tagEvidence/canEnterRecommendationBasis/isExistenceRecordOnly`、`AttributionLowSideRules.EVIDENCE_PUB/COM`、`Food.adjustmentReason` 瞬态字段、`loadProfile` 读 `lifestyle.allergySymptoms`、`generateIngredientList` 读 `dietType`)。
+4. **修改目的**:把 TDD 红态收口为可发布能力。
+5. **闭环方式**:对应契约类逐类转绿 + 状态机置 YELLOW。
+
+---
+
+## 八、门禁结论
+
+**不通过**(用户双目标均未达成,但本轮为「首次取得可复算全量口径」的结构性改善轮)。
+
+| 门禁 | 阈值 | 实测 | 结论 |
+|---|---|---|---|
+| G1 全量用例通过率 | ≥96% | **93.45%**(后端 94.75%) | ❌ |
+| G2 红例数 | 0 | **218**(143+49+16+10) | ❌ |
+| G3 管理端全绿 | 100% | 116/116 | ✅ |
+| G4 后端可构建 | 0 编译错 | testCompile 339 源 0 错 | ✅ |
+| G5 结果完整性 | 无静默丢失 | 333/333 XML | ✅ |
+| G6 浏览器套件 | 28/28 | **28/28(零 skip)** | ✅ |
+| G7 langgraph 零失败 | 0 红 | 10 红 | ❌ |
+| G8 鉴权契约 | 13/13 | 13/13 | ✅ |
+| G9 SRS/TC 可执行锚点 | ≥80% | **11.5%** | ❌ |
+| **G10 节点派发** | 251 实派 | **pytest 43 例在 251 实跑回传** | ✅ **首次通过** |
+| G11 CI 硬门禁 | 全量 blocking | 仍为 blocking/observed 两级,observed 含 33 红类 | ❌ |
+| G12 SRS 变更→用例更新 | 100% | 3 份 SRS 均有对应用例 | ✅ |
+
+---
+
+## 九、对照用户双目标
+
+- **目标① 所有用例通过**:❌ 3210/3435 = 93.45%。结构:**预期 RED 契约 94 + 前端预期 RED 49 + A 类装置 37 + B 类产品 12 + 根级环境 16 + langgraph 10**。其中「一修即绿」的 A 类 37 例由测试侧自处置(下轮)。
+- **目标② 覆盖全部 SRS + 用户场景(含异常流)**:❌ 场景 245 个、被测试引用 137(55.9%);TC 台账 824 条、异常流 496(60.2%,**设计层够**)、但可执行锚点仅 95(11.5%)→ 异常流「有设计、缺执行锚定」。
+
+## 十、复评条件(下轮验收判据)
+
+1. P-1(thread_id)修 → 251 `test_analyze` 2 例转绿 + 本地 venv 同步转绿。
+2. P-2(mind 白名单)修 → `MindFortuneControllerTest` 转绿 + 线上 4 端点匿名 401。
+3. P-3(A 类 37 例)测试侧处置 → 后端红例 143→106、通过率升至约 96.0%。
+4. P-5(test_chat 超时)处置 → langgraph 套件可在 ≤5 分钟收敛,251 派发纳入常规。
+5. P-6(94 契约)逐类实现 → 后端通过率越过 99%。
+
+## 十一、证据清单(本机,未入库)
+
+| 文件 | 内容 |
+|---|---|
+| `_r97_backend.log` | 后端全量,1:00h,BUILD FAILURE(测试红) |
+| `_r97_aggregate.py` / `_r97_red_cases.tsv` | 333 类聚合 + 143 红例逐条(类/方法/消息) |
+| `_r97_jest_fe.log` | 前端 423/371/49/3 |
+| `_r97_jest_web.log` | 管理端 116/116 |
+| `_r97_browser_live2.json` | live 15/15(零 skip) |
+| `_r97_browser_health.json` | health 13/13 |
+| `_r97_pytest2.log` | 本机 langgraph 部分输出(后因 CPU 争抢主动终止) |
+| 251 会话 `ses_e42f48001ffe68snntYwLew6N6` | 251 实派 pytest:10 failed / 33 passed / 183.67s |
+| 251 会话 `ses_e4303bd70ffexUVzJkgwbxS5n4` | 251 仓库 HEAD `4f1cf92ec` / 分支 `cfclub` |

+ 179 - 0
docs/tdd/test/automation-run-2026-11-09-R97.md

@@ -0,0 +1,179 @@
+# 全栈测试执行记录 · R97(2026-11-09 04:56 起跑)
+
+> 自动化闭环第 97 轮。执行档:**review(离线)**——未联网装依赖、未启停任何服务、未修改生产代码;仅修改测试侧装置(`tests/e2e-live/admin-live.spec.js`)。
+> 汇报正文:`docs/tdd/orchestration/report-test-2026-11-09-0610.md`;台账:`docs/tdd/test/blockers.md`。
+
+---
+
+## 一、历史审查记录
+
+| 项 | 内容 |
+|---|---|
+| 上次审查 | R96(2026-11-09 04:31),结论**不通过** |
+| R96 遗留 | 后端 2753/115 红(**承袭未实测**)、前端 49 红(预期 RED)、langgraph 本机 44/32/12、251 派发不可用、B-65/B-69 续催、B-83 新增 |
+| 本次改善 | ①**后端首次实测**(推翻「无 Maven」);②**251 派发达成**(G10 首次通过);③**浏览器零 skip**(B-76/B-59 闭环);④B-65 定性升级(非「已修」而是「依赖版本侥幸」);⑤B-69 定位到 PUBLIC_PATHS |
+| 历史教训应用 | B-28(分支切换带走工作区)→ 跑前/跑后双查 branch+HEAD;B-32(口径漂移)→ 本轮所有度量给出可复算口径并剔除生成物(B-86);B-67(假红三证)→ U04 失败执行「2 次独立复跑 + 接口 curl 5 连测」后才定性 |
+
+---
+
+## 二、项目画像
+
+| 维度 | 内容 |
+|---|---|
+| 业务定位 | 家庭教育成长平台「浠艾福(XAF / CFC)」:家长端/规划师端/孩子端小程序 + 管理后台 + 商城 + AI 服务 |
+| 技术栈 | `cfc-backend` Spring Boot 2.7.18 + MyBatis-Plus + Java 8;`cfc-frontend` uni-app(Vue2);`cfc-web` Vue2+Element UI;`cfc-langgraph` FastAPI+LangGraph+ChromaDB;`dan/danshop` 独立商城 |
+| 模块规模 | 后端 2110 生产源 / 249 控制器 / 333 测试类;前端 26 测试套件;管理端 17 套件;langgraph 44 用例 |
+| 关键路径 | 微信登录 → 家庭/成员 → 五维能量 → 健康报告上传解析 → 测评订单 → 成长任务 → 商城 → AI 管家 |
+| 测试现状 | 后端单测/集成较厚(2802 例);前端用例集中在 diet/signature 改版;E2E 仅 live 28 例;无覆盖率/变异/压测/SAST/无障碍资产 |
+| 质量目标 | 用户口径:**全部用例通过 + 覆盖全部 SRS 与用户场景(含异常流)** |
+| 主要风险 | ①TDD 契约红态堆积(94 例)导致通过率长期 <96%;②A 类装置红 37 例污染门禁信号;③SRS→可执行锚点仅 11.5%;④CI 仍为两级软门禁 |
+
+---
+
+## 三、20 类测试覆盖矩阵
+
+> 三态:**已发现**=取得可执行资产与实测数据;**未发现**=扫描未命中(≠不存在);**待核实**=扫描截断/无法判定。
+
+### 3.1 功能层次(5 类)
+
+| # | 类型 | 状态 | 证据 |
+|---|---|---|---|
+| 1 | 单元测试 | 已发现 | 333 类 / 2802 例,1:00h 实跑 |
+| 2 | 测试覆盖率 | 未发现 | 无 jacoco 报告产物(`target/site/jacoco` 不存在) |
+| 3 | 变异测试 | 未发现 | 无 PIT/pitest 配置 |
+| 4 | 集成测试 | 已发现 | `EnergySystemFlowTest`/`PlannerBindInviteFlowTest` 等 40+ 类,13 例红 |
+| 5 | 契约测试 | 已发现 | `Guhe*ContractTest` 13 类 85 例(预期 RED)+ health 契约 13/13 |
+
+### 3.2 功能保障(4 类)
+
+| # | 类型 | 状态 | 证据 |
+|---|---|---|---|
+| 6 | 冒烟测试 | 已发现 | `playwright.config.smoke.js` 存在;live 15/15 承担冒烟职能 |
+| 7 | 回归测试 | 已发现 | 后端全量 + 前端 423 + 管理端 116 每轮复跑 |
+| 8 | 探索性测试 | 未发现 | 无 charter/记录 |
+| 9 | 验收测试(UAT) | 未发现 | 无用例与签署记录 |
+
+### 3.3 非功能(6 类)
+
+| # | 类型 | 状态 | 证据 |
+|---|---|---|---|
+| 10 | 性能测试 | 未发现 | 无 k6/jmeter;仅 `nonfunc-perf-compat.spec.js` 轻量探针 |
+| 11 | 可靠性测试 | 待核实 | 无故障注入/混沌;251 两次断连(B-55)为被动观测 |
+| 12 | 安全测试(SAST) | 未发现 | 无 semgrep/codeql 产物 |
+| 13 | 依赖漏洞(SCA) | 未发现 | 无 OWASP DC/trivy 产物(离线档亦不允许联网拉库) |
+| 14 | 兼容性测试 | 已发现 | admin-desktop + H5-mobile 两档(`playwright.config.live.js` projects) |
+| 15 | 无障碍测试 | 未发现 | 无 axe 断言 |
+
+### 3.4 度量与流程(5 类)
+
+| # | 类型 | 状态 | 证据 |
+|---|---|---|---|
+| 16 | QA 流程 | 已发现 | `docs/tdd/` 台账 + 双负责人轮次 + blockers 编号体系 |
+| 17 | 质量指标 | 已发现 | 本轮首次给出可复算全量口径(3435/3210=93.45%) |
+| 18 | 缺陷管理 | 已发现 | `blockers.md` B-1~B-86 |
+| 19 | 全项目代码审查 | 待核实 | 抽样(控制器 249/接口 2110 源);`@GetMapping` 8 处违规 |
+| 20 | 变更影响分析 | 已发现 | SRS 3 份 ↔ 用例映射(§五) |
+
+**缺口统计**:已发现 11 / 未发现 8 / 待核实 1。
+
+---
+
+## 四、测试方案(本轮实际执行)
+
+| 项 | 内容 |
+|---|---|
+| 目标 | ①取得可复算全量通过率;②定位 143 红例分层归因;③langgraph 派 251;④浏览器零 skip |
+| 范围 | 后端全量 JUnit、前端/管理端/根级 jest、langgraph 251 实派、浏览器 live+health |
+| 排除 | 覆盖率/变异/压测/SAST/SCA/UAT/BDD/无障碍(无工具或无资产,标未实测) |
+| 执行档 | review(离线);251 派发走内网 opencode 通道(禁装依赖、禁启停服务) |
+| 优先级 | P0 analyze thread_id → P1 装置 37 例 → P1 产品 12 例 → P1 契约 94 例 |
+
+---
+
+## 五、执行结果明细
+
+### 5.1 后端(JDK1.8.0_25 + IDEA Maven 3.9.9,`mvn -o -B clean test`,1:00:34)
+
+```
+Tests run: 2802, Failures: 132, Errors: 11, Skipped: 4
+passed=2655  pass_rate=94.75%  (excl_skip 94.89%)
+classes=333  red_classes=34  red_cases=143
+恒等式: @Test 2792 + 参数化展开 26 − @Disabled 4 − 早退截断 12 = 2802(误差 0)
+testCompile: 339 源 0 错;XML 完整性 333/333;无静默丢失
+```
+
+**红类 TOP10**:`GuheEvidenceIsolateContractTest` 11、`GuheNoPathMapContractTest` 10、`DietFoodHealthAdjustContractTest` 9、`GuheKbVersionSenseContractTest` 9、`GuheLowSideRuleLoadContractTest` 9、`CfCommissionServiceSettleTest` 8、`GuheReportPersonalAssertContractTest` 7、`EnergySystemFlowTest` 6、`GuheKbQueryLowSidePatchContractTest` 6、`GuheV2RouteContractTest` 6。
+
+**三层归因**:预期 RED 94 / A 类 37 / B 类 12。
+
+### 5.2 前端与管理端
+
+| 套件 | 结果 | 红例分布 |
+|---|---|---|
+| `cfc-frontend` | 423 / 371 / **49** / 3 skip | `recommendation.uiRework` 25、`signature.canvas2d` 9、`recommendation.healthAdjustUi` 8、`recommendation.participantSelect` 8(**全为预期 RED,源码能力键命中 0**) |
+| `cfc-web` | 116 / 116 / 0 | — |
+| `tests/` 根级 | 23 / 7 / 16 | B-36 黑盒环境依赖(零漂移) |
+
+### 5.3 langgraph(251 实派,权威口径)
+
+```
+251 仓库: /sc-data/cfc/cfc-langgraph  HEAD 4f1cf92ec  branch cfclub
+命令: timeout 720 .venv/bin/python -m pytest tests --ignore=tests/qna --ignore=tests/test_chat.py -q
+结果: 10 failed, 33 passed, 6 warnings in 183.67s
+```
+
+| 红例分组 | 例数 | 归因 |
+|---|---|---|
+| `test_analyze.py` | 2 | **B-65 源码级**(thread_id),251 pytest 可复现 |
+| `test_auto_tag.py` | 4 | B-74 装置(ASGITransport 不触发 startup) |
+| `test_intent.py` | 4 | 环境(251 模型/凭据对意图分类不稳定;R95 曾 4/4 绿) |
+| `test_chat.py` | 1 | **B-84 新增**:真实 LLM 无超时,>10min 不收敛 → 本轮排除执行 |
+
+**251 live 端到端**(权威能力验证):`/api/v1/chat` 200(28.44s,完整 answer)、`/api/v1/analyze` 200(15.77s)、`/health` 200、`/docs` 200、OpenAPI 40 paths。
+
+### 5.4 浏览器自动化(本机单节点串行,`workers=1`)
+
+| 套件 | 结果 | 耗时 |
+|---|---|---|
+| live(admin 5 + frontend 6 + shop 4) | **15/15,0 skip** | 1.6m |
+| health-contract | **13/13** | ~20s |
+
+**装置修复(B-76/B-59 闭环)**:`admin-live.spec.js` 新增 `waitLoginFormReady()`、`fillPhoneAndAwaitRoles()`(等 `account-roles` XHR 返回并断言 200),`TC-A-U05` 的 `test.skip` 改硬断言。修复前首跑 U04 红/U05 skip;修复后 5/5、全套件零 skip。
+
+### 5.5 节点分派实测
+
+| 节点 | 端口 | 结果 |
+|---|---|---|
+| 192.168.16.251 | 9000 / 4096 / 22 | **全部可达**;9000 服务正常(40 paths)、4096 opencode 可用 → **pytest 实派成功** |
+| 192.168.16.251 | 4090 / 8000 | CLOSED |
+| 192.168.16.189 | 4090 / 22 | **Connection refused**(B-56 延续:节点失联) |
+| 本机(204) | Chromium-1228 | 浏览器单任务串行 ✅ |
+
+---
+
+## 六、覆盖缺口清单
+
+| 缺口类型 | 现状 | 风险 | 建议测试设计 | 优先级 | 闭环方式 |
+|---|---|---|---|---|---|
+| SRS→可执行锚点 | 95/824 = 11.5% | 需求变更无法被用例守护 | 每份 SRS 的验收点必须落 1 个可执行 TC 锚点注释 | P0 | 新 SRS 合并时 CI 校验「新增 spec 含 TC-* 注释」 |
+| 异常流执行级验证 | 设计 496 条(60.2%),执行锚点极少 | 异常分支回归无守护 | 对 -E/-P 用例按控制器分组补 MockMvc 负向用例 | P1 | 每控制器 ≥3 条负向用例 |
+| 覆盖率 | 无 jacoco 产物 | 无法判定测试有效性 | 引入 jacoco + 行覆盖 ≥60% 门禁 | P1 | `mvn test` 出 HTML 报告并入库 |
+| 变异测试 | 无 | 断言强度未知 | 引入 pitest 对 service 层抽样 | P2 | 变异存活率 ≤40% |
+| 性能/可靠性 | 无压测 | 慢查询/抖动无基线 | 对 live 关键 API 做 p95 基线采集 | P2 | 每轮采集 p95 并对比 |
+| SAST/SCA | 无 | 漏洞不可见 | 引入 semgrep + dependency-check(需联网授权) | P2 | CI 增 security job |
+| 无障碍/多浏览器 | 仅 2 档 | 体验缺保障 | 增 firefox/webkit + axe 断言 | P3 | projects 扩展 |
+| UAT/BDD | 无资产 | 业务验收无记录 | 每迭代 1 份 Gherkin 场景 | P3 | 与产品共签 |
+
+---
+
+## 七、门禁结论
+
+**不通过**(详见汇报正文 §八)。本轮结构性改善:**G10 节点派发首次通过**、**G6 浏览器零 skip**、**首份可复算全量口径**。
+
+## 八、未实测项(如实登记)
+
+1. 覆盖率、变异测试、性能压测、SAST、SCA —— 无工具/离线档不允许联网。
+2. `tests/test_chat.py` 1 例 —— 真实 LLM 无超时,两节点均不收敛(B-84)。
+3. UAT、BDD、探索性、无障碍、多浏览器 —— 无资产。
+4. 192.168.16.189 浏览器派发 —— 节点 22/4090 全拒。
+5. 后端 A 类 37 例的转绿 —— 测试侧下轮处置,本轮仅归因未修改(除浏览器装置)。

+ 12 - 0
docs/tdd/test/blockers.md

@@ -126,3 +126,15 @@
 | B-83(第 96 轮新增) | `cfc-frontend` S-cfc-97 `diet.recommendation.healthAdjustUi` 8 例 RED 契约测试 | 研发 | **P1 预期 RED(第 96 轮实测,2026-11-09 04:31)**。`cfc-frontend/__tests__/diet/recommendation.healthAdjustUi.test.js`(commit `eea38d6ec`,S-cfc-97)随 SRS 变更 `docs/superpowers/specs/2026-11-09-diet-recommendation-health-questionnaire-adjust.md`(§2 调整分级语义 blocked/warning/boost/normal、§3 前端 B1-B3、§4 验收 V/C-1/6)新增;本轮 jest 实跑 **8 失败**(B2 食物卡片 adjustment 提示条 5 + B3 filteredFoods 分组 3)。均为「功能未实现」类断言。非既有用例回归;前端其余 371 通过用例零转红。性质 = 预期 RED(S-cfc-97 处于 RED 态,待研发实现 `recommendation.vue` 调整提示条与分组)。影响:用户目标①暂未达成(8 红);但「SRS 变更→测试用例更新」闭环已满足。通知研发:实现 S-cfc-97 使 8 例转绿。闭环:研发实现 → 该套件 8/8 全绿 + 状态机 S-cfc-97 置 YELLOW | 测试负责人(第 96 轮实测登记) | 2026-11-09 04:31 | 待处理(P1,预期 RED,待实现) |
 | B-55-Reopen(第 96 轮再断) | 251:9000/4090 CLOSED,langgraph 派 251 通道再次中断 | 运维 | **P1 派发阻断(第 96 轮实测,2026-11-09 04:31)**。探活 `192.168.16.251:9000` → HTTP 404、`192.168.16.251:4090` → closed(连接拒绝)。R95 曾记 B-55-Close(251:9000 服务恢复、llm/chromadb/java_backend 全 ok),本轮复探确认服务维度再次下线。影响:用户指令「langgraph 部署于 251 应派发该节点」本轮无法落地,langgraph 测试退回本机 venv 兜底(44/32/12,其中 intent 4 例因本机 LLM key 401 环境红,权威口径缺失)。闭环:运维放通 251:9000(uvicorn langgraph 服务)+:4090(opencode),使 `cfc-langgraph` 源码节点可实派 pytest | 测试负责人(第 96 轮实测) | 2026-11-09 04:31 | 待处理(运维:放通 251 端口) |
 | B-81-Update(第 96 轮 · 连续第 2 轮) | 签名 v2 重写(S4-1/S4-2)契约 9 例 RED 未变 | 研发 | **连续第 2 轮未修(2026-11-09 04:31)**。本轮 jest 实跑 `signature.canvas2d.test.js` 仍 **9 失败 / 2 通过**(TC-SIG2D-01~07 + 01b/01c/03b),源码级确认 `signature.vue`/`hand-signature.vue` 仍为旧横屏+旧 canvas 实现(S4-1 竖屏直签、S4-2 `type="2d"` 重写均未落地)。研发负责人 04:10 轮记「签名 v2 实现未推(待 08:15 派)」,S-cfc-116 已入池。闭环判据不变:9 RED 转绿 + `tests/test-cases/signature.md` 状态置已实现 | 测试负责人(第 96 轮复现登记) | 2026-11-09 04:31 | 待处理(P1,预期 RED,待实现) |
+
+<!-- ===== 第 97 轮(2026-11-09 06:10)追加 ===== -->
+
+| B-84(第 97 轮新增) | langgraph `tests/test_chat.py` 走真实 LLM 且无超时 → 套件不收敛 | 测试/研发 | **P1 套件非终止(第 97 轮实测,2026-11-09 05:20~05:50)**。本机 venv `pytest tests --ignore=tests/qna` 运行 **>35 分钟未结束**(后因 CPU 争抢主动终止);251 实派首次尝试同样 **>10 分钟**未收敛(251 agent 只能转后台等待)。根因:`test_chat_endpoint` 串起「意图分类 → 主生成 → 任务提取 → 记忆 embedding」4 次真实 LLM 调用(provider `etotem/deepseek`,单次可达数十秒),且无 `pytest-timeout`、无 LLM 桩。影响:整套件无法在有界时间内给出汇总行,CI/251 派发都会挂死;本轮只能 `--ignore=tests/test_chat.py` 才拿到 10 failed / 33 passed / 183.67s。闭环:该用例加 `@pytest.mark.timeout(120)` 或 monkeypatch 掉 LLM/embedding(仅断言编排链路);`pytest.ini` 设 `timeout = 180`;复跑全量 ≤5 分钟自然结束 | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:50 | 待处理(P1,套件非终止) |
+| B-85(第 97 轮新增) | 251 部署实例与仓库依赖版本不一致:同源码 live 200 / pytest 500 | 研发/运维 | **P1 定性修正(第 97 轮实测,2026-11-09 05:15)**。251 `/sc-data/cfc/cfc-langgraph` HEAD `4f1cf92ec`(branch cfclub)实派 pytest:`test_analyze.py` 2 例 **500**,异常 `ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id`;但**同节点 live HTTP** `POST /api/v1/analyze {report_id:1,user_id:1,focus:"general"}` → **200** 完整分析 JSON(15.77s),`POST /api/v1/chat {query,...}` → **200**(28.44s)。本机 venv(langgraph **0.6.11**)对同一源码直接抛同一 `ValueError`。结论:**不是研发已修**,而是运行实例的 langgraph 版本低于仓库锁版本(旧版不强制 thread_id)——**一旦依赖升级即 100% 500**。B-65 因此维持 P0(源码级缺陷仍在)。闭环:同 B-65(去 `MemorySaver()` / 传 `thread_id`),并要求部署锁版本与 `requirements` 对齐,之后 live 与 pytest 应同时 200 | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:15 | 待处理(P1,支撑 B-65 定性) |
+| B-86(第 97 轮新增 · 度量) | `tests/e2e/api/generated/cases.json` 使「TC 锚点覆盖率」虚高 | 测试(口径) | **P2 度量失真(第 97 轮实测,2026-11-09 05:35)**。按 `tests/e2e*` + `__tests__` + `src/test` 全量 grep TC 编号,台账 824 条中有 **797 条**命中,覆盖率看似 96.7%;抽样 6 条(TC-X-16 / TC-P-74 / TC-P-20-E / TC-P-18 / TC-S-N04 / TC-P-08-P)**全部只命中 `tests/e2e/api/generated/cases.json`**——该文件是描述性生成物,非可执行资产。剔除 `--exclude-dir=generated` 后,真实可执行锚点 **95/824 = 11.5%**(抽样验证:TC-G-05→`ScG05AnonymousBrowseContractTest.java`、TC-DV-06→`assessment-appointment-functional.spec.js`,均为真实用例)。与 B-30/B-32 同族陷阱(描述性产物被误判为测试资产)。闭环:所有「锚点/覆盖率」统计脚本必须排除 `generated/`;该目录加 `.gitattributes` 标注 `linguist-generated`,度量文档写明排除规则 | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:35 | 待处理(P2,口径规则) |
+| B-76-Close(第 97 轮闭环) | admin-desktop 非确定 flaky(TC-A-U04 首跑红 / U05 skip) | 测试(已自处置) | **已解除(第 97 轮,2026-11-09 05:10)**。全量串行首跑 U04 红(断言「输入手机号后「登录身份」应自动带出」失败,耗时 7662ms)、U05 被 skip;**独立复跑 U04 两次 2/2 通过**(20.5s / 17.7s)、`/api/admin-auth/account-roles` curl **5/5 均 200 且 56~112ms** → 按 B-67 三证判为**装置时序竞态,非产品回归**。根因:全量串行时 `page.goto('/admin/login', {waitUntil:'domcontentloaded'})` 返回时 Vue 尚未挂载,`fill()` 触发的 `@input` 无监听器 → 500ms 防抖查询永不发出 → 身份恒为空。修复(`tests/e2e-live/admin-live.spec.js`):新增 `waitLoginFormReady()`(等手机号/密码输入框可见可编辑 + 登录按钮渲染 + `!disabled && !readOnly`)与 `fillPhoneAndAwaitRoles()`(**`waitForResponse(/api/admin-auth/account-roles/)` 等 XHR 实际返回并断言 200**,禁固定 sleep);`waitIdentityAutoFilled` 超时 6s→12s。复跑:admin **5/5**、全套件 **15/15 零 skip** | 测试负责人(第 97 轮修复并复验) | 2026-11-09 05:10 | **已解除** |
+| B-59-Close(第 97 轮闭环) | `TC-A-U05` 用 `test.skip` 吞掉前置登录失败 | 测试(已自处置) | **已解除(第 97 轮,2026-11-09 05:10)**。原代码 `test.skip(page.url().includes('/login'), '登录未通过(见 TC-A-U04)…')`:U04 红时 U05 变 skipped,line reporter 汇总显示「1 skipped / 14 passed」,**门禁看不到任何失败**,而菜单可访问性实际根本没测(假绿)。改为硬断言 `expect(page.url().includes('/login'), '前置登录未通过…').toBeFalsy()`。修复后整套件 15 例全绿、零 skip | 测试负责人(第 97 轮修复并复验) | 2026-11-09 05:10 | **已解除** |
+| B-55-Close(第 97 轮 · 复通) | 251 langgraph 派发通道中断 | 运维(已恢复) | **已解除(第 97 轮,2026-11-09 05:02)**。探活:`251:9000/health` → **200 status ok(13ms)**、`/docs` 200、`/openapi.json` **40 paths**;`251:4096`(opencode)→ 401(服务在、需鉴权)、`251:22` OPEN。**pytest 实派成功**:`/sc-data/cfc/cfc-langgraph` HEAD `4f1cf92ec` branch `cfclub`,43 例 33 过 10 红 183.67s(会话 `ses_e42f48001ffe68snntYwLew6N6`)。另:`251:4090/8000` CLOSED;**`189:22/4090` 均 Connection refused**(B-56 延续,该节点仍失联)。派发客户端修正:provider 默认 `axon` 在 251 不存在 → 必须显式 `OPENCODE_PROVIDER=etotem OPENCODE_MODEL=deepseek`,否则 `outcome=failed, tokens=0`(B-56 客户端侧根因复现) | 测试负责人(第 97 轮实测) | 2026-11-09 05:02 | **已解除** |
+| B-69-Update(第 97 轮 · 连续第 5 轮 · 证据升级) | 4 个 `/api/mind/**` 位于公开白名单,匿名可达业务层 | 研发 | **连续第 5 轮未修(2026-11-09 05:20)**。源码 `cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java:79-82` 把 `/api/mind/alert/active`、`/api/mind/alert/checkin-alerts`、`/api/mind/traditional/mirror`、`/api/mind/traditional/compatibility` **列入 PUBLIC_PATHS(匿名白名单)**,而非 `LOGIN_REQUIRED_PATHS`。线上匿名实测:`POST /api/mind/alert/active` → code 500「参数缺失:memberId」(已进 controller);`POST /api/mind/traditional/mirror` → code 200 data {};对照 `POST /api/health/plan/list` → **401**。后端 `MindFortuneControllerTest.testGetTodayFortuneWithoutAuth` 断言 401 实际 400(`@RequestAttribute` 绑定层先 400,与 B-57 同族)。闭环判据升级:①4 条移入 `LOGIN_REQUIRED_PATHS`;②线上 4 端点匿名 curl 全 401;③`MindFortuneControllerTest` 转绿 | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:20 | 待处理(P1,连续第 5 轮) |
+| B-65-Update(第 97 轮 · 连续第 6 轮 · 定性修正) | langgraph `/api/v1/analyze` 源码级 checkpointer 缺 thread_id | 研发 | **连续第 6 轮未修(2026-11-09 05:15)**。见 B-85:251 pytest 2 例 500 复现 ValueError,而同机 live 200;本机 venv(langgraph 0.6.11)同源码直接 ValueError。源码未变:`cfc-langgraph/app/graphs/analysis_graph.py:96 checkpointer = MemorySaver()`、`app/api/analyze.py:35 await graph.ainvoke({...})`(无 config)。**推翻前几轮「线上 500」的表述**——线上因依赖版本低而侥幸 200,属「未爆的 P0」。闭环:去 MemorySaver 或传 thread_id;部署锁版本;之后 `pytest tests/test_analyze.py` 2 例绿 + live 连跑 3 次 200 | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:15 | 待处理(P0,连续第 6 轮) |
+| B-87(第 97 轮新增 · 测试侧待处置) | 后端 A 类测试装置红例 37 例(桩/夹具/断言过期) | 测试 | **P1 装置缺陷(第 97 轮实测,2026-11-09 05:58)**。后端 143 红例中 37 例失败信息指向测试装置而非产品:`CfCommissionServiceSettleTest` 8(档位/依赖未 stub → Wanted but not invoked)、`ArticleAutoTagServiceTest` 4(verify taskExecutor.execute 与注入/实现不一致)、`DimensionScoreServiceImplTest` 4(No value present)、`ArticleWorkflowTest` 3(源码文案已改「仅已驳回或已撤回」,断言仍旧文案)、`DailyCheckinStreakFlowTest` 1 与 `PlannerBindInviteFlowTest` 1(doNothing 作用于非 void 方法)、`GrowthRecordSyncFlowTest` 3、`DimensionRegistryMapContractTest` 3、`ServiceRoleApproveTest` 2、`Orchestration*` 2、其余 6。影响:门禁信号被装置噪声污染,无法从通过率判断产品健康度。闭环:逐类对齐(文案/桩/断言/注入名),定向 `mvn -Dtest=A,B,C -DfailIfNoTests=false test`(**类分隔必须用逗号**,`+` 会静默 0 执行)→ 37 例转绿且其余类零转红,后端通过率预期 94.75%→约 96.0% | 测试负责人(第 97 轮实测登记) | 2026-11-09 05:58 | 待处理(P1,下轮自处置) |

+ 51 - 0
docs/tdd/研发交接通知-2026-11-09-第97轮.md

@@ -0,0 +1,51 @@
+# 研发交接通知 · 第 97 轮(2026-11-09 06:10)
+
+> 来源:定时自动化「测试负责人闭环测试」第 97 轮。基线 `cfclub` @ `f985f9274`(跑测期间 HEAD 前进至 `1abfc7348`,7 个提交**全部 docs/版本号,零源码变更**)。
+> 详细报告:`docs/tdd/orchestration/report-test-2026-11-09-0610.md`;执行记录:`docs/tdd/test/automation-run-2026-11-09-R97.md`;台账:`docs/tdd/test/blockers.md`。
+
+## 一、本轮结论概览
+
+| 维度 | 结果 |
+|---|---|
+| 后端全量(**首次实测**) | 333 类 / 2802 例 / 2655 过 / **143 红** / 4 skip = **94.75%** |
+| 前端 / 管理端 | 423(49 红)/ 116(全绿) |
+| langgraph **@251 实派** | 43 例 / 33 过 / 10 红(183.67s) |
+| 浏览器 | live 15/15 + health 13/13(**零 skip**) |
+| 合计 | **3435 例 / 3210 过 = 93.45%** |
+
+**143 红例分层**:预期 RED 契约 **94** / A 类测试装置 **37** / B 类产品 **12**。
+
+## 二、请研发优先处理
+
+| 优先级 | 编号 | 事项 | 证据(可复现) | 期望动作 |
+|---|---|---|---|---|
+| **P0** | **B-65(第 6 轮·定性修正)** | langgraph `analysis_graph.py:96` 仍 `MemorySaver()`,`analyze.py:35` `ainvoke` 未传 `thread_id` | 251 **pytest 2 例 500**(`ValueError: Checkpointer requires ... thread_id`);**同机 live HTTP 却 200** → 说明运行实例依赖版本低于仓库锁版本,**一升级依赖即 100% 失败** | 去 `MemorySaver()`(改用 `app/checkpointer.py` 共享 saver)或 `ainvoke` 传 `config={"configurable":{"thread_id":...}}` |
+| **P1** | **B-69(第 5 轮·证据升级)** | 4 个 `/api/mind/**` 在 `JwtInterceptor.java:79-82` 的 **PUBLIC_PATHS(匿名白名单)** 内 | 线上匿名:`/api/mind/alert/active` → `{"code":500,"message":"参数缺失:memberId"}`;`/api/mind/traditional/mirror` → `{"code":200,...,"data":{}}`;对照 `/api/health/plan/list` → **401** | 4 条移入 `LOGIN_REQUIRED_PATHS` |
+| **P1** | **B-84(新增)** | langgraph `tests/test_chat.py` 走真实 LLM 无超时 → 套件**不收敛**(本机 >35min、251 >10min 均未结束) | 两节点实测均挂起;本轮只能 `--ignore=tests/test_chat.py` 才拿到汇总 | 该用例加 `@pytest.mark.timeout(120)` 或桩掉 LLM/embedding;`pytest.ini` 设全局 `timeout=180` |
+| **P1** | **P-3:A 类 37 例(测试侧下轮自处置,仅供知会)** | `CfCommissionServiceSettleTest` 8(档位未 stub)、`ArticleAutoTagServiceTest` 4(verify 目标与注入名不一致)、`DimensionScoreServiceImplTest` 4(`No value present`)、`ArticleWorkflowTest` 3(**源码文案已改「仅已驳回或已撤回」**)、`doNothing()` 误用 2、其余 16 | `_r97_red_cases.tsv` 逐条 | 无需研发改代码;若 `ArticleWorkflowTest` 的文案变更确为有意,请确认后我们更新断言 |
+| **P1** | **B 类 12 例** | `EnergySystemFlowTest` **6×500**;`PlannerBindInviteFlowTest` **4×403**;`GuideModuleIntegrationTest` `Data truncation: Out of range value for column 'platform_fee'`;`MindFortune` 401/400 | 类名+方法见报告 §二 | EnergySystem 查 500 的 `Caused by` 最内层;PlannerBind 核对绑定态角色闸门;`platform_fee` 扩列宽或写入前裁剪 |
+| **P1** | **预期 RED 94 例** | `Guhe*ContractTest` 13 类 85 例 + `DietFoodHealthAdjustContractTest` 9 例 | 失败消息内嵌需求编号(如「`AttributionComplianceGate#tagEvidence` 未实现(G-3,T11)」) | 按契约实现;逐类转绿后请把类名从 `.gitlab-ci.yml` 的 `BACKEND_OBSERVED_CLASSES` 移除 |
+| P1 | B-81 / B-83 / B-73 / S-cfc-80 | 前端 49 红(预期 RED) | `recommendation.vue` 对 `currentTab/pagedFoods/loadMore` 命中 **0**;`signature.vue` 仍 `enterLandscape` 4 处命中、`handle-signature` `canvasToTempFilePath` **0** 命中 | 实现 S-cfc-116(签名 v2)、S-cfc-97(健康问卷调整提示)、S-cfc-79/80(diet 改版)后各自转绿 |
+
+## 三、本轮已闭环(测试侧)
+
+| 编号 | 事项 | 处置 |
+|---|---|---|
+| **B-76** | admin-desktop 非确定 flaky(U04 首跑红) | 新增 `waitLoginFormReady()` + `fillPhoneAndAwaitRoles()`(**等 `account-roles` XHR 返回并断言 200**,禁固定 sleep)→ admin 5/5、全套件 15/15 零 skip |
+| **B-59** | `TC-A-U05` 的 `test.skip` 吞掉前置失败(门禁显示"无失败"实际没测菜单) | 改**硬断言**,前置登录失败即红 |
+| **B-77 / B-78** | 251 chat 挂起 / 无源码 | 本轮 251 全通:`/health` 200、`/api/v1/chat` 200(28.44s)、`/api/v1/analyze` 200(15.77s);`/sc-data/cfc/cfc-langgraph` HEAD `4f1cf92ec` 实派 pytest 成功 |
+
+## 四、新登记问题
+
+| 编号 | 事项 | 影响 |
+|---|---|---|
+| **B-84(P1)** | langgraph `test_chat` 无超时 → 套件不收敛 | CI/派发会挂死,必须加超时或桩 |
+| **B-85(P1)** | 251 部署实例与仓库依赖版本不一致:live 200 / pytest 500(同源码) | 属「部署态侥幸」,依赖升级即回归;建议部署锁版本并与仓库 `requirements` 对齐 |
+| **B-86(P2·度量)** | `tests/e2e/api/generated/cases.json` 内含 797 个 TC 编号,使「锚点覆盖」虚高至 96.7%;剔除后真实可执行锚点仅 **95/824=11.5%** | 度量口径失真,后续统计必须排除生成物 |
+
+## 五、环境提示
+
+- `192.168.16.189`:**:22 / :4090 均 Connection refused**(节点失联,B-56 延续)→ 该节点浏览器任务无法派发。
+- `192.168.16.251`:`:9000 / :4096 / :22` 全部可达,本轮 langgraph pytest 实派成功;`:4090/:8000` CLOSED。
+- 本机 `mvn` 不在 PATH 但 **IDEA Maven 3.9.9 + `JAVA_HOME=D:\jdk1.8.0_25`** 可用(后续请勿再判「本机无 Maven」)。
+- 4 核机器并行跑「后端全量 + pytest + 浏览器」会把全量从 50 分钟拉到 60+ 分钟,**排期时请勿与后端全量并行**。

+ 47 - 4
tests/e2e-live/admin-live.spec.js

@@ -31,7 +31,7 @@ const PASSWORD = process.env.CFC_ADMIN_PASSWORD || 'admin123';
  * 导致后续 mouse.down 落在浮层上、滑块位移恒为 0、登录按钮永久 disabled。
  * 因此一律「等它自己带出」,不点开、并在拖动前兜底关闭残留浮层。
  */
-async function waitIdentityAutoFilled(page, timeoutMs = 6000) {
+async function waitIdentityAutoFilled(page, timeoutMs = 12000) {
   const roleInput = page.locator('input[placeholder="登录身份(输入手机号后自动带出)"]');
   const deadline = Date.now() + timeoutMs;
   let val = '';
@@ -43,6 +43,40 @@ async function waitIdentityAutoFilled(page, timeoutMs = 6000) {
   return val;
 }
 
+/**
+ * 等登录表单「可交互」再输入(R97 / B-76 根因)。
+ * ⚠️ 全量串行跑时 U01~U03 已多次导航,U04 的 goto 可能只到 domcontentloaded
+ * 而 Vue 仍未挂载 → 此时 fill() 触发的 @input 没有监听器,500ms 防抖查询永不发出
+ * → 「登录身份」恒为空 → 断言红(孤立单跑 20s 内必过,属典型时序竞态)。
+ * 判据:手机号输入框可见 + 可编辑 + 登录按钮已渲染。
+ */
+async function waitLoginFormReady(page) {
+  const phoneInput = page.locator('input[placeholder="用户名 / 手机号"]');
+  await phoneInput.waitFor({ state: 'visible', timeout: 20000 });
+  await page.locator('input[placeholder="密码"]').waitFor({ state: 'visible', timeout: 20000 });
+  await page.getByRole('button', { name: /登\s*录/ }).waitFor({ state: 'visible', timeout: 20000 });
+  await page.waitForFunction(
+    () => {
+      const el = document.querySelector('input[placeholder="用户名 / 手机号"]');
+      return !!el && !el.disabled && !el.readOnly;
+    },
+    { timeout: 20000 }
+  );
+}
+
+/**
+ * 填手机号并「确定性地」等 account-roles XHR 返回(不靠固定 sleep)。
+ * 返回该响应的 HTTP 状态码,便于失败时给出可诊断信息。
+ */
+async function fillPhoneAndAwaitRoles(page, phone) {
+  const respPromise = page
+    .waitForResponse((r) => r.url().includes('/api/admin-auth/account-roles'), { timeout: 20000 })
+    .catch(() => null);
+  await page.locator('input[placeholder="用户名 / 手机号"]').fill(phone);
+  const resp = await respPromise;
+  return resp ? resp.status() : 0;
+}
+
 /** 关闭可能残留的下拉浮层,避免遮挡滑块(B-46 直接诱因) */
 async function dismissPoppers(page) {
   await page.keyboard.press('Escape').catch(() => {});
@@ -121,7 +155,9 @@ test.describe('管理端 UI · 登录与主框架', () => {
 
   test('TC-A-U04 管理员真实登录(滑块 + 凭据)进入后台主框架', async ({ page }) => {
     await page.goto('/admin/login', { waitUntil: 'domcontentloaded' });
-    await page.locator('input[placeholder="用户名 / 手机号"]').fill(PHONE);
+    await waitLoginFormReady(page);
+    const rolesStatus = await fillPhoneAndAwaitRoles(page, PHONE);
+    expect(rolesStatus, 'account-roles 接口应 200(身份带出的前置条件)').toBe(200);
     await page.locator('input[placeholder="密码"]').fill(PASSWORD);
 
     // 「登录身份」为只读带出项:等自动带出,禁止点开下拉(点开会用 popper 遮住滑块 → B-46)
@@ -147,7 +183,9 @@ test.describe('管理端 UI · 登录与主框架', () => {
 
   test('TC-A-U05 后台核心菜单可访问(用户管理/数据看板)', async ({ page }) => {
     await page.goto('/admin/login', { waitUntil: 'domcontentloaded' });
-    await page.locator('input[placeholder="用户名 / 手机号"]').fill(PHONE);
+    await waitLoginFormReady(page);
+    const rolesStatus2 = await fillPhoneAndAwaitRoles(page, PHONE);
+    expect(rolesStatus2, 'account-roles 接口应 200(身份带出的前置条件)').toBe(200);
     await page.locator('input[placeholder="密码"]').fill(PASSWORD);
     const roleVal2 = await waitIdentityAutoFilled(page);
     expect(roleVal2, '输入手机号后「登录身份」应自动带出').not.toBe('');
@@ -155,7 +193,12 @@ test.describe('管理端 UI · 登录与主框架', () => {
     await page.waitForTimeout(600);
     await page.getByRole('button', { name: /登\s*录/ }).click();
     await page.waitForTimeout(5000);
-    test.skip(page.url().includes('/login'), '登录未通过(见 TC-A-U04),本用例无法在无会话下判定');
+    // B-59 修正(R97):原 `test.skip(登录未通过)` 会把前置失败吞成 skipped,
+    // 让门禁显示「无失败」却实际没测菜单。改为硬断言——前置登录失败是真失败,不是跳过。
+    expect(
+      page.url().includes('/login'),
+      `前置登录未通过,本用例无法进入后台(实际 URL=${page.url()})`
+    ).toBeFalsy();
 
     const menuTexts = await page.locator('.el-menu-item, .el-submenu__title').allInnerTexts();
     const joined = menuTexts.join('|').replace(/\s+/g, '');