/** * Copyright (c) 2009-2011 VMware, Inc. All Rights Reserved. * * 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 com.springsource.insight.plugin.springweb.http; import java.io.IOException; import java.io.OutputStream; import java.io.StreamCorruptedException; import java.net.URI; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import com.springsource.insight.collection.ObscuredValueSetMarker; import com.springsource.insight.collection.http.HttpObfuscator; import com.springsource.insight.collection.test.OperationCollectionAspectTestSupport; import com.springsource.insight.intercept.operation.Operation; import com.springsource.insight.intercept.operation.OperationFields; import com.springsource.insight.intercept.operation.OperationList; import com.springsource.insight.intercept.operation.OperationMap; import com.springsource.insight.intercept.operation.OperationUtils; import com.springsource.insight.intercept.trace.TraceError; import com.springsource.insight.util.ListUtil; import com.springsource.insight.util.StringUtil; /** * */ public class ClientHttpRequestCollectionOperationAspectTest extends OperationCollectionAspectTestSupport { private static final ClientHttpRequestTraceErrorAnalyzer errAnalyzer = ClientHttpRequestTraceErrorAnalyzer.getInstance(); private HttpObfuscator originalObfuscator; private final ObscuredValueSetMarker marker = new ObscuredValueSetMarker(); public ClientHttpRequestCollectionOperationAspectTest() { super(); } @Before @Override public void setUp() { super.setUp(); ClientHttpRequestCollectionOperationAspect aspectInstance = getAspect(); ClientHttpRequestOperationCollector collector = (ClientHttpRequestOperationCollector) aspectInstance.getCollector(); originalObfuscator = collector.getHttpObfuscator(); marker.clear(); collector.setHttpObfuscator(new HttpObfuscator(marker)); } @After @Override public void restore() { ClientHttpRequestCollectionOperationAspect aspectInstance = getAspect(); ClientHttpRequestOperationCollector collector = (ClientHttpRequestOperationCollector) aspectInstance.getCollector(); collector.setHttpObfuscator(originalObfuscator); marker.clear(); super.restore(); } @Test public void testSuccessfulExecutionCollected() throws Exception { ClientHttpRequest request = new TestClientHttpRequest(HttpMethod.GET, new URI("http://somewhere:7365/testExecutionCollected"), createIdentityHttpHeaders(Arrays.asList("Req-Header1", "Req-Header2")), createMockClientHttpResponse(HttpStatus.OK, createIdentityHttpHeaders(Arrays.asList("Rsp-Header1", "Rsp-Header2")))); ClientHttpResponse response = request.execute(); Operation op = assertExecuteRequest(request); assertRequestDetails(op, request); assertResponseDetails(op, response); TraceError err = assertTraceError(op, response); assertNull("Unexpected trace error: " + err, err); } @Test public void testFailedExecutionCollected() throws Exception { ClientHttpRequest request = new TestClientHttpRequest(HttpMethod.GET, new URI("http://somewhere:7365/testExecutionCollected"), createIdentityHttpHeaders(Arrays.asList("Req-Header1", "req-value1", "Req-Header2", "req-value2")), createMockClientHttpResponse(HttpStatus.GATEWAY_TIMEOUT, createIdentityHttpHeaders(Arrays.asList("Rsp-Header1", "Rsp-Header2")))); ClientHttpResponse response = request.execute(); Operation op = assertExecuteRequest(request); TraceError err = assertTraceError(op, response); assertNotNull("No error detected", err); } @Test public void testDefaultHeadersObfuscation() throws Exception { runObfuscationTest("testDefaultHeadersObfuscation", HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST, true); } @Test public void testNonDefaultHeadersObfuscation() throws Exception { runObfuscationTest("testNonDefaultHeadersObfuscation", Arrays.asList("Hdr1", "Hdr2"), false); } @Override public ClientHttpRequestCollectionOperationAspect getAspect() { return ClientHttpRequestCollectionOperationAspect.aspectOf(); } private void runObfuscationTest(String testName, Collection<String> hdrs, boolean defaultHeaders) throws Exception { ClientHttpRequestCollectionOperationAspect aspectInstance = getAspect(); ClientHttpRequestOperationCollector collector = (ClientHttpRequestOperationCollector) aspectInstance.getCollector(); HttpObfuscator obfuscator = collector.getHttpObfuscator(); if (!defaultHeaders) { obfuscator.incrementalUpdate(HttpObfuscator.OBFUSCATED_HEADERS_SETTING, StringUtil.implode(hdrs, ",")); } HttpHeaders reqHdrs = createIdentityHttpHeaders(HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST); assertNotNull("Failed to remove response header value", reqHdrs.remove("WWW-Authenticate")); if (!defaultHeaders) { addIdentityHttpHeaders(reqHdrs, hdrs); } HttpHeaders rspHdrs = createIdentityHttpHeaders(Collections.singletonList("WWW-Authenticate")); ClientHttpRequest request = new TestClientHttpRequest(HttpMethod.GET, new URI("http://somewhere:7365/" + testName), reqHdrs, createMockClientHttpResponse(HttpStatus.OK, rspHdrs)); ClientHttpResponse response = request.execute(); assertNotNull("No response", response); ObscuredValueSetMarker obsMarker = (ObscuredValueSetMarker) obfuscator.getSensitiveValueMarker(); for (String name : hdrs) { assertTrue("Value not obscured for " + name, obsMarker.remove(name)); } // if obscured headers are not the defaults, make sure defaults are not obscured if (!defaultHeaders) { for (String name : HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST) { assertFalse("Value un-necessarily obscured for " + name, obsMarker.contains(name)); } } } private TraceError assertTraceError(Operation op, ClientHttpResponse rsp) throws IOException { int statusCode = rsp.getRawStatusCode(); String reasonPhrase = rsp.getStatusText(); List<TraceError> errors = errAnalyzer.locateErrors(creatMockOperationTraceWrapper(op)); if (ClientHttpRequestTraceErrorAnalyzer.httpStatusIsError(statusCode)) { assertEquals("Mismatched number of errors", 1, ListUtil.size(errors)); TraceError err = errors.get(0); assertEquals("Mismatched error message", ClientHttpRequestTraceErrorAnalyzer.createErrorMessage(statusCode, reasonPhrase), err.getMessage()); return err; } else { assertEquals("Unexpected errors: " + errors, 0, ListUtil.size(errors)); return null; } } private OperationMap assertRequestDetails(Operation op, ClientHttpRequest request) { return assertRequestDetails(op.get("request", OperationMap.class), request); } private OperationMap assertRequestDetails(OperationMap op, ClientHttpRequest request) { assertNotNull("No request details", op); URI uri = request.getURI(); HttpMethod method = request.getMethod(); assertEquals("Mismatched method", method.name(), op.get("method", String.class)); assertEquals("Mismatched URI", uri.toString(), op.get(OperationFields.URI, String.class)); assertMethodHeaders("request", op, request.getHeaders()); return op; } private OperationMap assertResponseDetails(Operation op, ClientHttpResponse response) throws IOException { return assertResponseDetails(op.get("response", OperationMap.class), response); } private OperationMap assertResponseDetails(OperationMap op, ClientHttpResponse response) throws IOException { assertNotNull("No response details", op); assertEquals("Mismatched response code", response.getRawStatusCode(), op.get("statusCode", Number.class).intValue()); assertEquals("Mismatched response text", response.getStatusText(), op.get("reasonPhrase", String.class)); assertMethodHeaders("response", op, response.getHeaders()); return op; } private OperationList assertMethodHeaders(String type, OperationMap op, HttpHeaders hdrs) { return assertMethodHeaders(type, op.get("headers", OperationList.class), hdrs); } private OperationList assertMethodHeaders(String type, OperationList op, HttpHeaders hdrs) { assertEquals(type + ": mismatched num. of headers", op.size(), hdrs.size()); for (int index = 0; index < op.size(); index++) { OperationMap nvp = op.get(index, OperationMap.class); assertNotNull(type + ": missing name-value pair for index=" + index, nvp); String key = nvp.get(OperationUtils.NAME_KEY, String.class); assertFalse(type + ": missing header name for index=" + index, StringUtil.isEmpty(key)); String actual = nvp.get(OperationUtils.VALUE_KEY, String.class), expected = hdrs.getFirst(key); assertEquals(type + ": mismatched value for header=" + key, expected, actual); } return op; } private Operation assertExecuteRequest(ClientHttpRequest request) { Operation op = getLastEntered(); assertNotNull("No operation collected", op); assertEquals("Mismatched type", ClientHttpRequestExternalResourceAnalyzer.TYPE, op.getType()); URI uri = request.getURI(); HttpMethod method = request.getMethod(); assertEquals("Mismatched label", method + " " + uri.toString(), op.getLabel()); return op; } @SuppressWarnings("boxing") private static ClientHttpResponse createMockClientHttpResponse(HttpStatus status, HttpHeaders hdrs) throws IOException { ClientHttpResponse response = Mockito.mock(ClientHttpResponse.class); Mockito.when(response.getHeaders()).thenReturn(hdrs); Mockito.when(response.getStatusCode()).thenReturn(status); Mockito.when(response.getRawStatusCode()).thenReturn(status.value()); Mockito.when(response.getStatusText()).thenReturn(status.getReasonPhrase()); return response; } private static HttpHeaders createIdentityHttpHeaders(Collection<String> hdrNames) { return addIdentityHttpHeaders(new HttpHeaders(), hdrNames); } private static HttpHeaders addIdentityHttpHeaders(HttpHeaders hdrs, Collection<String> hdrNames) { if (ListUtil.size(hdrNames) <= 0) { return hdrs; } for (String name : hdrNames) { hdrs.add(name, name); } return hdrs; } static class TestClientHttpRequest implements ClientHttpRequest { private final ClientHttpResponse response; private final HttpMethod method; private final URI uri; private final HttpHeaders hdrs; TestClientHttpRequest(HttpMethod mthd, URI uriValue, HttpHeaders hdrsMap, ClientHttpResponse rsp) { if (((method = mthd) == null) || ((uri = uriValue) == null) || ((hdrs = hdrsMap) == null) || ((response = rsp) == null)) { throw new IllegalStateException("Incomplete state"); } } public HttpMethod getMethod() { return method; } public URI getURI() { return uri; } public OutputStream getBody() throws IOException { throw new StreamCorruptedException("No body available"); } public HttpHeaders getHeaders() { return hdrs; } public ClientHttpResponse execute() throws IOException { return response; } } }