diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index 56152f3b07..9e6ae48cfc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -19,7 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive; import java.util.Arrays; import java.util.Collections; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Consumer; import org.junit.Test; @@ -39,23 +38,24 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; -import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; +import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; -import org.springframework.boot.web.reactive.context.ReactiveWebServerInitializedEvent; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.HttpHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.Base64Utils; import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -67,6 +67,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link CloudFoundryWebFluxEndpointHandlerMapping}. * * @author Madhura Bhave + * @author Stephane Nicoll */ public class CloudFoundryWebFluxEndpointIntegrationTests { @@ -76,16 +77,24 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { private static ReactiveCloudFoundrySecurityService securityService = mock( ReactiveCloudFoundrySecurityService.class); + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner( + AnnotationConfigReactiveWebServerApplicationContext::new) + .withConfiguration( + AutoConfigurations.of(WebFluxAutoConfiguration.class, + HttpHandlerAutoConfiguration.class, + ReactiveWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(TestEndpointConfiguration.class) + .withPropertyValues("server.port=0"); + @Test public void operationWithSecurityInterceptorForbidden() { given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(securityService.getAccessLevel(any(), eq("app-id"))) .willReturn(Mono.just(AccessLevel.RESTRICTED)); - load(TestEndpointConfiguration.class, - (client) -> client.get().uri("/cfApplication/test") - .accept(MediaType.APPLICATION_JSON) - .header("Authorization", "bearer " + mockAccessToken()).exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN)); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication/test").accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()).exchange() + .expectStatus().isEqualTo(HttpStatus.FORBIDDEN))); } @Test @@ -93,22 +102,21 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(securityService.getAccessLevel(any(), eq("app-id"))) .willReturn(Mono.just(AccessLevel.FULL)); - load(TestEndpointConfiguration.class, - (client) -> client.get().uri("/cfApplication/test") - .accept(MediaType.APPLICATION_JSON) - .header("Authorization", "bearer " + mockAccessToken()).exchange() - .expectStatus().isEqualTo(HttpStatus.OK)); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication/test").accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()).exchange() + .expectStatus().isEqualTo(HttpStatus.OK))); } @Test public void responseToOptionsRequestIncludesCorsHeaders() { - load(TestEndpointConfiguration.class, (client) -> client.options() + this.contextRunner.run(withWebTestClient((client) -> client.options() .uri("/cfApplication/test").accept(MediaType.APPLICATION_JSON) .header("Access-Control-Request-Method", "POST") .header("Origin", "http://example.com").exchange().expectStatus().isOk() .expectHeader() .valueEquals("Access-Control-Allow-Origin", "http://example.com") - .expectHeader().valueEquals("Access-Control-Allow-Methods", "GET,POST")); + .expectHeader().valueEquals("Access-Control-Allow-Methods", "GET,POST"))); } @Test @@ -116,7 +124,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(securityService.getAccessLevel(any(), eq("app-id"))) .willReturn(Mono.just(AccessLevel.FULL)); - load(TestEndpointConfiguration.class, (client) -> client.get() + this.contextRunner.run(withWebTestClient((client) -> client.get() .uri("/cfApplication").accept(MediaType.APPLICATION_JSON) .header("Authorization", "bearer " + mockAccessToken()).exchange() .expectStatus().isOk().expectBody().jsonPath("_links.length()") @@ -128,7 +136,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { .isEqualTo(false).jsonPath("_links.test.href").isNotEmpty() .jsonPath("_links.test.templated").isEqualTo(false) .jsonPath("_links.test-part.href").isNotEmpty() - .jsonPath("_links.test-part.templated").isEqualTo(true)); + .jsonPath("_links.test-part.templated").isEqualTo(true))); } @Test @@ -136,11 +144,10 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException( Reason.INVALID_TOKEN, "invalid-token"); willThrow(exception).given(tokenValidator).validate(any()); - load(TestEndpointConfiguration.class, - (client) -> client.get().uri("/cfApplication") - .accept(MediaType.APPLICATION_JSON) - .header("Authorization", "bearer " + mockAccessToken()).exchange() - .expectStatus().isUnauthorized()); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication").accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()).exchange() + .expectStatus().isUnauthorized())); } @Test @@ -148,17 +155,16 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(securityService.getAccessLevel(any(), eq("app-id"))) .willReturn(Mono.just(AccessLevel.RESTRICTED)); - load(TestEndpointConfiguration.class, - (client) -> client.get().uri("/cfApplication") - .accept(MediaType.APPLICATION_JSON) - .header("Authorization", "bearer " + mockAccessToken()).exchange() - .expectStatus().isOk().expectBody().jsonPath("_links.length()") - .isEqualTo(2).jsonPath("_links.self.href").isNotEmpty() - .jsonPath("_links.self.templated").isEqualTo(false) - .jsonPath("_links.info.href").isNotEmpty() - .jsonPath("_links.info.templated").isEqualTo(false) - .jsonPath("_links.env").doesNotExist().jsonPath("_links.test") - .doesNotExist().jsonPath("_links.test-part").doesNotExist()); + this.contextRunner.run(withWebTestClient((client) -> client.get() + .uri("/cfApplication").accept(MediaType.APPLICATION_JSON) + .header("Authorization", "bearer " + mockAccessToken()).exchange() + .expectStatus().isOk().expectBody().jsonPath("_links.length()") + .isEqualTo(2).jsonPath("_links.self.href").isNotEmpty() + .jsonPath("_links.self.templated").isEqualTo(false) + .jsonPath("_links.info.href").isNotEmpty() + .jsonPath("_links.info.templated").isEqualTo(false).jsonPath("_links.env") + .doesNotExist().jsonPath("_links.test").doesNotExist() + .jsonPath("_links.test-part").doesNotExist())); } private AnnotationConfigReactiveWebServerApplicationContext createApplicationContext( @@ -168,23 +174,14 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { return context; } - private void load(Class configuration, Consumer clientConsumer) { - BiConsumer consumer = (context, - client) -> clientConsumer.accept(client); - AnnotationConfigReactiveWebServerApplicationContext context = createApplicationContext( - configuration, CloudFoundryReactiveConfiguration.class); - context.refresh(); - try { - consumer.accept(context, WebTestClient.bindToServer() - .baseUrl("http://localhost:" + getPort(context)).build()); - } - finally { - context.close(); - } - } - - protected int getPort(AnnotationConfigReactiveWebServerApplicationContext context) { - return context.getBean(CloudFoundryReactiveConfiguration.class).port; + private ContextConsumer withWebTestClient( + Consumer clientConsumer) { + return (context) -> { + int port = ((AnnotationConfigReactiveWebServerApplicationContext) context + .getSourceApplicationContext()).getWebServer().getPort(); + clientConsumer.accept(WebTestClient.bindToServer() + .baseUrl("http://localhost:" + port).build()); + }; } private String mockAccessToken() { @@ -194,11 +191,8 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { } @Configuration - @EnableWebFlux static class CloudFoundryReactiveConfiguration { - private int port; - @Bean public CloudFoundrySecurityInterceptor interceptor() { return new CloudFoundrySecurityInterceptor(tokenValidator, securityService, @@ -242,21 +236,6 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { return mock(EndpointDelegate.class); } - @Bean - public NettyReactiveWebServerFactory netty() { - return new NettyReactiveWebServerFactory(0); - } - - @Bean - public HttpHandler httpHandler(ApplicationContext applicationContext) { - return WebHttpHandlerBuilder.applicationContext(applicationContext).build(); - } - - @Bean - public ApplicationListener serverInitializedListener() { - return (event) -> this.port = event.getWebServer().getPort(); - } - } @Endpoint(id = "test")