ad2fcf5dfc579ded1012e874b96762b33bdf80c4
[dmaap/datarouter.git] / datarouter-node / src / test / java / org / onap / dmaap / datarouter / node / NodeServletTest.java
1 /*******************************************************************************
2  * ============LICENSE_START==================================================
3  * * org.onap.dmaap
4  * * ===========================================================================
5  * * Copyright © 2017 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  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  * *
22  ******************************************************************************/
23 package org.onap.dmaap.datarouter.node;
24
25 import static org.junit.Assert.assertEquals;
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyObject;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.anyString;
30 import static org.mockito.Mockito.doNothing;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34
35 import ch.qos.logback.classic.Logger;
36 import ch.qos.logback.classic.spi.ILoggingEvent;
37 import ch.qos.logback.core.read.ListAppender;
38 import java.io.File;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Enumeration;
44 import java.util.List;
45 import javax.servlet.http.HttpServletRequest;
46 import javax.servlet.http.HttpServletResponse;
47 import org.apache.commons.lang3.reflect.FieldUtils;
48 import org.junit.AfterClass;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.Mock;
53 import org.powermock.api.mockito.PowerMockito;
54 import org.powermock.core.classloader.annotations.PowerMockIgnore;
55 import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
56 import org.powermock.modules.junit4.PowerMockRunner;
57 import org.slf4j.LoggerFactory;
58
59 @RunWith(PowerMockRunner.class)
60 @SuppressStaticInitializationFor("org.onap.dmaap.datarouter.node.NodeConfigManager")
61 @PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*"})
62 public class NodeServletTest {
63
64     private NodeServlet nodeServlet;
65     private Delivery delivery;
66
67     @Mock
68     private HttpServletRequest request;
69
70     @Mock
71     private HttpServletResponse response;
72
73     private ListAppender<ILoggingEvent> listAppender;
74
75     private NodeConfigManager config = mock(NodeConfigManager.class);
76
77     @Before
78     public void setUp() throws Exception {
79         listAppender = setTestLogger();
80         setBehalfHeader("Stub_Value");
81         when(request.getPathInfo()).thenReturn("2");
82         when(request.isSecure()).thenReturn(true);
83         createFilesAndDirectories();
84         setUpConfig();
85         setUpNodeMainDelivery();
86         delivery = mock(Delivery.class);
87         when(delivery.markTaskSuccess("spool/s/0/1", "dmaap-dr-node.1234567")).thenReturn(true);
88         nodeServlet = new NodeServlet(delivery);
89         when(request.getHeader("Authorization")).thenReturn("User1");
90         when(request.getHeader("X-DMAAP-DR-PUBLISH-ID")).thenReturn("User1");
91     }
92
93     @AfterClass
94     public static void tearDown() {
95         deleteCreatedDirectories();
96     }
97
98     @Test
99     public void Given_Request_Is_HTTP_GET_And_Config_Is_Down_Then_Service_Unavailable_Response_Is_Generated() throws Exception {
100         setNodeConfigManagerIsConfiguredToReturnFalse();
101         nodeServlet.doGet(request, response);
102         verify(response).sendError(eq(HttpServletResponse.SC_SERVICE_UNAVAILABLE));
103         verifyEnteringExitCalled(listAppender);
104     }
105
106     @Test
107     public void Given_Request_Is_HTTP_GET_And_Endpoint_Is_Internal_FetchProv_Then_No_Content_Response_Is_Generated() {
108         when(request.getPathInfo()).thenReturn("/internal/fetchProv");
109         nodeServlet.doGet(request, response);
110         verify(response).setStatus(eq(HttpServletResponse.SC_NO_CONTENT));
111         verifyEnteringExitCalled(listAppender);
112     }
113
114     @Test
115     public void Given_Request_Is_HTTP_GET_And_Endpoint_Is_ResetSubscription_Then_No_Content_Response_Is_Generated() {
116         when(request.getPathInfo()).thenReturn("/internal/resetSubscription/1");
117         nodeServlet.doGet(request, response);
118         verify(response).setStatus(eq(HttpServletResponse.SC_NO_CONTENT));
119         verifyEnteringExitCalled(listAppender);
120     }
121
122     @Test
123     public void Given_Request_Is_HTTP_GET_To_Invalid_Endpoint_Then_Not_Found_Response_Is_Generated() throws Exception {
124         when(request.getPathInfo()).thenReturn("/incorrect");
125         nodeServlet.doGet(request, response);
126         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND));
127         verifyEnteringExitCalled(listAppender);
128     }
129
130     @Test
131     public void Given_Request_Is_HTTP_PUT_And_Config_Is_Down_Then_Service_Unavailable_Response_Is_Generated() throws Exception {
132         setNodeConfigManagerIsConfiguredToReturnFalse();
133         nodeServlet.doPut(request, response);
134         verify(response).sendError(eq(HttpServletResponse.SC_SERVICE_UNAVAILABLE));
135         verifyEnteringExitCalled(listAppender);
136     }
137
138     @Test
139     public void Given_Request_Is_HTTP_PUT_And_Endpoint_Is_Incorrect_Then_Not_Found_Response_Is_Generated() throws Exception {
140         when(request.getPathInfo()).thenReturn("/incorrect/");
141         nodeServlet.doPut(request, response);
142         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
143         verifyEnteringExitCalled(listAppender);
144     }
145
146     @Test
147     public void Given_Request_Is_HTTP_PUT_And_Request_Is_Not_Secure_Then_Forbidden_Response_Is_Generated() throws Exception {
148         when(request.isSecure()).thenReturn(false);
149         nodeServlet.doPut(request, response);
150         verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
151         verifyEnteringExitCalled(listAppender);
152     }
153
154     @Test
155     public void Given_Request_Is_HTTP_PUT_And_File_Id_Is_Null_Then_Not_Found_Response_Is_Generated() throws Exception {
156         when(request.getPathInfo()).thenReturn(null);
157         nodeServlet.doPut(request, response);
158         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
159         verifyEnteringExitCalled(listAppender);
160     }
161
162     @Test
163     public void Given_Request_Is_HTTP_PUT_And_Authorization_Is_Null_Then_Forbidden_Response_Is_Generated() throws Exception {
164         when(request.getHeader("Authorization")).thenReturn(null);
165         nodeServlet.doPut(request, response);
166         verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
167         verifyEnteringExitCalled(listAppender);
168     }
169
170     @Test
171     public void Given_Request_Is_HTTP_PUT_And_Publish_Does_Not_Include_File_Id_Then_Not_Found_Response_Is_Generated() throws Exception {
172         when(request.getPathInfo()).thenReturn("/publish/");
173         nodeServlet.doPut(request, response);
174         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
175         verifyEnteringExitCalled(listAppender);
176     }
177
178     @Test
179     public void Given_Request_Is_HTTP_PUT_And_Publish_Not_Permitted_Then_Forbidden_Response_Is_Generated() throws Exception {
180         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
181         when(request.getRemoteAddr()).thenReturn("1.2.3.4");
182         setNodeConfigManagerIsPublishPermittedToReturnAReason();
183         nodeServlet.doPut(request, response);
184         verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
185         verifyEnteringExitCalled(listAppender);
186     }
187
188     @Test
189     public void Given_Request_Is_HTTP_PUT_And_Internal_Publish_On_Same_Node_Then_Forbidden_Response_Is_Generated() throws Exception {
190         when(request.getPathInfo()).thenReturn("/internal/publish/1/fileName");
191         setNodeConfigManagerIsPublishPermittedToReturnAReason();
192         nodeServlet.doPut(request, response);
193         verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN));
194         verifyEnteringExitCalled(listAppender);
195     }
196
197     @Test
198     public void Given_Request_Is_HTTP_PUT_And_Internal_Publish_But_Invalid_File_Id_Then_Not_Found_Response_Is_Generated() throws Exception {
199         when(request.getPathInfo()).thenReturn("/internal/publish/1/blah");
200         when(request.getRemoteAddr()).thenReturn("1.2.3.4");
201         when(config.isAnotherNode(anyString(), anyString())).thenReturn(true);
202         nodeServlet.doPut(request, response);
203         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
204         verifyEnteringExitCalled(listAppender);
205     }
206
207     @Test
208     public void Given_Request_Is_HTTP_PUT_And_Internal_Publish_But_Invalid_Header_Then_Bad_Request_Response_Is_Generated() throws Exception {
209         when(request.getPathInfo()).thenReturn("/internal/publish/1/blah");
210         when(request.getRemoteAddr()).thenReturn("1.2.3.4");
211         when(config.isAnotherNode(anyString(), anyString())).thenReturn(true);
212         when(request.getHeader("X-DMAAP-DR-PUBLISH-ID")).thenReturn("User1+");
213         nodeServlet.doPut(request, response);
214         verify(response).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString());
215         verifyEnteringExitCalled(listAppender);
216     }
217
218     @Test
219     public void Given_Request_Is_HTTP_PUT_On_Publish_And_Ingress_Node_Is_Provided_Then_Request_Is_Redirected() throws Exception {
220         setNodeConfigManagerToAllowRedirectOnIngressNode();
221         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
222         when(request.getRemoteAddr()).thenReturn("1.2.3.4");
223         nodeServlet.doPut(request, response);
224         verify(response).sendRedirect(anyString());
225         verifyEnteringExitCalled(listAppender);
226     }
227
228     @Test
229     public void Given_Request_Is_HTTP_PUT_On_Publish_With_Meta_Data_Too_Long_Then_Bad_Request_Response_Is_Generated() throws Exception {
230         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
231         setHeadersForValidRequest(true);
232         nodeServlet.doPut(request, response);
233         verify(response).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString());
234     }
235
236     @Test
237     public void Given_Request_Is_HTTP_PUT_On_Publish_With_Meta_Data_Malformed_Then_Bad_Request_Response_Is_Generated() throws Exception {
238         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
239         setHeadersForValidRequest(false);
240         nodeServlet.doPut(request, response);
241         verify(response).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString());
242     }
243
244     @Test
245     public void Given_Request_Is_HTTP_PUT_On_Publish_On_AAF_Feed_And_Cadi_Enabled_And_No_Permissions_Then_Forbidden_Response_Is_Generated() throws Exception {
246         when(config.getCadiEnabled()).thenReturn(true);
247         when(config.getAafInstance("1")).thenReturn("*");
248         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
249         setHeadersForValidRequest(true);
250         nodeServlet.doPut(request, response);
251         verify(response).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
252         verifyEnteringExitCalled(listAppender);
253     }
254
255     @Test
256     public void Given_Request_Is_HTTP_DELETE_On_Publish_With_Meta_Data_Malformed_Then_Bad_Request_Response_Is_Generated() throws Exception {
257         when(request.getPathInfo()).thenReturn("/publish/1/fileName");
258         setHeadersForValidRequest(false);
259         nodeServlet.doDelete(request, response);
260         verify(response).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString());
261     }
262
263     @Test
264     public void Given_Request_Is_HTTP_DELETE_File_With_Invalid_Endpoint_Then_Not_Found_Response_Is_Generated() throws Exception {
265         when(request.getPathInfo()).thenReturn("/delete/1");
266         nodeServlet.doDelete(request, response);
267         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
268         verifyEnteringExitCalled(listAppender);
269     }
270
271     @Test
272     public void Given_Request_Is_HTTP_DELETE_File_And_Is_Not_Privileged_Subscription_Then_Not_Found_Response_Is_Generated() throws Exception {
273         when(request.getPathInfo()).thenReturn("/delete/1/dmaap-dr-node.1234567");
274         setUpConfigToReturnUnprivilegedSubscriber();
275         nodeServlet.doDelete(request, response);
276         verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED));
277         verifyEnteringExitCalled(listAppender);
278     }
279
280     @Test
281     public void Given_Request_Is_HTTP_DELETE_File_And_Subscription_Does_Not_Exist_Then_Not_Found_Response_Is_Generated() throws Exception {
282         when(request.getPathInfo()).thenReturn("/delete/1/dmaap-dr-node.1234567");
283         setUpConfigToReturnNullOnIsDeletePermitted();
284         nodeServlet.doDelete(request, response);
285         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND));
286         verifyEnteringExitCalled(listAppender);
287     }
288
289     @Test
290     public void Given_Request_Is_HTTP_DELETE_File_Then_Request_Succeeds() throws Exception {
291         when(request.getPathInfo()).thenReturn("/delete/1/dmaap-dr-node.1234567");
292         createFilesAndDirectories();
293         nodeServlet.doDelete(request, response);
294         verify(response).setStatus(eq(HttpServletResponse.SC_OK));
295         verifyEnteringExitCalled(listAppender);
296     }
297
298     @Test
299     public void Given_Request_Is_HTTP_DELETE_File_And_File_Does_Not_Exist_Then_Not_Found_Response_Is_Generated() throws IOException {
300         when(request.getPathInfo()).thenReturn("/delete/1/nonExistingFile");
301         nodeServlet.doDelete(request, response);
302         verify(response).sendError(eq(HttpServletResponse.SC_NOT_FOUND), anyString());
303         verifyEnteringExitCalled(listAppender);
304     }
305
306     private void setBehalfHeader(String headerValue) {
307         when(request.getHeader("X-DMAAP-DR-ON-BEHALF-OF")).thenReturn(headerValue);
308     }
309
310     private ListAppender<ILoggingEvent> setTestLogger() {
311         Logger Logger = (Logger) LoggerFactory.getLogger(NodeServlet.class);
312         ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
313         listAppender.start();
314         Logger.addAppender(listAppender);
315         return listAppender;
316     }
317
318     private void verifyEnteringExitCalled(ListAppender<ILoggingEvent> listAppender) {
319         assertEquals("EELF0004I  Entering data router node component with RequestId and InvocationId", listAppender.list.get(0).getMessage());
320         assertEquals("EELF0005I  Exiting data router node component with RequestId and InvocationId", listAppender.list.get(listAppender.list.size() -1).getMessage());
321     }
322
323     private void setUpConfig() throws IllegalAccessException {
324         PowerMockito.mockStatic(NodeConfigManager.class);
325         when(config.isShutdown()).thenReturn(false);
326         when(config.isConfigured()).thenReturn(true);
327         when(config.getSpoolDir()).thenReturn("spool/f");
328         when(config.getSpoolBase()).thenReturn("spool");
329         when(config.getLogDir()).thenReturn("log/dir");
330         when(config.getPublishId()).thenReturn("User1");
331         when(config.isAnotherNode(anyString(), anyString())).thenReturn(false);
332         when(config.getEventLogInterval()).thenReturn("40");
333         when(config.isDeletePermitted("1")).thenReturn(true);
334         when(config.getAllDests()).thenReturn(new DestInfo[0]);
335         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
336         FieldUtils.writeDeclaredStaticField(NodeRunner.class, "nodeConfigManager", config, true);
337         PowerMockito.when(NodeConfigManager.getInstance()).thenReturn(config);
338     }
339
340     private void setUpConfigToReturnUnprivilegedSubscriber() throws IllegalAccessException {
341         NodeConfigManager config = mock(NodeConfigManager.class);
342         PowerMockito.mockStatic(NodeConfigManager.class);
343         when(config.isShutdown()).thenReturn(false);
344         when(config.isConfigured()).thenReturn(true);
345         when(config.isDeletePermitted("1")).thenReturn(false);
346         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
347         FieldUtils.writeDeclaredStaticField(NodeRunner.class, "nodeConfigManager", config, true);
348         PowerMockito.when(NodeConfigManager.getInstance()).thenReturn(config);
349     }
350
351     private void setUpConfigToReturnNullOnIsDeletePermitted() throws IllegalAccessException {
352         NodeConfigManager config = mock(NodeConfigManager.class);
353         PowerMockito.mockStatic(NodeConfigManager.class);
354         when(config.isShutdown()).thenReturn(false);
355         when(config.isConfigured()).thenReturn(true);
356         when(config.isDeletePermitted("1")).thenThrow(new NullPointerException());
357         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
358         FieldUtils.writeDeclaredStaticField(NodeRunner.class, "nodeConfigManager", config, true);
359         PowerMockito.when(NodeConfigManager.getInstance()).thenReturn(config);
360     }
361
362     private void setUpNodeMainDelivery() throws IllegalAccessException{
363         Delivery delivery = mock(Delivery.class);
364         doNothing().when(delivery).resetQueue(anyObject());
365         FieldUtils.writeDeclaredStaticField(NodeServer.class, "delivery", delivery, true);
366     }
367
368     private void setNodeConfigManagerIsConfiguredToReturnFalse() throws IllegalAccessException{
369         NodeConfigManager config = mock(NodeConfigManager.class);
370         when(config.isConfigured()).thenReturn(false);
371         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
372     }
373
374     private void setNodeConfigManagerIsPublishPermittedToReturnAReason() throws IllegalAccessException{
375         NodeConfigManager config = mock(NodeConfigManager.class);
376         when(config.isShutdown()).thenReturn(false);
377         when(config.getMyName()).thenReturn("dmaap-dr-node");
378         when(config.isConfigured()).thenReturn(true);
379         when(config.getSpoolDir()).thenReturn("spool/dir");
380         when(config.getLogDir()).thenReturn("log/dir");
381         when(config.isPublishPermitted(anyString(), anyString(), anyString())).thenReturn("Publisher not permitted for this feed");
382         when(config.isAnotherNode(anyString(), anyString())).thenReturn(false);
383         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
384     }
385
386     private void setNodeConfigManagerToAllowRedirectOnIngressNode() throws IllegalAccessException{
387         NodeConfigManager config = mock(NodeConfigManager.class);
388         when(config.isShutdown()).thenReturn(false);
389         when(config.isConfigured()).thenReturn(true);
390         when(config.getSpoolDir()).thenReturn("spool/dir");
391         when(config.getLogDir()).thenReturn("log/dir");
392         when(config.getPublishId()).thenReturn("User1");
393         when(config.isAnotherNode(anyString(), anyString())).thenReturn(true);
394         when(config.getAuthUser(anyString(), anyString())).thenReturn("User1");
395         when(config.getIngressNode(anyString(), anyString(), anyString())).thenReturn("NewNode");
396         when(config.getExtHttpsPort()).thenReturn(8080);
397         FieldUtils.writeDeclaredStaticField(NodeServlet.class, "config", config, true);
398     }
399
400     private String createLargeMetaDataString() {
401         StringBuilder myString = new StringBuilder("meta");
402         for (int i = 0; i <= 4098; ++i) {
403             myString.append('x');
404         }
405         return myString.toString();
406     }
407
408     private void setHeadersForValidRequest(boolean isMetaTooLong) {
409         String metaDataString;
410         if (isMetaTooLong) {
411             metaDataString = createLargeMetaDataString();
412         } else {
413             metaDataString = "?#@><";
414         }
415         List<String> headers = new ArrayList<>();
416         headers.add("Content-Type");
417         headers.add("X-DMAAP-DR-ON-BEHALF-OF");
418         headers.add("X-DMAAP-DR-META");
419         Enumeration<String> headerNames = Collections.enumeration(headers);
420         when(request.getHeaderNames()).thenReturn(headerNames);
421         Enumeration<String> contentTypeHeader = Collections.enumeration(Arrays.asList("text/plain"));
422         Enumeration<String> behalfHeader = Collections.enumeration(Arrays.asList("User1"));
423         Enumeration<String> metaDataHeader = Collections.enumeration(Arrays.asList(metaDataString));
424         when(request.getHeaders("Content-Type")).thenReturn(contentTypeHeader);
425         when(request.getHeaders("X-DMAAP-DR-ON-BEHALF-OF")).thenReturn(behalfHeader);
426         when(request.getHeaders("X-DMAAP-DR-META")).thenReturn(metaDataHeader);
427     }
428
429     private void createFilesAndDirectories() throws IOException {
430         File nodeDir = new File("spool/n/172.0.0.1");
431         File spoolDir = new File("spool/s/0/1");
432         File dataFile = new File("spool/s/0/1/dmaap-dr-node.1234567");
433         File metaDataFile = new File("spool/s/0/1/dmaap-dr-node.1234567.M");
434         nodeDir.mkdirs();
435         spoolDir.mkdirs();
436         dataFile.createNewFile();
437         metaDataFile.createNewFile();
438     }
439
440     private static void deleteCreatedDirectories() {
441         File spoolDir = new File("spool");
442         delete(spoolDir);
443     }
444
445     private static void delete(File file) {
446         if (file.isDirectory()) {
447             for (File f: file.listFiles()) {
448                 delete(f);
449             }
450         }
451         if (!file.delete()) {
452             System.out.println("Failed to delete: " + file);
453         }
454     }
455 }