/******************************************************************************* * Copyright 2013 SAP AG * * 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.sap.core.odata.core.debug; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.junit.Test; import com.sap.core.odata.api.commons.HttpContentType; import com.sap.core.odata.api.commons.HttpHeaders; import com.sap.core.odata.api.commons.HttpStatusCodes; import com.sap.core.odata.api.commons.ODataHttpMethod; import com.sap.core.odata.api.edm.EdmNavigationProperty; import com.sap.core.odata.api.edm.EdmProperty; import com.sap.core.odata.api.exception.ODataException; import com.sap.core.odata.api.exception.ODataMessageException; import com.sap.core.odata.api.processor.ODataContext; import com.sap.core.odata.api.processor.ODataContext.RuntimeMeasurement; import com.sap.core.odata.api.processor.ODataResponse; import com.sap.core.odata.api.uri.NavigationPropertySegment; import com.sap.core.odata.api.uri.PathInfo; import com.sap.core.odata.api.uri.SelectItem; import com.sap.core.odata.api.uri.UriInfo; import com.sap.core.odata.api.uri.UriParser; import com.sap.core.odata.api.uri.expression.CommonExpression; import com.sap.core.odata.api.uri.expression.ExpressionParserException; import com.sap.core.odata.api.uri.expression.FilterExpression; import com.sap.core.odata.api.uri.expression.OrderByExpression; import com.sap.core.odata.testutil.fit.BaseTest; import com.sap.core.odata.testutil.helper.StringHelper; /** * Tests for the debug information output. * @author SAP AG */ public class ODataDebugResponseWrapperTest extends BaseTest { private static final String EXPECTED = "{" + "\"request\":{\"method\":\"GET\",\"uri\":\"http://test/entity\"}," + "\"response\":{\"status\":{\"code\":200,\"info\":\"OK\"}}}"; private ODataContext mockContext(final ODataHttpMethod method) throws ODataException { ODataContext context = mock(ODataContext.class); when(context.getHttpMethod()).thenReturn(method.name()); PathInfo pathInfo = mock(PathInfo.class); when(pathInfo.getRequestUri()).thenReturn(URI.create("http://test/entity")); when(context.getPathInfo()).thenReturn(pathInfo); when(context.getRuntimeMeasurements()).thenReturn(null); return context; } private ODataResponse mockResponse(final HttpStatusCodes status, final String body, final String contentType) { ODataResponse response = mock(ODataResponse.class); when(response.getStatus()).thenReturn(status); when(response.getEntity()).thenReturn(body); if (contentType != null) { when(response.getHeaderNames()).thenReturn(new HashSet<String>(Arrays.asList(HttpHeaders.CONTENT_TYPE))); when(response.getHeader(HttpHeaders.CONTENT_TYPE)).thenReturn(contentType); when(response.getContentHeader()).thenReturn(contentType); } return response; } private RuntimeMeasurement mockRuntimeMeasurement(final String method, final long startTime, final long stopTime, final long startMemory, final long stopMemory) { RuntimeMeasurement measurement = mock(RuntimeMeasurement.class); when(measurement.getClassName()).thenReturn("class"); when(measurement.getMethodName()).thenReturn(method); when(measurement.getTimeStarted()).thenReturn(startTime); when(measurement.getTimeStopped()).thenReturn(stopTime); when(measurement.getMemoryStarted()).thenReturn(startMemory); when(measurement.getMemoryStopped()).thenReturn(stopMemory); return measurement; } private RuntimeMeasurement mockRuntimeMeasurement(final String method, final long start, final long stop) { return mockRuntimeMeasurement(method, start, stop, 1000, 4000); } @Test public void minimal() throws Exception { final ODataContext context = mockContext(ODataHttpMethod.PUT); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.NO_CONTENT, null, null); final ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String actualJson = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace(ODataHttpMethod.GET.name(), ODataHttpMethod.PUT.name()) .replace(Integer.toString(HttpStatusCodes.OK.getStatusCode()), Integer.toString(HttpStatusCodes.NO_CONTENT.getStatusCode())) .replace(HttpStatusCodes.OK.getInfo(), HttpStatusCodes.NO_CONTENT.getInfo()), actualJson); } @Test public void body() throws Exception { final ODataContext context = mockContext(ODataHttpMethod.GET); ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, "\"test\"", HttpContentType.APPLICATION_JSON); ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("{\"request", "{\"body\":\"test\",\"request") .replace("}}}", "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}}}"), entity); wrappedResponse = mockResponse(HttpStatusCodes.OK, "test", HttpContentType.TEXT_PLAIN); response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("{\"request", "{\"body\":\"test\",\"request") .replace("}}}", "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.TEXT_PLAIN + "\"}}}"), entity); wrappedResponse = mockResponse(HttpStatusCodes.OK, null, "image/png"); when(wrappedResponse.getEntity()).thenReturn(new ByteArrayInputStream("test".getBytes())); response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("{\"request", "{\"body\":\"dGVzdA==\",\"request") .replace("}}}", "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"image/png\"}}}"), entity); } @Test public void headers() throws Exception { ODataContext context = mockContext(ODataHttpMethod.GET); Map<String, List<String>> headers = new HashMap<String, List<String>>(); headers.put(HttpHeaders.CONTENT_TYPE, Arrays.asList(HttpContentType.APPLICATION_JSON)); when(context.getRequestHeaders()).thenReturn(headers); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, HttpContentType.APPLICATION_JSON); final ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("},\"response", ",\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}},\"response") .replace("}}}", "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}}}"), entity); } @Test public void uri() throws Exception { final ODataContext context = mockContext(ODataHttpMethod.GET); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null); UriInfo uriInfo = mock(UriInfo.class); final FilterExpression filter = UriParser.parseFilter(null, null, "true"); when(uriInfo.getFilter()).thenReturn(filter); final OrderByExpression orderBy = UriParser.parseOrderBy(null, null, "true"); when(uriInfo.getOrderBy()).thenReturn(orderBy); List<ArrayList<NavigationPropertySegment>> expand = new ArrayList<ArrayList<NavigationPropertySegment>>(); NavigationPropertySegment segment = mock(NavigationPropertySegment.class); EdmNavigationProperty navigationProperty = mock(EdmNavigationProperty.class); when(navigationProperty.getName()).thenReturn("nav"); when(segment.getNavigationProperty()).thenReturn(navigationProperty); ArrayList<NavigationPropertySegment> segments = new ArrayList<NavigationPropertySegment>(); segments.add(segment); expand.add(segments); when(uriInfo.getExpand()).thenReturn(expand); SelectItem select1 = mock(SelectItem.class); SelectItem select2 = mock(SelectItem.class); EdmProperty property = mock(EdmProperty.class); when(property.getName()).thenReturn("property"); when(select1.getProperty()).thenReturn(property); when(select2.getProperty()).thenReturn(property); when(select2.getNavigationPropertySegments()).thenReturn(segments); when(uriInfo.getSelect()).thenReturn(Arrays.asList(select1, select2)); final ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("}}}", "}},\"uri\":{\"filter\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"}," + "\"orderby\":{\"nodeType\":\"order collection\"," + "\"orders\":[{\"nodeType\":\"ORDER\",\"sortorder\":\"asc\"," + "\"expression\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"}}]}," + "\"expand/select\":{\"all\":false,\"properties\":[\"property\"]," + "\"links\":[{\"nav\":{\"all\":false,\"properties\":[\"property\"],\"links\":[]}}]}}}"), entity); } @Test public void uriWithException() throws Exception { final ODataContext context = mockContext(ODataHttpMethod.GET); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null); UriInfo uriInfo = mock(UriInfo.class); FilterExpression filter = mock(FilterExpression.class); when(filter.getExpressionString()).thenReturn("wrong"); when(uriInfo.getFilter()).thenReturn(filter); ExpressionParserException exception = mock(ExpressionParserException.class); when(exception.getMessageReference()).thenReturn(ExpressionParserException.COMMON_ERROR); when(exception.getStackTrace()).thenReturn(new StackTraceElement[] { new StackTraceElement("class", "method", "file", 42) }); CommonExpression filterTree = mock(CommonExpression.class); when(filterTree.getUriLiteral()).thenReturn("wrong"); when(exception.getFilterTree()).thenReturn(filterTree); final ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, exception, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("}}}", "}},\"uri\":{\"error\":{\"filter\":\"wrong\"},\"filter\":null}," + "\"stacktrace\":{\"exceptions\":[{\"class\":\"" + exception.getClass().getName() + "\"," + "\"message\":\"Error while parsing a ODATA expression.\"," + "\"invocation\":{\"class\":\"class\",\"method\":\"method\",\"line\":42}}]," + "\"stacktrace\":[{\"class\":\"class\",\"method\":\"method\",\"line\":42}]}}"), entity); } @Test public void runtime() throws Exception { ODataContext context = mockContext(ODataHttpMethod.GET); List<RuntimeMeasurement> runtimeMeasurements = new ArrayList<RuntimeMeasurement>(); runtimeMeasurements.add(mockRuntimeMeasurement("method", 1000, 42000)); runtimeMeasurements.add(mockRuntimeMeasurement("inner", 2000, 5000)); runtimeMeasurements.add(mockRuntimeMeasurement("inner", 7000, 12000)); runtimeMeasurements.add(mockRuntimeMeasurement("inner", 13000, 16000)); runtimeMeasurements.add(mockRuntimeMeasurement("inner2", 14000, 15000)); runtimeMeasurements.add(mockRuntimeMeasurement("child", 17000, 21000)); runtimeMeasurements.add(mockRuntimeMeasurement("second", 45000, 99000)); when(context.getRuntimeMeasurements()).thenReturn(runtimeMeasurements); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null); ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED.replace("}}}", "}},\"runtime\":[{\"class\":\"class\",\"method\":\"method\",\"duration\":41,\"memory\":3," + "\"children\":[{\"class\":\"class\",\"method\":\"inner\",\"duration\":8,\"memory\":6,\"children\":[]}," + "{\"class\":\"class\",\"method\":\"inner\",\"duration\":3,\"memory\":3,\"children\":[" + "{\"class\":\"class\",\"method\":\"inner2\",\"duration\":1,\"memory\":3,\"children\":[]}]}," + "{\"class\":\"class\",\"method\":\"child\",\"duration\":4,\"memory\":3,\"children\":[]}]}," + "{\"class\":\"class\",\"method\":\"second\",\"duration\":54,\"memory\":3,\"children\":[]}]}"), entity); } @Test public void exception() throws Exception { final ODataContext context = mockContext(ODataHttpMethod.GET); final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.BAD_REQUEST, null, null); ODataMessageException exception = mock(ODataMessageException.class); when(exception.getMessageReference()).thenReturn(ODataMessageException.COMMON); RuntimeException innerException = mock(RuntimeException.class); when(innerException.getLocalizedMessage()).thenReturn("error"); final StackTraceElement[] stackTrace = new StackTraceElement[] { new StackTraceElement("innerClass", "innerMethod", "innerFile", 99), new StackTraceElement("class", "method", "file", 42), new StackTraceElement("outerClass", "outerMethod", "outerFile", 11) }; when(innerException.getStackTrace()).thenReturn(stackTrace); when(exception.getCause()).thenReturn(innerException); when(exception.getStackTrace()).thenReturn(Arrays.copyOfRange(stackTrace, 1, 3)); final ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception, ODataDebugResponseWrapper.ODATA_DEBUG_JSON) .wrapResponse(); String entity = StringHelper.inputStreamToString((InputStream) response.getEntity()); assertEquals(EXPECTED .replace(Integer.toString(HttpStatusCodes.OK.getStatusCode()), Integer.toString(HttpStatusCodes.BAD_REQUEST.getStatusCode())) .replace(HttpStatusCodes.OK.getInfo(), HttpStatusCodes.BAD_REQUEST.getInfo()) .replace("}}}", "}}," + "\"stacktrace\":{\"exceptions\":[{\"class\":\"" + exception.getClass().getName() + "\"," + "\"message\":\"Common exception\"," + "\"invocation\":{\"class\":\"class\",\"method\":\"method\",\"line\":42}}," + "{\"class\":\"" + innerException.getClass().getName() + "\",\"message\":\"error\"," + "\"invocation\":{\"class\":\"innerClass\",\"method\":\"innerMethod\",\"line\":99}}]," + "\"stacktrace\":[{\"class\":\"class\",\"method\":\"method\",\"line\":42}," + "{\"class\":\"outerClass\",\"method\":\"outerMethod\",\"line\":11}]}}"), entity); } }