Refactor LoadBalancerExchangeFilterFunction integration tests.

Signed-off-by: Olga Maciaszek-Sharma <olga.maciaszek-sharma@broadcom.com>
This commit is contained in:
Olga Maciaszek-Sharma
2025-04-22 19:09:12 +02:00
parent 999a323a1b
commit 383d0672ea
3 changed files with 186 additions and 344 deletions

View File

@@ -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<ServiceInstance> factory;
protected ReactiveLoadBalancer.Factory<ServiceInstance> 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<String> 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<Void> response = WebClient.builder()
.baseUrl("http://testservice")
.filter(loadBalancerFunction)
.build()
.get()
.uri("/callback")
.exchange()
.retrieve()
.toBodilessEntity()
.block();
Collection<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("testservice", LoadBalancerLifecycle.class)
.get("loadBalancerLifecycle")).getStartLog().values();
Collection<Request<Object>> lifecycleStartedLogRequests = ((TestLoadBalancerLifecycle) factory
Collection<Request<Object>> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("testservice", LoadBalancerLifecycle.class)
.get("loadBalancerLifecycle")).getStartRequestLog().values();
Collection<CompletionContext<Object, ServiceInstance, Object>> 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<ServiceInstance> {
@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<ServiceInstance> getInstance(String serviceId) {
return new DiscoveryClientBasedReactiveLoadBalancer(serviceId, discoveryClient);
}
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient,
LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<>() {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <X> Map<String, X> getInstances(String name, Class<X> 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> X getInstance(String name, Class<?> clazz, Class<?>... generics) {
return null;
}
private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle();
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return new DiscoveryClientBasedReactiveLoadBalancer(serviceId, discoveryClient);
}
@Override
public <X> Map<String, X> getInstances(String name, Class<X> type) {
if (name.equals("serviceWithNoLifecycleProcessors")) {
return null;
}
Map lifecycleProcessors = new HashMap<>();
lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle);
lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle);
return lifecycleProcessors;
}
@Override
public <X> 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<Object, ServiceInstance, Object> 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();
}
}
}

View File

@@ -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<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient,
LoadBalancerProperties properties) {
return new TestLoadBalancerFactory(discoveryClient, properties);
}
}
}

View File

@@ -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<ServiceInstance> 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<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("testservice", LoadBalancerLifecycle.class)
.get("loadBalancerLifecycle")).getStartLog().values();
Collection<Request<Object>> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle) factory
.getInstances("testservice", LoadBalancerLifecycle.class)
.get("loadBalancerLifecycle")).getStartRequestLog().values();
Collection<CompletionContext<Object, ServiceInstance, Object>> 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<String> 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<String> 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<String> 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<ServiceInstance> reactiveLoadBalancerFactory(DiscoveryClient discoveryClient,
LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<>() {
private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle();
private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle();
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return new org.springframework.cloud.client.loadbalancer.reactive.DiscoveryClientBasedReactiveLoadBalancer(
serviceId, discoveryClient);
}
@Override
public <X> Map<String, X> getInstances(String name, Class<X> type) {
if (name.equals("serviceWithNoLifecycleProcessors")) {
return null;
}
Map lifecycleProcessors = new HashMap<>();
lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle);
lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle);
return lifecycleProcessors;
}
@Override
public <X> 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<ServiceInstance> factory) {
return new RetryableLoadBalancerExchangeFilterFunction(
@@ -319,55 +156,4 @@ class RetryableLoadBalancerExchangeFilterFunctionIntegrationTests {
}
protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
Map<String, Request<Object>> startLog = new ConcurrentHashMap<>();
Map<String, Request<Object>> startRequestLog = new ConcurrentHashMap<>();
Map<String, CompletionContext<Object, ServiceInstance, Object>> completeLog = new ConcurrentHashMap<>();
@Override
public void onStart(Request<Object> request) {
startLog.put(getName() + UUID.randomUUID(), request);
}
@Override
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
startRequestLog.put(getName() + UUID.randomUUID(), request);
}
@Override
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
completeLog.clear();
completeLog.put(getName() + UUID.randomUUID(), completionContext);
}
Map<String, Request<Object>> getStartLog() {
return startLog;
}
Map<String, CompletionContext<Object, ServiceInstance, Object>> getCompleteLog() {
return completeLog;
}
Map<String, Request<Object>> getStartRequestLog() {
return startRequestLog;
}
protected String getName() {
return this.getClass().getSimpleName();
}
}
protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle {
@Override
protected String getName() {
return this.getClass().getSimpleName();
}
}
}