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