9b436871aa2a5289ccb1ee10976071b9db2db77e
[dcaegen2/collectors/ves.git] / src / test / java / org / onap / dcae / restapi / VesRestControllerTest.java
1 /*
2  * ============LICENSE_START=======================================================
3  * VES Collector
4  * ================================================================================
5  * Copyright (C) 2020-2021 Nokia. 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.dcae.restapi;
22
23 import com.fasterxml.jackson.databind.ObjectMapper;
24 import com.google.common.reflect.TypeToken;
25 import com.google.gson.Gson;
26 import com.networknt.schema.JsonSchema;
27 import io.vavr.collection.HashMap;
28 import org.jetbrains.annotations.NotNull;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.ExtendWith;
32 import org.junit.jupiter.params.ParameterizedTest;
33 import org.junit.jupiter.params.provider.Arguments;
34 import org.junit.jupiter.params.provider.MethodSource;
35 import org.mockito.ArgumentCaptor;
36 import org.mockito.Mock;
37 import org.mockito.junit.jupiter.MockitoExtension;
38 import org.onap.dcae.ApplicationSettings;
39 import org.onap.dcae.JSonSchemasSupplier;
40 import org.onap.dcae.common.EventSender;
41 import org.onap.dcae.common.EventTransformation;
42 import org.onap.dcae.common.HeaderUtils;
43 import org.onap.dcae.common.JsonDataLoader;
44 import org.onap.dcae.common.model.InternalException;
45 import org.onap.dcae.common.model.PayloadToLargeException;
46 import org.onap.dcae.common.publishing.DMaaPEventPublisher;
47 import org.onap.dcae.common.validator.StndDefinedDataValidator;
48 import org.slf4j.Logger;
49 import org.springframework.http.HttpStatus;
50 import org.springframework.http.ResponseEntity;
51 import org.springframework.mock.web.MockHttpServletRequest;
52 import org.springframework.web.context.request.RequestContextHolder;
53 import org.springframework.web.context.request.ServletRequestAttributes;
54
55 import java.io.FileReader;
56 import java.io.IOException;
57 import java.lang.reflect.Type;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.stream.Stream;
61
62 import static org.assertj.core.api.Assertions.assertThat;
63 import static org.junit.jupiter.params.provider.Arguments.arguments;
64 import static org.mockito.ArgumentMatchers.any;
65 import static org.mockito.ArgumentMatchers.anyString;
66 import static org.mockito.ArgumentMatchers.eq;
67 import static org.mockito.Mockito.never;
68 import static org.mockito.Mockito.times;
69 import static org.mockito.Mockito.verify;
70 import static org.mockito.Mockito.when;
71
72
73 @ExtendWith(MockitoExtension.class)
74 public class VesRestControllerTest {
75
76     private static final String EVENT_TRANSFORM_FILE_PATH = "/eventTransform.json";
77     private static final String ACCEPTED = "Successfully send event";
78     private static final String VERSION_V7 = "v7";
79     static final String VES_FAULT_TOPIC = "ves-fault";
80     static final String VES_3_GPP_FAULT_SUPERVISION_TOPIC = "ves-3gpp-fault-supervision";
81
82     private VesRestController vesRestController;
83
84     @Mock
85     private ApplicationSettings applicationSettings;
86
87     @Mock
88     private Logger logger;
89
90     @Mock
91     private Logger errorLogger;
92
93     @Mock
94     private HeaderUtils headerUtils;
95
96     @Mock
97     private DMaaPEventPublisher eventPublisher;
98
99     @Mock
100     private StndDefinedDataValidator stndDefinedDataValidator;
101
102     @BeforeEach
103     void setUp(){
104         final HashMap<String, String> streamIds = HashMap.of(
105                 "fault", VES_FAULT_TOPIC,
106                 "3GPP-FaultSupervision", VES_3_GPP_FAULT_SUPERVISION_TOPIC
107         );
108         this.vesRestController = new VesRestController(applicationSettings, logger,
109                 errorLogger, new EventSender(eventPublisher, streamIds), headerUtils, stndDefinedDataValidator);
110     }
111
112     @Test
113     void shouldReportThatApiVersionIsNotSupported() {
114         // given
115         when(applicationSettings.isVersionSupported("v20")).thenReturn(false);
116         MockHttpServletRequest request = givenMockHttpServletRequest();
117
118         // when
119         final ResponseEntity<String> event = vesRestController.event("", "v20", request);
120
121         // then
122         assertThat(event.getStatusCodeValue()).isEqualTo(HttpStatus.BAD_REQUEST.value());
123         assertThat(event.getBody()).isEqualTo("API version v20 is not supported");
124         verifyThatEventWasNotSend();
125     }
126
127     @Test
128     void shouldTransformEventAccordingToEventTransformFile() throws IOException {
129         //given
130         configureEventTransformations();
131         configureHeadersForEventListener();
132
133         MockHttpServletRequest request = givenMockHttpServletRequest();
134         String validEvent = JsonDataLoader.loadContent("/ves7_valid_30_1_1_event.json");
135         when(eventPublisher.sendEvent(any(), any())).thenReturn((HttpStatus.OK));
136
137         //when
138         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
139
140         //then
141         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
142         assertThat(response.getBody()).isEqualTo(ACCEPTED);
143         verifyThatTransformedEventWasSend(eventPublisher, validEvent);
144     }
145
146
147     @Test
148     void shouldSendBatchEvent() throws IOException {
149         //given
150         configureEventTransformations();
151         configureHeadersForEventListener();
152
153         MockHttpServletRequest request = givenMockHttpServletRequest();
154
155         String validEvent = JsonDataLoader.loadContent("/ves7_batch_valid.json");
156         when(eventPublisher.sendEvent(any(), any())).thenReturn(HttpStatus.OK);
157         //when
158         final ResponseEntity<String> response = vesRestController.events(validEvent, VERSION_V7, request);
159
160         //then
161         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
162         assertThat(response.getBody()).isEqualTo(ACCEPTED);
163         verify(eventPublisher, times(1)).sendEvent(any(),any());
164     }
165
166     @Test
167     void shouldSendStndDomainEventIntoDomainStream() throws IOException {
168         //given
169         configureEventTransformations();
170         configureHeadersForEventListener();
171
172         MockHttpServletRequest request = givenMockHttpServletRequest();
173         configureSchemasSupplierForStndDefineEvent();
174
175         String validEvent = JsonDataLoader.loadContent("/ves_stdnDefined_valid.json");
176         when(eventPublisher.sendEvent(any(), any())).thenReturn(HttpStatus.OK);
177
178         //when
179         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
180
181         //then
182         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
183         assertThat(response.getBody()).isEqualTo(ACCEPTED);
184         verify(eventPublisher).sendEvent(any(),eq(VES_3_GPP_FAULT_SUPERVISION_TOPIC));
185     }
186
187
188     @Test
189     void shouldReportThatStndDomainEventHasntGotNamespaceParameter() throws IOException {
190         //given
191         configureEventTransformations();
192         configureHeadersForEventListener();
193
194         MockHttpServletRequest request = givenMockHttpServletRequest();
195         configureSchemasSupplierForStndDefineEvent();
196
197         String validEvent = JsonDataLoader.loadContent("/ves_stdnDefined_missing_namespace_invalid.json");
198
199         //when
200         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
201
202         //then
203         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.BAD_REQUEST.value());
204         verifyErrorResponse(
205                 response,
206                 "SVC2006",
207                 "Mandatory input %1 %2 is missing from request",
208                 List.of("attribute", "event.commonEventHeader.stndDefinedNamespace")
209         );
210         verifyThatEventWasNotSend();
211     }
212
213     @Test
214     void shouldReportThatStndDomainEventNamespaceParameterIsEmpty() throws IOException {
215         //given
216         configureEventTransformations();
217         configureHeadersForEventListener();
218
219         MockHttpServletRequest request = givenMockHttpServletRequest();
220         configureSchemasSupplierForStndDefineEvent();
221
222         String validEvent = JsonDataLoader.loadContent("/ves_stdnDefined_empty_namespace_invalid.json");
223
224         //when
225         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
226
227         //then
228         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.BAD_REQUEST.value());
229         verifyErrorResponse(
230                 response,
231                 "SVC2006",
232                 "Mandatory input %1 %2 is empty in request",
233                 List.of("attribute", "event.commonEventHeader.stndDefinedNamespace")
234         );
235         verifyThatEventWasNotSend();
236     }
237
238     @Test
239     void shouldNotSendStndDomainEventWhenTopicCannotBeFoundInConfiguration() throws IOException {
240         //given
241         configureEventTransformations();
242         configureHeadersForEventListener();
243
244         MockHttpServletRequest request = givenMockHttpServletRequest();
245         String validEvent = JsonDataLoader.loadContent("/ves_stdnDefined_valid_unknown_topic.json");
246
247         //when
248         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
249
250         //then
251         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.BAD_REQUEST.value());
252         verifyThatEventWasNotSend();
253     }
254
255     @Test
256     void shouldExecuteStndDefinedValidationWhenFlagIsOnTrue() throws IOException {
257         //given
258         configureEventTransformations();
259         configureHeadersForEventListener();
260
261         MockHttpServletRequest request = givenMockHttpServletRequest();
262         String validEvent = JsonDataLoader.loadContent("/ves7_batch_with_stndDefined_valid.json");
263         when(applicationSettings.getExternalSchemaValidationCheckflag()).thenReturn(true);
264         when(eventPublisher.sendEvent(any(), any())).thenReturn(HttpStatus.OK);
265         //when
266         final ResponseEntity<String> response = vesRestController.events(validEvent, VERSION_V7, request);
267
268         //then
269         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
270         assertThat(response.getBody()).isEqualTo(ACCEPTED);
271         verify(stndDefinedDataValidator, times(2)).validate(any());
272     }
273
274     @Test
275     void shouldNotExecuteStndDefinedValidationWhenFlagIsOnFalse() throws IOException {
276         //given
277         configureEventTransformations();
278         configureHeadersForEventListener();
279
280         MockHttpServletRequest request = givenMockHttpServletRequest();
281         String validEvent = JsonDataLoader.loadContent("/ves7_batch_with_stndDefined_valid.json");
282         when(applicationSettings.getExternalSchemaValidationCheckflag()).thenReturn(false);
283         when(eventPublisher.sendEvent(any(), any())).thenReturn(HttpStatus.OK);
284
285         //when
286         final ResponseEntity<String> response = vesRestController.events(validEvent, VERSION_V7, request);
287
288         //then
289         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
290         assertThat(response.getBody()).isEqualTo(ACCEPTED);
291         verify(stndDefinedDataValidator, times(0)).validate(any());
292     }
293
294     @Test
295     void shouldReturn413WhenPayloadIsTooLarge() throws IOException {
296         //given
297         configureEventTransformations();
298         configureHeadersForEventListener();
299
300         MockHttpServletRequest request = givenMockHttpServletRequest();
301         when(eventPublisher.sendEvent(any(), any())).thenThrow(new PayloadToLargeException());
302         String validEvent = JsonDataLoader.loadContent("/ves7_valid_30_1_1_event.json");
303
304         //when
305         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
306
307         //then
308         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.PAYLOAD_TOO_LARGE.value());
309         verifyErrorResponse(
310                 response,
311                 "SVC2000",
312                 "The following service error occurred: %1. Error code is %2",
313                 List.of("Request Entity Too Large","413")
314         );
315     }
316
317     @ParameterizedTest
318     @MethodSource("errorsCodeAndResponseBody")
319     void shouldMapErrorTo503AndReturnOriginalBody(ApiException apiException,String bodyVariable,String bodyVariable2) throws IOException {
320         //given
321         configureEventTransformations();
322         configureHeadersForEventListener();
323
324         MockHttpServletRequest request = givenMockHttpServletRequest();
325         when(eventPublisher.sendEvent(any(), any())).thenThrow(new InternalException(apiException));
326         String validEvent = JsonDataLoader.loadContent("/ves7_valid_30_1_1_event.json");
327
328         //when
329         final ResponseEntity<String> response = vesRestController.event(validEvent, VERSION_V7, request);
330
331         //then
332         assertThat(response.getStatusCodeValue()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value());
333         verifyErrorResponse(
334                 response,
335                 "SVC2000",
336                 "The following service error occurred: %1. Error code is %2",
337                 List.of(bodyVariable,bodyVariable2)
338         );
339     }
340
341     private static Stream<Arguments> errorsCodeAndResponseBody() {
342         return Stream.of(
343                 arguments(ApiException.NOT_FOUND, "Not Found","404"),
344                 arguments(ApiException.REQUEST_TIMEOUT, "Request Timeout","408"),
345                 arguments(ApiException.TOO_MANY_REQUESTS, "Too Many Requests","429"),
346                 arguments(ApiException.INTERNAL_SERVER_ERROR, "Internal Server Error","500"),
347                 arguments(ApiException.BAD_GATEWAY, "Bad Gateway","502"),
348                 arguments(ApiException.SERVICE_UNAVAILABLE, "Service Unavailable","503"),
349                 arguments(ApiException.GATEWAY_TIMEOUT, "Gateway Timeout","504")
350         );
351     }
352
353     private void verifyThatEventWasNotSend() {
354         verify(eventPublisher, never()).sendEvent(any(), any());
355     }
356
357     private void configureSchemasSupplierForStndDefineEvent() {
358         String collectorSchemaFile = "{\"v7\":\"./etc/CommonEventFormat_30.2_ONAP.json\"}";
359         final io.vavr.collection.Map<String, JsonSchema> loadedJsonSchemas = new JSonSchemasSupplier().loadJsonSchemas(collectorSchemaFile);
360
361         when(applicationSettings.eventSchemaValidationEnabled()).thenReturn(true);
362         when(applicationSettings.jsonSchema(eq(VERSION_V7))).thenReturn(loadedJsonSchemas.get(VERSION_V7).get());
363     }
364
365     private void verifyErrorResponse(ResponseEntity<String> response, String messageId, String messageText, List<String> variables) throws com.fasterxml.jackson.core.JsonProcessingException {
366         final Map<String, ?> errorDetails = fetchErrorDetails(response);
367         assertThat((Map<String, String>)errorDetails).containsEntry("messageId", messageId);
368         assertThat((Map<String, String>)errorDetails).containsEntry("text", messageText);
369         assertThat((Map<String, List<String>>)errorDetails).containsEntry("variables",  variables);
370     }
371
372     private Map<String, ?> fetchErrorDetails(ResponseEntity<String> response) throws com.fasterxml.jackson.core.JsonProcessingException {
373         final String body = response.getBody();
374         ObjectMapper mapper = new ObjectMapper();
375         Map<String, Map<String, Map<String,String>>> map = mapper.readValue(body, Map.class);
376         return map.get("requestError").get("ServiceException");
377     }
378
379     private void configureEventTransformations() throws IOException {
380         final List<EventTransformation> eventTransformations = loadEventTransformations();
381         when(applicationSettings.isVersionSupported(VERSION_V7)).thenReturn(true);
382         when(applicationSettings.eventTransformingEnabled()).thenReturn(true);
383         when(applicationSettings.getEventTransformations()).thenReturn((eventTransformations));
384     }
385
386     private void configureHeadersForEventListener() {
387         when(headerUtils.getRestApiIdentify(anyString())).thenReturn("eventListener");
388         when(applicationSettings.getApiVersionDescriptionFilepath()).thenReturn("etc/api_version_description.json");
389     }
390
391     private void verifyThatTransformedEventWasSend(DMaaPEventPublisher eventPublisher, String eventBeforeTransformation) {
392         // event before transformation
393         assertThat(eventBeforeTransformation).contains("\"version\": \"4.0.1\"");
394         assertThat(eventBeforeTransformation).contains("\"faultFieldsVersion\": \"4.0\"");
395
396         ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
397         ArgumentCaptor<String> domain = ArgumentCaptor.forClass(String.class);
398         verify(eventPublisher).sendEvent(argument.capture(), domain.capture());
399
400         final String transformedEvent = argument.getValue().toString();
401         final String eventSentAtTopic = domain.getValue();
402
403         // event after transformation
404         assertThat(transformedEvent).contains("\"priority\":\"High\",\"version\":3,");
405         assertThat(transformedEvent).contains(",\"faultFieldsVersion\":3,\"specificProblem");
406         assertThat(eventSentAtTopic).isEqualTo(VES_FAULT_TOPIC);
407     }
408
409     @NotNull
410     private MockHttpServletRequest givenMockHttpServletRequest() {
411         MockHttpServletRequest request = new MockHttpServletRequest();
412         request.setContentType("application/json");
413
414         RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
415         return request;
416     }
417
418     private List<EventTransformation> loadEventTransformations() throws IOException {
419         Type EVENT_TRANSFORM_LIST_TYPE = new TypeToken<List<EventTransformation>>() {
420         }.getType();
421
422         try (FileReader fr = new FileReader(this.getClass().getResource(EVENT_TRANSFORM_FILE_PATH).getPath())) {
423             return new Gson().fromJson(fr, EVENT_TRANSFORM_LIST_TYPE);
424         }
425     }
426 }