blob: 0a3536a4d66edca4bdb128e95889ac7e53c72d43 [file] [log] [blame]
/*-
* ============LICENSE_START=======================================================
* OSAM
* ================================================================================
* Copyright (C) 2018 AT&T
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============LICENSE_END=========================================================
*/
package org.onap.osam.aai;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.mockito.Mockito;
import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
import org.onap.portalsdk.core.util.SystemProperties;
import org.onap.osam.aai.model.AaiGetTenatns.GetTenantsResponse;
import org.onap.osam.aai.model.AaiNodeQueryResponse;
import org.onap.osam.aai.model.ResourceType;
import org.onap.osam.aai.util.AAIRestInterface;
import org.onap.osam.aai.util.HttpsAuthClient;
import org.onap.osam.aai.util.ServletRequestHelper;
import org.onap.osam.aai.util.SystemPropertyHelper;
import org.onap.osam.controllers.LocalWebConfig;
import org.onap.osam.exceptions.GenericUncheckedException;
import org.onap.osam.model.Subscriber;
import org.onap.osam.model.SubscriberList;
import org.onap.osam.model.probes.ExternalComponentStatus;
import org.onap.osam.model.probes.HttpRequestMetadata;
import org.onap.osam.model.probes.StatusMetadata;
import org.onap.osam.testUtils.TestUtils;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import sun.security.provider.certpath.SunCertPathBuilderException;
import sun.security.validator.ValidatorException;
import javax.crypto.BadPaddingException;
import javax.net.ssl.SSLHandshakeException;
import javax.servlet.ServletContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Response;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.*;
@ContextConfiguration(classes = {LocalWebConfig.class, SystemProperties.class})
@WebAppConfiguration
public class AaiClientTest {
private AaiClient aaiClientMock;
private ServletContext servletContext;
@BeforeMethod
public void initMocks(){
aaiClientMock = mock(AaiClient.class);
aaiClientMock.logger = mock(EELFLoggerDelegate.class);
servletContext = mock(ServletContext.class);
when(servletContext.getRealPath(any(String.class))).thenReturn("");
when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(null);
}
@DataProvider
public static Object[][] logicalLinkData() {
return new Object[][] {
{"", "network/logical-links/logical-link/"},
{"link", "network/logical-links/logical-link/link"}
};
}
@Test(dataProvider = "logicalLinkData")
public void getLogicalLink_Link_Is_Empty(String link, String expectedUrl) {
when(aaiClientMock.getLogicalLink(any(String.class))).thenCallRealMethod();
aaiClientMock.getLogicalLink(link);
Mockito.verify(aaiClientMock).doAaiGet(argThat(equalToIgnoringCase(expectedUrl)),any(Boolean.class));
}
@DataProvider
public static Object[][] subscribersResults() {
return new Object[][] {
{new SubscriberList(new ArrayList<Subscriber>() {{ add(new Subscriber()); add(new Subscriber()); }}), true},
{new SubscriberList(new ArrayList<Subscriber>() {{ add(new Subscriber()); }}), true},
{new SubscriberList(new ArrayList<Subscriber>()), false}
};
}
@Test(dataProvider = "subscribersResults")
public void testProbeAaiGetAllSubscribers_returnsTwoToZeroSubscribers_ResultsAsExpected(SubscriberList subscribers, boolean isAvailable){
ExternalComponentStatus expectedStatus = new ExternalComponentStatus(ExternalComponentStatus.Component.AAI,isAvailable, new HttpRequestMetadata(
HttpMethod.GET,
200,
"url",
"rawData",
isAvailable ? "OK" : "No subscriber received",
0
));
Mockito.when(aaiClientMock.getAllSubscribers(true)).thenReturn(
new AaiResponseWithRequestInfo<>(
HttpMethod.GET, "url", new AaiResponse<>(subscribers, null, 200),
"rawData"));
Mockito.when(aaiClientMock.probeAaiGetAllSubscribers()).thenCallRealMethod();
ExternalComponentStatus result = aaiClientMock.probeAaiGetAllSubscribers();
assertThat(statusDataReflected(result),is(statusDataReflected(expectedStatus)));
assertThat(requestMetadataReflected(result.getMetadata()),is(requestMetadataReflected(expectedStatus.getMetadata())));
}
//serialize fields except of fields we cannot know ahead of time
private static String requestMetadataReflected(StatusMetadata metadata) {
return new ReflectionToStringBuilder(metadata, ToStringStyle.SHORT_PREFIX_STYLE)
.setExcludeFieldNames("duration")
.toString();
}
private static String statusDataReflected(ExternalComponentStatus status) {
return new ReflectionToStringBuilder(status, ToStringStyle.SHORT_PREFIX_STYLE)
.setExcludeFieldNames("metadata")
.toString();
}
@DataProvider
public static Object[][] rawData() {
return new Object[][]{
{"errorMessage", }, {""}, {null}
};
}
@Test(dataProvider = "rawData")
public void testProbeAaiGetFullSubscribersWithNullResponse_returnsNotAvailableWithErrorRawData(String rawData){
Mockito.when(aaiClientMock.getAllSubscribers(true)).thenReturn(
new AaiResponseWithRequestInfo<>(HttpMethod.GET, "url", null,
rawData));
ExternalComponentStatus result = callProbeAaiGetAllSubscribersAndAssertNotAvailable();
assertThat(result.getMetadata(), instanceOf(HttpRequestMetadata.class));
assertEquals(((HttpRequestMetadata) result.getMetadata()).getRawData(), rawData);
}
@DataProvider
public static Object[][] exceptions() {
return new Object[][] {
{"NullPointerException", "errorMessage",
new ExceptionWithRequestInfo(HttpMethod.GET, "url",
"errorMessage", null, new NullPointerException())},
{"RuntimeException", null,
new ExceptionWithRequestInfo(HttpMethod.GET, "url",
null, null, new RuntimeException())},
{"RuntimeException", null,
new RuntimeException()},
};
}
@Test(dataProvider = "exceptions")
public void testProbeAaiGetFullSubscribersWithNullResponse_returnsNotAvailableWithErrorRawData(String description, String expectedRawData, Exception exception){
Mockito.when(aaiClientMock.getAllSubscribers(true)).thenThrow(exception);
ExternalComponentStatus result = callProbeAaiGetAllSubscribersAndAssertNotAvailable();
if (exception instanceof ExceptionWithRequestInfo) {
assertThat(result.getMetadata(), instanceOf(HttpRequestMetadata.class));
assertEquals(((HttpRequestMetadata) result.getMetadata()).getRawData(), expectedRawData);
}
assertThat(result.getMetadata().getDescription(), containsString(description));
}
private ExternalComponentStatus callProbeAaiGetAllSubscribersAndAssertNotAvailable() {
Mockito.when(aaiClientMock.probeAaiGetAllSubscribers()).thenCallRealMethod();
ExternalComponentStatus result = aaiClientMock.probeAaiGetAllSubscribers();
assertFalse(result.isAvailable());
return result;
}
@Test
public void getTenants_Arguments_Are_Null_Or_Empty() {
when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
AaiResponse response = aaiClientMock.getTenants("", "");
assertEquals(response.getErrorMessage(), "{\"statusText\":\" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.\"}");
response = aaiClientMock.getTenants(null, null);
assertEquals(response.getErrorMessage(), "{\"statusText\":\" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.\"}");
}
@Test
public void getTenants_Arguments_Are_Valid_But_Tenants_Not_Exist() {
when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
Response generalEmptyResponse = mock(Response.class);
when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(generalEmptyResponse);
AaiResponse response = aaiClientMock.getTenants("subscriberId", "serviceType");
assertEquals(response.getErrorMessage(), "{\"statusText\":\" A&AI has no LCP Region & Tenants associated to subscriber 'subscriberId' and service type 'serviceType'\"}");
}
@Test
public void getTenants_Arguments_Are_Valid_Get_The_Tenanats() {
when(aaiClientMock.getTenants(any(String.class), any(String.class))).thenCallRealMethod();
Response generalEmptyResponse = mock(Response.class);
when(generalEmptyResponse.readEntity(String.class)).thenReturn(tenantResponseRaw);
when(generalEmptyResponse.getStatus()).thenReturn(200);
when(generalEmptyResponse.getStatusInfo()).thenReturn(new Response.StatusType() {
@Override
public int getStatusCode() {
return 200;
}
@Override
public Response.Status.Family getFamily() {
return Response.Status.Family.SUCCESSFUL;
}
@Override
public String getReasonPhrase() {
return null;
}
});
when(aaiClientMock.doAaiGet(any(String.class),any(Boolean.class))).thenReturn(generalEmptyResponse);
AaiResponse<GetTenantsResponse[]> response = aaiClientMock.getTenants("subscriberId", "serviceType");
Assert.assertTrue(response.t.length> 0);
}
final String tenantResponseRaw ="" +
"{" +
"\"service-type\": \"VIRTUAL USP\"," +
"\"resource-version\": \"1494001841964\"," +
"\"relationship-list\": {" +
"\"relationship\": [{" +
"\"related-to\": \"tenant\"," +
"\"related-link\": \"/aai/v11/cloud-infrastructure/cloud-regions/cloud-region/att-aic/AAIAIC25/tenants/tenant/092eb9e8e4b7412e8787dd091bc58e86\"," +
"\"relationship-data\": [{" +
"\"relationship-key\": \"cloud-region.cloud-owner\"," +
"\"relationship-value\": \"att-aic\"" +
"}," +
"{" +
"\"relationship-key\": \"cloud-region.cloud-region-id\"," +
"\"relationship-value\": \"AAIAIC25\"" +
"}," +
"{" +
"\"relationship-key\": \"tenant.tenant-id\"," +
"\"relationship-value\": \"092eb9e8e4b7412e8787dd091bc58e86\"" +
"}" +
"]," +
"\"related-to-property\": [{" +
"\"property-key\": \"tenant.tenant-name\"," +
"\"property-value\": \"USP-SIP-IC-24335-T-01\"" +
"}]" +
"}]" +
"}" +
"}";
@DataProvider
public static Object[][] resourceTypesProvider() {
return new Object[][] {
{"service-instance", ResourceType.SERVICE_INSTANCE},
{"generic-vnf", ResourceType.GENERIC_VNF},
{"vf-module", ResourceType.VF_MODULE}
};
}
@Test(dataProvider = "resourceTypesProvider")
public void aaiNodeQueryResponseDeserializationTest(String resourceType, ResourceType expectedResourceType) throws IOException {
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";
String json =
"{\"result-data\": [{" +
"\"resource-type\": \""+resourceType+"\"," +
"\"resource-link\": \""+ link+ "\"" +
"}]}";
AaiNodeQueryResponse nodeQueryResponse = new ObjectMapper().readValue(json, AaiNodeQueryResponse.class);
assertThat(nodeQueryResponse.resultData.get(0).resourceLink, equalTo(link));
assertThat(nodeQueryResponse.resultData.get(0).resourceType, is(expectedResourceType));
}
@Test
public void aaiNodeQueryEmptyResponseDeserializationTest() throws IOException{
String json = "{}";
AaiNodeQueryResponse nodeQueryResponse = new ObjectMapper().readValue(json, AaiNodeQueryResponse.class);
assertNull(nodeQueryResponse.resultData);
}
@DataProvider
public static Object[][] nameAndResourceTypeProvider() {
return new Object[][] {
{"SRIOV_SVC", ResourceType.SERVICE_INSTANCE, "search/nodes-query?search-node-type=service-instance&filter=service-instance-name:EQUALS:SRIOV_SVC"},
{"b1707vidnf", ResourceType.GENERIC_VNF, "search/nodes-query?search-node-type=generic-vnf&filter=vnf-name:EQUALS:b1707vidnf"},
{"connectivity_test", ResourceType.VF_MODULE, "search/nodes-query?search-node-type=vf-module&filter=vf-module-name:EQUALS:connectivity_test"},
{"MjVg1234", ResourceType.VOLUME_GROUP, "search/nodes-query?search-node-type=volume-group&filter=volume-group-name:EQUALS:MjVg1234"}
};
}
@Test(dataProvider = "nameAndResourceTypeProvider")
public void whenSearchNodeTypeByName_callRightAaiPath(String name, ResourceType type, String expectedUrl) {
when(aaiClientMock.searchNodeTypeByName(any(String.class), any(ResourceType.class))).thenCallRealMethod();
aaiClientMock.searchNodeTypeByName(name, type);
Mockito.verify(aaiClientMock).doAaiGet(eq(expectedUrl), eq(false));
}
@DataProvider
public static Object[][] aaiClientInternalExceptions() {
return Stream.<Pair<Class<? extends Throwable>, UncheckedBiConsumer<HttpsAuthClient, Client>>>of(
// Exception out of httpsAuthClientMock
Pair.of(CertificateException.class, (httpsAuthClientMock, javaxClientMock) -> {
final CertificateException e0 = new CertificateException("No X509TrustManager implementation available");
SSLHandshakeException e = new SSLHandshakeException(e0.toString());
e.initCause(e0);
when(httpsAuthClientMock.getClient(any())).thenThrow(e);
}),
Pair.of(StringIndexOutOfBoundsException.class, mockExceptionOnClientProvider(new StringIndexOutOfBoundsException(4))),
Pair.of(NullPointerException.class, mockExceptionOnClientProvider(new NullPointerException("null"))),
Pair.of(FileNotFoundException.class, mockExceptionOnClientProvider(new FileNotFoundException("vid/WEB-INF/cert/aai-client-cert.p12"))),
Pair.of(BadPaddingException.class, mockExceptionOnClientProvider(
new IOException("keystore password was incorrect", new BadPaddingException("Given final block not properly padded")))
),
Pair.of(GenericUncheckedException.class, mockExceptionOnClientProvider(new GenericUncheckedException("basa"))),
Pair.of(NullPointerException.class, (httpsAuthClientMock, javaxClientMock) ->
when(httpsAuthClientMock.getClient(any())).thenReturn(null)),
// Exception out of javax's Client
Pair.of(SSLHandshakeException.class, (httpsAuthClientMock, javaxClientMock) -> {
when(javaxClientMock.target(anyString())).thenThrow(
new ProcessingException(new SSLHandshakeException("Received fatal alert: certificate_expired"))
);
}),
Pair.of(SunCertPathBuilderException.class, (httpsAuthClientMock, javaxClientMock) -> {
SunCertPathBuilderException e0 = new SunCertPathBuilderException("unable to find valid certification path to requested target");
when(javaxClientMock.target(anyString())).thenThrow(
new ProcessingException(new ValidatorException("PKIX path building failed: " + e0.toString(), e0))
);
}),
Pair.of(GenericUncheckedException.class, (httpsAuthClientMock, javaxClientMock) ->
when(javaxClientMock.target(anyString())).thenThrow(new GenericUncheckedException("basa")))
).flatMap(l -> Stream.of(
// double each case to propagateExceptions = true/false, to verify that "don't propagate" really still work
ImmutableList.of(l.getLeft(), l.getRight(), true).toArray(),
ImmutableList.of(l.getLeft(), l.getRight(), false).toArray()
)).collect(Collectors.toList()).toArray(new Object[][]{});
}
private static UncheckedBiConsumer<HttpsAuthClient, Client> mockExceptionOnClientProvider(Exception e) {
return (httpsAuthClientMock, javaxClientMock) ->
when(httpsAuthClientMock.getClient(any())).thenThrow(e);
}
@Test(dataProvider = "aaiClientInternalExceptions")
public void propagateExceptions_internalsThrowException_ExceptionRethrown(Class<? extends Throwable> expectedType, BiConsumer<HttpsAuthClient, Client> setupMocks, boolean propagateExceptions) throws Exception {
// prepare mocks
HttpsAuthClient httpsAuthClientMock = mock(HttpsAuthClient.class);
TestUtils.JavaxRsClientMocks mocks = new TestUtils.JavaxRsClientMocks();
Client javaxClientMock = mocks.getFakeClient();
Response responseMock = mocks.getFakeResponse();
// prepare real AAIRestInterface and AaiClient, and wire mocks
AAIRestInterface aaiRestInterface = new AAIRestInterface(httpsAuthClientMock, new ServletRequestHelper(), new SystemPropertyHelper());
final AaiClient aaiClient = new AaiClient(aaiRestInterface, null);
when(httpsAuthClientMock.getClient(any())).thenReturn(javaxClientMock);
// define atomic method under test, including reset of "aaiRestInterface.client"
final Function<Boolean, Response> doAaiGet = (propagateExceptions1) -> {
try {
FieldUtils.writeField(aaiRestInterface, "client", null, true);
return aaiClient.doAaiGet("uri", false, propagateExceptions1).getResponse();
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
};
// verify setup again
assertThat("mocks setup should make doAaiGet return our responseMock", doAaiGet.apply(true), is(sameInstance(responseMock)));
/// TEST:
setupMocks.accept(httpsAuthClientMock, javaxClientMock);
try {
final Response response = doAaiGet.apply(propagateExceptions);
} catch (Exception e) {
if (propagateExceptions) {
assertThat("root cause incorrect for " + ExceptionUtils.getStackTrace(e), ExceptionUtils.getRootCause(e), instanceOf(expectedType));
return; // ok, done
} else {
// Verify that "don't propagate" really still work
Assert.fail("calling doAaiGet when propagateExceptions is false must result with no exception", e);
}
}
// If no exception caught
// We're asserting that the legacy behaviour is still in place. Hopefully
// one day we will remove the non-propagateExceptions case
assertFalse(propagateExceptions, "calling doAaiGet when propagateExceptions is 'true' must result with an exception (in this test)");
}
@FunctionalInterface
public interface UncheckedBiConsumer<T, U> extends BiConsumer<T, U> {
@Override
default void accept(T t, U u) {
try {
acceptThrows(t, u);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
void acceptThrows(T t, U u) throws Exception;
}
}