2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.vid.utils;
23 import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals;
24 import static org.apache.commons.lang3.StringUtils.repeat;
25 import static org.hamcrest.CoreMatchers.is;
26 import static org.hamcrest.CoreMatchers.not;
27 import static org.hamcrest.CoreMatchers.sameInstance;
28 import static org.hamcrest.MatcherAssert.assertThat;
29 import static org.hamcrest.Matchers.equalTo;
30 import static org.hamcrest.Matchers.hasEntry;
31 import static org.hamcrest.Matchers.hasToString;
32 import static org.hamcrest.Matchers.matchesPattern;
33 import static org.hamcrest.Matchers.samePropertyValuesAs;
34 import static org.mockito.ArgumentMatchers.contains;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.when;
38 import static org.onap.vid.testUtils.RegExMatcher.matchesRegEx;
39 import static org.testng.AssertJUnit.assertEquals;
41 import com.att.eelf.configuration.EELFLogger;
42 import com.fasterxml.jackson.core.JsonLocation;
43 import com.fasterxml.jackson.core.JsonParseException;
44 import com.fasterxml.jackson.databind.JsonMappingException;
45 import com.google.common.collect.ImmutableMap;
46 import io.joshworks.restclient.http.HttpResponse;
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.nio.charset.StandardCharsets;
50 import java.security.UnrecoverableKeyException;
51 import java.security.cert.CertificateException;
52 import java.util.concurrent.Callable;
53 import java.util.function.Function;
54 import javax.crypto.BadPaddingException;
55 import javax.net.ssl.SSLHandshakeException;
56 import javax.ws.rs.ProcessingException;
57 import org.apache.commons.io.IOUtils;
58 import org.mockito.ArgumentCaptor;
59 import org.onap.vid.exceptions.GenericUncheckedException;
60 import org.onap.vid.testUtils.TestUtils;
61 import org.onap.vid.utils.Logging.Substring;
63 import org.springframework.http.HttpMethod;
64 import org.testng.annotations.BeforeMethod;
65 import org.testng.annotations.DataProvider;
66 import org.testng.annotations.Test;
67 import sun.security.provider.certpath.SunCertPathBuilderException;
68 import sun.security.validator.ValidatorException;
70 public class LoggingUtilsTest {
72 private static final String TEST_OBJECT_JSON = "{\"key\":\"myNumber\",\"value\":42}";
74 public static class TestModel {
78 public TestModel(String key, int value) {
86 private EELFLogger loggerMock;
88 private Logging logginService = new Logging();
89 private String url = "someUrl";
90 private final TestModel testObject = new TestModel("myNumber", 42);
95 loggerMock = mock(EELFLogger.class);
99 public void whenLogRequest_thenLoggedInDebug() {
101 logginService.logRequest(loggerMock, HttpMethod.GET, url);
104 ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
105 verify(loggerMock).debug(contains("Sending"), argumentCaptor.capture());
106 assertEquals("GET", argumentCaptor.getAllValues().get(0));
107 assertEquals(url, argumentCaptor.getAllValues().get(1));
113 public void whenLogResponseOfHttpResponse_thenLoggedInDebug() throws Exception {
114 HttpResponse<TestModel> response = TestUtils.createTestHttpResponse(200, testObject, TestModel.class);
115 logginService.logResponse(loggerMock, HttpMethod.POST, url, response);
117 ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
118 ArgumentCaptor<String> messageCaptur = ArgumentCaptor.forClass(String.class);
119 verify(loggerMock).debug(messageCaptur.capture(), argumentCaptor.capture());
121 assertThat(messageCaptur.getValue(), matchesPattern("Received.*Status.*Body.*"));
122 assertEquals("POST", argumentCaptor.getAllValues().get(0));
123 assertEquals(url, argumentCaptor.getAllValues().get(1));
124 assertEquals(200, argumentCaptor.getAllValues().get(2));
125 assertThat(argumentCaptor.getAllValues().get(3), samePropertyValuesAs(new Substring(TEST_OBJECT_JSON)));
129 public void whenLogResponseOfHttpResponse_thenCanReadEntityAfterwards() throws Exception {
130 HttpResponse<TestModel> response = TestUtils.createTestHttpResponse(200, testObject, TestModel.class);
131 logginService.logResponse(loggerMock, HttpMethod.POST, url, response);
132 assertThat(response.getBody(), jsonEquals(TEST_OBJECT_JSON));
136 public void whenLogResponseOfHttpResponse_thenCanReadRawEntityAfterwards() throws Exception {
137 HttpResponse<TestModel> response = TestUtils.createTestHttpResponse(200, testObject, TestModel.class);
138 logginService.logResponse(loggerMock, HttpMethod.POST, url, response);
139 assertThat(IOUtils.toString(response.getRawBody(), StandardCharsets.UTF_8), jsonEquals(TEST_OBJECT_JSON));
143 public static Object[][] exceptions() {
144 Exception e0 = new CertificateException("No X509TrustManager implementation available");
145 Exception noTrustMngrImplException = new SSLHandshakeException(e0.toString());
146 noTrustMngrImplException.initCause(e0);
148 Exception e1 = new BadPaddingException("Given final block not properly padded");
149 Exception incorrectPasswordException = new IOException("keystore password was incorrect",
150 new UnrecoverableKeyException("failed to decrypt safe contents entry: " + e1));
151 String incorrectPasswordExceptionDescription = "" +
152 "java.io.IOException: keystore password was incorrect: " +
153 "java.security.UnrecoverableKeyException: failed to decrypt safe contents entry: " +
154 "javax.crypto.BadPaddingException: Given final block not properly padded";
156 Exception e2 = new SunCertPathBuilderException("unable to find valid certification path to requested target");
157 Exception noValidCert = new ProcessingException(new ValidatorException("PKIX path building failed: " + e2.toString(), e2));
158 String noValidCertDescription = "" +
159 "javax.ws.rs.ProcessingException: " +
160 "sun.security.validator.ValidatorException: PKIX path building failed: " +
161 "sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target";
163 RuntimeException codehausParseException = new RuntimeException(new JsonParseException("Unexpected character ('<' (code 60)):" +
164 " expected a valid value (number, String, array, object, 'true', 'false' or 'null')",
165 new JsonLocation("<html>i'm an error</html>", 25, 1, 1)));
166 String codehausParseDescription = "" +
167 "com.fasterxml.jackson.core.JsonParseException: Unexpected character ('<' (code 60)):" +
168 " expected a valid value (number, String, array, object, 'true', 'false' or 'null')\n" +
169 " at [Source: (String)\"<html>i'm an error</html>\"; line: 1, column: 1]";
171 RuntimeException fasterxmlMappingException = new RuntimeException(new JsonMappingException("Can not deserialize instance of java.lang.String out of START_ARRAY token",
172 new com.fasterxml.jackson.core.JsonLocation("{ example json }", 15, 1, 20)));
173 String fasterxmlMappingDescription = "" +
174 "com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_ARRAY token\n" +
175 " at [Source: (String)\"{ example json }\"; line: 1, column: 20]";
177 return new Object[][]{
178 {"javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No X509TrustManager implementation available",
179 noTrustMngrImplException},
180 {"java.lang.StringIndexOutOfBoundsException: String index out of range: 4",
181 new StringIndexOutOfBoundsException(4)},
182 {"java.io.FileNotFoundException: vid/WEB-INF/cert/aai-client-cert.p12",
183 new FileNotFoundException("vid/WEB-INF/cert/aai-client-cert.p12")},
184 {"NullPointerException at LoggingUtilsTest.java:[0-9]+",
185 new NullPointerException("null")},
186 {incorrectPasswordExceptionDescription,
187 incorrectPasswordException},
188 {incorrectPasswordExceptionDescription,
189 new GenericUncheckedException(incorrectPasswordException)},
190 {"javax.ws.rs.ProcessingException: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_expired",
191 new ProcessingException(new SSLHandshakeException("Received fatal alert: certificate_expired"))},
192 {noValidCertDescription,
194 {escapeBrackets(codehausParseDescription),
195 codehausParseException},
196 {escapeBrackets(fasterxmlMappingDescription),
197 fasterxmlMappingException},
198 {"org.onap.vid.exceptions.GenericUncheckedException: top message: org.onap.vid.exceptions.GenericUncheckedException: root message",
199 new GenericUncheckedException("top message", new IOException("sandwich message", new GenericUncheckedException("root message")))},
200 {"org.onap.vid.exceptions.GenericUncheckedException: basa",
201 new GenericUncheckedException("basa")}
206 @Test(dataProvider = "exceptions")
207 public void testExceptionToDescription(String expectedDescription, Exception exceptionToDescribe) {
208 String expectedButDotsEscaped = expectedDescription.replace(".", "\\.");
210 assertThat(Logging.exceptionToDescription(exceptionToDescribe), matchesRegEx(expectedButDotsEscaped));
214 public void substringClass_givenNull_thenToStringIsNull() {
215 assertThat(new Substring(null), hasToString(equalTo("null")));
219 public void substringClass_givenAnObject_thenToStringIsEqualAndPassThrough() {
220 Object anyObject = mock(Object.class);
221 when(anyObject.toString()).thenReturn(TEST_OBJECT_JSON);
223 assertThat(new Substring(anyObject),
224 hasToString(sameInstance(TEST_OBJECT_JSON)));
228 public void substringClass_givenNotLongString_thenToStringIsNotTruncated() {
229 assertThat(new Substring(repeat(TEST_OBJECT_JSON, 100)),
230 hasToString(equalTo(repeat(TEST_OBJECT_JSON, 100))));
234 public void substringClass_givenLongString_thenToStringIsTruncatedToSize() {
235 int expectedLength = 1_000_000; // this is Substring's internal config
236 String headMarker = "head-";
238 assertThat(new Substring(headMarker + repeat("x", 2_000_000)),
239 hasToString(equalTo(headMarker + repeat("x", expectedLength - headMarker.length()))));
243 public void testWithMDCInternal_whenGivenProvider_functionShouldBeExtractedWithMdc() {
244 Object myAnything = new Object();
246 Object result = logginService.withMDCInternal(ImmutableMap.of("my key", "my value"),
248 assertThat("MDC values should be installed when extracting the supplier",
249 MDC.getCopyOfContextMap(), hasEntry("my key", "my value"));
254 assertThat("withMDCInternal should extract my function", result, is(sameInstance(myAnything)));
255 assertThat("MDC values should be removed", MDC.getCopyOfContextMap(), not(hasEntry("k", "v")));
259 public void testWithMDC_whenGivenFunction_functionShouldBeEncapsulated() {
261 String[] stringsArray = {"before"};
263 Function<String, Integer> myFunction = s -> {
264 assertThat("MDC values should be installed when inside myFunction",
265 MDC.getCopyOfContextMap(), hasEntry("my key", "my value"));
271 Function<String, Integer> functionWithMDC =
272 logginService.withMDC(ImmutableMap.of("my key", "my value"), myFunction);
275 assertThat("invocation of function must not happen yet", stringsArray[0], is("before"));
277 Integer result = functionWithMDC.apply("after");
279 assertThat("invocation of my function should have been deferred", stringsArray[0], is("after"));
280 assertThat("apply should return function's value", result, is(42));
284 public void testWithMDC_whenGivenCallable_callableShouldBeEncapsulated() throws Exception {
286 String[] stringsArray = {"before"};
288 Callable<Integer> myCallable = () -> {
289 assertThat("MDC values should be installed when inside myCallable",
290 MDC.getCopyOfContextMap(), hasEntry("my key", "my value"));
291 stringsArray[0] = "after";
296 Callable<Integer> callableWithMDC = logginService.withMDC(ImmutableMap.of("my key", "my value"), myCallable);
299 assertThat("invocation of callable must not happen yet", stringsArray[0], is("before"));
301 Integer result = callableWithMDC.call();
303 assertThat("invocation of my callable should have been deferred", stringsArray[0], is("after"));
304 assertThat("apply should return function's value", result, is(42));
307 private static String escapeBrackets(String in) {
308 return in.replaceAll("[\\(\\[\\{\\)]", "\\\\$0");