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