From 383d0672ea2ccbde2ad3006b5eb421cb70c68c4f Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Tue, 22 Apr 2025 19:09:12 +0200 Subject: [PATCH] Refactor LoadBalancerExchangeFilterFunction integration tests. Signed-off-by: Olga Maciaszek-Sharma --- ...changeFilterFunctionIntegrationTests.java} | 197 ++++++------- ...xchangeFilterFunctionIntegrationTests.java | 67 +++++ ...xchangeFilterFunctionIntegrationTests.java | 266 ++---------------- 3 files changed, 186 insertions(+), 344 deletions(-) rename spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/{ReactorLoadBalancerExchangeFilterFunctionTests.java => AbstractLoadBalancerExchangeFilterFunctionIntegrationTests.java} (59%) create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.java diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/AbstractLoadBalancerExchangeFilterFunctionIntegrationTests.java similarity index 59% rename from spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java rename to spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/AbstractLoadBalancerExchangeFilterFunctionIntegrationTests.java index d5f1c291..06c84d18 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/AbstractLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2025 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. @@ -27,16 +27,13 @@ import java.util.concurrent.ConcurrentHashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; import org.springframework.cloud.client.loadbalancer.CompletionContext; import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; @@ -45,51 +42,47 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; -import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.http.ResponseEntity; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.BDDAssertions.then; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** - * Tests for {@link ReactorLoadBalancerExchangeFilterFunction}. + * Base class for {@link LoadBalancedExchangeFilterFunction} integration tests. * * @author Olga Maciaszek-Sharma - * @author Charu Covindane */ -@SuppressWarnings("ConstantConditions") -@SpringBootTest(webEnvironment = RANDOM_PORT) -class ReactorLoadBalancerExchangeFilterFunctionTests { +@SuppressWarnings("DataFlowIssue") +abstract class AbstractLoadBalancerExchangeFilterFunctionIntegrationTests { @Autowired - private ReactorLoadBalancerExchangeFilterFunction loadBalancerFunction; + protected LoadBalancedExchangeFilterFunction loadBalancerFunction; @Autowired - private SimpleDiscoveryProperties properties; + protected SimpleDiscoveryProperties properties; @Autowired - private LoadBalancerProperties loadBalancerProperties; + protected LoadBalancerProperties loadBalancerProperties; @Autowired - private ReactiveLoadBalancer.Factory factory; + protected ReactiveLoadBalancer.Factory factory; @LocalServerPort - private int port; + protected int port; @BeforeEach - void setUp() { + protected void setUp() { DefaultServiceInstance instance = new DefaultServiceInstance(); instance.setServiceId("testservice"); - instance.setUri(URI.create("http://localhost:" + this.port)); + instance.setUri(URI.create("http://localhost:" + port)); DefaultServiceInstance instanceWithNoLifecycleProcessors = new DefaultServiceInstance(); instanceWithNoLifecycleProcessors.setServiceId("serviceWithNoLifecycleProcessors"); - instanceWithNoLifecycleProcessors.setUri(URI.create("http://localhost:" + this.port)); + instanceWithNoLifecycleProcessors.setUri(URI.create("http://localhost:" + port)); properties.getInstances().put("testservice", Collections.singletonList(instance)); properties.getInstances() .put("serviceWithNoLifecycleProcessors", Collections.singletonList(instanceWithNoLifecycleProcessors)); @@ -97,41 +90,50 @@ class ReactorLoadBalancerExchangeFilterFunctionTests { @Test void correctResponseReturnedForExistingHostAndInstancePresent() { - ClientResponse clientResponse = WebClient.builder() + ResponseEntity response = WebClient.builder() .baseUrl("http://testservice") .filter(loadBalancerFunction) .build() .get() .uri("/hello") - .exchange() + .retrieve() + .toEntity(String.class) .block(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); - then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + then(response.getStatusCode()).isEqualTo(HttpStatus.OK); + then(response.getBody()).isEqualTo("Hello World"); } @Test void serviceUnavailableReturnedWhenNoInstancePresent() { - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http://xxx") - .filter(this.loadBalancerFunction) - .build() - .get() - .exchange() - .block(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + assertThatIllegalStateException() + .isThrownBy(() -> WebClient.builder() + .baseUrl("http://xxx") + .filter(loadBalancerFunction) + .defaultStatusHandler(httpStatusCode -> httpStatusCode.equals(HttpStatus.SERVICE_UNAVAILABLE), + clientResponse -> Mono.just(new IllegalStateException("503"))) + .build() + .get() + .retrieve() + .toBodilessEntity() + .block()) + .withMessage("503"); } @Test @Disabled // FIXME 3.0.0 void badRequestReturnedForIncorrectHost() { - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http:///xxx") - .filter(this.loadBalancerFunction) - .build() - .get() - .exchange() - .block(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + assertThatIllegalStateException() + .isThrownBy(() -> WebClient.builder() + .baseUrl("http:///xxx") + .filter(loadBalancerFunction) + .defaultStatusHandler(httpStatusCode -> httpStatusCode.equals(HttpStatus.BAD_REQUEST), + response -> Mono.just(new IllegalStateException("400"))) + .build() + .get() + .retrieve() + .toBodilessEntity() + .block()) + .withMessage("400"); } @Test @@ -142,7 +144,7 @@ class ReactorLoadBalancerExchangeFilterFunctionTests { .build() .get() .uri("/hello") - .exchange() + .exchangeToMono(clientResponse -> clientResponse.bodyToMono(String.class)) .block()).doesNotThrowAnyException(); } @@ -150,89 +152,80 @@ class ReactorLoadBalancerExchangeFilterFunctionTests { void loadBalancerLifecycleCallbacksExecuted() { final String callbackTestHint = "callbackTestHint"; loadBalancerProperties.getHint().put("testservice", "callbackTestHint"); - ClientResponse clientResponse = WebClient.builder() + + ResponseEntity response = WebClient.builder() .baseUrl("http://testservice") .filter(loadBalancerFunction) .build() .get() .uri("/callback") - .exchange() + .retrieve() + .toBodilessEntity() .block(); Collection> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory .getInstances("testservice", LoadBalancerLifecycle.class) .get("loadBalancerLifecycle")).getStartLog().values(); - Collection> lifecycleStartedLogRequests = ((TestLoadBalancerLifecycle) factory + Collection> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle) factory .getInstances("testservice", LoadBalancerLifecycle.class) .get("loadBalancerLifecycle")).getStartRequestLog().values(); Collection> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory .getInstances("testservice", LoadBalancerLifecycle.class) .get("anotherLoadBalancerLifecycle")).getCompleteLog().values(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); + then(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(lifecycleLogRequests).extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) .contains(callbackTestHint); - assertThat(lifecycleStartedLogRequests) + assertThat(lifecycleLogStartRequests) .extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) .contains(callbackTestHint); assertThat(anotherLifecycleLogRequests) .extracting(completionContext -> ((ResponseData) completionContext.getClientResponse()).getRequestData() - .getUrl() - .toString()) - .contains("http://testservice/callback"); + .getHttpMethod()) + .contains(HttpMethod.GET); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - @EnableDiscoveryClient - @EnableAutoConfiguration - @SpringBootConfiguration(proxyBeanMethods = false) - @RestController - static class Config { + protected static class TestLoadBalancerFactory implements ReactiveLoadBalancer.Factory { - @GetMapping("/hello") - public String hello() { - return "Hello World"; + private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.TestLoadBalancerLifecycle testLoadBalancerLifecycle; + + private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.TestLoadBalancerLifecycle anotherLoadBalancerLifecycle; + + private final DiscoveryClient discoveryClient; + + private final LoadBalancerProperties properties; + + public TestLoadBalancerFactory(DiscoveryClient discoveryClient, LoadBalancerProperties properties) { + this.discoveryClient = discoveryClient; + this.properties = properties; + testLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.TestLoadBalancerLifecycle(); + anotherLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.AnotherLoadBalancerLifecycle(); } - @GetMapping("/callback") - String callbackTestResult() { - return "callbackTestResult"; + @Override + public ReactiveLoadBalancer getInstance(String serviceId) { + return new DiscoveryClientBasedReactiveLoadBalancer(serviceId, discoveryClient); } - @Bean - ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient, - LoadBalancerProperties properties) { - return new ReactiveLoadBalancer.Factory<>() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map getInstances(String name, Class type) { + if (name.equals("serviceWithNoLifecycleProcessors")) { + return null; + } + Map lifecycleProcessors = new HashMap<>(); + lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle); + lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle); + return lifecycleProcessors; + } - private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle(); + @Override + public X getInstance(String name, Class clazz, Class... generics) { + return null; + } - private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle(); - - @Override - public ReactiveLoadBalancer getInstance(String serviceId) { - return new DiscoveryClientBasedReactiveLoadBalancer(serviceId, discoveryClient); - } - - @Override - public Map getInstances(String name, Class type) { - if (name.equals("serviceWithNoLifecycleProcessors")) { - return null; - } - Map lifecycleProcessors = new HashMap<>(); - lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle); - lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle); - return lifecycleProcessors; - } - - @Override - public X getInstance(String name, Class clazz, Class... generics) { - return null; - } - - @Override - public LoadBalancerProperties getProperties(String serviceId) { - return properties; - } - }; + @Override + public LoadBalancerProperties getProperties(String serviceId) { + return properties; } } @@ -257,6 +250,7 @@ class ReactorLoadBalancerExchangeFilterFunctionTests { @Override public void onComplete(CompletionContext completionContext) { + completeLog.clear(); completeLog.put(getName() + UUID.randomUUID(), completionContext); } @@ -273,18 +267,13 @@ class ReactorLoadBalancerExchangeFilterFunctionTests { } protected String getName() { - return this.getClass().getSimpleName(); + return getClass().getSimpleName(); } } protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle { - @Override - protected String getName() { - return this.getClass().getSimpleName(); - } - } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.java new file mode 100644 index 00000000..5b83151f --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2025 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.client.loadbalancer.reactive; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Tests for {@link ReactorLoadBalancerExchangeFilterFunction}. + * + * @author Olga Maciaszek-Sharma + * @author Charu Covindane + */ +@SuppressWarnings("ConstantConditions") +@SpringBootTest(webEnvironment = RANDOM_PORT) +class ReactorLoadBalancerExchangeFilterFunctionIntegrationTests + extends AbstractLoadBalancerExchangeFilterFunctionIntegrationTests { + + @EnableDiscoveryClient + @EnableAutoConfiguration + @SpringBootConfiguration(proxyBeanMethods = false) + @RestController + static class Config { + + @GetMapping("/hello") + public String hello() { + return "Hello World"; + } + + @GetMapping("/callback") + String callbackTestResult() { + return "callbackTestResult"; + } + + @Bean + ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient, + LoadBalancerProperties properties) { + return new TestLoadBalancerFactory(discoveryClient, properties); + } + + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java index 7cd4333c..9ef145e6 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2025 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. @@ -18,45 +18,27 @@ package org.springframework.cloud.client.loadbalancer.reactive; import java.net.URI; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; -import org.springframework.cloud.client.loadbalancer.CompletionContext; -import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.cloud.client.loadbalancer.Request; -import org.springframework.cloud.client.loadbalancer.Response; -import org.springframework.cloud.client.loadbalancer.ResponseData; import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpMethod; +import org.springframework.context.annotation.Primary; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @@ -66,103 +48,28 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Olga Maciaszek-Sharma * @since 3.0.0 */ +@SuppressWarnings("DataFlowIssue") @SpringBootTest(webEnvironment = RANDOM_PORT) -class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests { - - @Autowired - private RetryableLoadBalancerExchangeFilterFunction loadBalancerFunction; - - @Autowired - private SimpleDiscoveryProperties properties; - - @Autowired - private LoadBalancerProperties loadBalancerProperties; - - @Autowired - private ReactiveLoadBalancer.Factory factory; - - @LocalServerPort - private int port; - - @BeforeEach - void setUp() { - DefaultServiceInstance instance = new DefaultServiceInstance(); - instance.setServiceId("testservice"); - instance.setUri(URI.create("http://localhost:" + port)); - DefaultServiceInstance instanceWithNoLifecycleProcessors = new DefaultServiceInstance(); - instanceWithNoLifecycleProcessors.setServiceId("serviceWithNoLifecycleProcessors"); - instanceWithNoLifecycleProcessors.setUri(URI.create("http://localhost:" + port)); - properties.getInstances().put("testservice", Collections.singletonList(instance)); - properties.getInstances() - .put("serviceWithNoLifecycleProcessors", Collections.singletonList(instanceWithNoLifecycleProcessors)); - } - - @Test - void loadBalancerLifecycleCallbacksExecuted() { - final String callbackTestHint = "callbackTestHint"; - loadBalancerProperties.getHint().put("testservice", "callbackTestHint"); - - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http://testservice") - .filter(this.loadBalancerFunction) - .build() - .get() - .uri("/callback") - .exchange() - .block(); - - Collection> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory - .getInstances("testservice", LoadBalancerLifecycle.class) - .get("loadBalancerLifecycle")).getStartLog().values(); - Collection> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle) factory - .getInstances("testservice", LoadBalancerLifecycle.class) - .get("loadBalancerLifecycle")).getStartRequestLog().values(); - Collection> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory - .getInstances("testservice", LoadBalancerLifecycle.class) - .get("anotherLoadBalancerLifecycle")).getCompleteLog().values(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); - assertThat(lifecycleLogRequests).extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) - .contains(callbackTestHint); - assertThat(lifecycleLogStartRequests) - .extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) - .contains(callbackTestHint); - assertThat(anotherLifecycleLogRequests) - .extracting(completionContext -> ((ResponseData) completionContext.getClientResponse()).getRequestData() - .getHttpMethod()) - .contains(HttpMethod.GET); - } - - @Test - void correctResponseReturnedForExistingHostAndInstancePresent() { - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http://testservice") - .filter(this.loadBalancerFunction) - .build() - .get() - .uri("/hello") - .exchange() - .block(); - - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); - then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); - } +class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests + extends AbstractLoadBalancerExchangeFilterFunctionIntegrationTests { @Test void correctResponseReturnedAfterRetryingOnSameServiceInstance() { loadBalancerProperties.getRetry().setMaxRetriesOnSameServiceInstance(1); loadBalancerProperties.getRetry().getRetryableStatusCodes().add(500); - ClientResponse clientResponse = WebClient.builder() + ResponseEntity response = WebClient.builder() .baseUrl("http://testservice") - .filter(this.loadBalancerFunction) + .filter(loadBalancerFunction) .build() .get() .uri("/exception") - .exchange() + .retrieve() + .toEntity(String.class) .block(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); - then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World!"); + then(response.getStatusCode()).isEqualTo(HttpStatus.OK); + then(response.getBody()).isEqualTo("Hello World!"); } @Test @@ -178,72 +85,33 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests { properties.getInstances().put("retrytest", Arrays.asList(badRetryTestInstance, goodRetryTestInstance)); loadBalancerProperties.getRetry().getRetryableStatusCodes().add(500); - ClientResponse clientResponse = WebClient.builder() + ResponseEntity response = WebClient.builder() .baseUrl("http://retrytest") - .filter(this.loadBalancerFunction) + .filter(loadBalancerFunction) .build() .get() .uri("/hello") - .exchange() + .retrieve() + .toEntity(String.class) .block(); - then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); - then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + then(response.getStatusCode()).isEqualTo(HttpStatus.OK); + then(response.getBody()).isEqualTo("Hello World"); - ClientResponse secondClientResponse = WebClient.builder() + ResponseEntity secondResponse = WebClient.builder() .baseUrl("http://retrytest") - .filter(this.loadBalancerFunction) + .filter(loadBalancerFunction) .build() .get() .uri("/hello") - .exchange() + .retrieve() + .toEntity(String.class) .block(); - then(secondClientResponse.statusCode()).isEqualTo(HttpStatus.OK); - then(secondClientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + then(secondResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + then(secondResponse.getBody()).isEqualTo("Hello World"); } - @Test - void serviceUnavailableReturnedWhenNoInstancePresent() { - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http://xxx") - .filter(this.loadBalancerFunction) - .build() - .get() - .exchange() - .block(); - - then(clientResponse.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - } - - @Test - @Disabled - // FIXME 3.0.0 - void badRequestReturnedForIncorrectHost() { - ClientResponse clientResponse = WebClient.builder() - .baseUrl("http:///xxx") - .filter(this.loadBalancerFunction) - .build() - .get() - .exchange() - .block(); - - then(clientResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - } - - @Test - void exceptionNotThrownWhenFactoryReturnsNullLifecycleProcessorsMap() { - assertThatCode(() -> WebClient.builder() - .baseUrl("http://serviceWithNoLifecycleProcessors") - .filter(this.loadBalancerFunction) - .build() - .get() - .uri("/hello") - .exchange() - .block()).doesNotThrowAnyException(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) @EnableDiscoveryClient @EnableAutoConfiguration @SpringBootConfiguration(proxyBeanMethods = false) @@ -274,42 +142,11 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests { @Bean ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient, LoadBalancerProperties properties) { - return new ReactiveLoadBalancer.Factory<>() { - - private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle(); - - private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle(); - - @Override - public ReactiveLoadBalancer getInstance(String serviceId) { - return new org.springframework.cloud.client.loadbalancer.reactive.DiscoveryClientBasedReactiveLoadBalancer( - serviceId, discoveryClient); - } - - @Override - public Map getInstances(String name, Class type) { - if (name.equals("serviceWithNoLifecycleProcessors")) { - return null; - } - Map lifecycleProcessors = new HashMap<>(); - lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle); - lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle); - return lifecycleProcessors; - } - - @Override - public X getInstance(String name, Class clazz, Class... generics) { - return null; - } - - @Override - public LoadBalancerProperties getProperties(String serviceId) { - return properties; - } - }; + return new TestLoadBalancerFactory(discoveryClient, properties); } @Bean + @Primary RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction( ReactiveLoadBalancer.Factory factory) { return new RetryableLoadBalancerExchangeFilterFunction( @@ -319,55 +156,4 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests { } - protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { - - Map> startLog = new ConcurrentHashMap<>(); - - Map> startRequestLog = new ConcurrentHashMap<>(); - - Map> completeLog = new ConcurrentHashMap<>(); - - @Override - public void onStart(Request request) { - startLog.put(getName() + UUID.randomUUID(), request); - } - - @Override - public void onStartRequest(Request request, Response lbResponse) { - startRequestLog.put(getName() + UUID.randomUUID(), request); - } - - @Override - public void onComplete(CompletionContext completionContext) { - completeLog.clear(); - completeLog.put(getName() + UUID.randomUUID(), completionContext); - } - - Map> getStartLog() { - return startLog; - } - - Map> getCompleteLog() { - return completeLog; - } - - Map> getStartRequestLog() { - return startRequestLog; - } - - protected String getName() { - return this.getClass().getSimpleName(); - } - - } - - protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle { - - @Override - protected String getName() { - return this.getClass().getSimpleName(); - } - - } - }