2026-05-06-REQ-MOD-003.md 2.75 KB

req_id: REQ-MOD-003 date: 2026-05-06 round: 2

reviewer: superpower-code-reviewer

Review: REQ-MOD-003 — round 2

结论

approve

Must-fix

(无;round 1 两条 must_fix 已在 commit 24196599 中修复)

Nice-to-have

  • backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java:16 — org.mockito.ArgumentMatchers 仍未使用(round 1 提过,pre-existing;可顺手清掉)。
  • backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java:358/375/387 — (Wrapper<ModuleEntity>) any() 强转触发 unchecked 警告,可在类上 @SuppressWarnings("unchecked") 或就近用 ArgumentMatchers.<Wrapper<ModuleEntity>>any()
  • 可选:增加 delete_writesSDeletedByNull_onSoftDelete 单元测试,用 ArgumentCaptor 捕 LambdaUpdateWrapper 解析 wrapper.getSqlSet() 断言含 sDeletedBy=NULL / bDeleted=1 / tDeletedDate=...,把 SET 子句的列覆盖也在单元层钉一遍(目前 SET 列内容仅由 IT 兜底)。
  • docs/05-API接口契约.md § REQ-MOD-003 写「40912 响应附 data.references」,spec 未实现;属于已知契约漂移,留给 module-report 时统一对齐。

反例 / 测试覆盖缺口

Round 1 两条 must_fix 均已落实:

  1. ModuleServiceImpl.delete() 改为 moduleMapper.update(null, uw) + LambdaUpdateWrapper.set(BDeleted,true).set(TDeletedDate,now()).set(SDeletedBy,null)eq(BDeleted,false) 并发兜底保留;affected==0 → 40421 保留。三件套全部由 wrapper 显式声明,绕开 iParentId.FieldStrategy.IGNORED 副作用。
  2. ModuleControllerIT#delete_preservesOtherFields_onChildModule 已新增:用自定义字段值(sDisplayType='接口' / sModuleType='AUDIT' / sManageDeptEn='OPS' / bShowPermission=true / sModuleNameZh='待保留中文名' / iSortOrder=7)建 child(parentId),DELETE 后 reload 断言 8 个字段全部保持原值 + bDeleted=true + tDeletedDate 非 null。

单元测试降级合理性:架构改动后 entity 参数为 null,原 ArgumentCaptor 对 entity 字段的断言失去对象;MP 真正写入的 SET 列在 wrapper 内部 SqlSegment,单元层断言列覆盖复杂度高且偏离职责。新 IT 在真实 MySQL 端到端验证「除三件套外其他列保持原值 + iParentId 不被清空」,比 mock 层 ArgumentCaptor 严格得多。verify(moduleMapper).update((ModuleEntity) isNull(), ...) 把"entity 参数必须是 null"这一架构不变量钉死,防止未来误回滚到 entity-driven update。整体是 mock 层小幅放宽 + IT 层显著加强的净增强。

非阻塞遗留:(a) docs/05 § REQ-MOD-003 data.references 描述与实现不一致;(b) 单元测试 ArgumentMatchers 未使用 import;(c) 重复 DELETE 集成层显式用例缺失(间接覆盖足够)。