9 changed files with 929 additions and 4 deletions
@ -0,0 +1,105 @@ |
|||
# 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。 |
|||
@ -0,0 +1,87 @@ |
|||
# W3-T8 独立评审报告 |
|||
|
|||
## 评审结论 |
|||
|
|||
- Verdict:**FAIL** |
|||
- 评审对象: |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/skill/SkillCloudController.java` |
|||
- `mydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.java` |
|||
- `mydreamjob-server/src/test/java/com/mydreamjob/skill/SkillCloudControllerTest.java` |
|||
- 依据:修订后的 `execution-contract.md`、`design.md`、`tasks.md`,三份 spec,以及现有 JWT/Security/Controller 约定。 |
|||
- 评审方式:静态核对实现与契约,独立运行指定回归,检查 Git 工作区范围;未修改实现文件。 |
|||
|
|||
## 独立测试结果 |
|||
|
|||
执行命令: |
|||
|
|||
```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' test |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
Tests run: 69, Failures: 0, Errors: 0, Skipped: 0 |
|||
BUILD SUCCESS |
|||
``` |
|||
|
|||
其中 `SkillCloudControllerTest` 为 19 项通过。`git diff --check HEAD` 对 T8 三个文件无空白错误。`docs/openapi-rc.json`、`docs/frontend-integration.md` 无改动,`docs/skill-cloud-api.md` 不存在;未发现 T9/T10 实现。 |
|||
|
|||
## 阻断项 |
|||
|
|||
### T8-I1(Important):OpenAPI 成功示例与 DTO/运行时契约矛盾 |
|||
|
|||
文件与行号: |
|||
|
|||
- `SkillCloudController.java:56`:词云示例中 `data.total=15`,但 `items` 只有 1 项。 |
|||
- `SkillCloudController.java:103`:匹配示例中 `matchedCount=8`、`totalCount=10`,但同时返回 `matchedSkills=[]`、`missingSkills=[]`。 |
|||
- `SkillCloudController.java:150`:学习路径示例中 `data.total=2`,但 `items` 只有 1 项。 |
|||
- `SkillCloudControllerTest.java:247-269`:OpenAPI 测试只检查路径存在、`bearerAuth`、状态码和参数必填性,没有校验示例内容或 200 响应 schema,因此上述矛盾不会使测试失败。 |
|||
- `changes/n03-skill-cloud/execution-contract.md:120`:明确要求“OpenAPI 注解、示例和实际响应必须一致”。 |
|||
|
|||
影响:客户端联调、接口文档和运行时契约可能按错误示例理解 TOP10、命中/缺失列表与总数关系。应修正三个示例,使计数、数组长度和 DTO 字段一致,并在 T8 的 OpenAPI 测试中锁定关键示例字段;同时建议为 200 响应声明对应 `SkillCloudResponse`、`SkillMatchResponse`、`SkillLearningPathResponse` schema,而非只有示例。 |
|||
|
|||
### T8-I2(Important):角色测试通过 Mock 按 userId 放行,未证明真实 STUDENT 校验和多角色集合 |
|||
|
|||
文件与行号: |
|||
|
|||
- `SkillCloudControllerTest.java:93-97`:`ProfileService` 被 `@MockBean` 替换,并按 `100L/200L/300L` 直接设置成功或抛 `FORBIDDEN`。 |
|||
- `SkillCloudControllerTest.java:161-171`:测试传入 `COMPANY`/`ADMIN` 角色声明,但实际 403 取决于 mock 对 `200L` 的硬编码行为,而非数据库角色集合。 |
|||
- `SkillCloudControllerTest.java:296-301`:JWT 的 `roles` claim 只作为测试辅助值传入。 |
|||
- `SkillCloudController.java:185-198`:Controller 确实从 `SecurityContext` 取身份并调用 `profileService.assertStudentRole(userId)`。 |
|||
- `ProfileService.java:50-57`:生产实现从数据库读取用户,并使用 `anyMatch(STUDENT)`,因此单角色和包含 STUDENT 的多角色在代码层面是正确的。 |
|||
- 现有 `ProfileControllerTest.java:167-185` 覆盖了数据库单角色 COMPANY/ADMIN 的 403,但没有覆盖 STUDENT+COMPANY 或 STUDENT+ADMIN 多角色集合,也没有覆盖本组 `/skills/**` 端点的真实角色链路。 |
|||
|
|||
影响:T8 的 Controller 测试无法证明 `COMPANY/ADMIN` 是被真实服务端角色检查拒绝,也无法证明多角色用户中只要包含 STUDENT 就能通过;它可能在 `ProfileService.assertStudentRole` 被替换或角色逻辑回归时仍通过。应增加使用真实 `ProfileService`、真实数据库用户的集成场景,至少覆盖: |
|||
|
|||
- COMPANY、ADMIN:预期 403。 |
|||
- STUDENT+COMPANY:预期允许。 |
|||
- 无 STUDENT 的多角色集合:预期 403。 |
|||
|
|||
## 非阻断项 |
|||
|
|||
### T8-M1:身份注入测试只覆盖 `/skills/match` |
|||
|
|||
文件与行号:`SkillCloudControllerTest.java:214-225`。 |
|||
|
|||
当前实现三个端点都调用同一个 `resolveAuthenticatedStudent()`,代码层面正确;但测试只验证 `match` 端点会忽略 `userId`/`studentId`,没有对词云和学习路径端点做同样断言。建议参数化覆盖三个路径,并 `verify` 服务收到的是 JWT 中的 `100L`。 |
|||
|
|||
### T8-M2:隐私断言只匹配 `skills_json` |
|||
|
|||
文件与行号:`SkillCloudControllerTest.java:227-244`。 |
|||
|
|||
测试没有覆盖驼峰形式 `skillsJson` 或其他可能的新增个人技能字段。当前返回 DTO 为受控记录类型,因此非阻断;建议后续端到端测试使用真实服务响应,并对 DTO schema/序列化字段做更严格断言。 |
|||
|
|||
## 检查结论 |
|||
|
|||
- 三个路径和参数语义:**通过**。`industryId`、`occupationId` 必填,`categoryId` 可选;实测缺参/0/负数返回 400。 |
|||
- 身份来源:**通过**。Controller 只从 `SecurityContext` 取主体,不绑定 `userId`/`studentId`。 |
|||
- 权限行为:**实现通过,测试证明不完整**。匿名 401、COMPANY/ADMIN 403 的 MockMvc 场景通过;但角色拒绝依赖 mock,多角色未覆盖。 |
|||
- `ApiResponse` 与成功 DTO:**通过**。成功响应统一包装,字段与 Service DTO 一致。 |
|||
- OpenAPI:**不通过**。注解具备 Tag、Operation、SecurityRequirement、参数说明和 200/400/401/403,但成功示例与 DTO/运行时数据矛盾,且 200 响应缺少明确 schema。 |
|||
- 改动范围:**通过**。未修改 `docs/openapi-rc.json`,未实现 T9/T10。 |
|||
|
|||
## 未决问题 |
|||
|
|||
- 当前安全层的 401/403 JSON 由既有 `SecurityConfig` 直接写出,不是 `ApiResponse` 实体序列化结果;是否在本变更中统一该历史约定,需由项目维护者决定。本次未将其列为 T8 阻断项,因为它与仓库既有安全配置约定一致。 |
|||
@ -0,0 +1,177 @@ |
|||
# 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/发布级验证门禁中完成。 |
|||
- 本次评审未修改任何实现或测试源码。 |
|||
@ -0,0 +1,168 @@ |
|||
# N03 SDD W3 最终独立复审报告 |
|||
|
|||
## 复审对象 |
|||
|
|||
- 变更:`n03-skill-cloud` |
|||
- 基线:`df7238b` |
|||
- 复审焦点:`W3-N-I1` 修复及其对 `W3-I1`、`W3-I2` 的回归影响。 |
|||
- 工作区对象:`docs/openapi-rc.json`、`docs/verify_openapi.py`,并核对 `SkillCloudController` 和权限测试中的既有修复。 |
|||
- 环境:JDK 17(`D:\java\jdk`)、Maven 3.9.7(`D:\maven\apache-maven-3.9.7\bin\mvn.cmd`)。 |
|||
- 前轮报告:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave-rereview.md`。 |
|||
|
|||
## Verdict |
|||
|
|||
**PASS** |
|||
|
|||
`W3-N-I1` 已关闭。两个既有 `cancel` operationId 已恢复,静态 OpenAPI 不再受无关历史契约变更影响;Skill Cloud 三个 operationId 的冲突检查仍有效。`W3-I1`、`W3-I2` 保持关闭。W3 代码可以进入收尾和提交阶段。 |
|||
|
|||
该 PASS 仅针对 W3/N-I1 代码与契约修复,不代表完整项目回归通过。完整 `mvn test` 仍有 2 failures + 9 errors,变更最终关闭或 TO-11 前仍需修复或正式豁免。 |
|||
|
|||
## N-I1 关闭证据 |
|||
|
|||
### 既有 cancel 契约已恢复 |
|||
|
|||
当前 `docs/openapi-rc.json`: |
|||
|
|||
- `POST /interviews/{interviewId}/cancel`:`operationId = cancel` |
|||
- `POST /profile/me/deletion/cancel`:`operationId = cancel` |
|||
|
|||
对三个 Git 状态进行了全量 operationId 对比: |
|||
|
|||
```text |
|||
df7238b -> HEAD |
|||
新增: |
|||
/skills/word-cloud getWordCloud |
|||
/skills/match getMatch |
|||
/skills/learning-path getLearningPath |
|||
|
|||
HEAD -> 当前工作区 |
|||
仅: |
|||
/skills/match getMatch -> getSkillMatch |
|||
|
|||
df7238b -> 当前工作区 |
|||
仅三个新增 Skill Cloud operationId,其中 /skills/match 为 getSkillMatch |
|||
``` |
|||
|
|||
因此: |
|||
|
|||
- 两个历史 `cancel` 值已恢复,没有再次被改名为 `cancelInterview` 或 `cancelAccountDeletion`。 |
|||
- 除三个 Skill Cloud operationId 外,没有其他 operationId 在基线 `df7238b` 到当前工作区之间发生变化。 |
|||
- `docs/verify_openapi.py` 已移除全文档唯一性要求,只对三个 Skill Cloud operationId 逐个执行全文档“恰好出现于对应路径”的断言。 |
|||
- 历史重复值 `cancel` 不再阻断校验,但 Skill Cloud 的新 operationId 仍不能与其他 operation 冲突。 |
|||
|
|||
结论:**N-I1 已关闭。** |
|||
|
|||
## W3-I1 保持关闭 |
|||
|
|||
当前契约与运行时测试保持一致: |
|||
|
|||
- `/skills/word-cloud` -> `getWordCloud` |
|||
- `/skills/match` -> `getSkillMatch` |
|||
- `/skills/learning-path` -> `getLearningPath` |
|||
|
|||
`SkillCloudControllerTest` 在运行时 `/v3/api-docs` 上校验三个 operationId 的精确值及其唯一归属;`docs/verify_openapi.py` 对静态 OpenAPI 执行同等约束。前轮的 `getMatch` 重复冲突没有回退。 |
|||
|
|||
结论:**W3-I1 保持关闭。** |
|||
|
|||
## W3-I2 保持关闭 |
|||
|
|||
401 契约未回退: |
|||
|
|||
- 运行时匿名请求和非法 JWT 均返回 `code=401`、`message=not authenticated`、`timestamp=""`。 |
|||
- 响应不包含 `requestId` 或 `data`。 |
|||
- `SkillCloudAuthorizationIntegrationTest` 同时覆盖匿名和 `Bearer invalid-token`。 |
|||
- 静态 OpenAPI 中三个 Skill Cloud 接口的 401 均引用 `UnauthorizedResponse`,示例与运行时一致。 |
|||
- `docs/verify_openapi.py` 继续校验 401 schema、精确响应字段和示例内容。 |
|||
|
|||
结论:**W3-I2 保持关闭。** |
|||
|
|||
## 独立验证结果 |
|||
|
|||
### 静态 OpenAPI 校验 |
|||
|
|||
命令: |
|||
|
|||
```powershell |
|||
python docs\verify_openapi.py |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
paths: 129 |
|||
All checks passed! |
|||
``` |
|||
|
|||
通过。 |
|||
|
|||
### 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: 73, Failures: 0, Errors: 0, Skipped: 0 |
|||
BUILD SUCCESS |
|||
Total time: 54.628 s |
|||
``` |
|||
|
|||
通过。 |
|||
|
|||
### 打包 |
|||
|
|||
命令: |
|||
|
|||
```powershell |
|||
$env:JAVA_HOME='D:\java\jdk' |
|||
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -DskipTests package |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
BUILD SUCCESS |
|||
Total time: 2.387 s |
|||
``` |
|||
|
|||
通过。 |
|||
|
|||
## 项目级 blocker |
|||
|
|||
完整回归命令: |
|||
|
|||
```powershell |
|||
$env:JAVA_HOME='D:\java\jdk' |
|||
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' test |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
Tests run: 1395, Failures: 2, Errors: 9, Skipped: 0 |
|||
BUILD FAILURE |
|||
Total time: 01:50 min |
|||
``` |
|||
|
|||
失败项: |
|||
|
|||
- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:期望 1,实际 0,时间依赖问题。 |
|||
- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:期望 200,实际 500,旧 Mock 签名问题。 |
|||
- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:返回结果 null 导致 NPE,旧 Repository stub 问题。 |
|||
- `LocalResumeStorageServiceTest`:8 项错误,Windows JUnit 临时目录清理阶段异常。 |
|||
|
|||
Skill Cloud 目标测试在完整套件中均通过,但完整 `mvn test` 未全绿。上述 2 failures + 9 errors 仍是项目级 blocker;在修复或正式豁免前,不得声称完整回归通过,也不得进入最终变更关闭或 TO-11。 |
|||
|
|||
## 收尾结论 |
|||
|
|||
- W3-N-I1:**关闭** |
|||
- W3-I1:**保持关闭** |
|||
- W3-I2:**保持关闭** |
|||
- 是否允许 W3 代码收尾:**允许** |
|||
- 是否允许最终变更关闭:**不允许**,完整 `mvn test` 仍有 2 failures + 9 errors,需修复或正式豁免。 |
|||
- 提交范围要求:仓库中存在与本复审无关的暂存、修改和未跟踪文件;W3 收尾提交时必须只包含 N03 范围内的文件,避免混入无关变更。 |
|||
@ -0,0 +1,178 @@ |
|||
# N03 SDD W3 聚焦复审报告 |
|||
|
|||
## 复审对象 |
|||
|
|||
- 代码基线:`cd9220b`,修复差异仍位于工作区,未提交。 |
|||
- 复审范围:W3-I1、W3-I2 两项 Important finding 的聚焦修复。 |
|||
- 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 |
|||
- 对照报告:`changes/n03-skill-cloud/.superpowers/sdd/reviews/W3-wave.md`。 |
|||
|
|||
## Verdict |
|||
|
|||
**FAIL** |
|||
|
|||
W3-I1 的原始 `/skills/match` operationId 冲突已修复,W3-I2 要求的运行时 401 契约、Markdown 文档和静态 OpenAPI 已对齐;但本次为满足全局 operationId 唯一性而修改了两处既有 `cancel` 契约,新增一项 Important 问题。 |
|||
|
|||
因此 W3 仍不能判定通过,不能进入收尾或 TO-11。 |
|||
|
|||
## W3-I1:operationId 冲突 |
|||
|
|||
### 关闭状态 |
|||
|
|||
**原始 finding 已关闭。** |
|||
|
|||
- `SkillCloudController.java:44` 显式设置 `operationId=getWordCloud`。 |
|||
- `SkillCloudController.java:92` 显式设置 `operationId=getSkillMatch`。 |
|||
- `SkillCloudController.java:140` 显式设置 `operationId=getLearningPath`。 |
|||
- 静态 OpenAPI 三个 Skill Cloud operationId 与上述值完全一致: |
|||
- `/skills/word-cloud` = `getWordCloud` |
|||
- `/skills/match` = `getSkillMatch` |
|||
- `/skills/learning-path` = `getLearningPath` |
|||
- `docs/verify_openapi.py:165-168` 对三个 operationId 做精确断言。 |
|||
- `SkillCloudControllerTest.skillOperationIdsAreExactAndGloballyUnique` 通过运行时 `/v3/api-docs` 校验三个接口的 operationId,并确认每个目标 operationId 只对应目标路径。 |
|||
|
|||
### 新增问题 N-I1(Important):修改了既有 OpenAPI 契约 |
|||
|
|||
`docs/verify_openapi.py:27-31` 新增了对全文档 operationId 的无条件唯一性断言。为让该断言通过,本次修复还修改了与 N03 无关的两个既有接口: |
|||
|
|||
- `POST /interviews/{interviewId}/cancel`:`cancel` → `cancelInterview` |
|||
- `POST /profile/me/deletion/cancel`:`cancel` → `cancelAccountDeletion` |
|||
|
|||
问题如下: |
|||
|
|||
1. `operationId` 属于 OpenAPI 公共契约,常用于客户端和 SDK 生成。修改既有 operationId 属于兼容性破坏,即使 HTTP 路径和运行时行为不变,也可能导致生成客户端的方法名变化或升级失败。 |
|||
2. 两个 `cancel` 重复值是本次变更前已经存在的历史问题,不是 N03 引入的问题,也不在 W3-I1 的修复范围内。 |
|||
3. `InterviewController.java:132-136` 和 `AccountDeletionController.java:112-115` 仍使用方法名 `cancel`,且没有显式 operationId;因此静态 OpenAPI 被改为两个新名称后,静态契约与运行时 SpringDoc 文档发生漂移。 |
|||
4. `SkillCloudControllerTest.skillOperationIdsAreExactAndGloballyUnique` 的名称虽然包含 “GloballyUnique”,但实际只验证三个 Skill Cloud operationId 各自只出现一次,并没有验证全文档唯一性。真正强制全文档唯一性的是 `verify_openapi.py`,而它迫使本次变更修改历史契约。 |
|||
|
|||
**严重级别:Important。** |
|||
|
|||
**最小修复建议:** |
|||
|
|||
- 将 `docs/openapi-rc.json` 中两个既有接口的 operationId 恢复为 `cancel`,不要在本变更中修改既有接口契约。 |
|||
- 将 `docs/verify_openapi.py` 的检查改为“新增接口不得产生新冲突”,而不是“修复全部历史冲突”。例如: |
|||
- 保留已知历史重复集合 `{'cancel'}`; |
|||
- 断言除该允许集合外没有其他重复 operationId; |
|||
- 断言 `getWordCloud/getSkillMatch/getLearningPath` 各出现且仅出现一次; |
|||
- 断言它们不与历史或未来任何非允许 operationId 冲突。 |
|||
- 如果项目要求彻底消除全局 operationId 重复,应另开变更,同时在两个既有 Controller 上显式设置 operationId,并评估客户端兼容性与迁移方案。 |
|||
|
|||
## W3-I2:401 运行时与文档契约 |
|||
|
|||
### 关闭状态 |
|||
|
|||
**本次要求范围内的 finding 已关闭。** |
|||
|
|||
- 运行时实现仍由全局 Security 入口返回: |
|||
- `code=401` |
|||
- `message="not authenticated"` |
|||
- `timestamp=""` |
|||
- 不包含 `requestId` |
|||
- 不包含 `data` |
|||
- `SkillCloudControllerTest.anonymousRequestUsesSecurityEntryPointContract` 对三个接口执行上述字段级断言,并额外断言响应字段集合恰好为 `code/message/timestamp`。 |
|||
- `SkillCloudAuthorizationIntegrationTest.anonymousAndInvalidJwtUseSecurityEntryPointContract` 同时覆盖匿名请求和 `Bearer invalid-token`。 |
|||
- `docs/skill-cloud-api.md` 已明确区分 `401` 裸 JSON 与统一 `ApiResponse`,示例与运行时一致。 |
|||
- 静态 OpenAPI 三个 Skill Cloud `401` 响应均引用 `UnauthorizedResponse`,示例均为: |
|||
- `code=401` |
|||
- `message="not authenticated"` |
|||
- `timestamp=""` |
|||
- 无 `requestId`、无 `data` |
|||
- `docs/verify_openapi.py:173-185` 精确校验上述静态 401 契约。 |
|||
|
|||
### 非阻断观察(Minor) |
|||
|
|||
`SkillCloudController.java:66、114、162` 的 401 注解仍使用 `@Schema(implementation = ApiResponse.class)`。因此运行时 `/v3/api-docs` 的 401 schema 仍可能展示通用 `ApiResponse`,而静态 OpenAPI 已改为 `UnauthorizedResponse`。本次要求明确检查的是 Markdown 文档和静态 OpenAPI,且该问题不阻碍运行时行为或本 finding 的既定修复;如运行时 API 文档也被视为发布契约,应另行聚焦修正控制器注解并补充运行时 OpenAPI 断言。 |
|||
|
|||
## 独立验证结果 |
|||
|
|||
### 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: 73, Failures: 0, Errors: 0, Skipped: 0 |
|||
BUILD SUCCESS |
|||
Total time: 55.399 s |
|||
``` |
|||
|
|||
分类统计: |
|||
|
|||
- SkillTaxonomyMigrationTest:2 |
|||
- SkillCloudSeedDataIntegrationTest:8 |
|||
- SkillTaxonomyRepositoryTest:6 |
|||
- SkillCloudQueryServiceTest:8 |
|||
- SkillMatchServiceTest:16 |
|||
- SkillLearningPathServiceTest:10 |
|||
- SkillCloudControllerTest:15 |
|||
- SkillCloudAuthorizationIntegrationTest:3 |
|||
- SkillCloudEndToEndIntegrationTest:5 |
|||
|
|||
### OpenAPI 静态校验 |
|||
|
|||
命令: |
|||
|
|||
```powershell |
|||
python docs\verify_openapi.py |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
paths: 129 |
|||
All checks passed! |
|||
``` |
|||
|
|||
结论:静态校验通过,但该通过依赖于 N-I1 所描述的既有 operationId 修改,不能据此判定 W3 通过。 |
|||
|
|||
### 打包 |
|||
|
|||
命令: |
|||
|
|||
```powershell |
|||
$env:JAVA_HOME='D:\java\jdk' |
|||
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' -DskipTests package |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
BUILD SUCCESS |
|||
``` |
|||
|
|||
### 完整 `mvn test` 项目级回归 |
|||
|
|||
命令: |
|||
|
|||
```powershell |
|||
$env:JAVA_HOME='D:\java\jdk' |
|||
& 'D:\maven\apache-maven-3.9.7\bin\mvn.cmd' test |
|||
``` |
|||
|
|||
结果: |
|||
|
|||
```text |
|||
Tests run: 1395, Failures: 2, Errors: 9, Skipped: 0 |
|||
BUILD FAILURE |
|||
``` |
|||
|
|||
所有 Skill Cloud 目标测试在全量套件中通过;失败仍集中在既有或环境相关测试: |
|||
|
|||
- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:时间依赖失败。 |
|||
- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:旧 Mock 签名导致 500。 |
|||
- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:旧 Repository stub 导致 NPE。 |
|||
- `LocalResumeStorageServiceTest`:Windows JUnit 临时目录 `AccessDeniedException`,8 个错误。 |
|||
|
|||
因此不能声称完整 `mvn test` 通过。上述 2 failures + 9 errors 仍是项目级 blocker,未形成正式豁免前不能关闭变更或进入 TO-11。 |
|||
|
|||
## 剩余阻断项 |
|||
|
|||
1. **N-I1(Important)**:恢复既有 `cancel` operationId,并调整校验脚本,使唯一性检查只拦截新增冲突,不以修改历史契约为代价。 |
|||
2. **项目级验证 blocker**:完整 `mvn test` 仍有 2 failures + 9 errors,需修复或正式豁免。 |
|||
3. **Minor**:运行时 `/v3/api-docs` 的 401 schema 仍可能需要从通用 `ApiResponse` 切换为 `UnauthorizedResponse`,以避免静态/运行时 OpenAPI 漂移。 |
|||
@ -0,0 +1,198 @@ |
|||
# N03 SDD W3 整批独立评审报告 |
|||
|
|||
## 评审对象 |
|||
|
|||
- 范围:`df7238b..cd9220b` |
|||
- Wave:W3 |
|||
- 批次:B3 / T8、T9、T10 |
|||
- 提交: |
|||
- `3e353bd feat: expose student skill cloud endpoints` |
|||
- `7873a42 test: add skill cloud end-to-end regression` |
|||
- `cd9220b docs: publish skill cloud api contract` |
|||
- 评审依据:修订后的 `execution-contract.md`、`design.md`、`tasks.md`、三份 spec,以及 `W3-T8*`、`W3-T9.md`。 |
|||
- 环境:`JAVA_HOME=D:\java\jdk`,Maven `D:\maven\apache-maven-3.9.7\bin\mvn.cmd`。 |
|||
|
|||
## Verdict |
|||
|
|||
**FAIL** |
|||
|
|||
W3 的运行时接口、STUDENT 权限、参数校验、DTO 序列化、端到端一致性和打包均通过独立验证;但 T10 发布的 OpenAPI 契约存在两项 Important 问题: |
|||
|
|||
1. 新增 `/skills/match` 的 `operationId=getMatch` 与既有 `/applications/{id}/match` 冲突。 |
|||
2. 匿名或无效 JWT 的运行时 401 响应为 `code=401` 的裸 JSON,而文档和静态 OpenAPI 声明 `2001` 及统一 `ApiResponse` 语义。 |
|||
|
|||
因此 T10 不能判定通过,且本 wave 不允许进入收尾或 TO-11。 |
|||
|
|||
## T8:Controller、权限、参数与 ApiResponse |
|||
|
|||
### 通过项 |
|||
|
|||
- 三个路由存在且均为 GET: |
|||
- `/skills/word-cloud` |
|||
- `/skills/match` |
|||
- `/skills/learning-path` |
|||
- `industryId`、`occupationId` 为必填正整数;`categoryId` 为可选正整数。 |
|||
- 成功后统一返回 `ResponseEntity<ApiResponse<T>>`,并保留 `X-Request-Id`。 |
|||
- `SecurityConfig` 仅放行已认证请求到 `/skills/**`;Controller 再通过真实 `ProfileService` 按数据库角色校验 STUDENT。 |
|||
- `SkillCloudAuthorizationIntegrationTest` 使用真实 `UserRepository`、数据库角色集合、JWT 过滤器和 `ProfileService`,没有 mock 核心权限链路。 |
|||
- 已覆盖 STUDENT=200、COMPANY=403、ADMIN=403、STUDENT+COMPANY=200、COMPANY+ADMIN=403。 |
|||
- 额外传入 `userId`、`studentId` 不能覆盖 JWT 主体。 |
|||
- `SkillCloudControllerTest` 共 14 项,授权集成测试共 2 项,独立执行均通过。 |
|||
- 未发现 B3 修改已有接口语义或引入无关模块变更。提交范围仅涉及新增 Controller、SecurityConfig 一条 GET 规则、技能测试和文档/OpenAPI。 |
|||
|
|||
### 阻断项 |
|||
|
|||
#### W3-I1:`operationId=getMatch` 重复 |
|||
|
|||
- `docs/openapi-rc.json:1186`:既有 `/applications/{id}/match` GET。 |
|||
- `docs/openapi-rc.json:6460`:新增 `/skills/match` GET。 |
|||
- 两处均使用 `operationId: "getMatch"`。 |
|||
- `SkillCloudController.java:120-121` 的方法名也是 `getMatch`,运行时生成的 operationId 很可能同样冲突;现有测试没有检查全局 operationId 唯一性。 |
|||
- `docs/verify_openapi.py:146` 反而明确断言新增接口的 operationId 必须是 `getMatch`,把冲突值固化为通过条件。 |
|||
- OpenAPI 规范要求 operationId 全局唯一;重复值会导致客户端/文档代码生成器出现歧义或相互覆盖。 |
|||
|
|||
修复方向:给 `SkillCloudController.getMatch` 显式设置唯一 operationId,例如 `getSkillMatch`,同步静态 OpenAPI 和 verify 脚本,并增加全局 operationId 唯一性断言。 |
|||
|
|||
#### W3-I2:401 运行时响应与文档/OpenAPI 不一致 |
|||
|
|||
- 运行时匿名请求由 `SecurityConfig.java:50-54` 直接写出 `{"code":401,...}`,且不包含 `requestId`。 |
|||
- `SkillCloudControllerTest.java:158-164` 明确断言匿名 401 的 `$.code=401`。 |
|||
- `docs/skill-cloud-api.md:14` 声明使用统一 `ApiResponse`。 |
|||
- `docs/skill-cloud-api.md:230-240` 声明 401 的典型业务码为 `2001`,示例也返回 `code=2001` 和 `requestId`。 |
|||
- `docs/openapi-rc.json` 的三个新增 401 响应示例均使用 `code=2001`。 |
|||
- 因此静态契约与真实运行时对相同的“未登录/无效 JWT”场景给出了不同的业务码和响应语义。 |
|||
|
|||
修复方向:二选一并做可证伪测试: |
|||
|
|||
- 保持现有全局安全行为,更新接口文档和静态 OpenAPI 为真实 `code=401` 响应;或 |
|||
- 在授权范围内调整安全入口,使 401 使用统一的 `ApiResponse` 和 `2001`,同时评估对所有既有接口的兼容性。 |
|||
|
|||
## T9:端到端一致性 |
|||
|
|||
### 通过项 |
|||
|
|||
`SkillCloudEndToEndIntegrationTest` 共 5 项,使用真实 H2/Flyway、Security、Controller、Service、Repository 和 V36 数据,仅 Mock `RedisTemplate` 以隔离基础设施依赖,独立执行通过。 |
|||
|
|||
已验证: |
|||
|
|||
- 词云返回完整排序列表,`rank` 连续,`heat DESC`,空热度末位。 |
|||
- 匹配集合严格等于 `min(10,total)`,`matchedSkills + missingSkills` 等于同一 TOP10,且各自保持顺序。 |
|||
- 学习路径严格复用 `missingSkills` 顺序,`priority` 从 1 连续递增。 |
|||
- 少于 10 项时按实际大小计算,`1/3` 返回 33;空范围返回 0 和空列表。 |
|||
- 重复调用三个接口返回完全相同的数据。 |
|||
- 合法空分类覆盖 `total=0`、`matchScore=0`、`learningPath.total=0`。 |
|||
- 成功响应未发现手机号、邮箱、简历文件名/URL 或原始技能 JSON。 |
|||
- 额外身份查询参数不能覆盖学生 JWT 主体。 |
|||
|
|||
### 非阻断测试缺口 |
|||
|
|||
- “无写入”目前只比较 8 张表的行数,只能发现 INSERT/DELETE,不能发现仅 UPDATE 的写操作。 |
|||
- 隐私断言未显式覆盖 `realName`、`bio`、`skillsJson` 等驼峰或实际值。 |
|||
- 端到端测试主要证明三个接口复用同一 TOP10,具体名称/别名匹配分支由 T6 测试覆盖,这一点可以接受。 |
|||
|
|||
以上均为可加强项,不单独阻断 T9 运行时实现。 |
|||
|
|||
## T10:文档与 OpenAPI |
|||
|
|||
### 通过项 |
|||
|
|||
- 静态 OpenAPI path 数从 126 增至 129,三个新路径均存在。 |
|||
- 三个新路径均声明 `bearerAuth`、200/400/401/403,以及必填/可选参数。 |
|||
- 新增 Schema 字段与 Java DTO 字段在静态检查中一致。 |
|||
- `docs/skill-cloud-api.md` 和 `docs/frontend-integration.md` 覆盖 TOP10 截取、匹配公式、排序、空数据、权限、无 AI 和无持久化语义。 |
|||
- `python docs\verify_openapi.py` 独立执行成功,输出 `paths: 129` 和 `All checks passed`。 |
|||
- 未发现三个新接口之外的接口语义被改变。 |
|||
|
|||
### 阻断项 |
|||
|
|||
见上方 `W3-I1`、`W3-I2`。T10 当前不能判定通过。 |
|||
|
|||
### verify 脚本的不足 |
|||
|
|||
- `docs/verify_openapi.py:164` 使用 `expected_fields <= properties`,只验证字段至少存在,不验证字段集合完全相等、类型、nullable、required 或静态契约与运行时的对应关系;新增多余字段仍可通过。 |
|||
- 脚本没有全局 operationId 唯一性断言,因此 `W3-I1` 未被发现。 |
|||
- 脚本检查静态 OpenAPI,而 `SkillCloudControllerTest` 检查运行时 `/v3/api-docs`,两者没有做等价性比较;当前静态文件使用 `example`,运行时注解生成使用 `examples`,进一步说明两者可能各自漂移。 |
|||
- 精确 path 数断言可以用于回归基线,但不能替代 operationId 唯一性、Schema 完整性和错误响应一致性验证。 |
|||
|
|||
## 独立执行结果 |
|||
|
|||
### 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: 59.133 s |
|||
``` |
|||
|
|||
分类: |
|||
|
|||
- SkillTaxonomyMigrationTest:2 |
|||
- SkillCloudSeedDataIntegrationTest:8 |
|||
- SkillTaxonomyRepositoryTest:6 |
|||
- SkillCloudQueryServiceTest:8 |
|||
- SkillMatchServiceTest:16 |
|||
- SkillLearningPathServiceTest:10 |
|||
- SkillCloudControllerTest:14 |
|||
- SkillCloudAuthorizationIntegrationTest:2 |
|||
- SkillCloudEndToEndIntegrationTest:5 |
|||
|
|||
### OpenAPI 校验 |
|||
|
|||
```text |
|||
python docs\verify_openapi.py |
|||
paths: 129 |
|||
All checks passed! |
|||
``` |
|||
|
|||
该命令通过,但不覆盖上述 operationId 冲突、401 运行时差异和静态/运行时 Schema 等价性,因此不能抵消 W3-I1、W3-I2。 |
|||
|
|||
### 打包 |
|||
|
|||
```text |
|||
mvn -DskipTests package |
|||
BUILD SUCCESS |
|||
Total time: 2.401 s |
|||
``` |
|||
|
|||
### 完整 `mvn test` |
|||
|
|||
独立运行结果: |
|||
|
|||
```text |
|||
Tests run: 1393, Failures: 2, Errors: 9, Skipped: 0 |
|||
BUILD FAILURE |
|||
Total time: 01:58 min |
|||
``` |
|||
|
|||
Skill Cloud 全部测试类在该套件中均通过。 |
|||
|
|||
## 全量 blocker 归类 |
|||
|
|||
以下失败均不在 `df7238b..cd9220b` 修改范围内,不能归因于 W3 引入的行为回归: |
|||
|
|||
- `CareerProgressServiceTest.fullAggregation_returnsCorrectSummaries`:固定 Offer 过期时间 `2026-09-05` 已早于当前日期,属于时间依赖测试问题。 |
|||
- `JobReviewDimensionControllerTest.listReviews_shouldReturnDimensionScores`:仍 stub 旧服务方法签名。 |
|||
- `JobReviewDimensionServiceTest.listReviews_shouldReturnDimensionScoresWithoutN1`:仍 stub 旧 Repository 方法,导致 NPE。 |
|||
- `LocalResumeStorageServiceTest`:8 项均在 Windows JUnit 临时目录清理阶段因 `AccessDeniedException` 报错,属于环境/临时目录问题。 |
|||
|
|||
归类结论: |
|||
|
|||
- **不是 W3 代码阻断。** |
|||
- **仍然是项目级验证阻断,应阻止 `closing`/TO-11**,除非先修复,或由项目层面正式记录并批准豁免;合同退出条件不能被视为满足。 |
|||
- 本次独立验证未运行真实 MySQL 黑盒迁移,仍应保留为发布级验证风险。 |
|||
|
|||
## 最终放行结论 |
|||
|
|||
- **是否允许 T8 运行时实现进入后续:允许。** 权限、参数和运行时响应链路通过。 |
|||
- **是否允许 T9 端到端实现进入后续:允许。** 端到端行为通过,仅有非阻断覆盖缺口。 |
|||
- **是否允许 W3 整批通过:不允许。** T10 的 OpenAPI/文档契约存在 Important 阻断。 |
|||
- **是否允许进入收尾/TO-11:不允许。** 需要先修复 W3-I1、W3-I2 并进行聚焦复审;全量 `mvn test` 还需全绿或形成正式豁免记录。 |
|||
Loading…
Reference in new issue