From 431747928444181f2946966d4a78d32f193f4408 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Fri, 6 Jun 2025 12:57:11 +0300 Subject: [PATCH 1/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- .../filter/cors/CorsGatewayFilterApplicationListener.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java index ebe3e148f0..02c4579148 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -64,7 +65,7 @@ public CorsGatewayFilterApplicationListener(GlobalCorsProperties globalCorsPrope public void onApplicationEvent(RefreshRoutesResultEvent event) { routeLocator.getRoutes().collectList().subscribe(routes -> { // pre-populate with pre-existing global cors configurations to combine with. - var corsConfigurations = new HashMap<>(globalCorsProperties.getCorsConfigurations()); + var corsConfigurations = new LinkedHashMap<>(globalCorsProperties.getCorsConfigurations()); routes.forEach(route -> { var corsConfiguration = getCorsConfiguration(route); From 010a1a1fc7a6980916e61997c49b5bab91013017 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Fri, 6 Jun 2025 13:22:18 +0300 Subject: [PATCH 2/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- .../filter/cors/CorsGatewayFilterApplicationListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java index 02c4579148..636d09b3ef 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; From efa9e1d801fc33c9efc65d25ae4dd6c58b26fbf0 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Fri, 13 Jun 2025 16:21:07 +0300 Subject: [PATCH 3/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- ...GatewayFilterApplicationListenerTests.java | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java new file mode 100644 index 0000000000..e1dc7b49f2 --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.springframework.cloud.gateway.filter.cors; + +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; + +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; +import org.springframework.cloud.gateway.config.GlobalCorsProperties; +import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent; +import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; +import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.web.cors.CorsConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link CorsGatewayFilterApplicationListener}. + * + *

This test verifies that the merged CORS configurations composed of global and + * per-route metadata maintain insertion order as defined by the use of {@link LinkedHashMap}. + * Preserving insertion order helps for predictable and deterministic CORS behavior + * when resolving multiple matching path patterns. + *

+ * + *

The test builds actual {@link Route} instances with {@code Path} predicates and verifies + * that the resulting configuration map passed to {@link RoutePredicateHandlerMapping#setCorsConfigurations(Map)} + * respects the declared order of: + *

    + *
  • Global CORS configurations (in insertion order)
  • + *
  • Route-specific CORS configurations (in the order the routes are discovered)
  • + *
+ *

+ * + * @author Yavor Chamov + */ +@ExtendWith(MockitoExtension.class) +class CorsGatewayFilterApplicationListenerTests { + + private static final String GLOBAL_PATH_1 = "/global1"; + private static final String GLOBAL_PATH_2 = "/global2"; + private static final String ROUTE_PATH_1 = "/route1"; + private static final String ROUTE_PATH_2 = "/route2"; + private static final String ORIGIN_GLOBAL_1 = "https://global1.com"; + private static final String ORIGIN_GLOBAL_2 = "https://global2.com"; + private static final String ORIGIN_ROUTE_1 = "https://route1.com"; + private static final String ORIGIN_ROUTE_2 = "https://route2.com"; + private static final String ROUTE_ID_1 = "route1"; + private static final String ROUTE_ID_2 = "route2"; + private static final String ROUTE_URI = "https://spring.io"; + private static final String METADATA_KEY = "cors"; + private static final String ALLOWED_ORIGINS_KEY = "allowedOrigins"; + + @Mock + private RoutePredicateHandlerMapping handlerMapping; + + @Mock + private RouteLocator routeLocator; + + @Captor + private ArgumentCaptor> corsConfigurations; + + private GlobalCorsProperties globalCorsProperties; + + private CorsGatewayFilterApplicationListener listener; + + @BeforeEach + void setUp() { + globalCorsProperties = new GlobalCorsProperties(); + listener = new CorsGatewayFilterApplicationListener(globalCorsProperties, + handlerMapping, routeLocator); + } + + @Test + void testOnApplicationEvent_preservesInsertionOrder_withRealRoutes() { + + globalCorsProperties.getCorsConfigurations().put(GLOBAL_PATH_1, createCorsConfig(ORIGIN_GLOBAL_1)); + globalCorsProperties.getCorsConfigurations().put(GLOBAL_PATH_2, createCorsConfig(ORIGIN_GLOBAL_2)); + + WebFluxProperties webFluxProperties = new WebFluxProperties(); + + Route route1 = buildRoute(ROUTE_ID_1, ROUTE_PATH_1, ORIGIN_ROUTE_1, webFluxProperties); + Route route2 = buildRoute(ROUTE_ID_2, ROUTE_PATH_2, ORIGIN_ROUTE_2, webFluxProperties); + + when(routeLocator.getRoutes()).thenReturn(Flux.just(route1, route2)); + + listener.onApplicationEvent(new RefreshRoutesResultEvent(this)); + + Awaitility.await().atMost(Duration.ofSeconds(2)).untilAsserted(() -> { + + verify(handlerMapping).setCorsConfigurations(corsConfigurations.capture()); + + Map mergedCorsConfigurations = corsConfigurations.getValue(); + assertThat(mergedCorsConfigurations.keySet()).containsExactly(GLOBAL_PATH_1, + GLOBAL_PATH_2, ROUTE_PATH_1, ROUTE_PATH_2); + assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_1).getAllowedOrigins()) + .containsExactly(ORIGIN_GLOBAL_1); + assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_2).getAllowedOrigins()) + .containsExactly(ORIGIN_GLOBAL_2); + assertThat(mergedCorsConfigurations.get(ROUTE_PATH_1).getAllowedOrigins()) + .containsExactly(ORIGIN_ROUTE_1); + assertThat(mergedCorsConfigurations.get(ROUTE_PATH_2).getAllowedOrigins()) + .containsExactly(ORIGIN_ROUTE_2); + }); + } + + private CorsConfiguration createCorsConfig(String origin) { + + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of(origin)); + return config; + } + + private Route buildRoute(String id, String path, String allowedOrigin, WebFluxProperties webFluxProperties) { + + return Route.async() + .id(id) + .uri(ROUTE_URI) + .predicate(new PathRoutePredicateFactory(webFluxProperties) + .apply(config -> config.setPatterns(List.of(path)))) + .metadata(METADATA_KEY, Map.of(ALLOWED_ORIGINS_KEY, List.of(allowedOrigin))) + .build(); + } + +} From 7b328fa10f753ff809eea52672b4aea44f353ea4 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Thu, 19 Jun 2025 17:14:52 +0300 Subject: [PATCH 4/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- .../CorsGatewayFilterApplicationListenerTests.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java index e1dc7b49f2..13e491c58d 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java @@ -31,7 +31,6 @@ import org.mockito.junit.jupiter.MockitoExtension; import reactor.core.publisher.Flux; -import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.cloud.gateway.config.GlobalCorsProperties; import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; @@ -107,10 +106,8 @@ void testOnApplicationEvent_preservesInsertionOrder_withRealRoutes() { globalCorsProperties.getCorsConfigurations().put(GLOBAL_PATH_1, createCorsConfig(ORIGIN_GLOBAL_1)); globalCorsProperties.getCorsConfigurations().put(GLOBAL_PATH_2, createCorsConfig(ORIGIN_GLOBAL_2)); - WebFluxProperties webFluxProperties = new WebFluxProperties(); - - Route route1 = buildRoute(ROUTE_ID_1, ROUTE_PATH_1, ORIGIN_ROUTE_1, webFluxProperties); - Route route2 = buildRoute(ROUTE_ID_2, ROUTE_PATH_2, ORIGIN_ROUTE_2, webFluxProperties); + Route route1 = buildRoute(ROUTE_ID_1, ROUTE_PATH_1, ORIGIN_ROUTE_1); + Route route2 = buildRoute(ROUTE_ID_2, ROUTE_PATH_2, ORIGIN_ROUTE_2); when(routeLocator.getRoutes()).thenReturn(Flux.just(route1, route2)); @@ -141,12 +138,12 @@ private CorsConfiguration createCorsConfig(String origin) { return config; } - private Route buildRoute(String id, String path, String allowedOrigin, WebFluxProperties webFluxProperties) { + private Route buildRoute(String id, String path, String allowedOrigin) { return Route.async() .id(id) .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory(webFluxProperties) + .predicate(new PathRoutePredicateFactory() .apply(config -> config.setPatterns(List.of(path)))) .metadata(METADATA_KEY, Map.of(ALLOWED_ORIGINS_KEY, List.of(allowedOrigin))) .build(); From ff35c8a4658dc5d44904c013fa480fba7e9fa398 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Fri, 20 Jun 2025 01:51:48 +0300 Subject: [PATCH 5/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- .../cors/CorsGatewayFilterApplicationListener.java | 5 +++-- .../CorsGatewayFilterApplicationListenerTests.java | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java index 636d09b3ef..5bad22081f 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java @@ -64,16 +64,17 @@ public CorsGatewayFilterApplicationListener(GlobalCorsProperties globalCorsPrope public void onApplicationEvent(RefreshRoutesResultEvent event) { routeLocator.getRoutes().collectList().subscribe(routes -> { // pre-populate with pre-existing global cors configurations to combine with. - var corsConfigurations = new LinkedHashMap<>(globalCorsProperties.getCorsConfigurations()); + Map corsConfigurations = new LinkedHashMap<>(); routes.forEach(route -> { - var corsConfiguration = getCorsConfiguration(route); + Optional corsConfiguration = getCorsConfiguration(route); corsConfiguration.ifPresent(configuration -> { var pathPredicate = getPathPredicate(route); corsConfigurations.put(pathPredicate, configuration); }); }); + corsConfigurations.putAll(globalCorsProperties.getCorsConfigurations()); routePredicateHandlerMapping.setCorsConfigurations(corsConfigurations); }); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java index 13e491c58d..bc847bfde2 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java @@ -46,8 +46,8 @@ /** * Tests for {@link CorsGatewayFilterApplicationListener}. * - *

This test verifies that the merged CORS configurations composed of global and - * per-route metadata maintain insertion order as defined by the use of {@link LinkedHashMap}. + *

This test verifies that the merged CORS configurations - composed of per-route metadata + * and at the global level - maintain insertion order, as defined by the use of {@link LinkedHashMap}. * Preserving insertion order helps for predictable and deterministic CORS behavior * when resolving multiple matching path patterns. *

@@ -56,8 +56,8 @@ * that the resulting configuration map passed to {@link RoutePredicateHandlerMapping#setCorsConfigurations(Map)} * respects the declared order of: *
    - *
  • Global CORS configurations (in insertion order)
  • *
  • Route-specific CORS configurations (in the order the routes are discovered)
  • + *
  • Global CORS configurations (in insertion order)
  • *
*

* @@ -118,8 +118,8 @@ void testOnApplicationEvent_preservesInsertionOrder_withRealRoutes() { verify(handlerMapping).setCorsConfigurations(corsConfigurations.capture()); Map mergedCorsConfigurations = corsConfigurations.getValue(); - assertThat(mergedCorsConfigurations.keySet()).containsExactly(GLOBAL_PATH_1, - GLOBAL_PATH_2, ROUTE_PATH_1, ROUTE_PATH_2); + assertThat(mergedCorsConfigurations.keySet()) + .containsExactly(ROUTE_PATH_1, ROUTE_PATH_2, GLOBAL_PATH_1, GLOBAL_PATH_2); assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_1).getAllowedOrigins()) .containsExactly(ORIGIN_GLOBAL_1); assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_2).getAllowedOrigins()) From 2b8b8067f29af57cb03a52b1ad81b6ca5d85bf16 Mon Sep 17 00:00:00 2001 From: Yavor Chamov Date: Fri, 20 Jun 2025 19:02:59 +0300 Subject: [PATCH 6/6] Use LinkedHashMap for CORS configurations in CorsGatewayFilterApplicationListener to preserve insertion order. Fixes GH-3805. Signed-off-by: Yavor Chamov --- .../CorsGatewayFilterApplicationListener.java | 36 +++++++++-- ...GatewayFilterApplicationListenerTests.java | 61 +++++++++++-------- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java index 5bad22081f..89573a0504 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java @@ -34,12 +34,34 @@ import org.springframework.web.cors.CorsConfiguration; /** - * This class updates Cors configuration each time a {@link RefreshRoutesResultEvent} is - * consumed. The {@link Route}'s predicates are inspected for a - * {@link PathRoutePredicateFactory} and the first pattern is used. + *

+ * For each {@link Route}, this listener inspects its predicates and looks for an instance + * of {@link PathRoutePredicateFactory}. If a path predicate is found, the first defined + * path pattern is extracted and used as the key for associating the route-specific + * {@link CorsConfiguration}. + *

+ * + *

+ * After collecting all route-level CORS configurations, the listener merges them with + * globally defined configurations from {@link GlobalCorsProperties}, ensuring that + * route-specific configurations take precedence over global ones in case of conflicts + * (e.g., both defining CORS rules for {@code /**}). + *

+ * + *

+ * The merged configuration map is then applied to the + * {@link RoutePredicateHandlerMapping} via {@code setCorsConfigurations}. + *

+ * + *

+ * Note: A {@link LinkedHashMap} is used to store the merged configurations to preserve + * insertion order, which ensures predictable CORS resolution when multiple path patterns + * could match a request. + *

* * @author Fredrich Ombico * @author Abel Salgado Romero + * @author Yavor Chamov */ public class CorsGatewayFilterApplicationListener implements ApplicationListener { @@ -69,12 +91,16 @@ public void onApplicationEvent(RefreshRoutesResultEvent event) { routes.forEach(route -> { Optional corsConfiguration = getCorsConfiguration(route); corsConfiguration.ifPresent(configuration -> { - var pathPredicate = getPathPredicate(route); + String pathPredicate = getPathPredicate(route); corsConfigurations.put(pathPredicate, configuration); }); }); - corsConfigurations.putAll(globalCorsProperties.getCorsConfigurations()); + globalCorsProperties.getCorsConfigurations().forEach((path, config) -> { + if (!corsConfigurations.containsKey(path)) { + corsConfigurations.put(path, config); + } + }); routePredicateHandlerMapping.setCorsConfigurations(corsConfigurations); }); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java index bc847bfde2..585b063c6d 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListenerTests.java @@ -46,18 +46,21 @@ /** * Tests for {@link CorsGatewayFilterApplicationListener}. * - *

This test verifies that the merged CORS configurations - composed of per-route metadata - * and at the global level - maintain insertion order, as defined by the use of {@link LinkedHashMap}. - * Preserving insertion order helps for predictable and deterministic CORS behavior - * when resolving multiple matching path patterns. + *

+ * This test verifies that the merged CORS configurations - composed of per-route metadata + * and at the global level - maintain insertion order, as defined by the use of + * {@link LinkedHashMap}. Preserving insertion order helps for predictable and + * deterministic CORS behavior when resolving multiple matching path patterns. *

* - *

The test builds actual {@link Route} instances with {@code Path} predicates and verifies - * that the resulting configuration map passed to {@link RoutePredicateHandlerMapping#setCorsConfigurations(Map)} - * respects the declared order of: + *

+ * The test builds actual {@link Route} instances with {@code Path} predicates and + * verifies that the resulting configuration map passed to + * {@link RoutePredicateHandlerMapping#setCorsConfigurations(Map)} respects the declared + * order of: *

    - *
  • Route-specific CORS configurations (in the order the routes are discovered)
  • - *
  • Global CORS configurations (in insertion order)
  • + *
  • Route-specific CORS configurations (in the order the routes are discovered)
  • + *
  • Global CORS configurations (in insertion order)
  • *
*

* @@ -67,17 +70,29 @@ class CorsGatewayFilterApplicationListenerTests { private static final String GLOBAL_PATH_1 = "/global1"; + private static final String GLOBAL_PATH_2 = "/global2"; + private static final String ROUTE_PATH_1 = "/route1"; + private static final String ROUTE_PATH_2 = "/route2"; + private static final String ORIGIN_GLOBAL_1 = "https://global1.com"; + private static final String ORIGIN_GLOBAL_2 = "https://global2.com"; + private static final String ORIGIN_ROUTE_1 = "https://route1.com"; + private static final String ORIGIN_ROUTE_2 = "https://route2.com"; + private static final String ROUTE_ID_1 = "route1"; + private static final String ROUTE_ID_2 = "route2"; + private static final String ROUTE_URI = "https://spring.io"; + private static final String METADATA_KEY = "cors"; + private static final String ALLOWED_ORIGINS_KEY = "allowedOrigins"; @Mock @@ -96,8 +111,7 @@ class CorsGatewayFilterApplicationListenerTests { @BeforeEach void setUp() { globalCorsProperties = new GlobalCorsProperties(); - listener = new CorsGatewayFilterApplicationListener(globalCorsProperties, - handlerMapping, routeLocator); + listener = new CorsGatewayFilterApplicationListener(globalCorsProperties, handlerMapping, routeLocator); } @Test @@ -118,16 +132,14 @@ void testOnApplicationEvent_preservesInsertionOrder_withRealRoutes() { verify(handlerMapping).setCorsConfigurations(corsConfigurations.capture()); Map mergedCorsConfigurations = corsConfigurations.getValue(); - assertThat(mergedCorsConfigurations.keySet()) - .containsExactly(ROUTE_PATH_1, ROUTE_PATH_2, GLOBAL_PATH_1, GLOBAL_PATH_2); + assertThat(mergedCorsConfigurations.keySet()).containsExactly(ROUTE_PATH_1, ROUTE_PATH_2, GLOBAL_PATH_1, + GLOBAL_PATH_2); assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_1).getAllowedOrigins()) - .containsExactly(ORIGIN_GLOBAL_1); + .containsExactly(ORIGIN_GLOBAL_1); assertThat(mergedCorsConfigurations.get(GLOBAL_PATH_2).getAllowedOrigins()) - .containsExactly(ORIGIN_GLOBAL_2); - assertThat(mergedCorsConfigurations.get(ROUTE_PATH_1).getAllowedOrigins()) - .containsExactly(ORIGIN_ROUTE_1); - assertThat(mergedCorsConfigurations.get(ROUTE_PATH_2).getAllowedOrigins()) - .containsExactly(ORIGIN_ROUTE_2); + .containsExactly(ORIGIN_GLOBAL_2); + assertThat(mergedCorsConfigurations.get(ROUTE_PATH_1).getAllowedOrigins()).containsExactly(ORIGIN_ROUTE_1); + assertThat(mergedCorsConfigurations.get(ROUTE_PATH_2).getAllowedOrigins()).containsExactly(ORIGIN_ROUTE_2); }); } @@ -141,12 +153,11 @@ private CorsConfiguration createCorsConfig(String origin) { private Route buildRoute(String id, String path, String allowedOrigin) { return Route.async() - .id(id) - .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory() - .apply(config -> config.setPatterns(List.of(path)))) - .metadata(METADATA_KEY, Map.of(ALLOWED_ORIGINS_KEY, List.of(allowedOrigin))) - .build(); + .id(id) + .uri(ROUTE_URI) + .predicate(new PathRoutePredicateFactory().apply(config -> config.setPatterns(List.of(path)))) + .metadata(METADATA_KEY, Map.of(ALLOWED_ORIGINS_KEY, List.of(allowedOrigin))) + .build(); } }