Polish "Strip query string from Cloud Foundry reactive links base URL"

This commit polishes the proposed test for the WebFlux Cloud Foundry
endpoint. To protect against future regressions, it also adds tests
for the general purpose links endpoints (WebFlux, Web MVC, and
Jersey) and for the Web MVC Cloud Foundry endpoint.

See gh-50008
This commit is contained in:
Andy Wilkinson
2026-04-10 12:20:03 +01:00
parent 847b2e837b
commit f63f6d7252
3 changed files with 91 additions and 37 deletions
@@ -146,47 +146,27 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
.jsonPath("_links.length()")
.isEqualTo(5)
.jsonPath("_links.self.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.info.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/info"))
.jsonPath("_links.info.templated")
.isEqualTo(false)
.jsonPath("_links.env.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/env"))
.jsonPath("_links.env.templated")
.isEqualTo(false)
.jsonPath("_links.test.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/test"))
.jsonPath("_links.test.templated")
.isEqualTo(false)
.jsonPath("_links.test-part.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/test/{part}"))
.jsonPath("_links.test-part.templated")
.isEqualTo(true)));
}
@Test
void linksToOtherEndpointsWithQueryStringShouldNotContainQueryInHref() {
given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.FULL));
this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication?x=1")
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "bearer " + mockAccessToken())
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("_links.self.href")
.value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication"))
.jsonPath("_links.info.href")
.value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/info"))
.jsonPath("_links.env.href")
.value((href) -> assertThat((String) href).doesNotContain("?").endsWith("/cfApplication/env"))));
}
@Test
void linksToOtherEndpointsForbidden() {
CloudFoundryAuthorizationException exception = new CloudFoundryAuthorizationException(Reason.INVALID_TOKEN,
@@ -216,11 +196,11 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
.jsonPath("_links.length()")
.isEqualTo(2)
.jsonPath("_links.self.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.info.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/info"))
.jsonPath("_links.info.templated")
.isEqualTo(false)
.jsonPath("_links.env")
@@ -231,12 +211,34 @@ class CloudFoundryWebFluxEndpointIntegrationTests {
.doesNotExist()));
}
@Test
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
given(this.tokenValidator.validate(any())).willReturn(Mono.empty());
given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(Mono.just(AccessLevel.RESTRICTED));
this.contextRunner.run(withWebTestClient((client) -> client.get()
.uri("/cfApplication?x=1")
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "bearer " + mockAccessToken())
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("_links.self.href")
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.info.href")
.value(isLinkTo("/cfApplication/info"))));
}
@Test
void unknownEndpointsAreForbidden() {
this.contextRunner.run(withWebTestClient(
(client) -> client.get().uri("/cfApplication/unknown").exchange().expectStatus().isForbidden()));
}
private Consumer<Object> isLinkTo(String target) {
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
}
private ContextConsumer<AssertableReactiveWebApplicationContext> withWebTestClient(
Consumer<WebTestClient> clientConsumer) {
return (context) -> {
@@ -58,6 +58,7 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
@@ -133,23 +134,23 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.jsonPath("_links.length()")
.isEqualTo(5)
.jsonPath("_links.self.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.info.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/info"))
.jsonPath("_links.info.templated")
.isEqualTo(false)
.jsonPath("_links.env.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/env"))
.jsonPath("_links.env.templated")
.isEqualTo(false)
.jsonPath("_links.test.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/test"))
.jsonPath("_links.test.templated")
.isEqualTo(false)
.jsonPath("_links.test-part.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/test/{part}"))
.jsonPath("_links.test-part.templated")
.isEqualTo(true));
}
@@ -184,11 +185,11 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.jsonPath("_links.length()")
.isEqualTo(2)
.jsonPath("_links.self.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.info.href")
.isNotEmpty()
.value(isLinkTo("/cfApplication/info"))
.jsonPath("_links.info.templated")
.isEqualTo(false)
.jsonPath("_links.env")
@@ -199,6 +200,24 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.doesNotExist());
}
@Test
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
given(this.securityService.getAccessLevel(any(), eq("app-id"))).willReturn(AccessLevel.RESTRICTED);
load(TestEndpointConfiguration.class,
(client) -> client.get()
.uri("/cfApplication?x=1")
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "bearer " + mockAccessToken())
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("_links.self.href")
.value(isLinkTo("/cfApplication"))
.jsonPath("_links.info.href")
.value(isLinkTo("/cfApplication/info")));
}
@Test
void unknownEndpointsAreForbidden() {
load(TestEndpointConfiguration.class,
@@ -210,6 +229,10 @@ class CloudFoundryMvcWebEndpointIntegrationTests {
.isForbidden());
}
private Consumer<Object> isLinkTo(String target) {
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
}
private void load(Class<?> configuration, Consumer<WebTestClient> clientConsumer) {
BiConsumer<ApplicationContext, WebTestClient> consumer = (context, client) -> clientConsumer.accept(client);
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
@@ -162,15 +162,40 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
.jsonPath("_links.length()")
.isEqualTo(3)
.jsonPath("_links.self.href")
.isNotEmpty()
.value(isLinkTo("/endpoints"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.test.href")
.isNotEmpty()
.value(isLinkTo("/endpoints/test"))
.jsonPath("_links.test.templated")
.isEqualTo(false)
.jsonPath("_links.test-part.href")
.isNotEmpty()
.value(isLinkTo("/endpoints/test/{part}"))
.jsonPath("_links.test-part.templated")
.isEqualTo(true));
}
@Test
void whenRequestHasAQueryStringLinksToOtherEndpointsDoNotHaveAQueryString() {
load(TestEndpointConfiguration.class,
(client) -> client.get()
.uri("?a=alpha")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("_links.length()")
.isEqualTo(3)
.jsonPath("_links.self.href")
.value(isLinkTo("/endpoints"))
.jsonPath("_links.self.templated")
.isEqualTo(false)
.jsonPath("_links.test.href")
.value(isLinkTo("/endpoints/test"))
.jsonPath("_links.test.templated")
.isEqualTo(false)
.jsonPath("_links.test-part.href")
.value(isLinkTo("/endpoints/test/{part}"))
.jsonPath("_links.test-part.templated")
.isEqualTo(true));
}
@@ -668,6 +693,10 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
(client) -> client.get().uri("/customstatus").exchange().expectStatus().isEqualTo(234));
}
private Consumer<Object> isLinkTo(String target) {
return (href) -> assertThat(href).asString().doesNotContain("?").endsWith(target);
}
protected abstract int getPort(T context);
protected void validateErrorBody(WebTestClient.BodyContentSpec body, HttpStatus status, String path,