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