/* * 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. * You may obtain a copy of the License at * * http://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.actuate.trace; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.Collections; import java.util.EnumSet; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.junit.Test; import org.springframework.boot.actuate.trace.TraceProperties.Include; import org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorAttributes; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * Tests for {@link WebRequestTraceFilter}. * * @author Dave Syer * @author Wallace Wadge * @author Phillip Webb * @author Andy Wilkinson * @author Venil Noronha * @author Stephane Nicoll * @author Madhura Bhave */ public class WebRequestTraceFilterTests { private final InMemoryTraceRepository repository = new InMemoryTraceRepository(); private TraceProperties properties = new TraceProperties(); private WebRequestTraceFilter filter = new WebRequestTraceFilter(this.repository, this.properties); @Test @SuppressWarnings("unchecked") public void filterAddsTraceWithDefaultIncludes() { MockHttpServletRequest request = spy(new MockHttpServletRequest("GET", "/foo")); request.addHeader("Accept", "application/json"); Map<String, Object> trace = this.filter.getTrace(request); assertThat(trace.get("method")).isEqualTo("GET"); assertThat(trace.get("path")).isEqualTo("/foo"); Map<String, Object> map = (Map<String, Object>) trace.get("headers"); assertThat(map.get("request").toString()).isEqualTo("{Accept=application/json}"); verify(request, times(0)).getParameterMap(); } @Test @SuppressWarnings({ "rawtypes", "unchecked" }) public void filterAddsTraceWithCustomIncludes() throws IOException, ServletException { this.properties.setInclude(EnumSet.allOf(Include.class)); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.addHeader("Accept", "application/json"); request.addHeader("Cookie", "testCookie=testValue;"); request.setContextPath("some.context.path"); request.setContent("Hello, World!".getBytes()); request.setRemoteAddr("some.remote.addr"); request.setQueryString("some.query.string"); request.setParameter("param", "paramvalue"); File tmp = File.createTempFile("spring-boot", "tmp"); String url = tmp.toURI().toURL().toString(); request.setPathInfo(url); tmp.deleteOnExit(); request.setAuthType("authType"); Principal principal = new Principal() { @Override public String getName() { return "principalTest"; } }; request.setUserPrincipal(principal); MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); response.addHeader("Set-Cookie", "a=b"); this.filter.doFilterInternal(request, response, new FilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { BufferedReader bufferedReader = request.getReader(); while (bufferedReader.readLine() != null) { // read the contents as normal (forces cache to fill up) } response.getWriter().println("Goodbye, World!"); } }); assertThat(this.repository.findAll()).hasSize(1); Map<String, Object> trace = this.repository.findAll().iterator().next().getInfo(); Map<String, Object> map = (Map<String, Object>) trace.get("headers"); assertThat(map.get("response").toString()) .isEqualTo("{Content-Type=application/json, Set-Cookie=a=b, status=200}"); assertThat(trace.get("method")).isEqualTo("GET"); assertThat(trace.get("path")).isEqualTo("/foo"); assertThat(((String[]) ((Map) trace.get("parameters")).get("param"))[0]) .isEqualTo("paramvalue"); assertThat(trace.get("remoteAddress")).isEqualTo("some.remote.addr"); assertThat(trace.get("query")).isEqualTo("some.query.string"); assertThat(trace.get("userPrincipal")).isEqualTo(principal.getName()); assertThat(trace.get("contextPath")).isEqualTo("some.context.path"); assertThat(trace.get("pathInfo")).isEqualTo(url); assertThat(trace.get("authType")).isEqualTo("authType"); assertThat(map.get("request").toString()) .isEqualTo("{Accept=application/json, Cookie=testCookie=testValue;}"); } @Test @SuppressWarnings({ "unchecked" }) public void filterDoesNotAddResponseHeadersWithoutResponseHeadersInclude() throws ServletException, IOException { this.properties.setInclude(Collections.singleton(Include.REQUEST_HEADERS)); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); this.filter.doFilterInternal(request, response, new FilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { } }); Map<String, Object> info = this.repository.findAll().iterator().next().getInfo(); Map<String, Object> headers = (Map<String, Object>) info.get("headers"); assertThat(headers.get("response") == null).isTrue(); } @Test @SuppressWarnings({ "unchecked" }) public void filterDoesNotAddRequestCookiesWithCookiesExclude() throws ServletException, IOException { this.properties.setInclude(Collections.singleton(Include.REQUEST_HEADERS)); MockHttpServletRequest request = spy(new MockHttpServletRequest("GET", "/foo")); request.addHeader("Accept", "application/json"); request.addHeader("Cookie", "testCookie=testValue;"); Map<String, Object> map = (Map<String, Object>) this.filter.getTrace(request) .get("headers"); assertThat(map.get("request").toString()).isEqualTo("{Accept=application/json}"); } @Test @SuppressWarnings({ "unchecked" }) public void filterDoesNotAddAuthorizationHeaderWithoutAuthorizationHeaderInclude() throws ServletException, IOException { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.addHeader("Authorization", "my-auth-header"); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilterInternal(request, response, new FilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { } }); Map<String, Object> info = this.repository.findAll().iterator().next().getInfo(); Map<String, Object> headers = (Map<String, Object>) info.get("headers"); assertThat(((Map<Object, Object>) headers.get("request"))).hasSize(0); } @Test @SuppressWarnings({ "unchecked" }) public void filterAddsAuthorizationHeaderWhenAuthorizationHeaderIncluded() throws ServletException, IOException { this.properties.setInclude( EnumSet.of(Include.REQUEST_HEADERS, Include.AUTHORIZATION_HEADER)); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.addHeader("Authorization", "my-auth-header"); MockHttpServletResponse response = new MockHttpServletResponse(); this.filter.doFilterInternal(request, response, new FilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { } }); Map<String, Object> info = this.repository.findAll().iterator().next().getInfo(); Map<String, Object> headers = (Map<String, Object>) info.get("headers"); assertThat(((Map<Object, Object>) headers.get("request"))) .containsKey("Authorization"); } @Test @SuppressWarnings({ "unchecked" }) public void filterDoesNotAddResponseCookiesWithCookiesExclude() throws ServletException, IOException { this.properties.setInclude(Collections.singleton(Include.RESPONSE_HEADERS)); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); MockHttpServletResponse response = new MockHttpServletResponse(); response.addHeader("Content-Type", "application/json"); response.addHeader("Set-Cookie", "testCookie=testValue;"); Map<String, Object> trace = this.filter.getTrace(request); this.filter.enhanceTrace(trace, response); Map<String, Object> map = (Map<String, Object>) trace.get("headers"); assertThat(map.get("response").toString()) .isEqualTo("{Content-Type=application/json, status=200}"); } @Test public void filterHasResponseStatus() { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(404); response.addHeader("Content-Type", "application/json"); Map<String, Object> trace = this.filter.getTrace(request); this.filter.enhanceTrace(trace, response); @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) trace .get("headers")).get("response"); assertThat(map.get("status").toString()).isEqualTo("404"); } @Test public void filterAddsTimeTaken() throws Exception { MockHttpServletRequest request = spy(new MockHttpServletRequest("GET", "/foo")); MockHttpServletResponse response = new MockHttpServletResponse(); MockFilterChain chain = new MockFilterChain(); this.filter.doFilter(request, response, chain); String timeTaken = (String) this.repository.findAll().iterator().next().getInfo() .get("timeTaken"); assertThat(timeTaken).isNotNull(); } @Test public void filterHasError() { this.filter.setErrorAttributes(new DefaultErrorAttributes()); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(500); request.setAttribute("javax.servlet.error.exception", new IllegalStateException("Foo")); response.addHeader("Content-Type", "application/json"); Map<String, Object> trace = this.filter.getTrace(request); this.filter.enhanceTrace(trace, response); @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) trace.get("error"); System.err.println(map); assertThat(map.get("message").toString()).isEqualTo("Foo"); } @Test @SuppressWarnings("unchecked") public void filterHas500ResponseStatusWhenExceptionIsThrown() throws ServletException, IOException { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); MockHttpServletResponse response = new MockHttpServletResponse(); try { this.filter.doFilterInternal(request, response, new FilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { throw new RuntimeException(); } }); fail("Exception was swallowed"); } catch (RuntimeException ex) { Map<String, Object> headers = (Map<String, Object>) this.repository.findAll() .iterator().next().getInfo().get("headers"); Map<String, Object> responseHeaders = (Map<String, Object>) headers .get("response"); assertThat((String) responseHeaders.get("status")).isEqualTo("500"); } } @Test @SuppressWarnings("unchecked") public void postProcessRequestHeaders() throws Exception { this.filter = new WebRequestTraceFilter(this.repository, this.properties) { @Override protected void postProcessRequestHeaders(Map<String, Object> headers) { headers.remove("Test"); } }; MockHttpServletRequest request = spy(new MockHttpServletRequest("GET", "/foo")); request.addHeader("Accept", "application/json"); request.addHeader("Test", "spring"); Map<String, Object> map = (Map<String, Object>) this.filter.getTrace(request) .get("headers"); assertThat(map.get("request").toString()).isEqualTo("{Accept=application/json}"); } }