feat(fl2): 落地部门目录 / 退回重提(D14) / 授权管理后端(基线 origin/main)
build / build-push (push) Failing after 55s

在 b369527 之上增量移植 FL2.0 后端能力,不改既有线级停发/指派预览/流程图入口:

- 部门目录:FlowDepartmentController(/admin/department/{groups,options,resolve,members})
  + UserBaseDepartmentDirectory(先按会话校域解析引用,再读成员;目录不可用 fail-closed)
- 退回重提 D14:POST /oapi/flow/instance/{instanceId}/resubmit,仅原提交人 CAS 路径放行,
  OAPI 侧 submitterId 路径继续 fail-closed
- 授权管理:/api/flow/admin/usage-grant/{schoolList,summary}(校域取管理员会话,无 schId 入参)
- 来源主体:解析器派生 ub-app:{applicationInfo.id},不再读取 UserBase 的 sourcePrincipalId 字段
- 回调来源策略:SELF 身份强校验;NULL 来源不广播;复合幂等键 (event_id,reg_id)
- 测试夹具:许可/主体/开通桩按新契约修正,FlowEngineIT/FormalApplyCommandIT/
  CallbackDispatchIT/OapiFlowContractIT 共 117 例在真实 MySQL 8 通过

不含:FlSourceAuthorizationTest(其断言的两个 Repository 派生方法属另一实现线)
This commit is contained in:
chenxing
2026-10-30 10:15:12 +08:00
parent b369527fe1
commit d72128672f
52 changed files with 3208 additions and 859 deletions
@@ -42,14 +42,12 @@ class OapiFlowApplyDefVersionTest {
private static final Long SCH_ID = 1L;
private static final String FLOW_GUID = "g-assign";
private static final String SOURCE_ID = "11111111-2222-3333-4444-555555555555";
private FlwDefRepository defRepository;
private FlowInstanceService instanceService;
private AssigneeResolver assigneeResolver;
private OapiFlowService service;
private FlwInstanceRepository instances;
private OpenApiAppClient appClient;
@BeforeEach
@SuppressWarnings("unchecked")
@@ -58,29 +56,24 @@ class OapiFlowApplyDefVersionTest {
instanceService = mock(FlowInstanceService.class);
assigneeResolver = mock(AssigneeResolver.class);
instances = mock(FlwInstanceRepository.class);
appClient = mock(OpenApiAppClient.class);
com.fasterxml.jackson.databind.node.ObjectNode registered =
new ObjectMapper().createObjectNode()
.put("id", 100009)
.put("appCode", "flow-center")
.put("sourcePrincipalId", SOURCE_ID);
when(appClient.getAppByKey(any())).thenReturn(registered);
service = new OapiFlowService(defRepository, instances,
mock(FlwTaskRepository.class), mock(FlwCcRepository.class),
mock(FlwCallbackRegRepository.class), instanceService, mock(FlowTaskService.class),
mock(FlowEngine.class), assigneeResolver, new ObjectMapper(),
mock(OpenApiUserClient.class), appClient,
mock(OpenApiUserClient.class), mock(OpenApiAppClient.class),
mock(DiagramTokenService.class), mock(ObjectProvider.class));
// F01:来源解析与使用许可在真实装配中由 Spring 注入,单测按同一契约显式装配
org.springframework.test.util.ReflectionTestUtils.setField(
service, "sourcePrincipalResolver", new FlowSourcePrincipalResolver(appClient));
FlowUsageGrantGuard guard = mock(FlowUsageGrantGuard.class);
when(guard.requireNewApplyGrant(anyLong(), any(),
any(FlowSourcePrincipalResolver.Principal.class))).thenReturn(100009);
service.setUsageGrantGuard(guard);
// 本校校验在单测里关闭(@Value 字段未注入=默认 false),引擎出参给一个既有办件
when(instanceService.apply(any(FlowInstanceService.ApplyCommand.class)))
.thenReturn(new FlowInstanceService.ApplyOutcome(500L, false));
// F01/F02:来源主体解析与使用许可闸在生产装配里必然存在;单测手工构造服务时必须显式注入
// 测试替身,否则 apply 会因"闸门未装配/来源不可解析"被 fail-closed 拒(那是期望行为,不是靶子)。
FlowSourcePrincipalResolver stubPrincipals = mock(FlowSourcePrincipalResolver.class);
when(stubPrincipals.resolve(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(new FlowSourcePrincipalResolver.Principal(
"ub-app:18", 18, "100018", "flow-test"));
org.springframework.test.util.ReflectionTestUtils.setField(service, "sourcePrincipalResolver", stubPrincipals);
service.setUsageGrantGuard(mock(FlowUsageGrantGuard.class));
}
private static FlwDef publishedDef(int version) {