Move remaining integration tests out of actuator-autoconfigure-all

This commit is contained in:
Andy Wilkinson
2025-06-09 13:37:35 +01:00
committed by Phillip Webb
parent 18da9e1343
commit bf385649ec
32 changed files with 251 additions and 479 deletions

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for the Actuator's MVC
* {@link org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint
* controller endpoints}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class ControllerEndpointWebMvcIntegrationTests {
private AnnotationConfigServletWebApplicationContext context;
@AfterEach
void close() {
this.context.close();
}
@Test
void endpointsCanBeAccessed() {
this.context = new AnnotationConfigServletWebApplicationContext();
this.context.register(DefaultConfiguration.class, ExampleController.class);
TestPropertyValues
.of("management.endpoints.web.base-path:/management", "management.endpoints.web.exposure.include=*")
.applyTo(this.context);
MockMvcTester mvc = createMockMvcTester();
assertThat(mvc.get().uri("/management/example")).hasStatusOk();
}
private MockMvcTester createMockMvcTester() {
this.context.setServletContext(new MockServletContext());
this.context.refresh();
return MockMvcTester.from(this.context);
}
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementContextAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
BeansEndpointAutoConfiguration.class })
static class DefaultConfiguration {
}
@org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "example")
@SuppressWarnings("removal")
static class ExampleController {
@GetMapping("/")
String example() {
return "Example";
}
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import java.io.IOException;
import java.util.function.Supplier;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for controlling access to endpoints exposed by Spring MVC.
*
* @author Andy Wilkinson
*/
class WebMvcEndpointAccessIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(TomcatServletWebServerAutoConfiguration.class,
TomcatServletWebServerAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
HealthContributorAutoConfiguration.class, BeansEndpointAutoConfiguration.class))
.withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class)
.withPropertyValues("server.port:0");
@Test
void accessIsUnrestrictedByDefault() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*").run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isTrue();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isTrue();
});
}
@Test
void accessCanBeReadOnlyByDefault() {
this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=*",
"management.endpoints.access.default=READ_ONLY")
.run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse();
});
}
@Test
void accessCanBeNoneByDefault() {
this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=*",
"management.endpoints.access.default=NONE")
.run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse();
});
}
@Test
void accessForOneEndpointCanOverrideTheDefaultAccess() {
this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=*",
"management.endpoints.access.default=READ_ONLY",
"management.endpoint.customservlet.access=UNRESTRICTED")
.run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isTrue();
});
}
@Test
void accessCanBeCappedAtReadOnly() {
this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=*",
"management.endpoints.access.default=UNRESTRICTED",
"management.endpoints.access.max-permitted=READ_ONLY")
.run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isTrue();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse();
});
}
@Test
void accessCanBeCappedAtNone() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=*",
"management.endpoints.access.default=UNRESTRICTED", "management.endpoints.access.max-permitted=NONE")
.run((context) -> {
RestClient client = createClient(context);
assertThat(isAccessible(client, HttpMethod.GET, "beans")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.POST, "custommvc")).isFalse();
assertThat(isAccessible(client, HttpMethod.GET, "customservlet")).isFalse();
assertThat(isAccessible(client, HttpMethod.POST, "customservlet")).isFalse();
});
}
private RestClient createClient(AssertableWebApplicationContext context) {
int port = context.getSourceApplicationContext(ServletWebServerApplicationContext.class)
.getWebServer()
.getPort();
return RestClient.builder().defaultStatusHandler((status) -> true, (request, response) -> {
}).baseUrl("http://localhost:" + port).build();
}
private boolean isAccessible(RestClient client, HttpMethod method, String path) {
path = "/actuator/" + path;
ResponseEntity<byte[]> result = client.method(method).uri(path).retrieve().toEntity(byte[].class);
if (result.getStatusCode() == HttpStatus.OK) {
return true;
}
if (result.getStatusCode() == HttpStatus.NOT_FOUND || result.getStatusCode() == HttpStatus.METHOD_NOT_ALLOWED) {
return false;
}
throw new IllegalStateException(
String.format("Unexpected %s HTTP status for endpoint %s", result.getStatusCode(), path));
}
@org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "custommvc")
@SuppressWarnings("removal")
static class CustomMvcEndpoint {
@GetMapping("/")
String get() {
return "get";
}
@PostMapping("/")
String post() {
return "post";
}
}
@org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "customservlet")
@SuppressWarnings({ "deprecation", "removal" })
static class CustomServletEndpoint
implements Supplier<org.springframework.boot.actuate.endpoint.web.EndpointServlet> {
@Override
public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() {
return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
});
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import org.assertj.core.api.ThrowingConsumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for the MVC actuator endpoints' CORS support
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @see WebMvcEndpointManagementContextConfiguration
*/
class WebMvcEndpointCorsIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class))
.withPropertyValues("management.endpoints.web.exposure.include:*");
@Test
void corsIsDisabledByDefault() {
this.contextRunner.run(withMockMvc((mvc) -> assertThat(mvc.options()
.uri("/actuator/beans")
.header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)));
}
@Test
void settingAllowedOriginsEnablesCors() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com")
.run(withMockMvc((mvc) -> {
assertThat(mvc.options()
.uri("/actuator/beans")
.header("Origin", "bar.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).hasStatus(HttpStatus.FORBIDDEN);
performAcceptedCorsRequest(mvc);
}));
}
@Test
void settingAllowedOriginPatternsEnablesCors() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true")
.run(withMockMvc((mvc) -> {
assertThat(mvc.options()
.uri("/actuator/beans")
.header("Origin", "bar.example.org")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).hasStatus(HttpStatus.FORBIDDEN);
performAcceptedCorsRequest(mvc);
}));
}
@Test
void maxAgeDefaultsTo30Minutes() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com")
.run(withMockMvc((mvc) -> {
MvcTestResult result = performAcceptedCorsRequest(mvc);
assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800");
}));
}
@Test
void maxAgeCanBeConfigured() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.max-age: 2400")
.run(withMockMvc((mvc) -> {
MvcTestResult result = performAcceptedCorsRequest(mvc);
assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400");
}));
}
@Test
void requestsWithDisallowedHeadersAreRejected() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com")
.run(withMockMvc((mvc) -> assertThat(mvc.options()
.uri("/actuator/beans")
.header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")).hasStatus(HttpStatus.FORBIDDEN)));
}
@Test
void allowedHeadersCanBeConfigured() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allowed-headers:Alpha,Bravo")
.run(withMockMvc((mvc) -> assertThat(mvc.options()
.uri("/actuator/beans")
.header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")).hasStatusOk()
.headers()
.hasValue(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha")));
}
@Test
void requestsWithDisallowedMethodsAreRejected() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com")
.run(withMockMvc((mvc) -> assertThat(mvc.options()
.uri("/actuator/beans")
.header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PATCH")).hasStatus(HttpStatus.FORBIDDEN)));
}
@Test
void allowedMethodsCanBeConfigured() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allowed-methods:GET,HEAD")
.run(withMockMvc((mvc) -> assertThat(mvc.options()
.uri("/actuator/beans")
.header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).hasStatusOk()
.headers()
.hasValue(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD")));
}
@Test
void credentialsCanBeAllowed() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allow-credentials:true")
.run(withMockMvc((mvc) -> {
MvcTestResult result = performAcceptedCorsRequest(mvc);
assertThat(result).hasHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}));
}
@Test
void credentialsCanBeDisabled() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com",
"management.endpoints.web.cors.allow-credentials:false")
.run(withMockMvc((mvc) -> {
MvcTestResult result = performAcceptedCorsRequest(mvc);
assertThat(result).doesNotContainHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS);
}));
}
private ContextConsumer<WebApplicationContext> withMockMvc(ThrowingConsumer<MockMvcTester> mvc) {
return (context) -> mvc.accept(MockMvcTester.from(context));
}
private MvcTestResult performAcceptedCorsRequest(MockMvcTester mvc) {
return performAcceptedCorsRequest(mvc, "/actuator/beans");
}
private MvcTestResult performAcceptedCorsRequest(MockMvcTester mvc, String url) {
MvcTestResult result = mvc.options()
.uri(url)
.header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.exchange();
assertThat(result).hasStatusOk().hasHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "foo.example.com");
return result;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import java.io.IOException;
import java.util.function.Supplier;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.exchanges.ServletHttpExchangesAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Endpoints over Spring MVC.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class WebMvcEndpointExposureIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(TomcatServletWebServerAutoConfiguration.class,
TomcatServletWebServerAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletHttpExchangesAutoConfiguration.class,
HealthContributorAutoConfiguration.class, BeansEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, ShutdownEndpointAutoConfiguration.class))
.withUserConfiguration(CustomMvcEndpoint.class, CustomServletEndpoint.class,
HttpExchangeRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class)
.withPropertyValues("server.port:0");
@Test
void webEndpointsExceptHealthAreDisabledByDefault() {
this.contextRunner.run((context) -> {
RestClient client = createClient(context);
assertThat(isExposed(client, HttpMethod.GET, "beans")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue();
assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse();
});
}
@Test
void webEndpointsCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=*");
contextRunner.run((context) -> {
RestClient client = createClient(context);
assertThat(isExposed(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue();
assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse();
});
}
@Test
void singleWebEndpointCanBeExposed() {
WebApplicationContextRunner contextRunner = this.contextRunner
.withPropertyValues("management.endpoints.web.exposure.include=beans");
contextRunner.run((context) -> {
RestClient client = createClient(context);
assertThat(isExposed(client, HttpMethod.GET, "beans")).isTrue();
assertThat(isExposed(client, HttpMethod.GET, "health")).isFalse();
assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse();
});
}
@Test
void singleWebEndpointCanBeExcluded() {
WebApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
"management.endpoints.web.exposure.include=*", "management.endpoints.web.exposure.exclude=beans");
contextRunner.run((context) -> {
RestClient client = createClient(context);
assertThat(isExposed(client, HttpMethod.GET, "beans")).isFalse();
assertThat(isExposed(client, HttpMethod.GET, "health")).isTrue();
assertThat(isExposed(client, HttpMethod.POST, "shutdown")).isFalse();
});
}
private RestClient createClient(AssertableWebApplicationContext context) {
int port = context.getSourceApplicationContext(ServletWebServerApplicationContext.class)
.getWebServer()
.getPort();
return RestClient.builder().defaultStatusHandler((status) -> true, (request, response) -> {
}).baseUrl("http://localhost:" + port).build();
}
private boolean isExposed(RestClient client, HttpMethod method, String path) {
path = "/actuator/" + path;
ResponseEntity<byte[]> result = client.method(method).uri(path).retrieve().toEntity(byte[].class);
if (result.getStatusCode() == HttpStatus.OK) {
return true;
}
if (result.getStatusCode() == HttpStatus.NOT_FOUND) {
return false;
}
throw new IllegalStateException(
String.format("Unexpected %s HTTP status for endpoint %s", result.getStatusCode(), path));
}
@org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "custommvc")
@SuppressWarnings("removal")
static class CustomMvcEndpoint {
@GetMapping("/")
String main() {
return "test";
}
}
@org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "customservlet")
@SuppressWarnings({ "deprecation", "removal" })
static class CustomServletEndpoint
implements Supplier<org.springframework.boot.actuate.endpoint.web.EndpointServlet> {
@Override
public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() {
return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
}
});
}
}
@Configuration(proxyBeanMethods = false)
static class HttpExchangeRepositoryConfiguration {
@Bean
InMemoryHttpExchangeRepository httpExchangeRepository() {
return new InMemoryHttpExchangeRepository();
}
}
@Configuration(proxyBeanMethods = false)
static class AuditEventRepositoryConfiguration {
@Bean
InMemoryAuditEventRepository auditEventRepository() {
return new InMemoryAuditEventRepository();
}
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import java.io.IOException;
import java.util.function.Supplier;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import jakarta.servlet.http.HttpServlet;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext;
import org.springframework.boot.webmvc.actuate.endpoint.web.WebMvcEndpointHandlerMapping;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for the Actuator's MVC endpoints.
*
* @author Andy Wilkinson
*/
class WebMvcEndpointIntegrationTests {
private AnnotationConfigServletWebApplicationContext context;
@Test
void webMvcEndpointHandlerMappingIsConfiguredWithPathPatternParser() {
this.context = new AnnotationConfigServletWebApplicationContext();
this.context.register(DefaultConfiguration.class);
this.context.setServletContext(new MockServletContext());
this.context.refresh();
WebMvcEndpointHandlerMapping handlerMapping = this.context.getBean(WebMvcEndpointHandlerMapping.class);
assertThat(handlerMapping.getPatternParser()).isInstanceOf(PathPatternParser.class);
}
@Test
void linksAreProvidedToAllEndpointTypes() {
this.context = new AnnotationConfigServletWebApplicationContext();
this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class);
TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context);
MockMvcTester mvc = doCreateMockMvcTester();
assertThat(mvc.get().uri("/actuator").accept("*/*")).hasStatusOk()
.bodyJson()
.extractingPath("_links")
.asMap()
.containsKeys("beans", "servlet", "restcontroller", "controller");
}
@Test
void linksPageIsNotAvailableWhenDisabled() {
this.context = new AnnotationConfigServletWebApplicationContext();
this.context.register(DefaultConfiguration.class, EndpointsConfiguration.class);
TestPropertyValues.of("management.endpoints.web.discovery.enabled=false").applyTo(this.context);
MockMvcTester mvc = doCreateMockMvcTester();
assertThat(mvc.get().uri("/actuator").accept("*/*")).hasStatus(HttpStatus.NOT_FOUND);
}
@Test
void endpointObjectMapperCanBeApplied() {
this.context = new AnnotationConfigServletWebApplicationContext();
this.context.register(EndpointObjectMapperConfiguration.class, DefaultConfiguration.class);
TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context);
MockMvcTester mvc = doCreateMockMvcTester();
assertThat(mvc.get().uri("/actuator/beans")).hasStatusOk().bodyText().contains("\"scope\":\"notelgnis\"");
}
private MockMvcTester doCreateMockMvcTester() {
this.context.setServletContext(new MockServletContext());
this.context.refresh();
return MockMvcTester.from(this.context);
}
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementContextAutoConfiguration.class, AuditAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, BeansEndpointAutoConfiguration.class })
static class DefaultConfiguration {
}
@org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint(id = "servlet")
@SuppressWarnings({ "deprecation", "removal" })
static class TestServletEndpoint
implements Supplier<org.springframework.boot.actuate.endpoint.web.EndpointServlet> {
@Override
public org.springframework.boot.actuate.endpoint.web.EndpointServlet get() {
return new org.springframework.boot.actuate.endpoint.web.EndpointServlet(new HttpServlet() {
});
}
}
@org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint(id = "controller")
@SuppressWarnings("removal")
static class TestControllerEndpoint {
}
@org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint(id = "restcontroller")
@SuppressWarnings("removal")
static class TestRestControllerEndpoint {
}
@Configuration(proxyBeanMethods = false)
static class EndpointsConfiguration {
@Bean
TestServletEndpoint testServletEndpoint() {
return new TestServletEndpoint();
}
@Bean
TestControllerEndpoint testControllerEndpoint() {
return new TestControllerEndpoint();
}
@Bean
TestRestControllerEndpoint testRestControllerEndpoint() {
return new TestRestControllerEndpoint();
}
}
@Configuration
@SuppressWarnings({ "deprecation", "removal" })
static class EndpointObjectMapperConfiguration {
@Bean
EndpointObjectMapper endpointObjectMapper() {
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new ReverseStringSerializer());
ObjectMapper objectMapper = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json()
.modules(module)
.build();
return () -> objectMapper;
}
static class ReverseStringSerializer extends StdScalarSerializer<Object> {
ReverseStringSerializer() {
super(String.class, false);
}
@Override
public boolean isEmpty(SerializerProvider prov, Object value) {
return ((String) value).isEmpty();
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
serialize(value, gen);
}
@Override
public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider,
TypeSerializer typeSer) throws IOException {
serialize(value, gen);
}
private void serialize(Object value, JsonGenerator gen) throws IOException {
StringBuilder builder = new StringBuilder((String) value);
gen.writeString(builder.reverse().toString());
}
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.webmvc.actuate.autoconfigure.endpoint.web;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.integrationtest.AbstractHealthEndpointAdditionalPathIntegrationTests;
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
import org.springframework.boot.servlet.actuate.autoconfigure.ServletManagementContextAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.tomcat.actuate.autoconfigure.web.TomcatServletManagementContextAutoConfiguration;
import org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration;
import org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer;
import org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.webmvc.actuate.autoconfigure.health.WebMvcHealthEndpointExtensionAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration;
import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration;
import org.springframework.web.context.ConfigurableWebApplicationContext;
/**
* Integration tests for MVC health groups on an additional path.
*
* @author Madhura Bhave
*/
class WebMvcHealthEndpointAdditionalPathIntegrationTests extends
AbstractHealthEndpointAdditionalPathIntegrationTests<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
WebMvcHealthEndpointAdditionalPathIntegrationTests() {
super(new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, ManagementContextAutoConfiguration.class,
TomcatServletWebServerAutoConfiguration.class, TomcatServletWebServerAutoConfiguration.class,
TomcatServletManagementContextAutoConfiguration.class, WebMvcAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, WebMvcHealthEndpointExtensionAutoConfiguration.class,
DiskSpaceHealthContributorAutoConfiguration.class))
.withInitializer(new ServerPortInfoApplicationContextInitializer())
.withPropertyValues("server.port=0"));
}
}