|
|
@ -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.info.templated").isEqualTo(false).jsonPath("_links.env")
|
|
|
|
.jsonPath("_links.env").doesNotExist().jsonPath("_links.test")
|
|
|
|
.doesNotExist().jsonPath("_links.test").doesNotExist()
|
|
|
|
.doesNotExist().jsonPath("_links.test-part").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")
|
|
|
|