Merge branch '2.0.x'

pull/14149/head
Stephane Nicoll 6 years ago
commit b60fbe5a1f

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

Loading…
Cancel
Save