You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
9.6 KiB
9.6 KiB
N03 SDD W3 整批独立评审报告
评审对象
- 范围:
df7238b..cd9220b - Wave:W3
- 批次:B3 / T8、T9、T10
- 提交:
3e353bd feat: expose student skill cloud endpoints7873a42 test: add skill cloud end-to-end regressioncd9220b docs: publish skill cloud api contract
- 评审依据:修订后的
execution-contract.md、design.md、tasks.md、三份 spec,以及W3-T8*、W3-T9.md。 - 环境:
JAVA_HOME=D:\java\jdk,MavenD:\maven\apache-maven-3.9.7\bin\mvn.cmd。
Verdict
FAIL
W3 的运行时接口、STUDENT 权限、参数校验、DTO 序列化、端到端一致性和打包均通过独立验证;但 T10 发布的 OpenAPI 契约存在两项 Important 问题:
- 新增
/skills/match的operationId=getMatch与既有/applications/{id}/match冲突。 - 匿名或无效 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}/matchGET。docs/openapi-rc.json:6460:新增/skills/matchGET。- 两处均使用
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 指定测试组
命令:
$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
结果:
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 校验
python docs\verify_openapi.py
paths: 129
All checks passed!
该命令通过,但不覆盖上述 operationId 冲突、401 运行时差异和静态/运行时 Schema 等价性,因此不能抵消 W3-I1、W3-I2。
打包
mvn -DskipTests package
BUILD SUCCESS
Total time: 2.401 s
完整 mvn test
独立运行结果:
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还需全绿或形成正式豁免记录。