- FlowCenterClient:apply/getInstanceByBiz/registerCallback 由 ad-hoc TargetSpec
改走 YschFlowOpenApi 门面(ApplyBody/CallbackRegBody/ApplyResultVo/CallbackRegVo
继承 SDK DTO);instance 走 raw() 预绑视图保留 Map 全字段透传。
- CmsSiteDirectoryImpl:DIRECTORY_KEY 包可见;目录未配且 svcchall.cms.base-url
缺失时构造期 fail-fast(原 @Value 无默认值会直接炸启动,无明确提示)。
- FlowCenterClient:svchall.flow.base-url 默认值去掉 + 同款 fail-fast(enabled=false 不校验)。
- application.yml:新增 yh.open.services.{flowcenter,cms},删 svchall.cms.base-url /
svchall.flow.base-url —— 地址唯一来源=服务目录。
- 测试:新增 FlowCenterClientFacadeTest(6 例,冻结门面/归一目标/回落/fail-fast);
CmsSiteDirectoryImplTest 加目录命名目标 + fail-fast 用例。112 测试全绿。
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package com.yunhai.svchall.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yunhai.platform.openapi.core.YschOpenCall;
|
||||
import com.yunhai.platform.openapi.flow.YschFlowOpenApi;
|
||||
import com.yunhai.platform.openapi.flow.impl.DefaultOpenApiFlowClient;
|
||||
import com.yunhai.platform.openapi.flow.model.FlowApplyBody;
|
||||
import com.yunhai.platform.openapi.flow.model.FlowCallbackRegBody;
|
||||
import com.yunhai.svchall.model.svc.SvcModels;
|
||||
import com.yunhai.svchall.service.FlowClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
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.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* svc-hall → flow-center 出站契约测试:服务目录显式配置 {@code flowcenter} 时,
|
||||
* <b>全部</b>出站(listFlows / getFlow / apply / instance / callback·reg)必须走 SDK 命名目标
|
||||
* (门面 {@link YschFlowOpenApi} 或预绑 {@code BoundOpenCall}),不再回落应用专属
|
||||
* {@code svchall.flow.base-url}(《子应用配置优化方案》§4.2 零专属地址配置)。<br><br>
|
||||
* 此前只有 listFlows/getFlow 走门面,apply/instance/registerCallback 仍用 ad-hoc
|
||||
* {@code TargetSpec.of(baseUrl,"")}——地址仍来自应用配置,且 {@code hasTarget} 探测与真实调用
|
||||
* 分支不一致(探测说"目录已配",调用却打 legacy 地址)。
|
||||
*/
|
||||
@DisplayName("svc-hall FlowCenterClient - 服务目录命名目标(全端点)")
|
||||
class FlowCenterClientFacadeTest {
|
||||
|
||||
private static final String TARGET = "flowcenter";
|
||||
private static final String LEGACY_BASE = "http://flow-center-service:10008/flow-center";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private YschOpenCall openCall;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
openCall = mock(YschOpenCall.class);
|
||||
when(openCall.hasTarget(TARGET)).thenReturn(true);
|
||||
// 门面静态入口:setCall 由被测类构造器完成,setClient 由 starter 完成——单测手工对齐装配
|
||||
YschFlowOpenApi.setCall(openCall);
|
||||
YschFlowOpenApi.setClient(new DefaultOpenApiFlowClient(openCall));
|
||||
}
|
||||
|
||||
private FlowCenterClient client() {
|
||||
return new FlowCenterClient(openCall, objectMapper, true, LEGACY_BASE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("apply:走门面 → 命名目标 + SDK FlowApplyBody 子类 + 出参 instanceId/duplicated")
|
||||
void apply_goesThroughFacade() {
|
||||
SvcModels.ApplyResultVo stub = new SvcModels.ApplyResultVo();
|
||||
stub.setInstanceId(5L);
|
||||
stub.setDuplicated(true);
|
||||
when(openCall.post(eq(TARGET), eq("/oapi/flow/apply"), anyMap(),
|
||||
any(FlowApplyBody.class), eq(SvcModels.ApplyResultVo.class))).thenReturn(stub);
|
||||
|
||||
FlowClient.ApplyResult result = client().apply(3L, "guid-1", "svc_item", "1", "刘文娟的教师入职",
|
||||
"http://base.yunhai/svc-hall/front/#/apply-detail?id=1", "{\"a\":1}", 10133, "刘文娟", null);
|
||||
|
||||
assertEquals(Long.valueOf(5L), result.getInstanceId());
|
||||
assertTrue(result.isDuplicated());
|
||||
|
||||
ArgumentCaptor<FlowApplyBody> body = ArgumentCaptor.forClass(FlowApplyBody.class);
|
||||
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
|
||||
verify(openCall).post(eq(TARGET), eq("/oapi/flow/apply"), query.capture(),
|
||||
body.capture(), eq(SvcModels.ApplyResultVo.class));
|
||||
assertEquals("3", query.getValue().get("schId"));
|
||||
assertEquals("guid-1", body.getValue().getFlowGuid());
|
||||
assertEquals("svc_item", body.getValue().getBizType());
|
||||
assertEquals("1", body.getValue().getBizId());
|
||||
assertEquals(Integer.valueOf(10133), body.getValue().getSubmitterId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("apply:formDataJson 解析为 formData 对象;空串不传")
|
||||
void apply_parsesFormData() {
|
||||
SvcModels.ApplyResultVo stub = new SvcModels.ApplyResultVo();
|
||||
stub.setInstanceId(6L);
|
||||
when(openCall.post(eq(TARGET), eq("/oapi/flow/apply"), anyMap(),
|
||||
any(FlowApplyBody.class), eq(SvcModels.ApplyResultVo.class))).thenReturn(stub);
|
||||
|
||||
client().apply(3L, "g", "svc_item", "2", "t", "u", null, 1, "n", "{\"x\":\"y\"}");
|
||||
|
||||
ArgumentCaptor<FlowApplyBody> body = ArgumentCaptor.forClass(FlowApplyBody.class);
|
||||
verify(openCall).post(eq(TARGET), eq("/oapi/flow/apply"), anyMap(),
|
||||
body.capture(), eq(SvcModels.ApplyResultVo.class));
|
||||
assertEquals("y", body.getValue().getFormData().get("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getInstanceByBiz:走预绑命名目标(Map 全字段透传,不用 ad-hoc spec)")
|
||||
void instance_usesBoundTarget() {
|
||||
when(openCall.get(eq(TARGET), eq("/oapi/flow/instance"), anyMap(), eq(Map.class)))
|
||||
.thenReturn(java.util.Collections.singletonMap("status", 20));
|
||||
|
||||
Map<String, Object> out = client().getInstanceByBiz(3L, "svc_item", "2");
|
||||
|
||||
assertEquals(Integer.valueOf(20), out.get("status"));
|
||||
ArgumentCaptor<Map<String, String>> query = ArgumentCaptor.forClass(Map.class);
|
||||
verify(openCall).get(eq(TARGET), eq("/oapi/flow/instance"), query.capture(), eq(Map.class));
|
||||
assertEquals("svc_item", query.getValue().get("bizType"));
|
||||
assertEquals("2", query.getValue().get("bizId"));
|
||||
assertEquals("3", query.getValue().get("schId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("registerCallback:走门面 + basePath 从回调 URL 反推(/svc-hall 剥离口径)")
|
||||
void registerCallback_goesThroughFacade() {
|
||||
SvcModels.CallbackRegVo stub = new SvcModels.CallbackRegVo();
|
||||
stub.setSecret("secret-1");
|
||||
when(openCall.post(eq(TARGET), eq("/oapi/flow/callback/reg"), anyMap(),
|
||||
any(FlowCallbackRegBody.class), eq(SvcModels.CallbackRegVo.class))).thenReturn(stub);
|
||||
|
||||
String secret = client().registerCallback(3L, "guid-1",
|
||||
"http://svc-hall-service:10007/svc-hall/callback/flow", null);
|
||||
|
||||
assertEquals("secret-1", secret);
|
||||
ArgumentCaptor<FlowCallbackRegBody> body = ArgumentCaptor.forClass(FlowCallbackRegBody.class);
|
||||
verify(openCall).post(eq(TARGET), eq("/oapi/flow/callback/reg"), anyMap(),
|
||||
body.capture(), eq(SvcModels.CallbackRegVo.class));
|
||||
assertEquals("/svc-hall", body.getValue().getBasePath());
|
||||
assertEquals("http://svc-hall-service:10007/svc-hall/callback/flow", body.getValue().getCallbackUrl());
|
||||
assertNull(body.getValue().getEventTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("listFlows/getFlow:门面命名目标(回归既有行为)")
|
||||
void listAndDetail_useFacade() {
|
||||
when(openCall.getList(eq(TARGET), eq("/oapi/flow/list"), anyMap(), eq(SvcModels.FlowSummaryVo.class)))
|
||||
.thenReturn(java.util.Collections.singletonList(new SvcModels.FlowSummaryVo()));
|
||||
when(openCall.get(eq(TARGET), eq("/oapi/flow/guid-1"), anyMap(), eq(SvcModels.FlowSummaryVo.class)))
|
||||
.thenReturn(new SvcModels.FlowSummaryVo());
|
||||
|
||||
assertEquals(1, client().listFlows(3L).size());
|
||||
org.junit.jupiter.api.Assertions.assertNotNull(client().getFlow(3L, "guid-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("目录未配:回落 legacy svchall.flow.base-url(迁移期兼容,行为不变)")
|
||||
void fallsBackToLegacyWhenDirectoryMissing() {
|
||||
YschOpenCall legacyCall = mock(YschOpenCall.class);
|
||||
when(legacyCall.hasTarget(TARGET)).thenReturn(false);
|
||||
when(legacyCall.post(any(com.yunhai.platform.openapi.core.TargetSpec.class),
|
||||
eq("/oapi/flow/apply"), anyMap(), any(), eq(com.fasterxml.jackson.databind.JsonNode.class)))
|
||||
.thenReturn(objectMapper.createObjectNode().put("instanceId", 9));
|
||||
|
||||
FlowClient.ApplyResult result = new FlowCenterClient(legacyCall, objectMapper, true, LEGACY_BASE)
|
||||
.apply(3L, "g", "svc_item", "1", "t", "u", null, 1, "n", null);
|
||||
|
||||
assertEquals(Long.valueOf(9L), result.getInstanceId());
|
||||
assertFalse(result.isDuplicated());
|
||||
verify(legacyCall).post(any(com.yunhai.platform.openapi.core.TargetSpec.class),
|
||||
eq("/oapi/flow/apply"), anyMap(), any(), eq(com.fasterxml.jackson.databind.JsonNode.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("目录与 legacy 键皆未配且 enabled=true:构造期 fail-fast(不静默打 localhost)")
|
||||
void misconfiguredFailsFast() {
|
||||
YschOpenCall noDirectory = mock(YschOpenCall.class);
|
||||
when(noDirectory.hasTarget(TARGET)).thenReturn(false);
|
||||
|
||||
IllegalStateException e = assertThrows(IllegalStateException.class,
|
||||
() -> new FlowCenterClient(noDirectory, objectMapper, true, ""));
|
||||
assertTrue(e.getMessage().contains("yh.open.services." + TARGET), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("svchall.flow.base-url"), e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("enabled=false 且地址全缺:不 fail-fast(显式未接入)")
|
||||
void disabledWithoutAddressIsAllowed() {
|
||||
YschOpenCall noDirectory = mock(YschOpenCall.class);
|
||||
when(noDirectory.hasTarget(TARGET)).thenReturn(false);
|
||||
|
||||
FlowCenterClient c = new FlowCenterClient(noDirectory, objectMapper, false, "");
|
||||
assertFalse(c.enabled());
|
||||
com.yunhai.platform.apistd.BizException ex =
|
||||
org.junit.jupiter.api.Assertions.assertThrows(com.yunhai.platform.apistd.BizException.class,
|
||||
() -> c.listFlows(3L));
|
||||
assertTrue(ex.getMessage().contains("流程中心未接入"), ex.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user