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