fix count circuit breaker in gateway & return 404 when context api does not match ()

pull/1510/head
shedfreewu 1 month ago committed by GitHub
parent 4dbf8125d6
commit dd39bf7648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7,3 +7,4 @@
- [feat: support gateway context, feign eager-load support default value.](https://github.com/Tencent/spring-cloud-tencent/pull/1496)
- [feat:use polaris-all for shading third-party dependencies.](https://github.com/Tencent/spring-cloud-tencent/pull/1498)
- [feat:support default instance circuit breaker rule.](https://github.com/Tencent/spring-cloud-tencent/pull/1499)
- [fix: fix count circuit breaker in gateway & return 404 when context api does not match.](https://github.com/Tencent/spring-cloud-tencent/pull/1508)

@ -0,0 +1,31 @@
spring:
cloud:
tencent:
gateway:
groups:
group-scg2p:
predicate:
apiType: ms
context: /group1
namespace:
key: null
position: PATH
service:
key: null
position: PATH
routes:
- host: null
metadata: {}
method: GET
namespace: default
path: /echo/{param}
service: provider-demo
routes:
group1:
filters:
- Context=group1
order: -1
predicates:
- Context=group1
- Path=/group1/**
uri: lb://group1

@ -59,7 +59,7 @@ public class GatewayPluginAutoConfiguration {
@Value("${spring.cloud.polaris.discovery.eager-load.enabled:#{'true'}}")
private boolean commonEagerLoadEnabled;
@Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'true'}}")
@Value("${spring.cloud.polaris.discovery.eager-load.gateway.enabled:#{'false'}}")
private boolean gatewayEagerLoadEnabled;
@Bean

@ -46,7 +46,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// restore context from exchange
MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get(
MetadataContext metadataContext = exchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContext != null) {
MetadataContextHolder.set(metadataContext);

@ -26,6 +26,10 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements BeanPostProcessor, Ordered {
/**
* Order of this bean post processor.
*/
public static final int ORDER = 0;
private ApplicationContext applicationContext;
@ -46,6 +50,6 @@ public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessor implements
@Override
public int getOrder() {
return 0;
return ORDER;
}
}

@ -32,6 +32,7 @@ import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
@ -39,6 +40,7 @@ import org.springframework.web.server.ServerWebExchange;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
public class ContextGatewayFilter implements GatewayFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(ContextGatewayFilter.class);
@ -69,13 +71,15 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
String[] apis = rebuildExternalApi(request, request.getPath().value());
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
if (contextRoute == null) {
throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()));
String msg = String.format("[externalFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath());
logger.warn(msg);
throw NotFoundException.create(true, msg);
}
updateRouteMetadata(exchange, contextRoute);
URI requestUri = URI.create(contextRoute.getHost() + apis[1]);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
// 调整为正确路径
// to correct path
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
@ -83,22 +87,24 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
private Mono<Void> msFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext) {
ServerHttpRequest request = exchange.getRequest();
String[] apis = rebuildMsApi(request, groupContext, request.getPath().value());
// 判断 api 是否匹配
// check api
GroupContext.ContextRoute contextRoute = manager.getGroupPathRoute(config.getGroup(), apis[0]);
if (contextRoute == null) {
throw new RuntimeException(String.format("Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath()));
String msg = String.format("[msFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], request.getPath());
logger.warn(msg);
throw NotFoundException.create(true, msg);
}
updateRouteMetadata(exchange, contextRoute);
MetadataContext metadataContext = (MetadataContext) exchange.getAttributes().get(
MetadataContext metadataContext = exchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace());
if (metadataContext != null) {
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, contextRoute.getNamespace());
}
URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
// 调整为正确路径
// to correct path
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
@ -106,7 +112,7 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
/**
* e.g. "/context/api/test" [ "GET|/api/test", "/api/test"]
*/
private String[] rebuildExternalApi(ServerHttpRequest request, String path) {
String[] rebuildExternalApi(ServerHttpRequest request, String path) {
String[] pathSegments = path.split("/");
StringBuilder matchPath = new StringBuilder();
StringBuilder realPath = new StringBuilder();
@ -127,7 +133,7 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
* returns an array of two strings, the first is the match path, the second is the real path.
* e.g. "/context/namespace/svc/api/test" [ "GET|/namespace/svc/api/test", "/api/test"]
*/
private String[] rebuildMsApi(ServerHttpRequest request, GroupContext groupContext, String path) {
String[] rebuildMsApi(ServerHttpRequest request, GroupContext groupContext, String path) {
String[] pathSegments = path.split("/");
StringBuilder matchPath = new StringBuilder();
int index = 2;

@ -53,6 +53,4 @@ public class ContextGatewayFilterFactory extends AbstractGatewayFilterFactory<Co
this.group = group;
}
}
}

@ -52,6 +52,10 @@ public class ContextGatewayPropertiesManager {
return groupPathRouteMap;
}
public Map<String, Map<String, GroupContext.ContextRoute>> getGroupWildcardPathRouteMap() {
return groupWildcardPathRouteMap;
}
public void setGroupRouteMap(Map<String, GroupContext> groups) {
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupPathRouteMap = new ConcurrentHashMap<>();
@ -63,7 +67,7 @@ public class ContextGatewayPropertiesManager {
for (GroupContext.ContextRoute route : entry.getValue().getRoutes()) {
String path = route.getPath();
// convert path parameter to group wildcard path
if (path.contains("{") && path.contains("}") || path.contains("*")) {
if (path.contains("{") && path.contains("}") || path.contains("*") || path.contains("?")) {
newGroupWildcardPathRoute.put(buildPathKey(entry.getValue(), route), route);
}
else {

@ -46,8 +46,10 @@ public class GatewayConfigChangeListener {
public void onChangeTencentGatewayProperties(ConfigChangeEvent event) {
Binder binder = Binder.get(environment);
BindResult<ContextGatewayProperties> result = binder.bind(ContextGatewayProperties.PREFIX, ContextGatewayProperties.class);
manager.setGroupRouteMap(result.get().getGroups());
this.publisher.publishEvent(new RefreshRoutesEvent(event));
if (result.isBound()) {
manager.setGroupRouteMap(result.get().getGroups());
this.publisher.publishEvent(new RefreshRoutesEvent(event));
}
}
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = GatewayProperties.PREFIX)

@ -24,8 +24,6 @@ public class GroupContext {
private String comment;
private ApiType apiType;
private ContextPredicate predicate;
private List<ContextRoute> routes;
@ -38,14 +36,6 @@ public class GroupContext {
this.comment = comment;
}
public ApiType getApiType() {
return apiType;
}
public void setApiType(ApiType apiType) {
this.apiType = apiType;
}
public ContextPredicate getPredicate() {
return predicate;
}

@ -0,0 +1,18 @@
{
"properties": [
{
"name": "spring.cloud.tencent.gateway.routes",
"type": "java.util.Map<java.lang.String, org.springframework.cloud.gateway.route.RouteDefinition>",
"description": "Route definitions map for gateway context",
"sourceType": "com.tencent.cloud.plugin.gateway.context.ContextGatewayProperties",
"defaultValue": {}
},
{
"name": "spring.cloud.tencent.gateway.groups",
"type": "java.util.Map<java.lang.String, com.tencent.cloud.plugin.gateway.context.GroupContext>",
"description": "Group contexts configuration",
"sourceType": "com.tencent.cloud.plugin.gateway.context.ContextGatewayProperties",
"defaultValue": {}
}
]
}

@ -0,0 +1,143 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayFilterFactory;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayProperties;
import com.tencent.cloud.plugin.gateway.context.ContextGatewayPropertiesManager;
import com.tencent.cloud.plugin.gateway.context.ContextPropertiesRouteDefinitionLocator;
import com.tencent.cloud.plugin.gateway.context.ContextRoutePredicateFactory;
import com.tencent.cloud.plugin.gateway.context.GatewayConfigChangeListener;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link GatewayPluginAutoConfiguration}.
*/
class GatewayPluginAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
ConfigurationPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
PolarisContextAutoConfiguration.class,
GatewayAutoConfiguration.class,
GatewayPluginAutoConfiguration.class
))
.withPropertyValues(
"spring.cloud.gateway.enabled=false", // not needed for this test
"spring.cloud.tencent.plugin.scg.enabled=true",
"spring.cloud.tencent.plugin.scg.context.enabled=true",
"spring.cloud.tencent.gateway.routes.test_group.uri=lb://test-group"
)
.withUserConfiguration(MockPolarisClientsConfiguration.class);
@Test
void shouldCreateBeansWhenConditionsMet() {
contextRunner.run(context -> {
// Verify core beans
assertThat(context).hasSingleBean(ContextGatewayFilterFactory.class);
assertThat(context).hasSingleBean(ContextPropertiesRouteDefinitionLocator.class);
assertThat(context).hasSingleBean(ContextRoutePredicateFactory.class);
assertThat(context).hasSingleBean(ContextGatewayPropertiesManager.class);
assertThat(context).hasSingleBean(GatewayRegistrationCustomizer.class);
assertThat(context).hasSingleBean(GatewayConfigChangeListener.class);
assertThat(context).hasSingleBean(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.class);
GatewayPluginAutoConfiguration.ContextPluginConfiguration pluginConfiguration =
context.getBean(GatewayPluginAutoConfiguration.ContextPluginConfiguration.class);
assertThat(pluginConfiguration).hasFieldOrPropertyWithValue("commonEagerLoadEnabled", true)
.hasFieldOrPropertyWithValue("gatewayEagerLoadEnabled", false);
ContextGatewayProperties properties = context.getBean(ContextGatewayProperties.class);
properties.setRoutes(properties.getRoutes());
properties.setGroups(properties.getGroups());
properties.toString();
// test listener
GatewayConfigChangeListener listener = context.getBean(GatewayConfigChangeListener.class);
listener.onChangeTencentGatewayProperties(new ConfigChangeEvent(null, null));
listener.onChangeGatewayConfigChangeListener(new ConfigChangeEvent(null, null));
});
}
@Test
void shouldEagerLoad() {
contextRunner
.withPropertyValues("spring.cloud.polaris.discovery.eager-load.gateway.enabled=true")
.run(context -> {
// Verify eager loading
GatewayPluginAutoConfiguration.ContextPluginConfiguration pluginConfiguration = context.getBean(GatewayPluginAutoConfiguration.ContextPluginConfiguration.class);
assertThat(pluginConfiguration).hasFieldOrPropertyWithValue("commonEagerLoadEnabled", true)
.hasFieldOrPropertyWithValue("gatewayEagerLoadEnabled", true);
});
}
@Test
void shouldNotCreateBeansWhenPluginDisabled() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GatewayPluginAutoConfiguration.class))
.withPropertyValues("spring.cloud.tencent.plugin.scg.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean(ContextGatewayFilterFactory.class));
}
@Test
void shouldNotCreateContextBeansWhenContextPluginDisabled() {
contextRunner
.withPropertyValues("spring.cloud.tencent.plugin.scg.context.enabled=false")
.run(context -> {
assertThat(context).doesNotHaveBean(ContextGatewayFilterFactory.class);
assertThat(context).doesNotHaveBean(ContextPropertiesRouteDefinitionLocator.class);
});
}
@Configuration
static class MockPolarisClientsConfiguration {
@Bean
PolarisDiscoveryClient polarisDiscoveryClient() {
return mock(PolarisDiscoveryClient.class);
}
@Bean
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient() {
return mock(PolarisReactiveDiscoveryClient.class);
}
@Bean
PolarisConfigProperties polarisConfigProperties() {
return new PolarisConfigProperties();
}
}
}

@ -0,0 +1,70 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.registry.PolarisRegistration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GatewayRegistrationCustomizerTest {
@Mock
private PolarisRegistration polarisRegistration;
private final GatewayRegistrationCustomizer customizer = new GatewayRegistrationCustomizer();
@Test
void shouldAddServiceTypeMetadata() {
// Arrange
Map<String, String> metadata = new HashMap<>();
when(polarisRegistration.getMetadata()).thenReturn(metadata);
// Act
customizer.customize(polarisRegistration);
// Assert
assertThat(metadata)
.containsEntry("internal-service-type", "spring-cloud-gateway");
}
@Test
void shouldNotOverrideExistingMetadata() {
// Arrange
Map<String, String> metadata = new HashMap<>();
metadata.put("existing-key", "existing-value");
metadata.put("internal-service-type", "existing-type");
when(polarisRegistration.getMetadata()).thenReturn(metadata);
// Act
customizer.customize(polarisRegistration);
// Assert
assertThat(metadata)
.containsEntry("internal-service-type", "spring-cloud-gateway")
.containsEntry("existing-key", "existing-value");
}
}

@ -0,0 +1,94 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ApplicationContext;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
* Test for ${@link PolarisReactiveLoadBalancerClientFilterBeanPostProcessor}
*
* @author Shedfree Wu
*/
public class PolarisReactiveLoadBalancerClientFilterBeanPostProcessorTest {
@Mock
private ApplicationContext applicationContext;
@Mock
private LoadBalancerClientFactory loadBalancerClientFactory;
@Mock
private GatewayLoadBalancerProperties gatewayLoadBalancerProperties;
private PolarisReactiveLoadBalancerClientFilterBeanPostProcessor processor;
@BeforeEach
protected void setUp() {
MockitoAnnotations.openMocks(this);
processor = new PolarisReactiveLoadBalancerClientFilterBeanPostProcessor(applicationContext);
when(applicationContext.getBean(GatewayLoadBalancerProperties.class)).thenReturn(gatewayLoadBalancerProperties);
when(applicationContext.getBean(LoadBalancerClientFactory.class)).thenReturn(loadBalancerClientFactory);
}
@Test
void testGetOrder() {
int order = processor.getOrder();
Assertions.assertEquals(PolarisReactiveLoadBalancerClientFilterBeanPostProcessor.ORDER, order);
}
@Test
void testPostProcessBeforeInitializationWithReactiveLoadBalancerClientFilter() {
// Arrange
ReactiveLoadBalancerClientFilter originalInterceptor = mock(ReactiveLoadBalancerClientFilter.class);
String beanName = "testBean";
// Act
Object result = processor.postProcessAfterInitialization(originalInterceptor, beanName);
// Assert
Assertions.assertInstanceOf(PolarisReactiveLoadBalancerClientFilter.class, result);
}
@Test
void testPostProcessBeforeInitializationWithNonReactiveLoadBalancerClientFilter() {
// Arrange
Object originalBean = new Object();
String beanName = "testBean";
// Act
Object result = processor.postProcessAfterInitialization(originalBean, beanName);
// Assert
Assertions.assertSame(originalBean, result);
}
}

@ -0,0 +1,107 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link PolarisReactiveLoadBalancerClientFilter}.
*/
@ExtendWith(MockitoExtension.class)
class PolarisReactiveLoadBalancerClientFilterTest {
@Mock
private LoadBalancerClientFactory clientFactory;
@Mock
private GatewayLoadBalancerProperties properties;
@Mock
private ReactiveLoadBalancerClientFilter originalFilter;
@Mock
private ServerWebExchange exchange;
@Mock
private GatewayFilterChain chain;
private PolarisReactiveLoadBalancerClientFilter polarisFilter;
private final MetadataContext testContext = new MetadataContext();
@BeforeEach
void setUp() {
polarisFilter = new PolarisReactiveLoadBalancerClientFilter(clientFactory, properties, originalFilter);
MetadataContextHolder.remove();
}
@Test
void testGetOrderDelegatesToOriginalFilter() {
// Arrange
when(originalFilter.getOrder()).thenReturn(42);
// Act
assertThat(polarisFilter.getOrder()).isEqualTo(42);
verify(originalFilter).getOrder();
}
@Test
void testFilterRestoresMetadataContext() {
// Arrange
when(exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT))
.thenReturn(testContext);
when(originalFilter.filter(exchange, chain))
.thenReturn(Mono.empty());
MetadataContext before = MetadataContextHolder.get();
Assertions.assertNotEquals(testContext, before);
// Act
polarisFilter.filter(exchange, chain);
MetadataContext after = MetadataContextHolder.get();
Assertions.assertEquals(testContext, after);
}
@Test
void testFilterWithoutMetadataContext() {
// Arrange
when(exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT))
.thenReturn(null);
when(originalFilter.filter(exchange, chain))
.thenReturn(Mono.empty());
MetadataContext before = MetadataContextHolder.get();
// Act
polarisFilter.filter(exchange, chain);
MetadataContext after = MetadataContextHolder.get();
// Assert
Assertions.assertEquals(before, after);
}
}

@ -0,0 +1,98 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway.context;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ContextGatewayFilterFactory}.
*/
class ContextGatewayFilterFactoryTest {
@Mock
private ContextGatewayPropertiesManager mockManager;
private ContextGatewayFilterFactory filterFactory;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
filterFactory = new ContextGatewayFilterFactory(mockManager);
}
// Test filter factory creates filter with correct configuration
@Test
void shouldCreateFilterWithValidConfig() {
// Setup test config
ContextGatewayFilterFactory.Config config = new ContextGatewayFilterFactory.Config();
config.setGroup("test-group");
// Create filter instance
GatewayFilter filter = filterFactory.apply(config);
// Verify filter creation
assertThat(filter)
.isInstanceOf(ContextGatewayFilter.class)
.isNotNull();
}
// Test shortcut field order configuration
@Test
void shouldReturnCorrectShortcutFieldOrder() {
// Execute & Verify
assertThat(filterFactory.shortcutFieldOrder())
.containsExactly("group");
}
// Test config class getter/setter behavior
@Test
void shouldHandleConfigGroupPropertyCorrectly() {
// Setup config
ContextGatewayFilterFactory.Config config = new ContextGatewayFilterFactory.Config();
final String expectedGroup = "payment-service";
// Test setter/getter
config.setGroup(expectedGroup);
assertThat(config.getGroup())
.isEqualTo(expectedGroup);
}
// Test filter creation with empty group name
@Test
void shouldHandleEmptyGroupNameInConfig() {
// Setup config with empty group
ContextGatewayFilterFactory.Config config = new ContextGatewayFilterFactory.Config();
config.setGroup("");
// Create filter instance
GatewayFilter filter = filterFactory.apply(config);
// Verify filter creation
assertThat(filter)
.isNotNull()
.isInstanceOf(ContextGatewayFilter.class);
}
}

@ -0,0 +1,157 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway.context;
import java.net.URI;
import java.util.Collections;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
/**
* Test class for {@link ContextGatewayFilter}.
*/
class ContextGatewayFilterTest {
@Mock
private ContextGatewayPropertiesManager mockManager;
@Mock
private GatewayFilterChain mockChain;
private ContextGatewayFilter filter;
private GroupContext groupContext;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
ContextGatewayFilterFactory.Config config = new ContextGatewayFilterFactory.Config();
config.setGroup("testGroup");
filter = new ContextGatewayFilter(mockManager, config);
groupContext = new GroupContext();
when(mockManager.getGroups()).thenReturn(Collections.singletonMap("testGroup", groupContext));
}
// Test EXTERNAL API path reconstruction
@Test
void shouldHandleExternalApiPathCorrectly() {
// Setup group context
GroupContext.ContextPredicate predicate = createPredicate(ApiType.EXTERNAL, Position.PATH, Position.PATH);
groupContext.setPredicate(predicate);
groupContext.setRoutes(Collections.singletonList(
createContextRoute("GET|/external/api", "GET", "testNS", "testSvc")
));
// Create test request
MockServerHttpRequest request = MockServerHttpRequest.get("/context/external/api").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
GroupContext.ContextRoute route = createContextRoute("GET|/external/api", "GET", "http://test.com");
when(mockManager.getGroupPathRoute("testGroup", "GET|/external/api")).thenReturn(route);
// Execute filter
Mono<Void> result = filter.filter(exchange, mockChain);
// Verify final URL
URI finalUri = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
assertThat(finalUri.toString()).isEqualTo("http://test.com/external/api");
}
// Test metadata update functionality
@Test
void shouldUpdateRouteMetadataCorrectly() throws Exception {
// Setup context route with metadata
GroupContext.ContextRoute route = createContextRoute("GET|/api", "GET", "ns", "svc");
route.setMetadata(Collections.singletonMap("version", "v2"));
// Setup group context
GroupContext.ContextPredicate predicate = createPredicate(ApiType.MS, Position.PATH, Position.PATH);
groupContext.setPredicate(predicate);
groupContext.setRoutes(Collections.singletonList(route));
// Create test request
MockServerHttpRequest request = MockServerHttpRequest.get("/context/ns/svc/api").build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, Route.async().id("test").uri(URI.create("lb://svc")).
predicate((GatewayPredicate) serverWebExchange -> false).build());
when(mockManager.getGroupPathRoute("testGroup", "GET|/ns/svc/api")).thenReturn(route);
exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, MetadataContextHolder.get());
// Execute filter
filter.filter(exchange, mockChain);
// Route updatedRoute = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
// assertThat(updatedRoute.getMetadata()).containsEntry("version", "v2");
// no context in exchange
exchange.getAttributes().remove(MetadataConstant.HeaderName.METADATA_CONTEXT);
filter.filter(exchange, mockChain);
}
// Helper method to create test predicate
private GroupContext.ContextPredicate createPredicate(ApiType apiType, Position nsPos, Position svcPos) {
GroupContext.ContextPredicate predicate = new GroupContext.ContextPredicate();
predicate.setApiType(apiType);
GroupContext.ContextNamespace namespace = new GroupContext.ContextNamespace();
namespace.setPosition(nsPos);
namespace.setKey("ns-key");
predicate.setNamespace(namespace);
GroupContext.ContextService service = new GroupContext.ContextService();
service.setPosition(svcPos);
service.setKey("svc-key");
predicate.setService(service);
return predicate;
}
// Helper method to create context route
private GroupContext.ContextRoute createContextRoute(String path, String method, String ns, String svc) {
GroupContext.ContextRoute route = new GroupContext.ContextRoute();
route.setPath(path);
route.setMethod(method);
route.setNamespace(ns);
route.setService(svc);
return route;
}
private GroupContext.ContextRoute createContextRoute(String path, String method, String host) {
GroupContext.ContextRoute route = new GroupContext.ContextRoute();
route.setPath(path);
route.setMethod(method);
route.setHost(host);
return route;
}
}

@ -0,0 +1,305 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway.context;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClient;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import reactor.core.publisher.Flux;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
/**
* Test for {@link ContextGatewayPropertiesManager}.
*/
class ContextGatewayPropertiesManagerTest {
private ContextGatewayPropertiesManager manager;
private PolarisDiscoveryClient mockDiscoveryClient;
private PolarisReactiveDiscoveryClient mockReactiveClient;
@BeforeEach
void setup() {
manager = new ContextGatewayPropertiesManager();
mockDiscoveryClient = Mockito.mock(PolarisDiscoveryClient.class);
mockReactiveClient = Mockito.mock(PolarisReactiveDiscoveryClient.class);
}
@Test
void shouldHandleEmptyGroupsWhenSettingRouteMap() {
// Test empty groups handling
manager.setGroupRouteMap(null);
assertThat(manager.getGroupPathRouteMap()).isEmpty();
assertThat(manager.getGroups()).isNull();
}
@Test
void shouldClassifyRoutesByPathType() {
// Prepare test data
Map<String, GroupContext> groups = new HashMap<>();
GroupContext group1 = new GroupContext();
GroupContext.ContextRoute normalRoute = new GroupContext.ContextRoute();
normalRoute.setPath("/api/v1/normal");
normalRoute.setMethod("POST");
normalRoute.setNamespace("testNS");
normalRoute.setService("testSvc");
GroupContext.ContextRoute wildcardRoute = new GroupContext.ContextRoute();
wildcardRoute.setPath("/api/wildcard/**");
wildcardRoute.setMethod("GET");
wildcardRoute.setNamespace("testNS");
wildcardRoute.setService("testSvc");
group1.setRoutes(Arrays.asList(normalRoute, wildcardRoute));
GroupContext.ContextPredicate predicate = new GroupContext.ContextPredicate();
predicate.setApiType(ApiType.MS);
group1.setPredicate(predicate);
groups.put("group1", group1);
// Execute
manager.setGroupRouteMap(groups);
// Verify classification
Map<String, Map<String, GroupContext.ContextRoute>> pathMap = manager.getGroupPathRouteMap();
Map<String, Map<String, GroupContext.ContextRoute>> wildcardMap = manager.getGroupWildcardPathRouteMap();
assertThat(pathMap.get("group1")).hasSize(1);
assertThat(wildcardMap.get("group1")).hasSize(1);
// Execute eager load
manager.eagerLoad(mockDiscoveryClient, null);
when(mockReactiveClient.getInstances("testSvc")).thenReturn(Flux.fromIterable(Collections.emptyList()));
manager.eagerLoad(null, mockReactiveClient);
manager.eagerLoad(null, null);
}
@Test
void shouldMatchExactPathBeforeWildcard() {
// Setup test routes
GroupContext group = new GroupContext();
GroupContext.ContextRoute exactRoute = new GroupContext.ContextRoute();
exactRoute.setPath("/api/exact");
exactRoute.setMethod("POST");
exactRoute.setNamespace("testNS");
exactRoute.setService("testSvc");
GroupContext.ContextRoute wildcardRoute = new GroupContext.ContextRoute();
wildcardRoute.setPath("/api/*/wildcard");
wildcardRoute.setMethod("GET");
wildcardRoute.setNamespace("testNS");
wildcardRoute.setService("testSvc");
group.setRoutes(Arrays.asList(exactRoute, wildcardRoute));
GroupContext.ContextPredicate predicate = new GroupContext.ContextPredicate();
predicate.setApiType(ApiType.MS);
GroupContext.ContextNamespace namespace = new GroupContext.ContextNamespace();
namespace.setPosition(Position.PATH);
predicate.setNamespace(namespace);
GroupContext.ContextService service = new GroupContext.ContextService();
service.setPosition(Position.PATH);
predicate.setService(service);
group.setPredicate(predicate);
manager.setGroupRouteMap(Collections.singletonMap("testGroup", group));
ContextGatewayFilter filter = new ContextGatewayFilter(manager, null);
MockServerHttpRequest request = MockServerHttpRequest.post("http://localhost/context/testNS/testSvc/api/exact").build();
String[] apis = filter.rebuildMsApi(request, group, request.getPath().value());
// Test path matching
GroupContext.ContextRoute result = manager.getGroupPathRoute("testGroup", apis[0]);
assertThat(result).isEqualTo(exactRoute);
// Test wildcard matching
request = MockServerHttpRequest.get("http://localhost/context/testNS/testSvc/api/test/wildcard").build();
apis = filter.rebuildMsApi(request, group, request.getPath().value());
result = manager.getGroupPathRoute("testGroup", apis[0]);
assertThat(result).isEqualTo(wildcardRoute);
// Test non-matching
request = MockServerHttpRequest.get("http://localhost/context/testNS/testSvc/api/wildcard").build();
apis = filter.rebuildMsApi(request, group, request.getPath().value());
result = manager.getGroupPathRoute("testGroup", apis[0]);
assertThat(result).isNull();
}
// Helper method to create test context route
private GroupContext.ContextRoute createContextRoute(String path, String method, String namespace, String service) {
GroupContext.ContextRoute route = new GroupContext.ContextRoute();
route.setPath(path);
route.setMethod(method);
route.setNamespace(namespace);
route.setService(service);
return route;
}
// Helper method to create group context with configurable positions
private GroupContext createGroupContext(ApiType apiType, Position namespacePos, Position servicePos) {
GroupContext group = new GroupContext();
GroupContext.ContextPredicate predicate = new GroupContext.ContextPredicate();
predicate.setApiType(apiType);
GroupContext.ContextNamespace namespace = new GroupContext.ContextNamespace();
namespace.setPosition(namespacePos);
namespace.setKey("ns-key");
predicate.setNamespace(namespace);
GroupContext.ContextService service = new GroupContext.ContextService();
service.setPosition(servicePos);
service.setKey("svc-key");
predicate.setService(service);
group.setPredicate(predicate);
return group;
}
@Test
void shouldHandleMultiplePositionCombinations() {
// ns position PATH
testPositionCombination(ApiType.MS, Position.PATH, Position.PATH,
"/context/nsFromPath/svcFromPath/api/test",
"POST|/nsFromPath/svcFromPath/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.PATH, Position.HEADER,
"/context/nsFromPath/api/test",
"POST|/nsFromPath/svcFromHeader/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.PATH, Position.QUERY,
"/context/nsFromPath/api/test?svc-key=querySVC",
"POST|/nsFromPath/querySVC/api/test",
"/api/test");
// ns position QUERY
testPositionCombination(ApiType.MS, Position.QUERY, Position.PATH,
"/context/svcFromPath/api/test?ns-key=queryNS",
"POST|/queryNS/svcFromPath/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.QUERY, Position.QUERY,
"/context/api/test?ns-key=queryNS&svc-key=querySVC",
"POST|/queryNS/querySVC/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.QUERY, Position.HEADER,
"/context/api/test?ns-key=queryNS",
"POST|/queryNS/svcFromHeader/api/test",
"/api/test");
// ns position HEADER
testPositionCombination(ApiType.MS, Position.HEADER, Position.PATH,
"/context/svcFromPath/api/test",
"POST|/headerNS/svcFromPath/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.HEADER, Position.QUERY,
"/context/api/test?svc-key=querySVC",
"POST|/headerNS/querySVC/api/test",
"/api/test");
testPositionCombination(ApiType.MS, Position.HEADER, Position.HEADER,
"/context/api/test",
"POST|/headerNS/svcFromHeader/api/test",
"/api/test");
}
private void testPositionCombination(ApiType apiType, Position namespacePos, Position servicePos,
String inputPath, String expectedMatchPath, String expectedRealPath) {
// Setup group with specified positions
GroupContext group = createGroupContext(apiType, namespacePos, servicePos);
group.setRoutes(Collections.singletonList(
createContextRoute(expectedMatchPath, "POST", "testNS", "testSvc")
));
manager.setGroupRouteMap(Collections.singletonMap("testGroup", group));
// Build test request with appropriate parameters
MockServerHttpRequest.BaseBuilder<?> requestBuilder = MockServerHttpRequest.post(inputPath);
switch (namespacePos) {
case HEADER:
requestBuilder.header("ns-key", "headerNS");
break;
case QUERY:
// Query param already in URL
break;
}
switch (servicePos) {
case HEADER:
requestBuilder.header("svc-key", "svcFromHeader");
break;
case QUERY:
// Query param already in URL
break;
}
ContextGatewayFilter filter = new ContextGatewayFilter(manager, null);
MockServerHttpRequest mockServerHttpRequest = requestBuilder.build();
String[] apis = filter.rebuildMsApi(mockServerHttpRequest, group, mockServerHttpRequest.getPath().value());
// Verify path reconstruction
assertThat(apis[0]).isEqualTo(expectedMatchPath);
assertThat(apis[1]).isEqualTo(expectedRealPath);
}
@Test
void shouldHandleExternalApiType() {
// Test EXTERNAL API type
GroupContext group = createGroupContext(ApiType.EXTERNAL, Position.PATH, Position.PATH);
group.setRoutes(Collections.singletonList(
createContextRoute("POST|/external/api", "POST", null, null)
));
manager.setGroupRouteMap(Collections.singletonMap("externalGroup", group));
ContextGatewayFilter filter = new ContextGatewayFilter(manager, null);
String inputPath = "/context/external/api";
String[] apis = filter.rebuildExternalApi(
MockServerHttpRequest.post(inputPath).build(),
inputPath
);
assertThat(apis[0]).isEqualTo("POST|/external/api");
assertThat(apis[1]).isEqualTo("/external/api");
}
@Test
void testGroupContext() {
GroupContext group1 = new GroupContext();
group1.setComment("testComment");
Assertions.assertEquals("testComment", group1.getComment());
GroupContext.ContextPredicate contextPredicate = new GroupContext.ContextPredicate();
contextPredicate.setContext("testContext");
Assertions.assertEquals("testContext", contextPredicate.getContext());
GroupContext.ContextRoute contextRoute = new GroupContext.ContextRoute();
contextRoute.setPathMapping("testPathMapping");
Assertions.assertEquals("testPathMapping", contextRoute.getPathMapping());
contextRoute.setHost("testHost");
Assertions.assertEquals("testHost", contextRoute.getHost());
contextRoute.setMetadata(Collections.singletonMap("testKey", "testValue"));
Assertions.assertEquals(1, contextRoute.getMetadata().size());
}
}

@ -0,0 +1,69 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway.context;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.cloud.gateway.route.RouteDefinition;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests for {@link ContextPropertiesRouteDefinitionLocator}.
*/
class ContextPropertiesRouteDefinitionLocatorTest {
@Test
void shouldReturnEmptyWhenNoRoutes() {
ContextGatewayProperties mockProps = mock(ContextGatewayProperties.class);
when(mockProps.getRoutes()).thenReturn(Collections.emptyMap());
ContextPropertiesRouteDefinitionLocator locator = new ContextPropertiesRouteDefinitionLocator(mockProps);
Flux<RouteDefinition> result = locator.getRouteDefinitions();
StepVerifier.create(result)
.expectNextCount(0)
.verifyComplete();
}
@Test
void shouldReturnAllRouteDefinitions() {
Map<String, RouteDefinition> testRoutes = new HashMap<>();
testRoutes.put("route1", new RouteDefinition());
testRoutes.put("route2", new RouteDefinition());
ContextGatewayProperties mockProps = mock(ContextGatewayProperties.class);
when(mockProps.getRoutes()).thenReturn(testRoutes);
ContextPropertiesRouteDefinitionLocator locator = new ContextPropertiesRouteDefinitionLocator(mockProps);
Flux<RouteDefinition> result = locator.getRouteDefinitions();
StepVerifier.create(result)
.expectNextCount(2)
.verifyComplete();
}
}

@ -0,0 +1,99 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.gateway.context;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ContextRoutePredicateFactory}.
*/
class ContextRoutePredicateFactoryTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(ContextRoutePredicateFactory.class);
@Test
void shouldCreateConfigWithGroup() {
contextRunner.run(context -> {
// Arrange
ContextRoutePredicateFactory factory = context.getBean(ContextRoutePredicateFactory.class);
factory.shortcutFieldOrder();
// Act
ContextRoutePredicateFactory.Config config = factory.newConfig();
config.setGroup("g1");
Assertions.assertEquals("g1", config.getGroup());
GatewayPredicate gatewayPredicate = (GatewayPredicate) factory.apply(config);
gatewayPredicate.toString();
Assertions.assertTrue(gatewayPredicate.test(null));
Assertions.assertEquals(config, gatewayPredicate.getConfig());
});
}
@Test
void shouldAlwaysMatchWhenNotImplemented() {
contextRunner.run(context -> {
// Arrange
ContextRoutePredicateFactory factory = context.getBean(ContextRoutePredicateFactory.class);
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("/test").build());
// Act
ContextRoutePredicateFactory.Config config = new ContextRoutePredicateFactory.Config();
config.setGroup("test-group");
// Assert
boolean result = factory.apply(config).test(exchange);
assertThat(result).isTrue();
});
}
@Test
void shouldSupportShortcutFieldOrder() {
contextRunner.run(context -> {
ContextRoutePredicateFactory factory = context.getBean(ContextRoutePredicateFactory.class);
assertThat(factory.shortcutFieldOrder()).containsExactly("group");
});
}
@Test
void shouldCreateValidRoutePredicate() {
contextRunner.run(context -> {
// Arrange
Route route = Route.async()
.id("test-route")
.uri("http://example.com")
.predicate(context.getBean(ContextRoutePredicateFactory.class)
.apply(c -> c.setGroup("group1")))
.build();
// Act
assertThat(route.getPredicate()).isNotNull();
});
}
}

@ -139,6 +139,12 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
}
})
.doOnSuccess(v -> {
MetadataContext metadataContextOnSuccess = originExchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContextOnSuccess != null) {
MetadataContextHolder.set(metadataContextOnSuccess);
}
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
EnhancedResponseContext enhancedResponseContext = EnhancedResponseContext.builder()
.httpStatus(exchange.getResponse().getRawStatusCode())
@ -150,6 +156,12 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
pluginRunner.run(EnhancedPluginType.Client.POST, enhancedPluginContext);
})
.doOnError(t -> {
MetadataContext metadataContextOnError = originExchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContextOnError != null) {
MetadataContextHolder.set(metadataContextOnError);
}
enhancedPluginContext.setDelay(System.currentTimeMillis() - startTime);
enhancedPluginContext.setThrowable(t);
@ -157,6 +169,12 @@ public class EnhancedGatewayGlobalFilter implements GlobalFilter, Ordered {
pluginRunner.run(EnhancedPluginType.Client.EXCEPTION, enhancedPluginContext);
})
.doFinally(v -> {
MetadataContext metadataContextOnFinally = originExchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContextOnFinally != null) {
MetadataContextHolder.set(metadataContextOnFinally);
}
// Run finally enhanced plugins.
pluginRunner.run(EnhancedPluginType.Client.FINALLY, enhancedPluginContext);
});

Loading…
Cancel
Save