Update Actuator endpoints to use custom media type
Previously, the actuator's endpoints produced application/json and, where appropriate, also consumed application/json. Without a custom, versioned media type, it's impossible for us to make changes to the endpoints without breaking clients. This commit introduces a new media type, application/spring-boot.actuator.v1+json, that is now produced by default with application/json also being produced if requested. Endpoints that consume JSON will now also accept content the uses the new media type in addition to application/json. Closes gh-7967
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -37,6 +37,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -47,6 +49,7 @@ import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -70,6 +73,21 @@ public class AuditEventsMvcEndpointTests {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/auditevents")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(get("/auditevents").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception {
|
||||
this.context.getBean(AuditEventsMvcEndpoint.class).setEnabled(false);
|
||||
|
||||
@@ -39,6 +39,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@@ -49,6 +51,7 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -76,6 +79,36 @@ public class EnvironmentMvcEndpointTests {
|
||||
"foo:bar", "fool:baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homeContentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/env")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homeContentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(
|
||||
get("/env").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE))
|
||||
.andExpect(status().isOk()).andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subContentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/env/foo")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subContentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(get("/env/foo").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void home() throws Exception {
|
||||
this.mvc.perform(get("/env")).andExpect(status().isOk())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -61,17 +61,13 @@ public class HealthMvcEndpointTests {
|
||||
|
||||
private MockEnvironment environment;
|
||||
|
||||
private HttpServletRequest user = createAuthenticationToken(
|
||||
"ROLE_USER");
|
||||
private HttpServletRequest user = createAuthenticationToken("ROLE_USER");
|
||||
|
||||
private HttpServletRequest actuator = createAuthenticationToken(
|
||||
"ROLE_ACTUATOR");
|
||||
private HttpServletRequest actuator = createAuthenticationToken("ROLE_ACTUATOR");
|
||||
|
||||
private HttpServletRequest hero = createAuthenticationToken(
|
||||
"ROLE_HERO");
|
||||
private HttpServletRequest hero = createAuthenticationToken("ROLE_HERO");
|
||||
|
||||
private HttpServletRequest createAuthenticationToken(
|
||||
String role) {
|
||||
private HttpServletRequest createAuthenticationToken(String role) {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.declareRoles(role);
|
||||
return new MockHttpServletRequest(servletContext);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -38,6 +38,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -80,6 +83,21 @@ public class InfoMvcEndpointTests {
|
||||
"\"beanName2\":{\"key21\":\"value21\",\"key22\":\"value22\"}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/info")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(
|
||||
get("/info").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE))
|
||||
.andExpect(status().isOk()).andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Import({ JacksonAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@@ -55,6 +56,7 @@ import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -129,12 +131,35 @@ public class LoggersMvcEndpointTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLoggerShouldSetLogLevel() throws Exception {
|
||||
public void contentTypeForGetDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/loggers")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeForGetCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(get("/loggers").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLoggerUsingApplicationJsonShouldSetLogLevel() throws Exception {
|
||||
this.mvc.perform(post("/loggers/ROOT").contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"configuredLevel\":\"debug\"}")).andExpect(status().isOk());
|
||||
verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLoggerUsingActuatorV1JsonShouldSetLogLevel() throws Exception {
|
||||
this.mvc.perform(post("/loggers/ROOT")
|
||||
.contentType(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)
|
||||
.content("{\"configuredLevel\":\"debug\"}")).andExpect(status().isOk());
|
||||
verify(this.loggingSystem).setLogLevel("ROOT", LogLevel.DEBUG);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLoggerWhenDisabledShouldReturnNotFound() throws Exception {
|
||||
this.context.getBean(LoggersEndpoint.class).setEnabled(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -38,6 +38,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@@ -49,6 +51,7 @@ import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
@@ -80,6 +83,36 @@ public class MetricsMvcEndpointTests {
|
||||
.andExpect(content().string(containsString("\"foo\":1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homeContentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/metrics")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homeContentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(get("/metrics").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specificMetricContentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void specificMetricContentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(get("/metrics/foo").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homeWhenDisabled() throws Exception {
|
||||
this.context.getBean(MetricsEndpoint.class).setEnabled(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 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.
|
||||
@@ -16,34 +16,127 @@
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.mvc;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link ShutdownMvcEndpoint}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@SpringBootTest(properties = { "management.security.enabled=false",
|
||||
"endpoints.shutdown.enabled=true" })
|
||||
@RunWith(SpringRunner.class)
|
||||
public class ShutdownMvcEndpointTests {
|
||||
|
||||
private ShutdownEndpoint endpoint = mock(ShutdownEndpoint.class);
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private ShutdownMvcEndpoint mvc = new ShutdownMvcEndpoint(this.endpoint);
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabled() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ResponseEntity<Map<String, String>> response = (ResponseEntity<Map<String, String>>) this.mvc
|
||||
.invoke();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
public void contentTypeDefaultsToActuatorV1Json() throws Exception {
|
||||
this.mvc.perform(post("/shutdown")).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
"application/vnd.spring-boot.actuator.v1+json;charset=UTF-8"));
|
||||
assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeCanBeApplicationJson() throws Exception {
|
||||
this.mvc.perform(post("/shutdown").header(HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk())
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaType.APPLICATION_JSON_UTF8_VALUE));
|
||||
assertThat(this.context.getBean(CountDownLatch.class).await(30, TimeUnit.SECONDS))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class })
|
||||
public static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestShutdownEndpoint endpoint() {
|
||||
return new TestShutdownEndpoint(contextCloseLatch());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CountDownLatch contextCloseLatch() {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestShutdownEndpoint extends ShutdownEndpoint {
|
||||
|
||||
private final CountDownLatch contextCloseLatch;
|
||||
|
||||
TestShutdownEndpoint(CountDownLatch contextCloseLatch) {
|
||||
this.contextCloseLatch = contextCloseLatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context)
|
||||
throws BeansException {
|
||||
ConfigurableApplicationContext mockContext = mock(
|
||||
ConfigurableApplicationContext.class);
|
||||
willAnswer(new Answer<Void>() {
|
||||
|
||||
@Override
|
||||
public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
TestShutdownEndpoint.this.contextCloseLatch.countDown();
|
||||
return null;
|
||||
}
|
||||
|
||||
}).given(mockContext).close();
|
||||
super.setApplicationContext(mockContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user