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