1. 新增外部系统编码部门编码关联管理
2. 新增统一的 api 对外门户管理 3. 修正各个模块的 api 命名
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayExecutionService;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(ApiGatewayController.class)
|
||||
@AutoConfigureMockMvc(addFilters = false)
|
||||
@TestPropertySource(properties = {
|
||||
"spring.config.import=optional:",
|
||||
"spring.cloud.nacos.config.enabled=false",
|
||||
"spring.cloud.nacos.discovery.enabled=false"
|
||||
})
|
||||
class ApiGatewayControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private ApiGatewayExecutionService executionService;
|
||||
|
||||
@MockBean
|
||||
private ApiDefinitionService apiDefinitionService;
|
||||
|
||||
@Test
|
||||
void invokeShouldReturnGatewayEnvelope() throws Exception {
|
||||
ApiGatewayResponse response = ApiGatewayResponse.builder()
|
||||
.code(200)
|
||||
.message("OK")
|
||||
.response(Map.of("code", 0))
|
||||
.traceId("trace-123")
|
||||
.build();
|
||||
when(executionService.invokeForDebug(any(ApiGatewayInvokeReqVO.class)))
|
||||
.thenReturn(ResponseEntity.ok(response));
|
||||
|
||||
mockMvc.perform(post("/databus/gateway/invoke")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"apiCode\":\"demo\",\"version\":\"v1\"}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.message").value("OK"))
|
||||
.andExpect(jsonPath("$.response.code").value(0))
|
||||
.andExpect(jsonPath("$.traceId").value("trace-123"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ApiGatewayRequestMapperTest {
|
||||
|
||||
private ApiGatewayRequestMapper mapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ApiGatewayProperties properties = new ApiGatewayProperties();
|
||||
mapper = new ApiGatewayRequestMapper(new ObjectMapper(), properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseUriTemplateVariablesWhenPresent() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("apiCode", "demo.api", "version", "v1"));
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, "GET");
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/admin-api/databus/api/portal/demo.api/v1");
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.APPLICATION_JSON_VALUE));
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getApiCode()).isEqualTo("demo.api");
|
||||
assertThat(context.getApiVersion()).isEqualTo("v1");
|
||||
assertThat(context.getRequestPath()).isEqualTo("/admin-api/databus/api/portal/demo.api/v1");
|
||||
assertThat(context.getHttpMethod()).isEqualTo("GET");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInferFromAbsoluteRequestUrlWhenVariablesMissing() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "http://localhost:48080/admin-api/databus/api/portal/system.auth.quick-login/v1?foo=bar");
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.TEXT_PLAIN_VALUE));
|
||||
|
||||
ApiInvocationContext context = mapper.map("payload", headers);
|
||||
|
||||
assertThat(context.getApiCode()).isEqualTo("system.auth.quick-login");
|
||||
assertThat(context.getApiVersion()).isEqualTo("v1");
|
||||
assertThat(context.getRequestPath()).isEqualTo("/admin-api/databus/api/portal/system.auth.quick-login/v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToHeadersWhenAvailable() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("apiCode", "override.api");
|
||||
headers.put("version", "v3");
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/another/path");
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of(org.springframework.http.HttpHeaders.CONTENT_TYPE, org.springframework.http.MediaType.APPLICATION_JSON_VALUE));
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getApiCode()).isEqualTo("override.api");
|
||||
assertThat(context.getApiVersion()).isEqualTo("v3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNormalizeHeaderValuesAndSupportCaseInsensitiveLookup() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders", Map.of("ZT-Auth-Token", List.of("token-123")));
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getRequestHeaders().get("ZT-Auth-Token")).isEqualTo("token-123");
|
||||
assertThat(context.getRequestHeaders().get("zt-auth-token")).isEqualTo("token-123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractQueryParamsFromHeaders() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/api/demo");
|
||||
headers.put("http_requestParams", Map.of(
|
||||
"single", new String[]{"value"},
|
||||
"multi", List.of("a", "b")
|
||||
));
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getRequestQueryParams()).containsEntry("single", "value");
|
||||
assertThat(context.getRequestQueryParams().get("multi")).isEqualTo(List.of("a", "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToQueryStringWhenHeaderMissing() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri", "/api/demo?foo=bar&arr=1&arr=2");
|
||||
|
||||
ApiInvocationContext context = mapper.map("", headers);
|
||||
|
||||
assertThat(context.getRequestQueryParams()).containsEntry("foo", "bar");
|
||||
assertThat(context.getRequestQueryParams().get("arr")).isEqualTo(List.of("1", "2"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.policy;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DefaultAuthPolicyEvaluatorTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
void shouldResolveTokenFromPrimaryHeader() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, List.of(" token-123 "));
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("token-123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToAuthorizationHeader() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer token-456");
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("token-456");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseTokenFromStructuredPayload() {
|
||||
Map<String, Object> headers = new HashMap<>();
|
||||
headers.put("Authorization", List.of("", "{\"token\":\"abc-789\"}"));
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, Map.of(), objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("abc-789");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseQueryParameterAsLastResort() {
|
||||
Map<String, Object> headers = Map.of();
|
||||
Map<String, Object> queryParams = Map.of("token", " token-999 ");
|
||||
|
||||
String token = GatewayJwtResolver.resolveJwtToken(headers, queryParams, objectMapper);
|
||||
|
||||
assertThat(token).isEqualTo("token-999");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiClientCredentialDO;
|
||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||
import com.zt.plat.module.databus.service.gateway.ApiClientCredentialService;
|
||||
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class GatewaySecurityFilterTest {
|
||||
|
||||
@Test
|
||||
void shouldAllowRequestWhenIpPermitted() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
properties.getSecurity().setEnabled(false);
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRequestWhenIpDenied() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
properties.setDeniedIps(Collections.singletonList("10.0.0.1"));
|
||||
|
||||
properties.getSecurity().setEnabled(false);
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("10.0.0.1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
assertThat(chain.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldValidateSecurityHeadersAndEncryptResponse() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
|
||||
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiClientCredentialDO credential = new ApiClientCredentialDO();
|
||||
credential.setAppId("demo-app");
|
||||
credential.setSignatureType(null);
|
||||
credential.setEncryptionKey(null);
|
||||
credential.setEncryptionType(null);
|
||||
when(credentialService.findActiveCredential("demo-app")).thenReturn(java.util.Optional.of(credential));
|
||||
|
||||
properties.getSecurity().setRequireBodyEncryption(false);
|
||||
properties.getSecurity().setEncryptResponse(false);
|
||||
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
String signature = "d41d8cd98f00b204e9800998ecf8427e";
|
||||
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
|
||||
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
|
||||
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
|
||||
request.addHeader(properties.getSecurity().getSignatureHeader(), signature);
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncryptErrorResponseWhenValidationFails() throws Exception {
|
||||
ApiGatewayProperties properties = createProperties();
|
||||
properties.getSecurity().setRequireBodyEncryption(false);
|
||||
properties.getSecurity().setEncryptResponse(true);
|
||||
|
||||
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
|
||||
ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), any(Duration.class))).thenReturn(Boolean.TRUE);
|
||||
|
||||
ApiClientCredentialService credentialService = mock(ApiClientCredentialService.class);
|
||||
ApiClientCredentialDO credential = new ApiClientCredentialDO();
|
||||
credential.setAppId("demo-app");
|
||||
credential.setEncryptionKey("demo-secret-key");
|
||||
credential.setEncryptionType(CryptoSignatureUtils.ENCRYPT_TYPE_AES);
|
||||
when(credentialService.findActiveCredential("demo-app")).thenReturn(Optional.of(credential));
|
||||
|
||||
GatewaySecurityFilter filter = new GatewaySecurityFilter(properties, redisTemplate, credentialService, new ObjectMapper());
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin-api/databus/api/portal/demo/v1");
|
||||
request.setRemoteAddr("127.0.0.1");
|
||||
long timestamp = System.currentTimeMillis();
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
request.addHeader(properties.getSecurity().getAppIdHeader(), "demo-app");
|
||||
request.addHeader(properties.getSecurity().getTimestampHeader(), String.valueOf(timestamp));
|
||||
request.addHeader(properties.getSecurity().getNonceHeader(), nonce);
|
||||
request.addHeader(properties.getSecurity().getSignatureHeader(), "invalid-signature");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(request, response, new MockFilterChain());
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
|
||||
String cipherText = response.getContentAsString();
|
||||
assertThat(cipherText).isNotBlank();
|
||||
assertThat(cipherText.trim()).doesNotStartWith("{");
|
||||
|
||||
String decrypted = CryptoSignatureUtils.decrypt(cipherText, credential.getEncryptionKey(), credential.getEncryptionType());
|
||||
JsonNode node = new ObjectMapper().readTree(decrypted);
|
||||
assertThat(node.get("code").asInt()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
|
||||
assertThat(node.get("message").asText()).isEqualTo("签名校验失败");
|
||||
}
|
||||
|
||||
private ApiGatewayProperties createProperties() {
|
||||
ApiGatewayProperties properties = new ApiGatewayProperties();
|
||||
properties.setBasePath("/admin-api/databus/api/portal");
|
||||
properties.setAllowedIps(Collections.singletonList("127.0.0.1"));
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.step.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionEvaluatorRegistry;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.core.GenericHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_END_EXECUTION_FAILED;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class EndStepHandlerTest {
|
||||
|
||||
private EndStepHandler handler;
|
||||
private ApiDefinitionAggregate aggregate;
|
||||
|
||||
private static MessageHeaders emptyHeaders() {
|
||||
return new MessageHeaders(Collections.emptyMap());
|
||||
}
|
||||
|
||||
private static ObjectMapper createObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ExpressionEvaluatorRegistry registry = new ExpressionEvaluatorRegistry();
|
||||
registry.register(ExpressionTypeEnum.JSON, new JsonataExpressionEvaluator(createObjectMapper()));
|
||||
handler = new EndStepHandler(new ExpressionExecutor(registry));
|
||||
aggregate = ApiDefinitionAggregate.builder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyResponseMappingsToInvocationContext() {
|
||||
ApiStepDO stepDO = new ApiStepDO();
|
||||
stepDO.setId(303L);
|
||||
stepDO.setType("END");
|
||||
stepDO.setResponseMappingExpr("JSON::{\"responseBody\": {\"final\": $.raw}, \"responseStatus\": 201, \"responseMessage\": \"accepted\"}");
|
||||
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
context.setResponseBody(Map.of("raw", Map.of("value", 1)));
|
||||
context.setResponseStatus(200);
|
||||
context.setResponseMessage("ok");
|
||||
|
||||
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
|
||||
ApiInvocationContext result = (ApiInvocationContext) genericHandler.handle(context, emptyHeaders());
|
||||
|
||||
assertThat(result).isSameAs(context);
|
||||
assertThat(result.getResponseBody()).isInstanceOf(Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> responseBody = (Map<String, ?>) result.getResponseBody();
|
||||
assertThat(responseBody).containsKey("final");
|
||||
assertThat(result.getResponseStatus()).isEqualTo(201);
|
||||
assertThat(result.getResponseMessage()).isEqualTo("accepted");
|
||||
assertThat(result.getStepResults()).hasSize(1);
|
||||
assertThat(result.getStepResults().get(0).isSuccess()).isTrue();
|
||||
assertThat(result.getStepResults().get(0).getStepId()).isEqualTo(303L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordFailureWhenExpressionInvalid() {
|
||||
ApiStepDO stepDO = new ApiStepDO();
|
||||
stepDO.setId(404L);
|
||||
stepDO.setType("END");
|
||||
stepDO.setResponseMappingExpr("JSON::{broken}");
|
||||
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
genericHandler.handle(context, emptyHeaders());
|
||||
})
|
||||
.isInstanceOf(ServiceException.class)
|
||||
.extracting(ex -> ((ServiceException) ex).getCode())
|
||||
.isEqualTo(API_STEP_END_EXECUTION_FAILED.getCode());
|
||||
assertThat(context.getStepResults()).hasSize(1);
|
||||
assertThat(context.getStepResults().get(0).isSuccess()).isFalse();
|
||||
assertThat(context.getStepResults().get(0).getStepId()).isEqualTo(404L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.zt.plat.module.databus.framework.integration.gateway.step.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.zt.plat.framework.common.exception.ServiceException;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||
import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionEvaluatorRegistry;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator;
|
||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.integration.core.GenericHandler;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_START_EXECUTION_FAILED;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class StartStepHandlerTest {
|
||||
|
||||
private StartStepHandler handler;
|
||||
private ApiDefinitionAggregate aggregate;
|
||||
|
||||
private static MessageHeaders emptyHeaders() {
|
||||
return new MessageHeaders(Collections.emptyMap());
|
||||
}
|
||||
|
||||
private static ObjectMapper createObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ExpressionEvaluatorRegistry registry = new ExpressionEvaluatorRegistry();
|
||||
registry.register(ExpressionTypeEnum.JSON, new JsonataExpressionEvaluator(createObjectMapper()));
|
||||
handler = new StartStepHandler(new ExpressionExecutor(registry));
|
||||
aggregate = ApiDefinitionAggregate.builder().build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyRequestMappingsToInvocationContext() {
|
||||
ApiStepDO stepDO = new ApiStepDO();
|
||||
stepDO.setId(101L);
|
||||
stepDO.setType("START");
|
||||
stepDO.setRequestMappingExpr("JSON::{\"requestHeaders\": {\"x-trace\": \"trace-1\"}, \"body\": {\"wrapped\": $}, \"requestQuery\": {\"flag\": \"Y\"}} ");
|
||||
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
context.setRequestBody(Map.of("original", "value"));
|
||||
context.getRequestHeaders().put("tenant-id", "42");
|
||||
|
||||
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
|
||||
ApiInvocationContext result = (ApiInvocationContext) genericHandler.handle(context, emptyHeaders());
|
||||
|
||||
assertThat(result).isSameAs(context);
|
||||
assertThat(result.getRequestHeaders()).containsEntry("x-trace", "trace-1");
|
||||
assertThat(result.getRequestQueryParams()).containsEntry("flag", "Y");
|
||||
assertThat(result.getRequestBody()).isInstanceOf(Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> mappedBody = (Map<String, ?>) result.getRequestBody();
|
||||
assertThat(mappedBody).containsKey("wrapped");
|
||||
assertThat(result.getStepResults()).hasSize(1);
|
||||
assertThat(result.getStepResults().get(0).isSuccess()).isTrue();
|
||||
assertThat(result.getStepResults().get(0).getStepId()).isEqualTo(101L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordFailureWhenExpressionInvalid() {
|
||||
ApiStepDO stepDO = new ApiStepDO();
|
||||
stepDO.setId(202L);
|
||||
stepDO.setType("START");
|
||||
stepDO.setRequestMappingExpr("JSON::{invalid}");
|
||||
ApiStepDefinition stepDefinition = ApiStepDefinition.builder().step(stepDO).build();
|
||||
|
||||
ApiInvocationContext context = ApiInvocationContext.create();
|
||||
GenericHandler<ApiInvocationContext> genericHandler = handler.build(aggregate, stepDefinition);
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
genericHandler.handle(context, emptyHeaders());
|
||||
})
|
||||
.isInstanceOf(ServiceException.class)
|
||||
.extracting(ex -> ((ServiceException) ex).getCode())
|
||||
.isEqualTo(API_STEP_START_EXECUTION_FAILED.getCode());
|
||||
assertThat(context.getStepResults()).hasSize(1);
|
||||
assertThat(context.getStepResults().get(0).isSuccess()).isFalse();
|
||||
assertThat(context.getStepResults().get(0).getStepId()).isEqualTo(202L);
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,10 @@ import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefi
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO;
|
||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
|
||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
|
||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyAuthMapper;
|
||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper;
|
||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
|
||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
|
||||
@@ -60,8 +58,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
@Resource
|
||||
private ApiTransformMapper apiTransformMapper;
|
||||
@Resource
|
||||
private ApiPolicyAuthMapper apiPolicyAuthMapper;
|
||||
@Resource
|
||||
private ApiPolicyRateLimitMapper apiPolicyRateLimitMapper;
|
||||
|
||||
@MockBean
|
||||
@@ -84,9 +80,8 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
void testCreate_success() {
|
||||
TenantContextHolder.setTenantId(1L);
|
||||
Long authId = insertAuthPolicy();
|
||||
Long rateId = insertRateLimitPolicy();
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, authId, rateId);
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, rateId);
|
||||
Long definitionId = apiDefinitionService.create(reqVO);
|
||||
|
||||
ApiDefinitionDO definition = apiDefinitionMapper.selectById(definitionId);
|
||||
@@ -98,16 +93,18 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
assertEquals(reqVO.getDescription(), definition.getDescription());
|
||||
|
||||
List<ApiStepDO> steps = apiStepMapper.selectByApiId(definitionId);
|
||||
assertEquals(1, steps.size());
|
||||
ApiStepDO step = steps.get(0);
|
||||
assertEquals(1, step.getStepOrder());
|
||||
assertEquals("HTTP", step.getType());
|
||||
assertEquals(3, steps.size());
|
||||
assertEquals("START", steps.get(0).getType());
|
||||
assertEquals(Integer.valueOf(1), steps.get(0).getStepOrder());
|
||||
assertEquals("HTTP", steps.get(1).getType());
|
||||
assertEquals(Integer.valueOf(2), steps.get(1).getStepOrder());
|
||||
assertEquals("END", steps.get(2).getType());
|
||||
assertEquals(Integer.valueOf(3), steps.get(2).getStepOrder());
|
||||
|
||||
List<ApiTransformDO> apiLevelTransforms = apiTransformMapper.selectApiLevelTransforms(definitionId);
|
||||
assertEquals(1, apiLevelTransforms.size());
|
||||
assertEquals("REQUEST_PRE", apiLevelTransforms.get(0).getPhase());
|
||||
assertEquals(0, apiLevelTransforms.size());
|
||||
|
||||
List<ApiTransformDO> stepTransforms = apiTransformMapper.selectByStepId(step.getId());
|
||||
List<ApiTransformDO> stepTransforms = apiTransformMapper.selectByStepId(steps.get(1).getId());
|
||||
assertEquals(1, stepTransforms.size());
|
||||
assertEquals("RESPONSE_PRE", stepTransforms.get(0).getPhase());
|
||||
}
|
||||
@@ -121,11 +118,10 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
definition.setApiCode("order.create");
|
||||
definition.setVersion("v1");
|
||||
definition.setHttpMethod("POST");
|
||||
definition.setUriPattern("/order/create");
|
||||
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
|
||||
apiDefinitionMapper.insert(definition);
|
||||
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, null, null);
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, null);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () -> apiDefinitionService.create(reqVO));
|
||||
assertEquals(GatewayServiceErrorCodeConstants.API_DEFINITION_DUPLICATE.getCode(), exception.getCode());
|
||||
@@ -134,7 +130,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
@Test
|
||||
void testUpdate_replaceSteps() {
|
||||
TenantContextHolder.setTenantId(1L);
|
||||
Long authId = insertAuthPolicy();
|
||||
Long rateId = insertRateLimitPolicy();
|
||||
ApiDefinitionDO definition = new ApiDefinitionDO();
|
||||
definition.setTenantId(1L);
|
||||
@@ -142,7 +137,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
definition.setApiCode("order.update");
|
||||
definition.setVersion("v1");
|
||||
definition.setHttpMethod("POST");
|
||||
definition.setUriPattern("/order/update");
|
||||
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
|
||||
apiDefinitionMapper.insert(definition);
|
||||
|
||||
@@ -164,20 +158,21 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
oldTransform.setDeleted(false);
|
||||
apiTransformMapper.insert(oldTransform);
|
||||
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(definition.getId(), authId, rateId);
|
||||
ApiDefinitionSaveReqVO reqVO = buildSaveReq(definition.getId(), rateId);
|
||||
reqVO.setApiCode("order.update");
|
||||
reqVO.setVersion("v2");
|
||||
reqVO.getSteps().get(0).setStepOrder(2);
|
||||
apiDefinitionService.update(reqVO);
|
||||
|
||||
List<ApiStepDO> steps = apiStepMapper.selectByApiId(definition.getId());
|
||||
assertEquals(1, steps.size());
|
||||
assertEquals(2, steps.get(0).getStepOrder());
|
||||
assertEquals(3, steps.size());
|
||||
assertEquals("START", steps.get(0).getType());
|
||||
assertEquals("HTTP", steps.get(1).getType());
|
||||
assertEquals("END", steps.get(2).getType());
|
||||
|
||||
List<ApiTransformDO> transforms = apiTransformMapper.selectByApiId(definition.getId());
|
||||
assertThat(transforms)
|
||||
.extracting(ApiTransformDO::getPhase)
|
||||
.containsExactlyInAnyOrder("REQUEST_PRE", "RESPONSE_PRE");
|
||||
.containsExactlyInAnyOrder("RESPONSE_PRE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,7 +184,6 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
definition.setApiCode("order.delete");
|
||||
definition.setVersion("v1");
|
||||
definition.setHttpMethod("DELETE");
|
||||
definition.setUriPattern("/order/delete");
|
||||
definition.setStatus(ApiStatusEnum.ONLINE.getStatus());
|
||||
apiDefinitionMapper.insert(definition);
|
||||
|
||||
@@ -219,26 +213,24 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
assertThat(apiTransformMapper.selectByApiId(definition.getId())).isEmpty();
|
||||
}
|
||||
|
||||
private ApiDefinitionSaveReqVO buildSaveReq(Long id, Long authId, Long rateId) {
|
||||
private ApiDefinitionSaveReqVO buildSaveReq(Long id, Long rateId) {
|
||||
ApiDefinitionSaveReqVO reqVO = new ApiDefinitionSaveReqVO();
|
||||
reqVO.setId(id);
|
||||
reqVO.setApiCode("order.create");
|
||||
reqVO.setVersion("v1");
|
||||
reqVO.setHttpMethod("POST");
|
||||
reqVO.setUriPattern("/order/create");
|
||||
reqVO.setStatus(ApiStatusEnum.ONLINE.getStatus());
|
||||
reqVO.setDescription("create order");
|
||||
reqVO.setAuthPolicyId(authId);
|
||||
reqVO.setRateLimitId(rateId);
|
||||
|
||||
ApiDefinitionTransformSaveReqVO apiTransform = new ApiDefinitionTransformSaveReqVO();
|
||||
apiTransform.setPhase("REQUEST_PRE");
|
||||
apiTransform.setExpressionType("JSON");
|
||||
apiTransform.setExpression("{}");
|
||||
reqVO.getApiLevelTransforms().add(apiTransform);
|
||||
ApiDefinitionStepSaveReqVO start = new ApiDefinitionStepSaveReqVO();
|
||||
start.setStepOrder(1);
|
||||
start.setType("START");
|
||||
start.setRequestMappingExpr("JSON::{}");
|
||||
reqVO.getSteps().add(start);
|
||||
|
||||
ApiDefinitionStepSaveReqVO step = new ApiDefinitionStepSaveReqVO();
|
||||
step.setStepOrder(1);
|
||||
step.setStepOrder(2);
|
||||
step.setType("HTTP");
|
||||
step.setTargetEndpoint("https://api.example.com/order");
|
||||
|
||||
@@ -247,20 +239,14 @@ class ApiDefinitionServiceImplTest extends BaseDbUnitTest {
|
||||
stepTransform.setExpressionType("JSON");
|
||||
stepTransform.setExpression("{}");
|
||||
step.getTransforms().add(stepTransform);
|
||||
|
||||
reqVO.getSteps().add(step);
|
||||
return reqVO;
|
||||
}
|
||||
|
||||
private Long insertAuthPolicy() {
|
||||
ApiPolicyAuthDO policy = new ApiPolicyAuthDO();
|
||||
policy.setName("auth");
|
||||
policy.setType("BASIC");
|
||||
policy.setConfig("{}");
|
||||
policy.setTenantId(1L);
|
||||
policy.setDeleted(false);
|
||||
apiPolicyAuthMapper.insert(policy);
|
||||
return policy.getId();
|
||||
ApiDefinitionStepSaveReqVO end = new ApiDefinitionStepSaveReqVO();
|
||||
end.setStepOrder(3);
|
||||
end.setType("END");
|
||||
end.setResponseMappingExpr("JSON::{}");
|
||||
reqVO.getSteps().add(end);
|
||||
return reqVO;
|
||||
}
|
||||
|
||||
private Long insertRateLimitPolicy() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
DELETE FROM "databus_api_transform";
|
||||
DELETE FROM "databus_api_step";
|
||||
DELETE FROM "databus_api_definition";
|
||||
DELETE FROM "databus_policy_auth";
|
||||
DELETE FROM "databus_policy_rate_limit";
|
||||
DELETE FROM "databus_policy_audit";
|
||||
DELETE FROM "databus_api_flow_publish";
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
CREATE TABLE IF NOT EXISTS databus_api_definition (
|
||||
id BIGINT PRIMARY KEY,
|
||||
api_code VARCHAR(255) NOT NULL,
|
||||
uri_pattern VARCHAR(512),
|
||||
http_method VARCHAR(16),
|
||||
version VARCHAR(64),
|
||||
status INT,
|
||||
description VARCHAR(1024),
|
||||
auth_policy_id BIGINT,
|
||||
rate_limit_id BIGINT,
|
||||
audit_policy_id BIGINT,
|
||||
response_template CLOB,
|
||||
cache_strategy VARCHAR(255),
|
||||
updated_at TIMESTAMP,
|
||||
grey_released BOOLEAN,
|
||||
tenant_id BIGINT,
|
||||
create_time TIMESTAMP,
|
||||
update_time TIMESTAMP,
|
||||
@@ -32,7 +28,6 @@ CREATE TABLE IF NOT EXISTS databus_api_step (
|
||||
response_mapping_expr VARCHAR(1024),
|
||||
transform_id BIGINT,
|
||||
timeout BIGINT,
|
||||
retry_strategy VARCHAR(255),
|
||||
fallback_strategy VARCHAR(255),
|
||||
condition_expr VARCHAR(1024),
|
||||
stop_on_error BOOLEAN,
|
||||
@@ -60,20 +55,6 @@ CREATE TABLE IF NOT EXISTS databus_api_transform (
|
||||
deleted BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS databus_policy_auth (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(64),
|
||||
config CLOB,
|
||||
description VARCHAR(512),
|
||||
tenant_id BIGINT,
|
||||
create_time TIMESTAMP,
|
||||
update_time TIMESTAMP,
|
||||
creator VARCHAR(64),
|
||||
updater VARCHAR(64),
|
||||
deleted BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS databus_policy_rate_limit (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user