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