Refactor of an AAIRestInterface
[vid.git] / vid-app-common / src / test / java / org / onap / vid / aai / AaiClientTest.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2018 Nokia. 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.vid.aai;
23
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.google.common.collect.ImmutableList;
26 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
27 import org.apache.commons.lang3.builder.ToStringStyle;
28 import org.apache.commons.lang3.exception.ExceptionUtils;
29 import org.apache.commons.lang3.reflect.FieldUtils;
30 import org.apache.commons.lang3.tuple.Pair;
31 import org.mockito.Mockito;
32 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
33 import org.onap.portalsdk.core.util.SystemProperties;
34 import org.onap.vid.aai.model.AaiGetTenatns.GetTenantsResponse;
35 import org.onap.vid.aai.model.AaiNodeQueryResponse;
36 import org.onap.vid.aai.model.ResourceType;
37 import org.onap.vid.aai.util.AAIRestInterface;
38 import org.onap.vid.aai.util.HttpsAuthClient;
39 import org.onap.vid.aai.util.ServletRequestHelper;
40 import org.onap.vid.aai.util.SystemPropertyHelper;
41 import org.onap.vid.controllers.LocalWebConfig;
42 import org.onap.vid.exceptions.GenericUncheckedException;
43 import org.onap.vid.model.Subscriber;
44 import org.onap.vid.model.SubscriberList;
45 import org.onap.vid.model.probes.ExternalComponentStatus;
46 import org.onap.vid.model.probes.HttpRequestMetadata;
47 import org.onap.vid.model.probes.StatusMetadata;
48 import org.onap.vid.testUtils.TestUtils;
49 import org.springframework.http.HttpMethod;
50 import org.springframework.test.context.ContextConfiguration;
51 import org.springframework.test.context.web.WebAppConfiguration;
52 import org.testng.Assert;
53 import org.testng.annotations.BeforeMethod;
54 import org.testng.annotations.DataProvider;
55 import org.testng.annotations.Test;
56 import sun.security.provider.certpath.SunCertPathBuilderException;
57 import sun.security.validator.ValidatorException;
58
59 import javax.crypto.BadPaddingException;
60 import javax.net.ssl.SSLHandshakeException;
61 import javax.servlet.ServletContext;
62 import javax.ws.rs.ProcessingException;
63 import javax.ws.rs.client.Client;
64 import javax.ws.rs.core.Response;
65 import java.io.FileNotFoundException;
66 import java.io.IOException;
67 import java.security.cert.CertificateException;
68 import java.util.ArrayList;
69 import java.util.function.BiConsumer;
70 import java.util.function.Function;
71 import java.util.stream.Collectors;
72 import java.util.stream.Stream;
73
74 import static org.hamcrest.CoreMatchers.*;
75 import static org.hamcrest.MatcherAssert.assertThat;
76 import static org.hamcrest.Matchers.equalToIgnoringCase;
77 import static org.mockito.Matchers.any;
78 import static org.mockito.Matchers.*;
79 import static org.mockito.Mockito.mock;
80 import static org.mockito.Mockito.when;
81 import static org.testng.Assert.*;
82
83 @ContextConfiguration(classes = {LocalWebConfig.class, SystemProperties.class})
84 @WebAppConfiguration
85 public class AaiClientTest {
86
87     private AaiClient aaiClientMock;
88     private ServletContext servletContext;
89
90     @BeforeMethod
91     public void initMocks(){
92         aaiClientMock = mock(AaiClient.class);
93         aaiClientMock.logger = mock(EELFLoggerDelegate.class);
94         servletContext = mock(ServletContext.class);
95
96         when(servletContext.getRealPath(any(String.class))).thenReturn("");
97
98         when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(null);
99     }
100
101     @DataProvider
102     public static Object[][] logicalLinkData() {
103         return new Object[][] {
104                 {"", "network/logical-links/logical-link/"},
105                 {"link", "network/logical-links/logical-link/link"}
106         };
107     }
108
109     @Test(dataProvider = "logicalLinkData")
110     public void getLogicalLink_Link_Is_Empty(String link, String expectedUrl) {
111
112         when(aaiClientMock.getLogicalLink(any(String.class))).thenCallRealMethod();
113         aaiClientMock.getLogicalLink(link);
114         Mockito.verify(aaiClientMock).doAaiGet(argThat(equalToIgnoringCase(expectedUrl)),any(Boolean.class));
115     }
116
117     @DataProvider
118     public static Object[][] subscribersResults() {
119         return new Object[][] {
120                 {new SubscriberList(new ArrayList<Subscriber>() {{ add(new Subscriber());  add(new Subscriber()); }}), true},
121                 {new SubscriberList(new ArrayList<Subscriber>() {{ add(new Subscriber()); }}), true},
122                 {new SubscriberList(new ArrayList<Subscriber>()), false}
123         };
124     }
125
126     @Test(dataProvider = "subscribersResults")
127     public void testProbeAaiGetAllSubscribers_returnsTwoToZeroSubscribers_ResultsAsExpected(SubscriberList subscribers, boolean isAvailable){
128         ExternalComponentStatus expectedStatus = new ExternalComponentStatus(ExternalComponentStatus.Component.AAI,isAvailable, new HttpRequestMetadata(
129                 HttpMethod.GET,
130                 200,
131                 "url",
132                 "rawData",
133                 isAvailable ? "OK" : "No subscriber received",
134                 0
135         ));
136         Mockito.when(aaiClientMock.getAllSubscribers(true)).thenReturn(
137                 new AaiResponseWithRequestInfo<>(
138                         HttpMethod.GET, "url", new AaiResponse<>(subscribers, null, 200),
139                         "rawData"));
140         Mockito.when(aaiClientMock.probeAaiGetAllSubscribers()).thenCallRealMethod();
141         ExternalComponentStatus result  = aaiClientMock.probeAaiGetAllSubscribers();
142         assertThat(statusDataReflected(result),is(statusDataReflected(expectedStatus)));
143         assertThat(requestMetadataReflected(result.getMetadata()),is(requestMetadataReflected(expectedStatus.getMetadata())));
144     }
145
146     //serialize fields except of fields we cannot know ahead of time
147     private static String requestMetadataReflected(StatusMetadata metadata) {
148         return new ReflectionToStringBuilder(metadata, ToStringStyle.SHORT_PREFIX_STYLE)
149                 .setExcludeFieldNames("duration")
150                 .toString();
151     }
152
153     private static String statusDataReflected(ExternalComponentStatus status) {
154         return new ReflectionToStringBuilder(status, ToStringStyle.SHORT_PREFIX_STYLE)
155                 .setExcludeFieldNames("metadata")
156                 .toString();
157     }
158
159     @DataProvider
160     public static Object[][] rawData() {
161         return new Object[][]{
162                 {"errorMessage", }, {""}, {null}
163         };
164     }
165
166     @Test(dataProvider = "rawData")
167     public void testProbeAaiGetFullSubscribersWithNullResponse_returnsNotAvailableWithErrorRawData(String rawData){
168         Mockito.when(aaiClientMock.getAllSubscribers(true)).thenReturn(
169                 new AaiResponseWithRequestInfo<>(HttpMethod.GET, "url", null,
170                         rawData));
171         ExternalComponentStatus result = callProbeAaiGetAllSubscribersAndAssertNotAvailable();
172         assertThat(result.getMetadata(), instanceOf(HttpRequestMetadata.class));
173         assertEquals(((HttpRequestMetadata) result.getMetadata()).getRawData(), rawData);
174     }
175
176     @DataProvider
177     public static Object[][] exceptions() {
178         return new Object[][] {
179                 {"NullPointerException", "errorMessage",
180                         new ExceptionWithRequestInfo(HttpMethod.GET, "url",
181                                 "errorMessage", null, new NullPointerException())},
182                 {"RuntimeException", null,
183                         new ExceptionWithRequestInfo(HttpMethod.GET, "url",
184                                 null, null, new RuntimeException())},
185                 {"RuntimeException", null,
186                         new RuntimeException()},
187         };
188     }
189
190     @Test(dataProvider = "exceptions")
191     public void testProbeAaiGetFullSubscribersWithNullResponse_returnsNotAvailableWithErrorRawData(String description, String expectedRawData, Exception exception){
192         Mockito.when(aaiClientMock.getAllSubscribers(true)).thenThrow(exception);
193         ExternalComponentStatus result = callProbeAaiGetAllSubscribersAndAssertNotAvailable();
194         if (exception instanceof ExceptionWithRequestInfo) {
195             assertThat(result.getMetadata(), instanceOf(HttpRequestMetadata.class));
196             assertEquals(((HttpRequestMetadata) result.getMetadata()).getRawData(), expectedRawData);
197         }
198         assertThat(result.getMetadata().getDescription(), containsString(description));
199     }
200
201     private ExternalComponentStatus callProbeAaiGetAllSubscribersAndAssertNotAvailable() {
202         Mockito.when(aaiClientMock.probeAaiGetAllSubscribers()).thenCallRealMethod();
203         ExternalComponentStatus result  = aaiClientMock.probeAaiGetAllSubscribers();
204         assertFalse(result.isAvailable());
205         return result;
206     }
207
208
209     @Test
210     public void getTenants_Arguments_Are_Null_Or_Empty() {
211
212         when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
213
214         AaiResponse response = aaiClientMock.getTenants("", "");
215
216         assertEquals(response.getErrorMessage(), "{\"statusText\":\" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.\"}");
217
218
219         response = aaiClientMock.getTenants(null, null);
220
221         assertEquals(response.getErrorMessage(), "{\"statusText\":\" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.\"}");
222     }
223
224     @Test
225     public void getTenants_Arguments_Are_Valid_But_Tenants_Not_Exist() {
226
227         when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
228
229         Response generalEmptyResponse = mock(Response.class);
230         when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(generalEmptyResponse);
231
232         AaiResponse response = aaiClientMock.getTenants("subscriberId", "serviceType");
233
234         assertEquals(response.getErrorMessage(), "{\"statusText\":\" A&AI has no LCP Region & Tenants associated to subscriber 'subscriberId' and service type 'serviceType'\"}");
235
236     }
237
238     @Test
239     public void getTenants_Arguments_Are_Valid_Get_The_Tenanats() {
240
241         when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
242
243
244         Response generalEmptyResponse = mock(Response.class);
245
246         when(generalEmptyResponse.readEntity(String.class)).thenReturn(tenantResponseRaw);
247         when(generalEmptyResponse.getStatus()).thenReturn(200);
248         when(generalEmptyResponse.getStatusInfo()).thenReturn(new Response.StatusType() {
249             @Override
250             public int getStatusCode() {
251                 return 200;
252             }
253
254             @Override
255             public Response.Status.Family getFamily() {
256                 return Response.Status.Family.SUCCESSFUL;
257             }
258
259             @Override
260             public String getReasonPhrase() {
261                 return null;
262             }
263         });
264
265
266         when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(generalEmptyResponse);
267
268         AaiResponse<GetTenantsResponse[]> response = aaiClientMock.getTenants("subscriberId", "serviceType");
269
270         Assert.assertTrue(response.t.length> 0);
271     }
272
273     final String tenantResponseRaw ="" +
274             "{" +
275             "\"service-type\": \"VIRTUAL USP\"," +
276             "\"resource-version\": \"1494001841964\"," +
277             "\"relationship-list\": {" +
278             "\"relationship\": [{" +
279             "\"related-to\": \"tenant\"," +
280             "\"related-link\": \"/aai/v11/cloud-infrastructure/cloud-regions/cloud-region/att-aic/AAIAIC25/tenants/tenant/092eb9e8e4b7412e8787dd091bc58e86\"," +
281             "\"relationship-data\": [{" +
282             "\"relationship-key\": \"cloud-region.cloud-owner\"," +
283             "\"relationship-value\": \"att-aic\"" +
284             "}," +
285             "{" +
286             "\"relationship-key\": \"cloud-region.cloud-region-id\"," +
287             "\"relationship-value\": \"AAIAIC25\"" +
288             "}," +
289             "{" +
290             "\"relationship-key\": \"tenant.tenant-id\"," +
291             "\"relationship-value\": \"092eb9e8e4b7412e8787dd091bc58e86\"" +
292             "}" +
293             "]," +
294             "\"related-to-property\": [{" +
295             "\"property-key\": \"tenant.tenant-name\"," +
296             "\"property-value\": \"USP-SIP-IC-24335-T-01\"" +
297             "}]" +
298             "}]" +
299             "}" +
300             "}";
301
302     @DataProvider
303     public static Object[][] resourceTypesProvider() {
304         return new Object[][] {
305                 {"service-instance", ResourceType.SERVICE_INSTANCE},
306                 {"generic-vnf", ResourceType.GENERIC_VNF},
307                 {"vf-module", ResourceType.VF_MODULE}
308         };
309     }
310
311     @Test(dataProvider = "resourceTypesProvider")
312     public void aaiNodeQueryResponseDeserializationTest(String resourceType, ResourceType expectedResourceType) throws IOException {
313         String link = "/aai/v12/business/customers/customer/a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb/service-subscriptions/service-subscription/Nimbus/service-instances/service-instance/7131d483-b450-406f-8e30-0c650645fc67";
314         String json =
315                 "{\"result-data\": [{" +
316                     "\"resource-type\": \""+resourceType+"\"," +
317                     "\"resource-link\": \""+ link+ "\"" +
318                 "}]}";
319
320         AaiNodeQueryResponse nodeQueryResponse = new ObjectMapper().readValue(json, AaiNodeQueryResponse.class);
321         assertThat(nodeQueryResponse.resultData.get(0).resourceLink, equalTo(link));
322         assertThat(nodeQueryResponse.resultData.get(0).resourceType, is(expectedResourceType));
323     }
324
325     @Test
326     public void aaiNodeQueryEmptyResponseDeserializationTest() throws IOException{
327         String json = "{}";
328         AaiNodeQueryResponse nodeQueryResponse = new ObjectMapper().readValue(json, AaiNodeQueryResponse.class);
329         assertNull(nodeQueryResponse.resultData);
330     }
331
332     @DataProvider
333     public static Object[][] nameAndResourceTypeProvider() {
334         return new Object[][] {
335                 {"SRIOV_SVC", ResourceType.SERVICE_INSTANCE, "search/nodes-query?search-node-type=service-instance&filter=service-instance-name:EQUALS:SRIOV_SVC"},
336                 {"b1707vidnf", ResourceType.GENERIC_VNF, "search/nodes-query?search-node-type=generic-vnf&filter=vnf-name:EQUALS:b1707vidnf"},
337                 {"connectivity_test", ResourceType.VF_MODULE, "search/nodes-query?search-node-type=vf-module&filter=vf-module-name:EQUALS:connectivity_test"},
338                 {"MjVg1234", ResourceType.VOLUME_GROUP, "search/nodes-query?search-node-type=volume-group&filter=volume-group-name:EQUALS:MjVg1234"}
339         };
340     }
341
342     @Test(dataProvider = "nameAndResourceTypeProvider")
343     public void whenSearchNodeTypeByName_callRightAaiPath(String name, ResourceType type, String expectedUrl) {
344         when(aaiClientMock.searchNodeTypeByName(any(String.class), any(ResourceType.class))).thenCallRealMethod();
345         aaiClientMock.searchNodeTypeByName(name, type);
346         Mockito.verify(aaiClientMock).doAaiGet(eq(expectedUrl), eq(false));
347     }
348
349     @DataProvider
350     public static Object[][] aaiClientInternalExceptions() {
351         return Stream.<Pair<Class<? extends Throwable>, UncheckedBiConsumer<HttpsAuthClient, Client>>>of(
352
353                 // Exception out of httpsAuthClientMock
354                 Pair.of(CertificateException.class, (httpsAuthClientMock, javaxClientMock) -> {
355                     final CertificateException e0 = new CertificateException("No X509TrustManager implementation available");
356                     SSLHandshakeException e = new SSLHandshakeException(e0.toString());
357                     e.initCause(e0);
358
359                     when(httpsAuthClientMock.getClient(any())).thenThrow(e);
360                 }),
361
362                 Pair.of(StringIndexOutOfBoundsException.class, mockExceptionOnClientProvider(new StringIndexOutOfBoundsException(4))),
363
364                 Pair.of(NullPointerException.class, mockExceptionOnClientProvider(new NullPointerException("null"))),
365
366                 Pair.of(FileNotFoundException.class, mockExceptionOnClientProvider(new FileNotFoundException("vid/WEB-INF/cert/aai-client-cert.p12"))),
367
368                 Pair.of(BadPaddingException.class, mockExceptionOnClientProvider(
369                         new IOException("keystore password was incorrect", new BadPaddingException("Given final block not properly padded")))
370                 ),
371                 Pair.of(GenericUncheckedException.class, mockExceptionOnClientProvider(new GenericUncheckedException("basa"))),
372
373                 Pair.of(NullPointerException.class, (httpsAuthClientMock, javaxClientMock) ->
374                         when(httpsAuthClientMock.getClient(any())).thenReturn(null)),
375
376
377                 // Exception out of javax's Client
378                 Pair.of(SSLHandshakeException.class, (httpsAuthClientMock, javaxClientMock) -> {
379                     when(javaxClientMock.target(anyString())).thenThrow(
380                             new ProcessingException(new SSLHandshakeException("Received fatal alert: certificate_expired"))
381                     );
382                 }),
383
384                 Pair.of(SunCertPathBuilderException.class, (httpsAuthClientMock, javaxClientMock) -> {
385                     SunCertPathBuilderException e0 = new SunCertPathBuilderException("unable to find valid certification path to requested target");
386                     when(javaxClientMock.target(anyString())).thenThrow(
387                             new ProcessingException(new ValidatorException("PKIX path building failed: " + e0.toString(), e0))
388                     );
389                 }),
390
391                 Pair.of(GenericUncheckedException.class, (httpsAuthClientMock, javaxClientMock) ->
392                         when(javaxClientMock.target(anyString())).thenThrow(new GenericUncheckedException("basa")))
393
394         ).flatMap(l -> Stream.of(
395                 // double each case to propagateExceptions = true/false, to verify that "don't propagate" really still work
396                 ImmutableList.of(l.getLeft(), l.getRight(), true).toArray(),
397                 ImmutableList.of(l.getLeft(), l.getRight(), false).toArray()
398         )).collect(Collectors.toList()).toArray(new Object[][]{});
399     }
400
401     private static UncheckedBiConsumer<HttpsAuthClient, Client> mockExceptionOnClientProvider(Exception e) {
402         return (httpsAuthClientMock, javaxClientMock) ->
403                 when(httpsAuthClientMock.getClient(any())).thenThrow(e);
404     }
405
406     @Test(dataProvider = "aaiClientInternalExceptions")
407     public void propagateExceptions_internalsThrowException_ExceptionRethrown(Class<? extends Throwable> expectedType, BiConsumer<HttpsAuthClient, Client> setupMocks, boolean propagateExceptions) throws Exception {
408         /*
409         Call chain is like:
410             this test -> AaiClient -> AAIRestInterface -> HttpsAuthClient -> javax's Client
411
412         In this test, *AaiClient* and *AAIRestInterface* are under test (actual
413         implementation is used), while HttpsAuthClient and the javax's Client are
414         mocked to return pseudo-responses or - better- throw exceptions.
415          */
416
417         // prepare mocks
418         HttpsAuthClient httpsAuthClientMock = mock(HttpsAuthClient.class);
419         TestUtils.JavaxRsClientMocks mocks = new TestUtils.JavaxRsClientMocks();
420         Client javaxClientMock = mocks.getFakeClient();
421         Response responseMock = mocks.getFakeResponse();
422
423         // prepare real AAIRestInterface and AaiClient, and wire mocks
424         AAIRestInterface aaiRestInterface = new AAIRestInterface(httpsAuthClientMock, new ServletRequestHelper(), new SystemPropertyHelper());
425         final AaiClient aaiClient = new AaiClient(aaiRestInterface, null);
426         when(httpsAuthClientMock.getClient(any())).thenReturn(javaxClientMock);
427
428         // define atomic method under test, including reset of "aaiRestInterface.client"
429         final Function<Boolean, Response> doAaiGet = (propagateExceptions1) -> {
430             try {
431                 FieldUtils.writeField(aaiRestInterface, "client", null, true);
432                 return aaiClient.doAaiGet("uri", false, propagateExceptions1).getResponse();
433             } catch (IllegalAccessException e) {
434                 throw new RuntimeException(e);
435             }
436         };
437
438         // verify setup again
439         assertThat("mocks setup should make doAaiGet return our responseMock", doAaiGet.apply(true), is(sameInstance(responseMock)));
440
441
442         /// TEST:
443         setupMocks.accept(httpsAuthClientMock, javaxClientMock);
444
445         try {
446             final Response response = doAaiGet.apply(propagateExceptions);
447         } catch (Exception e) {
448             if (propagateExceptions) {
449                 assertThat("root cause incorrect for " + ExceptionUtils.getStackTrace(e), ExceptionUtils.getRootCause(e), instanceOf(expectedType));
450                 return; // ok, done
451             } else {
452                 // Verify that "don't propagate" really still work
453                 Assert.fail("calling doAaiGet when propagateExceptions is false must result with no exception", e);
454             }
455         }
456
457         // If no exception caught
458         // We're asserting that the legacy behaviour is still in place. Hopefully
459         // one day we will remove the non-propagateExceptions case
460         assertFalse(propagateExceptions, "calling doAaiGet when propagateExceptions is 'true' must result with an exception (in this test)");
461     }
462
463     @FunctionalInterface
464     public interface UncheckedBiConsumer<T, U> extends BiConsumer<T, U> {
465         @Override
466         default void accept(T t, U u) {
467             try {
468                 acceptThrows(t, u);
469             } catch (Exception e) {
470                 throw new RuntimeException(e);
471             }
472         }
473
474         void acceptThrows(T t, U u) throws Exception;
475     }
476 }