11/*
2- * Copyright 2012-2020 the original author or authors.
2+ * Copyright 2012-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2525import java .util .concurrent .ConcurrentHashMap ;
2626
2727import org .junit .jupiter .api .BeforeEach ;
28- import org .junit .jupiter .api .Disabled ;
2928import org .junit .jupiter .api .Test ;
29+ import reactor .core .publisher .Mono ;
3030
3131import org .springframework .beans .factory .annotation .Autowired ;
32- import org .springframework .boot .SpringBootConfiguration ;
33- import org .springframework .boot .autoconfigure .EnableAutoConfiguration ;
34- import org .springframework .boot .test .context .SpringBootTest ;
3532import org .springframework .boot .test .web .server .LocalServerPort ;
3633import org .springframework .cloud .client .DefaultServiceInstance ;
3734import org .springframework .cloud .client .ServiceInstance ;
3835import org .springframework .cloud .client .discovery .DiscoveryClient ;
39- import org .springframework .cloud .client .discovery .EnableDiscoveryClient ;
4036import org .springframework .cloud .client .discovery .simple .SimpleDiscoveryProperties ;
4137import org .springframework .cloud .client .loadbalancer .CompletionContext ;
4238import org .springframework .cloud .client .loadbalancer .DefaultRequestContext ;
4541import org .springframework .cloud .client .loadbalancer .Request ;
4642import org .springframework .cloud .client .loadbalancer .Response ;
4743import org .springframework .cloud .client .loadbalancer .ResponseData ;
48- import org .springframework .context . annotation . Bean ;
44+ import org .springframework .http . HttpMethod ;
4945import org .springframework .http .HttpStatus ;
50- import org .springframework .web .bind .annotation .GetMapping ;
51- import org .springframework .web .bind .annotation .RestController ;
52- import org .springframework .web .reactive .function .client .ClientResponse ;
46+ import org .springframework .http .ResponseEntity ;
5347import org .springframework .web .reactive .function .client .WebClient ;
5448
5549import static org .assertj .core .api .Assertions .assertThat ;
56- import static org .assertj .core .api .Assertions .assertThatCode ;
50+ import static org .assertj .core .api .Assertions .assertThatIllegalStateException ;
51+ import static org .assertj .core .api .AssertionsForClassTypes .assertThatCode ;
5752import static org .assertj .core .api .BDDAssertions .then ;
58- import static org .springframework .boot .test .context .SpringBootTest .WebEnvironment .RANDOM_PORT ;
5953
6054/**
61- * Tests for {@link ReactorLoadBalancerExchangeFilterFunction} .
55+ * Base class for {@link LoadBalancedExchangeFilterFunction} integration tests .
6256 *
6357 * @author Olga Maciaszek-Sharma
64- * @author Charu Covindane
6558 */
66- @ SuppressWarnings ("ConstantConditions" )
67- @ SpringBootTest (webEnvironment = RANDOM_PORT )
68- class ReactorLoadBalancerExchangeFilterFunctionTests {
59+ @ SuppressWarnings ("DataFlowIssue" )
60+ abstract class AbstractLoadBalancerExchangeFilterFunctionIntegrationTests {
6961
7062 @ Autowired
71- private ReactorLoadBalancerExchangeFilterFunction loadBalancerFunction ;
63+ protected LoadBalancedExchangeFilterFunction loadBalancerFunction ;
7264
7365 @ Autowired
74- private SimpleDiscoveryProperties properties ;
66+ protected SimpleDiscoveryProperties properties ;
7567
7668 @ Autowired
77- private LoadBalancerProperties loadBalancerProperties ;
69+ protected LoadBalancerProperties loadBalancerProperties ;
7870
7971 @ Autowired
80- private ReactiveLoadBalancer .Factory <ServiceInstance > factory ;
72+ protected ReactiveLoadBalancer .Factory <ServiceInstance > factory ;
8173
8274 @ LocalServerPort
83- private int port ;
75+ protected int port ;
8476
8577 @ BeforeEach
86- void setUp () {
78+ protected void setUp () {
8779 DefaultServiceInstance instance = new DefaultServiceInstance ();
8880 instance .setServiceId ("testservice" );
89- instance .setUri (URI .create ("http://localhost:" + this . port ));
81+ instance .setUri (URI .create ("http://localhost:" + port ));
9082 DefaultServiceInstance instanceWithNoLifecycleProcessors = new DefaultServiceInstance ();
9183 instanceWithNoLifecycleProcessors .setServiceId ("serviceWithNoLifecycleProcessors" );
92- instanceWithNoLifecycleProcessors .setUri (URI .create ("http://localhost:" + this . port ));
84+ instanceWithNoLifecycleProcessors .setUri (URI .create ("http://localhost:" + port ));
9385 properties .getInstances ().put ("testservice" , Collections .singletonList (instance ));
9486 properties .getInstances ()
9587 .put ("serviceWithNoLifecycleProcessors" , Collections .singletonList (instanceWithNoLifecycleProcessors ));
9688 }
9789
9890 @ Test
9991 void correctResponseReturnedForExistingHostAndInstancePresent () {
100- ClientResponse clientResponse = WebClient .builder ()
92+ ResponseEntity < String > response = WebClient .builder ()
10193 .baseUrl ("http://testservice" )
10294 .filter (loadBalancerFunction )
10395 .build ()
10496 .get ()
10597 .uri ("/hello" )
106- .exchange ()
98+ .retrieve ()
99+ .toEntity (String .class )
107100 .block ();
108- then (clientResponse . statusCode ()).isEqualTo (HttpStatus .OK );
109- then (clientResponse . bodyToMono ( String . class ). block ()).isEqualTo ("Hello World" );
101+ then (response . getStatusCode ()).isEqualTo (HttpStatus .OK );
102+ then (response . getBody ()).isEqualTo ("Hello World" );
110103 }
111104
112105 @ Test
113106 void serviceUnavailableReturnedWhenNoInstancePresent () {
114- ClientResponse clientResponse = WebClient .builder ()
115- .baseUrl ("http://xxx" )
116- .filter (this .loadBalancerFunction )
117- .build ()
118- .get ()
119- .exchange ()
120- .block ();
121- then (clientResponse .statusCode ()).isEqualTo (HttpStatus .SERVICE_UNAVAILABLE );
107+ assertThatIllegalStateException ()
108+ .isThrownBy (() -> WebClient .builder ()
109+ .baseUrl ("http://xxx" )
110+ .filter (loadBalancerFunction )
111+ .defaultStatusHandler (httpStatusCode -> httpStatusCode .equals (HttpStatus .SERVICE_UNAVAILABLE ),
112+ clientResponse -> Mono .just (new IllegalStateException ("503" )))
113+ .build ()
114+ .get ()
115+ .retrieve ()
116+ .toBodilessEntity ()
117+ .block ())
118+ .withMessage ("503" );
122119 }
123120
124121 @ Test
125- @ Disabled // FIXME 3.0.0
126122 void badRequestReturnedForIncorrectHost () {
127- ClientResponse clientResponse = WebClient .builder ()
128- .baseUrl ("http:///xxx" )
129- .filter (this .loadBalancerFunction )
130- .build ()
131- .get ()
132- .exchange ()
133- .block ();
134- then (clientResponse .statusCode ()).isEqualTo (HttpStatus .BAD_REQUEST );
123+ assertThatIllegalStateException ()
124+ .isThrownBy (() -> WebClient .builder ()
125+ .baseUrl ("http:///xxx" )
126+ .filter (loadBalancerFunction )
127+ .defaultStatusHandler (httpStatusCode -> httpStatusCode .equals (HttpStatus .BAD_REQUEST ),
128+ response -> Mono .just (new IllegalStateException ("400" )))
129+ .build ()
130+ .get ()
131+ .retrieve ()
132+ .toBodilessEntity ()
133+ .block ())
134+ .withMessage ("400" );
135135 }
136136
137137 @ Test
@@ -142,97 +142,88 @@ void exceptionNotThrownWhenFactoryReturnsNullLifecycleProcessorsMap() {
142142 .build ()
143143 .get ()
144144 .uri ("/hello" )
145- .exchange ( )
145+ .exchangeToMono ( clientResponse -> clientResponse . bodyToMono ( String . class ) )
146146 .block ()).doesNotThrowAnyException ();
147147 }
148148
149149 @ Test
150150 void loadBalancerLifecycleCallbacksExecuted () {
151151 final String callbackTestHint = "callbackTestHint" ;
152152 loadBalancerProperties .getHint ().put ("testservice" , "callbackTestHint" );
153- ClientResponse clientResponse = WebClient .builder ()
153+
154+ ResponseEntity <Void > response = WebClient .builder ()
154155 .baseUrl ("http://testservice" )
155156 .filter (loadBalancerFunction )
156157 .build ()
157158 .get ()
158159 .uri ("/callback" )
159- .exchange ()
160+ .retrieve ()
161+ .toBodilessEntity ()
160162 .block ();
161163
162164 Collection <Request <Object >> lifecycleLogRequests = ((TestLoadBalancerLifecycle ) factory
163165 .getInstances ("testservice" , LoadBalancerLifecycle .class )
164166 .get ("loadBalancerLifecycle" )).getStartLog ().values ();
165- Collection <Request <Object >> lifecycleStartedLogRequests = ((TestLoadBalancerLifecycle ) factory
167+ Collection <Request <Object >> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle ) factory
166168 .getInstances ("testservice" , LoadBalancerLifecycle .class )
167169 .get ("loadBalancerLifecycle" )).getStartRequestLog ().values ();
168170 Collection <CompletionContext <Object , ServiceInstance , Object >> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle ) factory
169171 .getInstances ("testservice" , LoadBalancerLifecycle .class )
170172 .get ("anotherLoadBalancerLifecycle" )).getCompleteLog ().values ();
171- then (clientResponse . statusCode ()).isEqualTo (HttpStatus .OK );
173+ then (response . getStatusCode ()).isEqualTo (HttpStatus .OK );
172174 assertThat (lifecycleLogRequests ).extracting (request -> ((DefaultRequestContext ) request .getContext ()).getHint ())
173175 .contains (callbackTestHint );
174- assertThat (lifecycleStartedLogRequests )
176+ assertThat (lifecycleLogStartRequests )
175177 .extracting (request -> ((DefaultRequestContext ) request .getContext ()).getHint ())
176178 .contains (callbackTestHint );
177179 assertThat (anotherLifecycleLogRequests )
178180 .extracting (completionContext -> ((ResponseData ) completionContext .getClientResponse ()).getRequestData ()
179- .getUrl ()
180- .toString ())
181- .contains ("http://testservice/callback" );
181+ .getHttpMethod ())
182+ .contains (HttpMethod .GET );
182183 }
183184
184- @ SuppressWarnings ({ "unchecked" , "rawtypes" })
185- @ EnableDiscoveryClient
186- @ EnableAutoConfiguration
187- @ SpringBootConfiguration (proxyBeanMethods = false )
188- @ RestController
189- static class Config {
185+ protected static class TestLoadBalancerFactory implements ReactiveLoadBalancer .Factory <ServiceInstance > {
186+
187+ private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle testLoadBalancerLifecycle ;
188+
189+ private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle anotherLoadBalancerLifecycle ;
190190
191- @ GetMapping ("/hello" )
192- public String hello () {
193- return "Hello World" ;
191+ private final DiscoveryClient discoveryClient ;
192+
193+ private final LoadBalancerProperties properties ;
194+
195+ public TestLoadBalancerFactory (DiscoveryClient discoveryClient , LoadBalancerProperties properties ) {
196+ this .discoveryClient = discoveryClient ;
197+ this .properties = properties ;
198+ testLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle ();
199+ anotherLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .AnotherLoadBalancerLifecycle ();
194200 }
195201
196- @ GetMapping ( "/callback" )
197- String callbackTestResult ( ) {
198- return "callbackTestResult" ;
202+ @ Override
203+ public ReactiveLoadBalancer < ServiceInstance > getInstance ( String serviceId ) {
204+ return new DiscoveryClientBasedReactiveLoadBalancer ( serviceId , discoveryClient ) ;
199205 }
200206
201- @ Bean
202- ReactiveLoadBalancer .Factory <ServiceInstance > reactiveLoadBalancerFactory (DiscoveryClient discoveryClient ,
203- LoadBalancerProperties properties ) {
204- return new ReactiveLoadBalancer .Factory <>() {
205-
206- private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle ();
207-
208- private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle ();
209-
210- @ Override
211- public ReactiveLoadBalancer <ServiceInstance > getInstance (String serviceId ) {
212- return new DiscoveryClientBasedReactiveLoadBalancer (serviceId , discoveryClient );
213- }
214-
215- @ Override
216- public <X > Map <String , X > getInstances (String name , Class <X > type ) {
217- if (name .equals ("serviceWithNoLifecycleProcessors" )) {
218- return null ;
219- }
220- Map lifecycleProcessors = new HashMap <>();
221- lifecycleProcessors .put ("loadBalancerLifecycle" , testLoadBalancerLifecycle );
222- lifecycleProcessors .put ("anotherLoadBalancerLifecycle" , anotherLoadBalancerLifecycle );
223- return lifecycleProcessors ;
224- }
225-
226- @ Override
227- public <X > X getInstance (String name , Class <?> clazz , Class <?>... generics ) {
228- return null ;
229- }
230-
231- @ Override
232- public LoadBalancerProperties getProperties (String serviceId ) {
233- return properties ;
234- }
235- };
207+ @ SuppressWarnings ({ "rawtypes" , "unchecked" })
208+ @ Override
209+ public <X > Map <String , X > getInstances (String name , Class <X > type ) {
210+ if (name .equals ("serviceWithNoLifecycleProcessors" )) {
211+ return null ;
212+ }
213+ Map lifecycleProcessors = new HashMap <>();
214+ lifecycleProcessors .put ("loadBalancerLifecycle" , testLoadBalancerLifecycle );
215+ lifecycleProcessors .put ("anotherLoadBalancerLifecycle" , anotherLoadBalancerLifecycle );
216+ return lifecycleProcessors ;
217+ }
218+
219+ @ Override
220+ public <X > X getInstance (String name , Class <?> clazz , Class <?>... generics ) {
221+ return null ;
222+ }
223+
224+ @ Override
225+ public LoadBalancerProperties getProperties (String serviceId ) {
226+ return properties ;
236227 }
237228
238229 }
@@ -257,6 +248,7 @@ public void onStartRequest(Request<Object> request, Response<ServiceInstance> lb
257248
258249 @ Override
259250 public void onComplete (CompletionContext <Object , ServiceInstance , Object > completionContext ) {
251+ completeLog .clear ();
260252 completeLog .put (getName () + UUID .randomUUID (), completionContext );
261253 }
262254
@@ -273,18 +265,13 @@ Map<String, Request<Object>> getStartRequestLog() {
273265 }
274266
275267 protected String getName () {
276- return this . getClass ().getSimpleName ();
268+ return getClass ().getSimpleName ();
277269 }
278270
279271 }
280272
281273 protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle {
282274
283- @ Override
284- protected String getName () {
285- return this .getClass ().getSimpleName ();
286- }
287-
288275 }
289276
290277}
0 commit comments