# W3-T9 独立评审报告 ## 评审结论 - Verdict:**PASS** - T9 是否允许提交:**允许** - 是否允许继续 T10:**允许继续实现与文档阶段,但不得在本次全量回归未全绿前判定 T10/B3 验证通过** - 评审范围: - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudEndToEndIntegrationTest.java` - `target/skill-cloud-t9-red.log` - `target/skill-cloud-t9-green.log` - `target/skill-cloud-t2-t9-regression.log` - `target/skill-cloud-t9-full-regression.log` - `target/skill-cloud-t9-unrelated-failures-isolated.log` - 修订后的 contract、design、tasks、三份 spec,以及 T8 Controller - 评审方式:静态核对测试与生产链路,使用 JDK 17 / Maven 3.9.7 独立执行指定 T2–T9 测试组、四个失败测试类及完整 `mvn test`。未修改实现、测试或其他源码文件。 ## T9 独立结论 ### 真实链路 T9 使用 `@SpringBootTest`、`@AutoConfigureMockMvc`、`@ActiveProfiles("integration-test")` 和独立 H2 数据库,实际经过 Controller、Security、ProfileService、Skill 查询/匹配/学习路径 Service、Repository、Flyway 与 V36 种子数据。仅 `RedisTemplate` 被 Mock,核心业务链路没有被替换。 可证伪证据: - `SkillCloudEndToEndIntegrationTest.java:48-55`:真实 Spring 上下文、MockMvc、H2 和生产数据库 URL/Profile。 - `:71-90`:注入真实 MockMvc、JdbcTemplate、JwtTokenProvider、Repository;唯一 `@MockBean` 为 RedisTemplate。 - `:100-134`:真实 STUDENT JWT 和真实 StudentProfile 调用三个端点,断言后端职位返回 16 项真实 V36 技能、rank 连续、heat 降序。 - `:269-304`:匹配 union、命中/缺失顺序、学习路径 priority/顺序、建议非空均有断言。 - 无 Flyway 或种子数据时,依赖的固定节点、16 项技能和 V36 来源字段无法成立;测试已独立通过,证明不是纯 Mock 测试。 ### 词云 TOP10 与匹配一致性 T9 从真实词云结果直接取 `allSkills.subList(0, min(10, allSkills.size()))` 作为 TOP10,再验证: - `totalCount` 等于实际 TOP10 大小。 - `matchedCount` 等于 matchedSkills 大小。 - matchedSkills 与 missingSkills 的并集严格等于词云 TOP10。 - matchedSkills 保持词云顺序,missingSkills 保持词云顺序。 - `matchScore` 等于 `round(matchedCount * 100 / expectedTop10.size())`。 - 3 项技能场景验证 `1/3 = 33`,并验证空热度末位。 该设计可证伪“匹配服务另起排序”“TOP10 与实际列表不一致”“missingSkills 丢项或重排”等缺陷。 ### 学习路径 T9 将学习路径逐项与 `match.missingSkills` 对比: - 总数为缺失技能数。 - 每项 `priority` 从 1 连续递增。 - `skillId`、`skillName` 与缺失技能逐项一致。 - `suggestion` 非空。 - 已命中的技能不会出现在学习路径中。 ### 确定性、空场景、身份与隐私 - `:123-133`:三个端点重复调用后 `data` 完全相等,并断言 8 张相关表的行数不变。 - `:179-225`:事务内插入空分类,验证词云、匹配、学习路径分别返回空列表和 0 值。 - `:227-240`:学生 JWT 额外传入 COMPANY 的 `userId`、`studentId`,三个端点结果均不被覆盖。 - `:242-267`:成功响应不包含 `skills_json`、手机号、邮箱、简历 URL、简历原始文件名和 JWT;错误响应不包含异常类、Spring 内部路径、SQL 或堆栈。 ### T9 findings - **Critical**:无。 - **Important**:无。 - **Minor T9-M1**:无写入断言以 8 张表行数前后不变为依据,可发现 INSERT/DELETE,但不能发现仅 UPDATE 同一行而不改变行数的情况。当前三个 Service 均为只读事务且测试通过,因此不阻断;后续可增加事务/数据库快照或只读约束断言。 - **Minor T9-M2**:隐私测试未单独断言 `realName`、`bio` 的哨兵值,也未显式检查 `skillsJson` 驼峰名;已检查字段覆盖敏感号码、邮箱、简历标识、原始 `skills_json` 和 Token,风险为受控 DTO 下的低风险,不阻断。 - **Minor T9-M3**:T9 的匹配一致性验证以结果中的 matchedIds 反推期望,因此主要证明“匹配结果与 TOP10 一致”,不独立证明具体学生技能命中哪一项标准技能;别名和匹配判定本身已由 T6 测试覆盖,不阻断。 ### 测试隔离与假绿风险 - T9 类级 `@Transactional`,创建的 Student、StudentProfile 和测试用空分类在方法结束后回滚。 - T9 使用独立 H2 URL,不与其他测试类共享数据库名。 - 空分类使用固定测试 ID 并依赖事务回滚,没有观察到跨方法或跨类污染。 - `@BeforeEach` 仅重置 Redis Mock,不依赖测试执行顺序。 - 未发现共享 H2、事务传播或顺序依赖导致的假绿。 ## 独立测试结果 ### T2–T9 指定测试组 执行命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' '-Dtest=SkillTaxonomyMigrationTest,SkillCloudSeedDataIntegrationTest,SkillTaxonomyRepositoryTest,SkillCloudQueryServiceTest,SkillMatchServiceTest,SkillLearningPathServiceTest,SkillCloudControllerTest,SkillCloudAuthorizationIntegrationTest,SkillCloudEndToEndIntegrationTest' test ``` 独立结果: ```text Tests run: 71, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS Total time: 01:08 min ``` 分类统计: - SkillTaxonomyMigrationTest:2 - SkillCloudSeedDataIntegrationTest:8 - SkillTaxonomyRepositoryTest:6 - SkillCloudQueryServiceTest:8 - SkillMatchServiceTest:16 - SkillLearningPathServiceTest:10 - SkillCloudControllerTest:14 - SkillCloudAuthorizationIntegrationTest:2 - SkillCloudEndToEndIntegrationTest:5 合计:71。 ### 全量测试 执行命令: ```powershell $env:JAVA_HOME='D:\java\jdk' & 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' test ``` 独立结果: ```text Tests run: 1393, Failures: 2, Errors: 9, Skipped: 0 BUILD FAILURE Total time: 01:57 min ``` 失败集合与实现者日志一致。 ## 全量失败归因 ### T9 阻断 无。全量失败集合中没有任何 Skill Cloud 测试或依赖 T9 测试数据的用例。 ### 项目既有失败 #### 1. CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries - 位置:`CareerProgressServiceTest.java:213`。 - 现象:期望 pendingOfferResponseCount=1,实际=0。 - 根因:测试构造的 Offer 固定过期时间 `2026-09-05T23:59:59`;当前日期为 `2026-09-15`,生产 `CareerProgressService.buildTodo()` 使用 `expireAt.isAfter(now)` 过滤,因此该 Offer 已过期。属于时间依赖的既有测试问题。 - 与 N03 关系:该测试和生产类自 `bf75fc0` 后未在 `d00a630..HEAD` 中修改,且在不运行任何 Skill Cloud 测试时独立复现。 #### 2. JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores - 位置:`JobReviewDimensionControllerTest.java:160-162`。 - 现象:HTTP 200 期望,实际 500;`JobReviewController.list` 收到 null page。 - 根因:Controller 已调用 7 参数 `listReviews(jobId, ratingType, createdFrom, createdTo, unreplied, page, size)`,测试仍 stub 旧的 3 参数 `listReviews(JOB_ID, 0, 20)`,Mockito 未命中导致 null。 - 与 N03 关系:相关 Controller、Service 和测试均未在 `d00a630..HEAD` 中修改,且独立复现。 #### 3. JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1 - 位置:`JobReviewDimensionServiceTest.java:206-210`。 - 现象:`reviewRepository.findPublicReviews(...)` 返回 null,触发 `JobReviewService.java:169` NPE。 - 根因:测试仍 stub 旧方法 `findByJobIdAndStatusAndModerationStatusInOrderByCreatedAtDesc(...)`,与当前实现调用的 `findPublicReviews(...)` 不一致。 - 与 N03 关系:相关 Repository、Service 和测试均未受 N03 变更修改,且独立复现。 ### 环境阻塞 #### 4. LocalResumeStorageServiceTest - 现象:8 项测试在 JUnit extension context 清理阶段报 `AccessDeniedException`,路径为 Windows 临时目录下的 `junit-*`。 - 根因:`WindowsPath.toRealPath()` 在测试 `@TempDir` 清理时访问被拒,测试业务逻辑本身未报断言失败。 - 与 N03 关系:属于 Windows 临时目录权限/沙箱环境问题;相关类和测试自初始提交后未受 N03 修改,且独立复现。 ## 门禁决定 - **T9**:PASS。T9 测试具备真实持久化链路、TOP10 一致性、学习路径顺序、确定性、空场景、身份和隐私断言,并已通过 T2–T9 指定测试组。 - **提交 T9**:允许仅提交 `SkillCloudEndToEndIntegrationTest.java` 及本评审报告;不得把当前工作区其他无关改动混入该提交。 - **继续 T10**:允许进入 T10 的实现与文档工作,但必须先明确处理全量 `mvn test` 的 11 个非 T9 失败。可以将其登记为项目既有/环境阻断并单独修复,或在项目层面正式接受并记录豁免;在此之前不得宣称 TO-11、B3 或发布级验证通过,也不得执行最终收尾。 - **剩余阻断**:仅存在项目级全量回归阻断,不存在 T9 阻断。 ## 证据限制 - 本次验证使用 H2 `MODE=MySQL` 和 Flyway,未执行真实 MySQL 黑盒迁移;MySQL 验证仍应在 T10/发布级验证门禁中完成。 - 本次评审未修改任何实现或测试源码。