f415bfbcb5647d1ad25ee9a452b94c46e8ba73f5
[vid.git] / vid-app-common / src / test / java / org / onap / vid / controller / filter / PromiseRequestIdFilterTest.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.onap.vid.controller.filter;
22
23 import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
24 import static org.hamcrest.CoreMatchers.equalTo;
25 import static org.hamcrest.CoreMatchers.is;
26 import static org.hamcrest.MatcherAssert.assertThat;
27 import static org.hamcrest.Matchers.anyOf;
28 import static org.hamcrest.Matchers.emptyOrNullString;
29 import static org.hamcrest.Matchers.equalToIgnoringCase;
30 import static org.hamcrest.Matchers.hasItems;
31 import static org.hamcrest.Matchers.not;
32 import static org.mockito.ArgumentMatchers.any;
33 import static org.mockito.ArgumentMatchers.argThat;
34 import static org.onap.portalsdk.core.util.SystemProperties.ECOMP_REQUEST_ID;
35
36 import com.google.common.collect.ImmutableMap;
37 import java.io.IOException;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.Enumeration;
42 import java.util.Map;
43 import java.util.UUID;
44 import java.util.function.Function;
45 import javax.servlet.FilterChain;
46 import javax.servlet.ServletException;
47 import javax.servlet.ServletRequest;
48 import javax.servlet.ServletResponse;
49 import javax.servlet.http.HttpServletRequest;
50 import javax.servlet.http.HttpServletResponse;
51 import org.mockito.ArgumentCaptor;
52 import org.mockito.Mockito;
53 import org.mockito.stubbing.Answer;
54 import org.onap.logging.ref.slf4j.ONAPLogConstants.MDCs;
55 import org.onap.portalsdk.core.web.support.UserUtils;
56 import org.onap.vid.logging.RequestIdHeader;
57 import org.slf4j.MDC;
58 import org.springframework.mock.web.MockHttpServletResponse;
59 import org.testng.annotations.AfterMethod;
60 import org.testng.annotations.DataProvider;
61 import org.testng.annotations.Test;
62
63 @Test
64 public class PromiseRequestIdFilterTest {
65
66     private final String anotherHeader = "ANDREI_RUBLEV";
67     private final String anotherValue = "foo value";
68     private final String mixedCaseHeader = "x-ecomp-REQUESTID";
69
70     private static final String onapRequestIdHeader = "x-onap-requestid";
71     private static final String transactionIdHeader = "x-transactionid";
72     private static final String requestIdHeader = "x-requestid";
73
74     private final PromiseRequestIdFilter promiseRequestIdFilter = new PromiseRequestIdFilter();
75
76     @AfterMethod
77     public void tearDown() {
78         MDC.remove(MDCs.REQUEST_ID);
79     }
80
81     @Test
82     public void givenMdcValueAndRequestIdHeader_headerValueNotChanged() throws IOException, ServletException {
83
84         final String someTxId = "863850e2-8545-4efd-94b8-afba5f52b3d5";
85         final String mdcTxId = "ed752ff1-3970-4f18-8219-2d821fa4eaea";
86
87         MDC.put(MDCs.REQUEST_ID, mdcTxId);
88
89         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
90                 anotherHeader, anotherValue,
91                 ECOMP_REQUEST_ID, someTxId
92         );
93
94         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, specificTxId(someTxId));
95     }
96
97
98     @Test
99     public void givenRequestIdHeaderThatIsNotAUUID_headerValueChanged() throws IOException, ServletException {
100
101         final String someTxId = "863850e28544efd94b8afba5f52b3d5";
102
103         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
104                 anotherHeader, anotherValue,
105                 ECOMP_REQUEST_ID, someTxId
106         );
107
108         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, UserUtils::getRequestId);
109     }
110
111
112     @Test
113     public void givenMixedCaseRequestIdHeader_headerValueNotChanged() throws IOException, ServletException {
114
115         final String someTxId = "729bbd8d-b0c2-4809-a794-dcccd9cda2c0";
116
117         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
118                 mixedCaseHeader, someTxId,
119                 anotherHeader, anotherValue
120         );
121
122         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, specificTxId(someTxId));
123     }
124
125     @Test
126     public void givenNoRequestIdHeader_headerValueWasGenerated() throws IOException, ServletException {
127
128         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
129                 anotherHeader, anotherValue
130         );
131
132         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, UserUtils::getRequestId);
133     }
134
135     @Test
136     public void givenMdcValueAndNoRequestIdHeader_headerValueWasFromMDC() throws IOException, ServletException {
137
138         final String mdcTxId = "ed752ff1-3970-4f18-8219-2d821fa4eaea";
139
140         MDC.put(MDCs.REQUEST_ID, mdcTxId);
141
142         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
143                 anotherHeader, anotherValue
144         );
145
146         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, specificTxId(mdcTxId));
147     }
148
149     @Test
150     public void givenTwoRequestIdHeader_onapHeaderValueIsUsed() throws IOException, ServletException {
151
152         final String onapTxId = "863850e2-8545-4efd-94b8-AFBA5F52B3D5"; // note mixed case
153         final String ecompTxId = "6e8ff89e-88a4-4977-b63f-3142892b6e08";
154
155         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
156             anotherHeader, anotherValue,
157             ECOMP_REQUEST_ID, ecompTxId,
158             onapRequestIdHeader, onapTxId
159         );
160
161         buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, specificTxId(onapTxId));
162     }
163
164     @Test
165     public void givenTwoRequestIdHeaderAndHigherPriorityIsMalformed_headerValueIsGenerated() throws IOException, ServletException {
166
167         final String malformedTxId = "6e8ff89e-88a4-4977-b63f-3142892b6e08-";
168         final String anotherTxId = "863850e2-8545-4efd-94b8-afba5f52b3d5";
169
170         final ImmutableMap<String, String> incomingRequestHeaders = ImmutableMap.of(
171             anotherHeader, anotherValue,
172             requestIdHeader, malformedTxId, // requestIdHeader as higher priority than transactionIdHeader
173             transactionIdHeader, anotherTxId
174         );
175
176         HttpServletRequest wrappedRequest =
177             buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(incomingRequestHeaders, UserUtils::getRequestId);
178
179         assertThat(UserUtils.getRequestId(wrappedRequest),
180             not(anyOf(equalTo(malformedTxId), equalTo(anotherTxId)))
181         );
182     }
183
184
185     @Test
186     public void toUuidOrElse_givenValid_yieldSame() {
187         final String someTxId = "729bbd8d-b0c2-4809-a794-DCCCD9CDA2C0"; // note mixed case
188         UUID unexpected = UUID.randomUUID();
189         assertThat(promiseRequestIdFilter.toUuidOrElse(someTxId, () -> unexpected), is(UUID.fromString(someTxId)));
190     }
191
192     @Test
193     public void toUuidOrElse_givenNull_yieldSupplier() {
194         UUID expected = UUID.fromString("729bbd8d-b0c2-4809-a794-dcccd9cda2c0");
195         assertThat(promiseRequestIdFilter.toUuidOrElse(null, () -> expected), is(expected));
196     }
197
198     @Test
199     public void toUuidOrElse_givenMalformed_yieldSupplier() {
200         UUID expected = UUID.fromString("729bbd8d-b0c2-4809-a794-dcccd9cda2c0");
201         assertThat(promiseRequestIdFilter.toUuidOrElse("malformed uuid", () -> expected), is(expected));
202     }
203
204     @DataProvider
205     public static Object[][] severalRequestIdHeaders() {
206         String someTxId = "69fa2575-d7f2-482c-ad1b-53a63ca03617";
207         String anotherTxId = "06de373b-7e19-4357-9bd1-ed95682ae3a4";
208
209         return new Object[][]{
210             {
211                 "header is selected when single", RequestIdHeader.TRANSACTION_ID,
212                 ImmutableMap.of(
213                     transactionIdHeader, someTxId
214                 )
215             }, {
216                 "header is selected when first", RequestIdHeader.ONAP_ID,
217                 ImmutableMap.of(
218                     onapRequestIdHeader, someTxId,
219                     "noise-header", anotherTxId,
220                     ECOMP_REQUEST_ID, anotherTxId
221                 )
222             }, {
223                 "header is selected when last", RequestIdHeader.ONAP_ID,
224                 ImmutableMap.of(
225                     ECOMP_REQUEST_ID, anotherTxId,
226                     "noise-header", anotherTxId,
227                     onapRequestIdHeader, someTxId
228                 )
229             }, {
230                 "header is selected when value is invalid uuid", RequestIdHeader.ONAP_ID,
231                 ImmutableMap.of(
232                     onapRequestIdHeader, "invalid-uuid"
233                 )
234             }, {
235                 "header is selected when no ecomp-request-id", RequestIdHeader.ONAP_ID,
236                 ImmutableMap.of(
237                     requestIdHeader, anotherTxId,
238                     onapRequestIdHeader, someTxId
239                 )
240             }, {
241                 "ECOMP_REQUEST_ID is returned when no request-id header", RequestIdHeader.ECOMP_ID,
242                 ImmutableMap.of(
243                     "tsamina-mina", anotherTxId,
244                     "waka-waka", anotherTxId
245                 )
246             },
247         };
248     }
249
250     @Test(dataProvider = "severalRequestIdHeaders")
251     public void highestPriorityHeader_givenSeveralRequestIdHeaders_correctHeaderIsUsed(String description, RequestIdHeader expectedHeader, Map<String, String> incomingRequestHeaders) {
252
253         HttpServletRequest mockedHttpServletRequest = createMockedHttpServletRequest(incomingRequestHeaders);
254
255         assertThat(description,
256             promiseRequestIdFilter.highestPriorityHeader(mockedHttpServletRequest), is(expectedHeader));
257     }
258
259
260     private HttpServletRequest buildRequestThenRunThroughFilterAndAssertResultRequestHeaders(
261             ImmutableMap<String, String> originalRequestHeaders,
262             Function<HttpServletRequest, String> txIdExtractor
263     ) throws IOException, ServletException {
264         HttpServletRequest servletRequest = createMockedHttpServletRequest(originalRequestHeaders);
265         HttpServletResponse servletResponse = createMockedHttpServletResponse();
266
267         final FilterChain capturingFilterChain = Mockito.mock(FilterChain.class);
268
269         //////////////////
270         //
271         // doFilter() is the function under test
272         //
273         promiseRequestIdFilter.doFilter(servletRequest, servletResponse, capturingFilterChain);
274         //
275         //////////////////
276
277         final ServletRequest capturedServletRequest = extractCapturedServletRequest(capturingFilterChain);
278         final ServletResponse capturedServletResponse = extractCapturedServletResponse(capturingFilterChain);
279         final String expectedTxId = txIdExtractor.apply((HttpServletRequest) capturedServletRequest);
280
281         assertRequestObjectHeaders(capturedServletRequest, expectedTxId);
282         assertResponseObjectHeaders(capturedServletResponse, expectedTxId);
283
284         return (HttpServletRequest) capturedServletRequest;
285     }
286
287
288     private void assertRequestObjectHeaders(ServletRequest request, String expectedTxId) {
289         /*
290         Assert that:
291         - Two headers are in place
292         - Direct value extraction is as expected
293         - UserUtils.getRequestId() returns correct and valid value
294          */
295         final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
296
297         assertThat(Collections.list(httpServletRequest.getHeaderNames()),
298                 hasItems(equalToIgnoringCase(ECOMP_REQUEST_ID), equalToIgnoringCase(anotherHeader)));
299
300         assertThat(httpServletRequest.getHeader(anotherHeader), is(anotherValue));
301
302         assertThat(httpServletRequest.getHeader(ECOMP_REQUEST_ID), equalToIgnoringCase(expectedTxId));
303         assertThat(httpServletRequest.getHeader(mixedCaseHeader), equalToIgnoringCase(expectedTxId));
304
305         assertThat(UserUtils.getRequestId(httpServletRequest), equalToIgnoringCase(expectedTxId));
306         assertThat(UserUtils.getRequestId(httpServletRequest), is(not(emptyOrNullString())));
307     }
308
309     private void assertResponseObjectHeaders(ServletResponse response, String txId) {
310         final String REQUEST_ID_HEADER_NAME_IN_RESPONSE = mixedCaseHeader + "-echo";
311         final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
312
313         assertThat("header " + REQUEST_ID_HEADER_NAME_IN_RESPONSE.toLowerCase() + " in response must be provided",
314                 httpServletResponse.getHeader(REQUEST_ID_HEADER_NAME_IN_RESPONSE), equalToIgnoringCase(txId));
315     }
316
317
318
319     private HttpServletRequest createMockedHttpServletRequest(Map<String, String> requestHeaders) {
320         HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class);
321         requestHeaders.forEach((k, v) -> {
322             Mockito.when(servletRequest.getHeader(argThat(s -> equalsIgnoreCase(s, k)))).thenReturn(v);
323             Mockito.when(servletRequest.getHeaders(argThat(s -> equalsIgnoreCase(s, k)))).then(returnEnumerationAnswer(v));
324         });
325         Mockito.when(servletRequest.getHeaderNames()).then(returnEnumerationAnswer(requestHeaders.keySet()));
326         return servletRequest;
327     }
328
329     private HttpServletResponse createMockedHttpServletResponse() {
330         return new MockHttpServletResponse();
331     }
332
333     private static Answer<Enumeration<String>> returnEnumerationAnswer(String ... items) {
334         return returnEnumerationAnswer(Arrays.asList(items));
335     }
336
337     private static Answer<Enumeration<String>> returnEnumerationAnswer(Collection<String> items) {
338         return invocation -> Collections.enumeration(items);
339     }
340
341     private Function<HttpServletRequest, String> specificTxId(String someTxId) {
342         return r -> someTxId;
343     }
344
345     private ServletRequest extractCapturedServletRequest(FilterChain capturingFilterChain) throws IOException, ServletException {
346         ArgumentCaptor<ServletRequest> captor = ArgumentCaptor.forClass(ServletRequest.class);
347         Mockito.verify(capturingFilterChain).doFilter(captor.capture(), any());
348         return captor.getValue();
349     }
350
351     private ServletResponse extractCapturedServletResponse(FilterChain capturingFilterChain) throws IOException, ServletException {
352         ArgumentCaptor<ServletResponse> captor = ArgumentCaptor.forClass(ServletResponse.class);
353         Mockito.verify(capturingFilterChain).doFilter(any(), captor.capture());
354         return captor.getValue();
355     }
356
357 }