# W3-T8 独立复审报告 ## 复审结论 - Verdict:**PASS** - 复审范围: - `mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillCloudController.java` - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudControllerTest.java` - `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudAuthorizationIntegrationTest.java` - 对照前次报告 `changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-T8.md` - 复审方式:静态核对实现、测试断言、DTO、JWT/角色链路与测试清理;独立运行指定 8 个测试类。未修改任何实现或测试文件。 ## 原 Important finding 关闭状态 ### T8-I1:CLOSED 前次问题为三个 OpenAPI 200 示例的 `total`、计数与数组长度矛盾,且测试未读取示例内容。当前修复已关闭该问题: - `SkillCloudController.java:56`:词云示例改为 `total=1` 且 `items` 为 1 项。 - `SkillCloudController.java:103`:匹配示例改为 `matchScore=50`、`matchedCount=1`、`totalCount=2`,命中 1 项、缺失 1 项。 - `SkillCloudController.java:150`:学习路径示例改为 `total=1` 且 `items` 为 1 项。 - `SkillCloudControllerTest.java:267-307`:测试读取 `/v3/api-docs` 中的 `responses.200.content.application/json.examples.success.value`,使用 Jackson 解析字符串示例或对象示例。 - `SkillCloudControllerTest.java:278-280`:词云 data 字段集合精确匹配,`total == items.size()`,技能项字段集合与 `SkillCloudItem` 一致。 - `SkillCloudControllerTest.java:282-292`:匹配字段集合精确匹配,`matchedSkills.size == matchedCount`、`missingSkills.size == totalCount - matchedCount`,并按计数校验 `matchScore`。 - `SkillCloudControllerTest.java:294-299`:学习路径字段集合精确匹配,`total == items.size()`,路径项字段集合与 `SkillLearningPathItem` 一致。 - 对照 DTO: - `SkillCloudResponse.java`:`industry`、`occupation`、`category`、`total`、`items`。 - `SkillCloudItem.java`:`rank`、`skillId`、`skillCode`、`nameZh`、`nameEn`、`sourceType`、`sourceCode`、`categoryId`、`categoryName`、`heat`、`heatSourceCode`、`heatElementId`、`heatProxy`。 - `SkillMatchResponse.java`:`matchScore`、`matchedCount`、`totalCount`、`matchedSkills`、`missingSkills`。 - `SkillLearningPathResponse.java`:`total`、`items`。 - `SkillLearningPathItem.java`:`priority`、`skillId`、`skillName`、`categoryId`、`categoryName`、`suggestion`。 可证伪性:单独篡改 `matchScore` 会在 `SkillCloudControllerTest.java:289-290` 失败;篡改 `total` 会在 `:279`、`:288` 或 `:297` 失败;缩短或扩长数组会因元素数量不一致失败;直接删除示例或改坏 JSON 会在 `:302-306` 失败。 ### T8-I2:CLOSED 前次问题为角色测试通过 Mock `ProfileService` 按 `userId` 决定结果,未证明真实数据库角色集合。当前修复已关闭该问题: - `SkillCloudAuthorizationIntegrationTest.java:42-46`:使用 `@SpringBootTest`、MockMvc、真实 `integration-test` profile 和独立 H2 数据库。 - `:55-74`:测试注入真实 `UserRepository`、真实 `JwtTokenProvider`;只 Mock 三个技能服务及 Redis,不 Mock `ProfileService`。 - `:95-108`:真实保存 `STUDENT`、`COMPANY`、`ADMIN`、`STUDENT+COMPANY`、`COMPANY+ADMIN` 五种角色集合,并在三个端点上分别断言 200、403、403、200、403。 - `SkillCloudController.java:185-198`:身份只取认证主体 `userId`,随后调用真实 `profileService.assertStudentRole(userId)`。 - `ProfileService.java:50-57`:真实读取 `UserRepository` 并按 `User.roles` 中是否包含 `STUDENT` 判断。 - `JwtAuthenticationFilter.java:34-40`:JWT 仅用于解析 subject 并建立认证主体;测试 token 即使统一携带 `roles=STUDENT` claim,生产链路仍按数据库角色拒绝 COMPANY/ADMIN,因此断言真实可证。 可证伪性:若实现改为信任 JWT 的 `roles` claim,COMPANY/ADMIN 场景会因测试 token 的 `STUDENT` claim 返回 200 而失败;若改回 Mock 按 userId 放行,数据库角色集合测试在本类中无法满足,且真实 `ProfileService` 未替换。 ## 额外身份查询参数 - `SkillCloudAuthorizationIntegrationTest.java:112-130`:对三个端点同时传入公司用户的 `userId` 和 `studentId`,但使用学生 JWT;全部返回 200,并验证 `SkillMatchService` 与 `SkillLearningPathService` 收到 JWT 学生 ID,`SkillCloudQueryService` 只收到筛选参数。 - `SkillCloudController.java:73-83`、`:120-130`、`:167-177`:三个接口均未声明 `userId` 或 `studentId` 请求参数,身份只来自 `resolveAuthenticatedStudent()`。 - 结论:额外查询参数不能覆盖 JWT 主体。 ## 端点、参数与安全回归 - `SkillCloudController.java:73`、`:120`、`:167`:三个 GET 路由保持为 `/skills/word-cloud`、`/skills/match`、`/skills/learning-path`。 - 三个方法均要求正整数 `industryId`、`occupationId`,可选正整数 `categoryId`;`@Validated` 与 `@Positive` 生效。 - `SecurityConfig.java:77-78`:`GET /skills/**` 要求 authenticated,匿名请求无法进入 Controller。 - `SkillCloudControllerTest.java:156-165`:三个端点匿名请求均断言 401。 - `SkillCloudControllerTest.java:168-206`:覆盖必填参数缺失、非正整数和可选参数非法返回 400。 - `SkillCloudControllerTest.java:110-154`:三个 STUDENT 成功路径均返回统一 `ApiResponse` 和对应 Service DTO 载荷。 - 未发现 T8 修改导致三个端点、参数校验或 SecurityConfig 行为回归。 ## 测试清理与范围 - `SkillCloudControllerTest.java:50-51` 与 `SkillCloudAuthorizationIntegrationTest.java:45-46` 使用不同的 H2 数据库名,避免类别间共享数据。 - `SkillCloudControllerTest.java:105-108`、`SkillCloudAuthorizationIntegrationTest.java:89-92` 均在每个测试后执行 `userRepository.deleteAll()`。 - 指定 8 个测试类完成后全部通过,未观察到外键残留或后续测试污染。 - 当前工作区中未出现 `docs/skill-cloud-api.md`,未修改 `docs/openapi-rc.json`、`docs/frontend-integration.md`,未实现 T9/T10。 ## 独立测试结果 环境:JDK 17.0.16,Maven 3.9.7。 执行命令: ```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' test ``` 结果: ```text Tests run: 66, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS Total time: 53.008 s ``` 分类结果: - `SkillTaxonomyMigrationTest`:2 - `SkillCloudSeedDataIntegrationTest`:8 - `SkillTaxonomyRepositoryTest`:6 - `SkillCloudQueryServiceTest`:8 - `SkillMatchServiceTest`:16 - `SkillLearningPathServiceTest`:10 - `SkillCloudControllerTest`:14 - `SkillCloudAuthorizationIntegrationTest`:2 ## 剩余阻断项 无 Critical、Important 阻断项。T8-I1、T8-I2 均已关闭,T8 可进入后续流程。 非阻断备注:200 响应目前通过示例和测试断言锁定字段及计数,未显式绑定具体响应 schema;本轮要求已满足,不阻断 T8。