Remove static state from CF web endpoint integration tests

The mocks being static meant that their state was shared across each
test in the class. This resulted in the tests being order dependent.
This commit uses instance variables to hold the mocks, thereby
ensuring that they're recreated for each test as part of the standard
JUnit lifecycle.

Closes gh-38363
This commit is contained in:
Andy Wilkinson 2023-11-15 15:03:06 +00:00
parent 9b8bcec33e
commit c2156d6803
2 changed files with 36 additions and 35 deletions

View File

@ -75,22 +75,23 @@ import static org.mockito.Mockito.mock;
*/ */
class CloudFoundryWebFluxEndpointIntegrationTests { class CloudFoundryWebFluxEndpointIntegrationTests {
private static ReactiveTokenValidator tokenValidator = mock(ReactiveTokenValidator.class); private final ReactiveTokenValidator tokenValidator = mock(ReactiveTokenValidator.class);
private static ReactiveCloudFoundrySecurityService securityService = mock( private final ReactiveCloudFoundrySecurityService securityService = mock(ReactiveCloudFoundrySecurityService.class);
ReactiveCloudFoundrySecurityService.class);
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner( private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(
AnnotationConfigReactiveWebServerApplicationContext::new) AnnotationConfigReactiveWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
ReactiveWebServerFactoryAutoConfiguration.class)) ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(TestEndpointConfiguration.class) .withUserConfiguration(TestEndpointConfiguration.class)
.withBean(ReactiveTokenValidator.class, () -> this.tokenValidator)
.withBean(ReactiveCloudFoundrySecurityService.class, () -> this.securityService)
.withPropertyValues("server.port=0"); .withPropertyValues("server.port=0");
@Test @Test
void operationWithSecurityInterceptorForbidden() { void operationWithSecurityInterceptorForbidden() {
given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED)); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED));
this.contextRunner.run(withWebTestClient((client) -> client.get() this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication/test") .uri("/cfApplication/test")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@ -102,8 +103,8 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
@Test @Test
void operationWithSecurityInterceptorSuccess() { void operationWithSecurityInterceptorSuccess() {
given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL)); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL));
this.contextRunner.run(withWebTestClient((client) -> client.get() this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication/test") .uri("/cfApplication/test")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@ -131,8 +132,8 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
@Test @Test
void linksToOtherEndpointsWithFullAccess() { void linksToOtherEndpointsWithFullAccess() {
given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL)); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL));
this.contextRunner.run(withWebTestClient((client) -> client.get() this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@ -169,7 +170,7 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
void linksToOtherEndpointsForbidden() { void linksToOtherEndpointsForbidden() {
CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN, CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
"invalid-token"); "invalid-token");
willThrow(exception).given(tokenValidator).validate(any()); willThrow(exception).given(this.tokenValidator).validate(any());
this.contextRunner.run(withWebTestClient((client) -> client.get() this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@ -181,8 +182,8 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
@Test @Test
void linksToOtherEndpointsWithRestrictedAccess() { void linksToOtherEndpointsWithRestrictedAccess() {
given(tokenValidator.validate(any())).willReturn(Mono.empty()); given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED)); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED));
this.contextRunner.run(withWebTestClient((client) -> client.get() this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@ -232,7 +233,8 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
static class CloudFoundryReactiveConfiguration { static class CloudFoundryReactiveConfiguration {
@Bean @Bean
CloudFoundrySecurityInterceptor interceptor() { CloudFoundrySecurityInterceptor interceptor(ReactiveTokenValidator tokenValidator,
ReactiveCloudFoundrySecurityService securityService) {
return new CloudFoundrySecurityInterceptor(tokenValidator, securityService, "app-id"); return new CloudFoundrySecurityInterceptor(tokenValidator, securityService, "app-id");
} }

View File

@ -42,6 +42,7 @@ 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.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -70,13 +71,13 @@ import static org.mockito.Mockito.mock;
*/ */
class CloudFoundryMvcWebEndpointIntegrationTests { class CloudFoundryMvcWebEndpointIntegrationTests {
private static TokenValidator tokenValidator = mock(TokenValidator.class); private final TokenValidator tokenValidator = mock(TokenValidator.class);
private static CloudFoundrySecurityService securityService = mock(CloudFoundrySecurityService.class); private final CloudFoundrySecurityService securityService = mock(CloudFoundrySecurityService.class);
@Test @Test
void operationWithSecurityInterceptorForbidden() { void operationWithSecurityInterceptorForbidden() {
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED);
load(TestEndpointConfiguration.class, load(TestEndpointConfiguration.class,
(client) -> client.get() (client) -> client.get()
.uri("/cfApplication/test") .uri("/cfApplication/test")
@ -89,7 +90,7 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
@Test @Test
void operationWithSecurityInterceptorSuccess() { void operationWithSecurityInterceptorSuccess() {
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.FULL); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.FULL);
load(TestEndpointConfiguration.class, load(TestEndpointConfiguration.class,
(client) -> client.get() (client) -> client.get()
.uri("/cfApplication/test") .uri("/cfApplication/test")
@ -119,7 +120,7 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
@Test @Test
void linksToOtherEndpointsWithFullAccess() { void linksToOtherEndpointsWithFullAccess() {
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.FULL); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.FULL);
load(TestEndpointConfiguration.class, load(TestEndpointConfiguration.class,
(client) -> client.get() (client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
@ -157,7 +158,7 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
void linksToOtherEndpointsForbidden() { void linksToOtherEndpointsForbidden() {
CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN, CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
"invalid-token"); "invalid-token");
willThrow(exception).given(tokenValidator).validate(any()); willThrow(exception).given(this.tokenValidator).validate(any());
load(TestEndpointConfiguration.class, load(TestEndpointConfiguration.class,
(client) -> client.get() (client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
@ -170,7 +171,7 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
@Test @Test
void linksToOtherEndpointsWithRestrictedAccess() { void linksToOtherEndpointsWithRestrictedAccess() {
given(securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED); given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED);
load(TestEndpointConfiguration.class, load(TestEndpointConfiguration.class,
(client) -> client.get() (client) -> client.get()
.uri("/cfApplication") .uri("/cfApplication")
@ -198,26 +199,23 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.doesNotExist()); .doesNotExist());
} }
private AnnotationConfigServletWebServerApplicationContext createApplicationContext(Class<?>... config) { private void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
return new AnnotationConfigServletWebServerApplicationContext(config); BiConsumer<ApplicationContext, WebTestClient> consumer = (context, client) -> clientConsumer.accept(client);
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withUserConfiguration(configuration, CloudFoundryMvcConfiguration.class)
.withBean(TokenValidator.class, () -> this.tokenValidator)
.withBean(CloudFoundrySecurityService.class, () -> this.securityService)
.run((context) -> consumer.accept(context, WebTestClient.bindToServer()
.baseUrl("http://localhost:" + getPort(
(AnnotationConfigServletWebServerApplicationContext) context.getSourceApplicationContext()))
.responseTimeout(Duration.ofMinutes(5))
.build()));
} }
private int getPort(AnnotationConfigServletWebServerApplicationContext context) { private int getPort(AnnotationConfigServletWebServerApplicationContext context) {
return context.getWebServer().getPort(); return context.getWebServer().getPort();
} }
private void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
BiConsumer<ApplicationContext, WebTestClient> consumer = (context, client) -> clientConsumer.accept(client);
try (AnnotationConfigServletWebServerApplicationContext context = createApplicationContext(configuration,
CloudFoundryMvcConfiguration.class)) {
consumer.accept(context,
WebTestClient.bindToServer()
.baseUrl("http://localhost:" + getPort(context))
.responseTimeout(Duration.ofMinutes(5))
.build());
}
}
private String mockAccessToken() { private String mockAccessToken() {
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu"
+ "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ." + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ."
@ -229,7 +227,8 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
static class CloudFoundryMvcConfiguration { static class CloudFoundryMvcConfiguration {
@Bean @Bean
CloudFoundrySecurityInterceptor interceptor() { CloudFoundrySecurityInterceptor interceptor(TokenValidator tokenValidator,
CloudFoundrySecurityService securityService) {
return new CloudFoundrySecurityInterceptor(tokenValidator, securityService, "app-id"); return new CloudFoundrySecurityInterceptor(tokenValidator, securityService, "app-id");
} }