1919import java .util .Arrays ;
2020import java .util .Collections ;
2121import java .util .Map ;
22- import java .util .function .BiConsumer ;
2322import java .util .function .Consumer ;
2423
2524import org .junit .Test ;
3938import org .springframework .boot .actuate .endpoint .web .EndpointMediaTypes ;
4039import org .springframework .boot .actuate .endpoint .web .PathMapper ;
4140import org .springframework .boot .actuate .endpoint .web .annotation .WebEndpointDiscoverer ;
42- import org .springframework .boot .web .embedded .netty .NettyReactiveWebServerFactory ;
41+ import org .springframework .boot .autoconfigure .AutoConfigurations ;
42+ import org .springframework .boot .autoconfigure .web .reactive .HttpHandlerAutoConfiguration ;
43+ import org .springframework .boot .autoconfigure .web .reactive .ReactiveWebServerFactoryAutoConfiguration ;
44+ import org .springframework .boot .autoconfigure .web .reactive .WebFluxAutoConfiguration ;
45+ import org .springframework .boot .test .context .assertj .AssertableReactiveWebApplicationContext ;
46+ import org .springframework .boot .test .context .runner .ContextConsumer ;
47+ import org .springframework .boot .test .context .runner .ReactiveWebApplicationContextRunner ;
4348import org .springframework .boot .web .reactive .context .AnnotationConfigReactiveWebServerApplicationContext ;
44- import org .springframework .boot .web .reactive .context .ReactiveWebServerInitializedEvent ;
4549import org .springframework .context .ApplicationContext ;
46- import org .springframework .context .ApplicationListener ;
4750import org .springframework .context .annotation .Bean ;
4851import org .springframework .context .annotation .Configuration ;
4952import org .springframework .context .annotation .Import ;
5053import org .springframework .core .convert .support .DefaultConversionService ;
5154import org .springframework .http .HttpStatus ;
5255import org .springframework .http .MediaType ;
53- import org .springframework .http .server .reactive .HttpHandler ;
5456import org .springframework .test .web .reactive .server .WebTestClient ;
5557import org .springframework .util .Base64Utils ;
5658import org .springframework .web .cors .CorsConfiguration ;
57- import org .springframework .web .reactive .config .EnableWebFlux ;
58- import org .springframework .web .server .adapter .WebHttpHandlerBuilder ;
5959
6060import static org .mockito .ArgumentMatchers .any ;
6161import static org .mockito .ArgumentMatchers .eq ;
6767 * Tests for {@link CloudFoundryWebFluxEndpointHandlerMapping}.
6868 *
6969 * @author Madhura Bhave
70+ * @author Stephane Nicoll
7071 */
7172public class CloudFoundryWebFluxEndpointIntegrationTests {
7273
@@ -76,47 +77,54 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
7677 private static ReactiveCloudFoundrySecurityService securityService = mock (
7778 ReactiveCloudFoundrySecurityService .class );
7879
80+ private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner (
81+ AnnotationConfigReactiveWebServerApplicationContext ::new )
82+ .withConfiguration (
83+ AutoConfigurations .of (WebFluxAutoConfiguration .class ,
84+ HttpHandlerAutoConfiguration .class ,
85+ ReactiveWebServerFactoryAutoConfiguration .class ))
86+ .withUserConfiguration (TestEndpointConfiguration .class )
87+ .withPropertyValues ("server.port=0" );
88+
7989 @ Test
8090 public void operationWithSecurityInterceptorForbidden () {
8191 given (tokenValidator .validate (any ())).willReturn (Mono .empty ());
8292 given (securityService .getAccessLevel (any (), eq ("app-id" )))
8393 .willReturn (Mono .just (AccessLevel .RESTRICTED ));
84- load (TestEndpointConfiguration .class ,
85- (client ) -> client .get ().uri ("/cfApplication/test" )
86- .accept (MediaType .APPLICATION_JSON )
87- .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
88- .expectStatus ().isEqualTo (HttpStatus .FORBIDDEN ));
94+ this .contextRunner .run (withWebTestClient ((client ) -> client .get ()
95+ .uri ("/cfApplication/test" ).accept (MediaType .APPLICATION_JSON )
96+ .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
97+ .expectStatus ().isEqualTo (HttpStatus .FORBIDDEN )));
8998 }
9099
91100 @ Test
92101 public void operationWithSecurityInterceptorSuccess () {
93102 given (tokenValidator .validate (any ())).willReturn (Mono .empty ());
94103 given (securityService .getAccessLevel (any (), eq ("app-id" )))
95104 .willReturn (Mono .just (AccessLevel .FULL ));
96- load (TestEndpointConfiguration .class ,
97- (client ) -> client .get ().uri ("/cfApplication/test" )
98- .accept (MediaType .APPLICATION_JSON )
99- .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
100- .expectStatus ().isEqualTo (HttpStatus .OK ));
105+ this .contextRunner .run (withWebTestClient ((client ) -> client .get ()
106+ .uri ("/cfApplication/test" ).accept (MediaType .APPLICATION_JSON )
107+ .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
108+ .expectStatus ().isEqualTo (HttpStatus .OK )));
101109 }
102110
103111 @ Test
104112 public void responseToOptionsRequestIncludesCorsHeaders () {
105- load ( TestEndpointConfiguration . class , (client ) -> client .options ()
113+ this . contextRunner . run ( withWebTestClient ( (client ) -> client .options ()
106114 .uri ("/cfApplication/test" ).accept (MediaType .APPLICATION_JSON )
107115 .header ("Access-Control-Request-Method" , "POST" )
108116 .header ("Origin" , "http://example.com" ).exchange ().expectStatus ().isOk ()
109117 .expectHeader ()
110118 .valueEquals ("Access-Control-Allow-Origin" , "http://example.com" )
111- .expectHeader ().valueEquals ("Access-Control-Allow-Methods" , "GET,POST" ));
119+ .expectHeader ().valueEquals ("Access-Control-Allow-Methods" , "GET,POST" ))) ;
112120 }
113121
114122 @ Test
115123 public void linksToOtherEndpointsWithFullAccess () {
116124 given (tokenValidator .validate (any ())).willReturn (Mono .empty ());
117125 given (securityService .getAccessLevel (any (), eq ("app-id" )))
118126 .willReturn (Mono .just (AccessLevel .FULL ));
119- load ( TestEndpointConfiguration . class , (client ) -> client .get ()
127+ this . contextRunner . run ( withWebTestClient ( (client ) -> client .get ()
120128 .uri ("/cfApplication" ).accept (MediaType .APPLICATION_JSON )
121129 .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
122130 .expectStatus ().isOk ().expectBody ().jsonPath ("_links.length()" )
@@ -128,37 +136,35 @@ public void linksToOtherEndpointsWithFullAccess() {
128136 .isEqualTo (false ).jsonPath ("_links.test.href" ).isNotEmpty ()
129137 .jsonPath ("_links.test.templated" ).isEqualTo (false )
130138 .jsonPath ("_links.test-part.href" ).isNotEmpty ()
131- .jsonPath ("_links.test-part.templated" ).isEqualTo (true ));
139+ .jsonPath ("_links.test-part.templated" ).isEqualTo (true ))) ;
132140 }
133141
134142 @ Test
135143 public void linksToOtherEndpointsForbidden () {
136144 CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException (
137145 Reason .INVALID_TOKEN , "invalid-token" );
138146 willThrow (exception ).given (tokenValidator ).validate (any ());
139- load (TestEndpointConfiguration .class ,
140- (client ) -> client .get ().uri ("/cfApplication" )
141- .accept (MediaType .APPLICATION_JSON )
142- .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
143- .expectStatus ().isUnauthorized ());
147+ this .contextRunner .run (withWebTestClient ((client ) -> client .get ()
148+ .uri ("/cfApplication" ).accept (MediaType .APPLICATION_JSON )
149+ .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
150+ .expectStatus ().isUnauthorized ()));
144151 }
145152
146153 @ Test
147154 public void linksToOtherEndpointsWithRestrictedAccess () {
148155 given (tokenValidator .validate (any ())).willReturn (Mono .empty ());
149156 given (securityService .getAccessLevel (any (), eq ("app-id" )))
150157 .willReturn (Mono .just (AccessLevel .RESTRICTED ));
151- load (TestEndpointConfiguration .class ,
152- (client ) -> client .get ().uri ("/cfApplication" )
153- .accept (MediaType .APPLICATION_JSON )
154- .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
155- .expectStatus ().isOk ().expectBody ().jsonPath ("_links.length()" )
156- .isEqualTo (2 ).jsonPath ("_links.self.href" ).isNotEmpty ()
157- .jsonPath ("_links.self.templated" ).isEqualTo (false )
158- .jsonPath ("_links.info.href" ).isNotEmpty ()
159- .jsonPath ("_links.info.templated" ).isEqualTo (false )
160- .jsonPath ("_links.env" ).doesNotExist ().jsonPath ("_links.test" )
161- .doesNotExist ().jsonPath ("_links.test-part" ).doesNotExist ());
158+ this .contextRunner .run (withWebTestClient ((client ) -> client .get ()
159+ .uri ("/cfApplication" ).accept (MediaType .APPLICATION_JSON )
160+ .header ("Authorization" , "bearer " + mockAccessToken ()).exchange ()
161+ .expectStatus ().isOk ().expectBody ().jsonPath ("_links.length()" )
162+ .isEqualTo (2 ).jsonPath ("_links.self.href" ).isNotEmpty ()
163+ .jsonPath ("_links.self.templated" ).isEqualTo (false )
164+ .jsonPath ("_links.info.href" ).isNotEmpty ()
165+ .jsonPath ("_links.info.templated" ).isEqualTo (false ).jsonPath ("_links.env" )
166+ .doesNotExist ().jsonPath ("_links.test" ).doesNotExist ()
167+ .jsonPath ("_links.test-part" ).doesNotExist ()));
162168 }
163169
164170 private AnnotationConfigReactiveWebServerApplicationContext createApplicationContext (
@@ -168,23 +174,14 @@ private AnnotationConfigReactiveWebServerApplicationContext createApplicationCon
168174 return context ;
169175 }
170176
171- private void load (Class <?> configuration , Consumer <WebTestClient > clientConsumer ) {
172- BiConsumer <ApplicationContext , WebTestClient > consumer = (context ,
173- client ) -> clientConsumer .accept (client );
174- AnnotationConfigReactiveWebServerApplicationContext context = createApplicationContext (
175- configuration , CloudFoundryReactiveConfiguration .class );
176- context .refresh ();
177- try {
178- consumer .accept (context , WebTestClient .bindToServer ()
179- .baseUrl ("http://localhost:" + getPort (context )).build ());
180- }
181- finally {
182- context .close ();
183- }
184- }
185-
186- protected int getPort (AnnotationConfigReactiveWebServerApplicationContext context ) {
187- return context .getBean (CloudFoundryReactiveConfiguration .class ).port ;
177+ private ContextConsumer <AssertableReactiveWebApplicationContext > withWebTestClient (
178+ Consumer <WebTestClient > clientConsumer ) {
179+ return (context ) -> {
180+ int port = ((AnnotationConfigReactiveWebServerApplicationContext ) context
181+ .getSourceApplicationContext ()).getWebServer ().getPort ();
182+ clientConsumer .accept (WebTestClient .bindToServer ()
183+ .baseUrl ("http://localhost:" + port ).build ());
184+ };
188185 }
189186
190187 private String mockAccessToken () {
@@ -194,11 +191,8 @@ private String mockAccessToken() {
194191 }
195192
196193 @ Configuration
197- @ EnableWebFlux
198194 static class CloudFoundryReactiveConfiguration {
199195
200- private int port ;
201-
202196 @ Bean
203197 public CloudFoundrySecurityInterceptor interceptor () {
204198 return new CloudFoundrySecurityInterceptor (tokenValidator , securityService ,
@@ -242,21 +236,6 @@ public EndpointDelegate endpointDelegate() {
242236 return mock (EndpointDelegate .class );
243237 }
244238
245- @ Bean
246- public NettyReactiveWebServerFactory netty () {
247- return new NettyReactiveWebServerFactory (0 );
248- }
249-
250- @ Bean
251- public HttpHandler httpHandler (ApplicationContext applicationContext ) {
252- return WebHttpHandlerBuilder .applicationContext (applicationContext ).build ();
253- }
254-
255- @ Bean
256- public ApplicationListener <ReactiveWebServerInitializedEvent > serverInitializedListener () {
257- return (event ) -> this .port = event .getWebServer ().getPort ();
258- }
259-
260239 }
261240
262241 @ Endpoint (id = "test" )
0 commit comments