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 派生方法属另一实现线)
181 lines
8.5 KiB
Java
181 lines
8.5 KiB
Java
package com.yunhai.flowcenter.service;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.yunhai.flowcenter.engine.AssigneeResolver;
|
|
import com.yunhai.flowcenter.engine.FlowEngine;
|
|
import com.yunhai.flowcenter.entity.FlwDef;
|
|
import com.yunhai.flowcenter.enums.FlwEnums;
|
|
import com.yunhai.flowcenter.model.OapiModels;
|
|
import com.yunhai.flowcenter.repository.FlwCallbackRegRepository;
|
|
import com.yunhai.flowcenter.repository.FlwCcRepository;
|
|
import com.yunhai.flowcenter.repository.FlwDefRepository;
|
|
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
|
|
import com.yunhai.flowcenter.repository.FlwTaskRepository;
|
|
import com.yunhai.platform.apistd.BizException;
|
|
import com.yunhai.platform.openapi.base.OpenApiAppClient;
|
|
import com.yunhai.platform.openapi.base.OpenApiUserClient;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.DisplayName;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.beans.factory.ObjectProvider;
|
|
|
|
import java.util.Optional;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.ArgumentMatchers.anyLong;
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.never;
|
|
import static org.mockito.Mockito.verify;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
/**
|
|
* S2.7 版本竞态(⭐):apply 可选回带预览时的定义版本,改版即人类可读拒绝。
|
|
* 三态必测——不传(存量调用方,行为逐字不变)、传对(放行)、传旧(拒绝且不落任何引擎动作)。
|
|
* 另钉住 preview 回带 defVersion(调用方拿得到的前提)。
|
|
*/
|
|
@DisplayName("apply 期望定义版本(版本竞态人类可读报错)")
|
|
class OapiFlowApplyDefVersionTest {
|
|
|
|
private static final Long SCH_ID = 1L;
|
|
private static final String FLOW_GUID = "g-assign";
|
|
|
|
private FlwDefRepository defRepository;
|
|
private FlowInstanceService instanceService;
|
|
private AssigneeResolver assigneeResolver;
|
|
private OapiFlowService service;
|
|
private FlwInstanceRepository instances;
|
|
|
|
@BeforeEach
|
|
@SuppressWarnings("unchecked")
|
|
void setUp() {
|
|
defRepository = mock(FlwDefRepository.class);
|
|
instanceService = mock(FlowInstanceService.class);
|
|
assigneeResolver = mock(AssigneeResolver.class);
|
|
instances = mock(FlwInstanceRepository.class);
|
|
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), mock(OpenApiAppClient.class),
|
|
mock(DiagramTokenService.class), mock(ObjectProvider.class));
|
|
// 本校校验在单测里关闭(@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) {
|
|
FlwDef def = new FlwDef();
|
|
def.setId(9L);
|
|
def.setSchId(SCH_ID);
|
|
def.setFlowGuid(FLOW_GUID);
|
|
def.setVersion(version);
|
|
def.setStatus(FlwEnums.DefStatus.PUBLISHED.getCode());
|
|
def.setContentJson("{\"nodes\":[], \"edges\":[], \"start\":\"n1\"}");
|
|
return def;
|
|
}
|
|
|
|
private static OapiModels.ApplyBody applyBody(Integer expectedDefVersion) {
|
|
OapiModels.ApplyBody body = new OapiModels.ApplyBody();
|
|
body.setFlowGuid(FLOW_GUID);
|
|
body.setBizType("notice");
|
|
body.setBizId("77");
|
|
body.setBizTitle("标题");
|
|
body.setSubmitterId(21);
|
|
body.setSubmitterName("甲");
|
|
body.setExpectedDefVersion(expectedDefVersion);
|
|
return body;
|
|
}
|
|
|
|
private void stubDef(int version) {
|
|
when(defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
|
|
eq(SCH_ID), eq(FLOW_GUID), eq(FlwEnums.DefStatus.PUBLISHED.getCode())))
|
|
.thenReturn(Optional.of(publishedDef(version)));
|
|
}
|
|
|
|
@Test
|
|
void withoutExpectedVersion_behavesExactlyAsBefore() {
|
|
stubDef(3);
|
|
|
|
OapiModels.ApplyResultVo vo = service.apply(SCH_ID, "100009", applyBody(null));
|
|
|
|
assertEquals(Long.valueOf(500L), vo.getInstanceId());
|
|
verify(instanceService).apply(any(FlowInstanceService.ApplyCommand.class));
|
|
}
|
|
|
|
@Test
|
|
void matchingVersion_passesThrough() {
|
|
stubDef(3);
|
|
|
|
OapiModels.ApplyResultVo vo = service.apply(SCH_ID, "100009", applyBody(3));
|
|
|
|
assertEquals(Long.valueOf(500L), vo.getInstanceId());
|
|
verify(instanceService).apply(any(FlowInstanceService.ApplyCommand.class));
|
|
}
|
|
|
|
/** 改版后按旧预览结果提交:人类可读文案,且引擎与指派校验都不被触发(不污染幂等键) */
|
|
@Test
|
|
void staleExpectedVersion_rejectedWithReadableMessage_beforeAnyEngineAction() {
|
|
stubDef(4);
|
|
|
|
BizException e = assertThrows(BizException.class,
|
|
() -> service.apply(SCH_ID, "100009", applyBody(3)));
|
|
|
|
assertTrue(e.getMessage().contains("流程已更新,请刷新后重新选择审批人"), e.getMessage());
|
|
assertTrue(e.getMessage().contains("期望定义版本 3") && e.getMessage().contains("当前已发布版本 4"),
|
|
e.getMessage());
|
|
verify(instanceService, never()).apply(any(FlowInstanceService.ApplyCommand.class));
|
|
}
|
|
|
|
@Test
|
|
void previewCarriesDefVersionSoCallerCanEchoIt() {
|
|
stubDef(7);
|
|
when(assigneeResolver.resolve(anyLong(), any())).thenReturn(java.util.Collections.emptyList());
|
|
|
|
OapiModels.ApplyPreviewVo vo = service.applyPreview(SCH_ID, FLOW_GUID, 21);
|
|
|
|
assertEquals(Integer.valueOf(7), vo.getDefVersion());
|
|
assertEquals(FLOW_GUID, vo.getFlowGuid());
|
|
assertTrue(vo.getNodes() == null || vo.getNodes().isEmpty());
|
|
}
|
|
|
|
/** 存量兼容:无指派节点的流程 preview 仍返回空节点集(defVersion 不影响既有消费方) */
|
|
@Test
|
|
void previewKeepsEmptyNodesForFlowWithoutAssignPolicy() {
|
|
stubDef(1);
|
|
|
|
OapiModels.ApplyPreviewVo vo = service.applyPreview(SCH_ID, FLOW_GUID, 21);
|
|
|
|
assertEquals(Integer.valueOf(1), vo.getDefVersion());
|
|
assertTrue(vo.getNodes() == null || vo.getNodes().isEmpty());
|
|
}
|
|
@Test
|
|
void v2ResponseCarriesRealRejectedStatusAndStableBranch() {
|
|
stubDef(3);
|
|
com.yunhai.flowcenter.model.FlowApplyRetryModels.ApplyRequestV2 body = new com.yunhai.flowcenter.model.FlowApplyRetryModels.ApplyRequestV2();
|
|
body.setFlowGuid(FLOW_GUID); body.setBizType("svc-hall"); body.setBizId("42"); body.setSubmitterId(21);
|
|
body.setRequestToken("token-1"); body.setAttempt(1);
|
|
when(instanceService.applyGuarded(any(), any())).thenReturn(
|
|
new FlowInstanceService.GuardedOutcome(500L, true, "SAME_REQUEST", 1));
|
|
com.yunhai.flowcenter.entity.FlwInstance row = new com.yunhai.flowcenter.entity.FlwInstance();
|
|
row.setId(500L); row.setStatus(30); row.setVersion(4); row.setApplyRound(1);
|
|
when(instances.findById(500L)).thenReturn(Optional.of(row));
|
|
com.yunhai.flowcenter.model.FlowApplyRetryModels.ApplyResultV2 result = service.applyV2(SCH_ID, "100009", body);
|
|
assertEquals("SAME_REQUEST", result.getBranch()); assertEquals(Integer.valueOf(1), result.getApplyRound());
|
|
assertEquals(Integer.valueOf(30), result.getStatus()); assertEquals(Integer.valueOf(4), result.getVersion());
|
|
}
|
|
|
|
}
|