feat(apply): 用户面撤回/进度/流程图后端中转 + 大厅HallController + flowEnabled 三态(SDK withdraw/progress/diagramUrl)
build / build-push (push) Successful in 1m11s

This commit is contained in:
chenxing
2026-09-17 01:05:35 +08:00
parent e462b71bef
commit f0279ae12d
9 changed files with 675 additions and 8 deletions
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -149,6 +150,100 @@ class FlowCenterClientFacadeTest {
org.junit.jupiter.api.Assertions.assertNotNull(client().getFlow(3L, "guid-1"));
}
@Test
@DisplayName("撤回:走门面 → /oapi/flow/instance/{id}/withdraw + schId/submitterId(对端复核身份)")
void withdraw_goesThroughFacade() {
client().withdraw(3L, 77L, 201);
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
verify(openCall).post(eq(TARGET), eq("/oapi/flow/instance/77/withdraw"), query.capture(),
isNull(), eq(Void.class));
assertEquals("3", query.getValue().get("schId"));
assertEquals("201", query.getValue().get("submitterId"));
}
@Test
@DisplayName("进度:走门面 → /oapi/flow/progress?instanceId=&schId=")
void progress_goesThroughFacade() {
SvcModels.FlowProgressVo stub = new SvcModels.FlowProgressVo();
stub.setInstanceId(77L);
when(openCall.get(eq(TARGET), eq("/oapi/flow/progress"), anyMap(),
eq(SvcModels.FlowProgressVo.class))).thenReturn(stub);
assertEquals(Long.valueOf(77L), client().progress(3L, 77L, null, null).getInstanceId());
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
verify(openCall).get(eq(TARGET), eq("/oapi/flow/progress"), query.capture(),
eq(SvcModels.FlowProgressVo.class));
assertEquals("77", query.getValue().get("instanceId"));
assertEquals("3", query.getValue().get("schId"));
}
@Test
@DisplayName("流程图:换图 + 取图两步都在后端;token 不出后端(前端拿不到,也不需要 flow-center 地址)")
void diagramImage_tokenStaysInBackend() {
SvcModels.FlowDiagramVo url = new SvcModels.FlowDiagramVo();
url.setImgUrl("/open/flow/diagram/img?token=tok-abc");
url.setExpiresIn(300);
when(openCall.get(eq(TARGET), eq("/oapi/flow/diagram/url"), anyMap(),
eq(SvcModels.FlowDiagramVo.class))).thenReturn(url);
byte[] png = new byte[]{(byte) 0x89, 'P', 'N', 'G'};
when(openCall.download(eq(TARGET), eq("/open/flow/diagram/img"),
eq(java.util.Collections.singletonMap("token", "tok-abc")))).thenReturn(png);
assertEquals(png, client().diagramImage(3L, 77L, null, "dept"));
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
verify(openCall).get(eq(TARGET), eq("/oapi/flow/diagram/url"), query.capture(),
eq(SvcModels.FlowDiagramVo.class));
assertEquals("77", query.getValue().get("instanceId"));
assertEquals("dept", query.getValue().get("mode"));
verify(openCall).download(eq(TARGET), eq("/open/flow/diagram/img"),
eq(java.util.Collections.singletonMap("token", "tok-abc")));
}
@Test
@DisplayName("tokenOf:只认 ?token= 段,容忍尾部其它参数;无 token 返回 null(不拼出脏地址)")
void tokenOf_parsesTokenOnly() {
assertEquals("abc", FlowCenterClient.tokenOf("/open/flow/diagram/img?token=abc"));
assertEquals("abc", FlowCenterClient.tokenOf("/open/flow/diagram/img?token=abc&x=1"));
assertEquals("abc", FlowCenterClient.tokenOf("http://h/flow-center/open/flow/diagram/img?token=abc"));
org.junit.jupiter.api.Assertions.assertNull(FlowCenterClient.tokenOf("/open/flow/diagram/img"));
org.junit.jupiter.api.Assertions.assertNull(FlowCenterClient.tokenOf(null));
org.junit.jupiter.api.Assertions.assertNull(FlowCenterClient.tokenOf("/x?token="));
}
@Test
@DisplayName("目录未配:撤回落 legacy base-url(ad-hoc TargetSpec POST,行为不变)")
void withdraw_fallsBackToLegacy() {
YschOpenCall legacyCall = mock(YschOpenCall.class);
when(legacyCall.hasTarget(TARGET)).thenReturn(false);
new FlowCenterClient(legacyCall, objectMapper, true, LEGACY_BASE).withdraw(3L, 77L, 201);
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
verify(legacyCall).post(any(com.yunhai.platform.openapi.core.TargetSpec.class),
eq("/oapi/flow/instance/77/withdraw"), query.capture(), isNull(), eq(Void.class));
assertEquals("201", query.getValue().get("submitterId"));
}
@Test
@DisplayName("enabled=false:三个新方法一律抛「未接入」(不静默成功让前端误判)")
void disabledClient_rejectsNewActions() {
YschOpenCall noDirectory = mock(YschOpenCall.class);
when(noDirectory.hasTarget(TARGET)).thenReturn(false);
FlowCenterClient c = new FlowCenterClient(noDirectory, objectMapper, false, "");
com.yunhai.platform.apistd.BizException withdrawEx =
org.junit.jupiter.api.Assertions.assertThrows(com.yunhai.platform.apistd.BizException.class,
() -> c.withdraw(3L, 77L, 201));
org.junit.jupiter.api.Assertions.assertTrue(withdrawEx.getMessage().contains("流程中心未接入"));
org.junit.jupiter.api.Assertions.assertThrows(com.yunhai.platform.apistd.BizException.class,
() -> c.progress(3L, 77L, null, null));
org.junit.jupiter.api.Assertions.assertThrows(com.yunhai.platform.apistd.BizException.class,
() -> c.diagramImage(3L, 77L, null, null));
}
@Test
@DisplayName("目录未配:回落 legacy svchall.flow.base-url(迁移期兼容,行为不变)")
void fallsBackToLegacyWhenDirectoryMissing() {