Carry actual flow state and freeze callback round identity

Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
This commit is contained in:
chenxing
2026-10-23 10:52:00 +08:00
parent 7eeaea55f8
commit f7819b14e2
13 changed files with 534 additions and 3 deletions
@@ -48,6 +48,9 @@ class OapiFlowTasksByBizTest extends FlowTestSupport {
private OapiSignedClient client;
@Autowired
private javax.sql.DataSource dataSource;
@TestConfiguration
static class StubConfig {
@Bean
@@ -58,7 +61,21 @@ class OapiFlowTasksByBizTest extends FlowTestSupport {
}
@BeforeEach
void setUp() {
void setUp() throws Exception {
// 在 cleanDb 之前验证真实连接,不能只相信环境变量已覆盖 profile。
try (java.sql.Connection connection = dataSource.getConnection()) {
String schema = connection.getCatalog();
String host = java.net.URI.create(connection.getMetaData().getURL().substring(5)).getHost();
System.out.println("CI_DB_TARGET=" + host + "/" + schema);
assertTrue("127.0.0.1".equals(host) || "localhost".equals(host)
|| (host != null && host.startsWith("mavenbuild-") && host.endsWith("-db")),
"真库门禁只允许本机或本次 CI 容器");
assertTrue("bsch_flowcenter_test".equals(schema) || "flowcenter_ci".equals(schema)
|| (schema != null && schema.startsWith("flowcenter_ci_")), "拒绝清理非测试 schema");
if (System.getenv("CI_TEST_DB_URL") != null) {
assertTrue(schema.startsWith("flowcenter_ci"), "CI 必须实际连接独立 schema");
}
}
cleanDb();
client = new OapiSignedClient("http://localhost:" + port, APP_KEY, APP_SECRET);
}
@@ -287,6 +287,35 @@ class FlApplyRecoveryTest {
assertThat(existing.getApplyRound()).isNull();
}
@Test
void callbackIdentityIsFrozenBeforeCommitAndNeverRelabelsOldEvent() throws Exception {
FlwInstance row = instance(900L, 30, TOKEN_A, 1, "100018");
row.setVersion(3);
when(instanceRepository.findById(900L)).thenReturn(Optional.of(row));
com.yunhai.flowcenter.engine.FlowEvent event = com.yunhai.flowcenter.engine.FlowEvent.of(
com.yunhai.flowcenter.engine.FlowEvent.Type.REJECTED, 900L);
new com.yunhai.flowcenter.callback.FlowEventIdentityListener(instanceRepository).capture(event);
row.setApplyRound(2); row.setApplyRequestToken(TOKEN_B); row.setVersion(4);
new com.yunhai.flowcenter.callback.FlowEventIdentityListener(instanceRepository).capture(event);
assertThat(event.getRoundIdentity().get("applyRound")).isEqualTo(1);
com.yunhai.flowcenter.repository.FlwCallbackRegRepository regs = mock(com.yunhai.flowcenter.repository.FlwCallbackRegRepository.class);
com.yunhai.flowcenter.callback.CallbackLogStore store = mock(com.yunhai.flowcenter.callback.CallbackLogStore.class);
com.yunhai.flowcenter.entity.FlwCallbackReg reg = new com.yunhai.flowcenter.entity.FlwCallbackReg();
reg.setId(7L);
when(regs.findBySchIdAndFlowGuidAndStatus(SCH, FLOW_GUID, 0)).thenReturn(java.util.Collections.singletonList(reg));
com.yunhai.flowcenter.callback.CallbackDispatcher dispatcher = new com.yunhai.flowcenter.callback.CallbackDispatcher(
instanceRepository, defRepository, regs, mock(com.yunhai.flowcenter.repository.FlwCallbackLogRepository.class),
store, mock(com.yunhai.flowcenter.callback.CallbackPushService.class), new com.yunhai.flowcenter.callback.CallbackProperties(), mapper);
org.springframework.test.util.ReflectionTestUtils.setField(dispatcher, "pushExecutor", mock(java.util.concurrent.ExecutorService.class));
dispatcher.onFlowEvent(event);
ArgumentCaptor<com.yunhai.flowcenter.entity.FlwCallbackLog> log = ArgumentCaptor.forClass(com.yunhai.flowcenter.entity.FlwCallbackLog.class);
verify(store).persist(log.capture());
com.fasterxml.jackson.databind.JsonNode data = mapper.readTree(log.getValue().getPayloadJson()).get("data");
assertThat(data.get("applyRound").asInt()).isEqualTo(1);
assertThat(data.get("requestToken").asText()).isEqualTo(TOKEN_A);
}
// ==================== 夹具 ====================
/** 提交者恒 1001、调用应用恒 100018;不符的一侧都在被测行上构造 */
@@ -47,6 +47,7 @@ class OapiFlowApplyDefVersionTest {
private FlowInstanceService instanceService;
private AssigneeResolver assigneeResolver;
private OapiFlowService service;
private FlwInstanceRepository instances;
@BeforeEach
@SuppressWarnings("unchecked")
@@ -54,7 +55,8 @@ class OapiFlowApplyDefVersionTest {
defRepository = mock(FlwDefRepository.class);
instanceService = mock(FlowInstanceService.class);
assigneeResolver = mock(AssigneeResolver.class);
service = new OapiFlowService(defRepository, mock(FlwInstanceRepository.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(),
@@ -150,4 +152,20 @@ class OapiFlowApplyDefVersionTest {
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());
}
}