Topology tree: enrich vfModules data from other versions of same model 84/102084/3
authorIttay Stern <ittay.stern@att.com>
Wed, 12 Feb 2020 10:28:59 +0000 (12:28 +0200)
committerIttay Stern <ittay.stern@att.com>
Thu, 20 Feb 2020 17:12:02 +0000 (19:12 +0200)
Depends on FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS

Issue-ID: VID-771

Change-Id: Ib25c6cf7269614f2f4d332b3aa84b3307a59ebda
Signed-off-by: Ittay Stern <ittay.stern@att.com>
vid-app-common/src/main/java/org/onap/vid/properties/Features.java
vid-app-common/src/main/java/org/onap/vid/services/AAIServiceTree.java
vid-app-common/src/main/java/org/onap/vid/services/AAITreeNodesEnricher.java
vid-app-common/src/main/java/org/onap/vid/services/VidService.java
vid-app-common/src/main/java/org/onap/vid/services/VidServiceImpl.java
vid-app-common/src/test/java/org/onap/vid/services/AAIServiceIntegrativeTest.java
vid-app-common/src/test/java/org/onap/vid/services/AAIServiceTreeIntegrativeTest.java
vid-app-common/src/test/java/org/onap/vid/services/AAITreeNodesEnricherTest.java

index 219b65d..939684c 100644 (file)
@@ -88,6 +88,7 @@ public enum Features implements Feature {
     FLAG_2006_USER_PERMISSIONS_BY_OWNING_ENTITY,
     FLAG_2006_LIMIT_OWNING_ENTITY_SELECTION_BY_ROLES,
     FLAG_2006_VFMODULE_TAKES_TENANT_AND_REGION_FROM_VNF,
+    FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS,
 
     ;
 
index 579fd09..5bfcc5f 100644 (file)
@@ -32,8 +32,6 @@ import java.util.stream.Stream;
 import javax.inject.Inject;
 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
 import org.onap.vid.aai.util.AAITreeConverter;
-import org.onap.vid.asdc.AsdcCatalogException;
-import org.onap.vid.exceptions.GenericUncheckedException;
 import org.onap.vid.model.ServiceModel;
 import org.onap.vid.model.aaiTree.AAITreeNode;
 import org.onap.vid.model.aaiTree.NodeType;
@@ -122,11 +120,13 @@ public class AAIServiceTree {
         //Populate nodes with model-name & model-version (from aai)
         aaiTreeNodesEnricher.enrichNodesWithModelVersionAndModelName(nodesAccumulator);
 
-        final ServiceModel serviceModel = getServiceModel(aaiTree.getModelVersionId());
+        final ServiceModel serviceModel = sdcService.getServiceModelOrThrow(aaiTree.getModelVersionId());
 
         //Populate nodes with model-customization-name (from sdc model)
         aaiTreeNodesEnricher.enrichNodesWithModelCustomizationName(nodesAccumulator, serviceModel);
 
+        aaiTreeNodesEnricher.enrichVfModulesWithModelCustomizationNameFromOtherVersions(nodesAccumulator, aaiTree.getModelInvariantId());
+
         return aaiTreeConverter.convertTreeToUIModel(aaiTree, globalCustomerId, serviceType, getInstantiationType(serviceModel), getInstanceRole(serviceModel), getInstanceType(serviceModel));
     }
 
@@ -163,18 +163,6 @@ public class AAIServiceTree {
         }
     }
 
-    private ServiceModel getServiceModel(String modelVersionId) {
-        try {
-            final ServiceModel serviceModel = sdcService.getService(modelVersionId);
-            if (serviceModel == null) {
-                throw new GenericUncheckedException("Model version '" + modelVersionId + "' not found");
-            }
-            return serviceModel;
-        } catch (AsdcCatalogException e) {
-            throw new GenericUncheckedException("Exception while loading model version '" + modelVersionId + "'", e);
-        }
-    }
-
     public static class AaiRelationship {
 
         public final String type;
index e1e35cb..97bc423 100644 (file)
 
 package org.onap.vid.services;
 
+import static java.util.Collections.emptyMap;
 import static java.util.stream.Collectors.toSet;
+import static org.apache.commons.lang3.StringUtils.isAllEmpty;
 import static org.onap.vid.utils.KotlinUtilsKt.JACKSON_OBJECT_MAPPER;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Predicate;
 import javax.inject.Inject;
 import javax.ws.rs.core.Response;
 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
 import org.onap.vid.aai.AaiClientInterface;
+import org.onap.vid.aai.model.ModelVer;
 import org.onap.vid.asdc.parser.ServiceModelInflator;
 import org.onap.vid.asdc.parser.ServiceModelInflator.Names;
 import org.onap.vid.model.ServiceModel;
 import org.onap.vid.model.aaiTree.AAITreeNode;
+import org.onap.vid.model.aaiTree.NodeType;
+import org.onap.vid.properties.Features;
 import org.springframework.stereotype.Component;
+import org.togglz.core.manager.FeatureManager;
 
 @Component
 public class AAITreeNodesEnricher {
 
     private final AaiClientInterface aaiClient;
 
+    private final VidService sdcService;
+
     private final ServiceModelInflator serviceModelInflator;
 
+    private final FeatureManager featureManager;
+
     private static final EELFLoggerDelegate LOGGER = EELFLoggerDelegate.getLogger(AAITreeNodesEnricher.class);
 
     @Inject
     public AAITreeNodesEnricher(
         AaiClientInterface aaiClient,
+        VidService sdcService,
+        FeatureManager featureManager,
         ServiceModelInflator serviceModelInflator
     ) {
         this.aaiClient = aaiClient;
+        this.sdcService = sdcService;
+        this.featureManager = featureManager;
         this.serviceModelInflator = serviceModelInflator;
     }
 
@@ -70,6 +87,71 @@ public class AAITreeNodesEnricher {
         });
     }
 
+    void enrichVfModulesWithModelCustomizationNameFromOtherVersions(Collection<AAITreeNode> nodes, String modelInvariantId) {
+        if (!featureManager.isActive(Features.FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS)) {
+            return;
+        }
+
+        if (nodes.stream().noneMatch(vfModuleWithMissingData())) {
+            return;
+        }
+
+        final List<ModelVer> allModelVersions = aaiClient.getSortedVersionsByInvariantId(modelInvariantId);
+
+        final ListIterator<ModelVer> modelVersionsIterator = allModelVersions.listIterator();
+        final Map<String, Names> namesByCustomizationId = new HashMap<>();
+
+        nodes.stream().filter(vfModuleWithMissingData()).forEach(node -> {
+            String modelCustomizationId = node.getModelCustomizationId();
+
+            fetchCustomizationIdsFromToscaModelsWhileNeeded(namesByCustomizationId, modelVersionsIterator, modelCustomizationId);
+
+            final Names names = namesByCustomizationId.get(modelCustomizationId);
+            if (names != null) {
+                node.setKeyInModel(names.getModelKey());
+                node.setModelCustomizationName(names.getModelCustomizationName());
+            }
+        });
+    }
+
+    private Predicate<AAITreeNode> vfModuleWithMissingData() {
+        final Predicate<AAITreeNode> isVfModule = node -> node.getType() == NodeType.VF_MODULE;
+
+        final Predicate<AAITreeNode> nodeWithMissingData =
+            node -> isAllEmpty(node.getKeyInModel(), node.getModelCustomizationName());
+
+        return isVfModule.and(nodeWithMissingData);
+    }
+
+    /**
+     * Loads inOutMutableNamesByCustomizationId with all customization IDs from the list of modelVersions. Will seize loading
+     * if yieldCustomizationId presents in inOutMutableNamesByCustomizationId.
+     * @param inOutMutableNamesByCustomizationId Mutable Map to fill-up
+     * @param modelVersions Iterable of model-version-ids to load
+     * @param yieldCustomizationId The key to stop loading on
+     */
+    private void fetchCustomizationIdsFromToscaModelsWhileNeeded(
+        Map<String, Names> inOutMutableNamesByCustomizationId, ListIterator<ModelVer> modelVersions, String yieldCustomizationId
+    ) {
+        while (modelVersions.hasNext() && !inOutMutableNamesByCustomizationId.containsKey(yieldCustomizationId)) {
+            inOutMutableNamesByCustomizationId.putAll(
+                fetchAllCustomizationIds(modelVersions.next().getModelVersionId())
+            );
+        }
+    }
+
+    private Map<String, Names> fetchAllCustomizationIds(String modelVersionId) {
+        try {
+            ServiceModel serviceModel = sdcService.getServiceModelOrThrow(modelVersionId);
+            return serviceModelInflator.toNamesByCustomizationId(serviceModel);
+        } catch (Exception e) {
+            // Ignore the failure: SDC may lack the historic model, but this is NOT a reason to fail the whole enrichment
+            LOGGER.debug(EELFLoggerDelegate.debugLogger,
+                "Could not get model customization ids from SCD where modelVersionId={}", modelVersionId, e);
+            return emptyMap();
+        }
+    }
+
     public void enrichNodesWithModelVersionAndModelName(Collection<AAITreeNode> nodes) {
 
         Collection<String> invariantIDs = getModelInvariantIds(nodes);
index f7bc1f2..30802f9 100644 (file)
@@ -23,10 +23,13 @@ package org.onap.vid.services;
 
 import org.onap.vid.asdc.AsdcCatalogException;
 import org.onap.vid.model.ServiceModel;
+import org.springframework.lang.NonNull;
 
 public interface VidService extends ProbeInterface {
 
        ServiceModel getService(String uuid) throws AsdcCatalogException;
 
+       @NonNull ServiceModel getServiceModelOrThrow(String modelVersionId);
+
     void invalidateServiceCache();
 }
index a5988a1..ef62bf9 100644 (file)
@@ -52,6 +52,7 @@ import org.onap.vid.properties.VidProperties;
 import org.onap.vid.utils.Logging;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpMethod;
+import org.springframework.lang.NonNull;
 import org.togglz.core.manager.FeatureManager;
 
 /**
@@ -99,11 +100,6 @@ public class VidServiceImpl implements VidService {
                 });
     }
 
-    /*
-     * (non-Javadoc)
-     *
-     * @see org.onap.vid.controller.VidService#getService(java.lang.String)
-     */
     @Override
     public ServiceModel getService(String uuid) throws AsdcCatalogException {
         if (featureManager.isActive(FLAG_SERVICE_MODEL_CACHE)) {
@@ -113,6 +109,20 @@ public class VidServiceImpl implements VidService {
         }
     }
 
+    @NonNull
+    @Override
+    public ServiceModel getServiceModelOrThrow(String modelVersionId) {
+        try {
+            final ServiceModel serviceModel = getService(modelVersionId);
+            if (serviceModel == null) {
+                throw new GenericUncheckedException("Model version '" + modelVersionId + "' not found");
+            }
+            return serviceModel;
+        } catch (AsdcCatalogException e) {
+            throw new GenericUncheckedException("Exception while loading model version '" + modelVersionId + "'", e);
+        }
+    }
+
     private ServiceModel getServiceFromCache(String uuid) throws AsdcCatalogException {
         try {
             return serviceModelCache.get(uuid);
index e447ac7..934a471 100644 (file)
@@ -65,7 +65,7 @@ public class AAIServiceIntegrativeTest extends TestWithAaiClient {
         ExecutorService executorService = MoreExecutors.newDirectExecutorService();
         AAIServiceTree aaiServiceTree = new AAIServiceTree(
             new AAITreeNodeBuilder(aaiClient, logging),
-            new AAITreeNodesEnricher(aaiClient, null),
+            new AAITreeNodesEnricher(aaiClient, null, null, null),
             new AAITreeConverter(new ModelUtil()), null,
             executorService
         );
index 02f127d..03b5b24 100644 (file)
@@ -56,6 +56,7 @@ import org.onap.vid.model.aaiTree.AAITreeNode;
 import org.onap.vid.model.aaiTree.FailureAAITreeNode;
 import org.onap.vid.model.aaiTree.ServiceInstance;
 import org.onap.vid.model.aaiTree.Vnf;
+import org.onap.vid.properties.Features;
 import org.onap.vid.testUtils.TestUtils;
 import org.onap.vid.utils.Logging;
 import org.springframework.http.HttpMethod;
@@ -304,7 +305,10 @@ public class AAIServiceTreeIntegrativeTest {
         TestUtils.initMockitoMocks(this);
         reboundLoggingWithMdcMock();
         aaiTreeNodeBuilder = new AAITreeNodeBuilder(aaiClient, logging);
-        aaiTreeNodesEnricher = new AAITreeNodesEnricher(aaiClient, serviceModelInflator);
+        aaiTreeNodesEnricher = new AAITreeNodesEnricher(aaiClient, null, featureManager, serviceModelInflator);
+
+        when(featureManager.isActive(Features.FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS))
+            .thenReturn(true);
     }
 
     private void reboundLoggingWithMdcMock() {
@@ -320,7 +324,7 @@ public class AAIServiceTreeIntegrativeTest {
 
         when(aaiGetVersionByInvariantIdResponse.readEntity(String.class)).thenReturn(getVersionByInvariantIdResponseString);
 
-        when(sdcService.getService(any())).thenReturn(mock(ServiceModel.class));
+        when(sdcService.getServiceModelOrThrow(any())).thenReturn(mock(ServiceModel.class));
         when(serviceModelInflator.toNamesByVersionId(any())).thenReturn(ImmutableMap.of(
                  "11c6dc3e-cd6a-41b3-a50e-b5a10f7157d0", new ServiceModelInflator.Names("vnf-model-customization-name", "vnf-key-in-model")
         ));
@@ -372,11 +376,11 @@ public class AAIServiceTreeIntegrativeTest {
         when(aaiGetVersionByInvariantIdResponse.readEntity(String.class)).
                 thenReturn(TestUtils.readFileAsString("/getTopology/serviceWithCR/service-design-and-creation.json"));
 
-        when(sdcService.getService(any())).thenReturn(
+        when(sdcService.getServiceModelOrThrow(any())).thenReturn(
                 TestUtils.readJsonResourceFileAsObject("/getTopology/serviceWithCR/serviceWithCRModel.json", ServiceModel.class));
 
         ServiceInstance serviceInstance = new AAIServiceTree(aaiTreeNodeBuilder,
-            new AAITreeNodesEnricher(aaiClient, new ServiceModelInflator()), aaiTreeConverter, sdcService, executorService)
+            new AAITreeNodesEnricher(aaiClient, null, featureManager, new ServiceModelInflator()), aaiTreeConverter, sdcService, executorService)
                 .getServiceInstanceTopology("a9a77d5a-123e-4ca2-9eb9-0b015d2ee0fb", "Emanuel", "a565e6ad-75d1-4493-98f1-33234b5c17e2");
 
         String expected = TestUtils.readFileAsString("/getTopology/serviceWithCR/getTopologyWithCR.json");
@@ -440,7 +444,7 @@ public class AAIServiceTreeIntegrativeTest {
 
         when(aaiGetVersionByInvariantIdResponse.readEntity(String.class)).thenReturn(getVersionByInvariantIdResponseString);
 
-        when(sdcService.getService(any())).thenReturn(mock(ServiceModel.class));
+        when(sdcService.getServiceModelOrThrow(any())).thenReturn(mock(ServiceModel.class));
         when(serviceModelInflator.toNamesByVersionId(any())).thenReturn(ImmutableMap.of());
 
         new AAIServiceTree(aaiTreeNodeBuilder, aaiTreeNodesEnricher, aaiTreeConverter, sdcService, executorService)
index 8aba279..2972256 100644 (file)
@@ -23,26 +23,36 @@ package org.onap.vid.services;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptyMap;
 import static java.util.stream.Collectors.toList;
+import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
 import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
+import java.util.Collection;
 import java.util.List;
+import net.javacrumbs.jsonunit.core.Option;
 import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.jetbrains.annotations.NotNull;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.onap.vid.aai.AaiClientInterface;
+import org.onap.vid.aai.model.ModelVer;
 import org.onap.vid.asdc.parser.ServiceModelInflator;
 import org.onap.vid.asdc.parser.ServiceModelInflator.Names;
+import org.onap.vid.exceptions.GenericUncheckedException;
+import org.onap.vid.model.ServiceModel;
 import org.onap.vid.model.aaiTree.AAITreeNode;
-import org.testng.annotations.BeforeTest;
+import org.onap.vid.model.aaiTree.NodeType;
+import org.onap.vid.properties.Features;
+import org.onap.vid.testUtils.TestUtils;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import org.togglz.core.manager.FeatureManager;
 
@@ -59,9 +69,9 @@ public class AAITreeNodesEnricherTest {
     @InjectMocks
     private AAITreeNodesEnricher aaiTreeNodesEnricher;
 
-    @BeforeTest
+    @BeforeMethod
     public void initMocks() {
-        MockitoAnnotations.initMocks(this);
+        TestUtils.initMockitoMocks(this);
     }
 
     private final static String nullString = "null placeholder";
@@ -142,6 +152,150 @@ public class AAITreeNodesEnricherTest {
         assertThat(toStrings(nodesUnderTest), containsInAnyOrder(toStringsArray(nodesWithVersionIdsAndCustomizationNames(versionIds, expectedNames))));
     }
 
+    @DataProvider
+    public static Object[][] enrichVfModulesFilteredOutCases() {
+        AAITreeNode volumeGroup = new AAITreeNode();
+        volumeGroup.setType(NodeType.VOLUME_GROUP);
+
+        AAITreeNode vfModuleWithoutData = new AAITreeNode();
+        vfModuleWithoutData.setType(NodeType.VF_MODULE);
+
+        AAITreeNode vfModuleWithModelCustomizationName = new AAITreeNode();
+        vfModuleWithModelCustomizationName.setType(NodeType.VF_MODULE);
+        vfModuleWithModelCustomizationName.setModelCustomizationName("foo");
+
+        AAITreeNode vfModuleWithKeyInModel = new AAITreeNode();
+        vfModuleWithKeyInModel.setType(NodeType.VF_MODULE);
+        vfModuleWithKeyInModel.setKeyInModel("foo");
+
+        return new Object[][]{
+            {"no nodes", null, true},
+            {"no vfmodules", volumeGroup, true},
+            {"flag is off", vfModuleWithoutData, false},
+            {"all vfmodules with either getKeyInModel or getModelCustomizationId", vfModuleWithModelCustomizationName, true},
+            {"all vfmodules with either getKeyInModel or getModelCustomizationId", vfModuleWithKeyInModel, true},
+        };
+    }
+
+    @Test(dataProvider = "enrichVfModulesFilteredOutCases")
+    public void enrichVfModulesWithModelCustomizationNameFromOtherVersions_givenFilteredOutCases_doNothing(String reasonToSkip, AAITreeNode node, boolean flagState) {
+        when(featureManager.isActive(Features.FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS))
+            .thenReturn(flagState);
+
+        when(aaiClient.getSortedVersionsByInvariantId(any()))
+            .thenThrow(new AssertionError("did not expect reaching getSortedVersionsByInvariantId"));
+
+        List<AAITreeNode> nodes = (node == null) ? emptyList() : ImmutableList.of(node);
+        aaiTreeNodesEnricher.enrichVfModulesWithModelCustomizationNameFromOtherVersions(nodes, "modelInvariantId");
+    }
+
+    @FunctionalInterface
+    public interface Creator<T, R> {
+        R by(T t);
+    }
+
+    @Test
+    public void enrichVfModulesWithModelCustomizationNameFromOtherVersions() {
+        /*
+        Verifies the following
+
+        [*] aaiClient.getSortedVersionsByInvariantId response is exhausted
+            and all models fetched from sdc
+            -> we can see that model #1 info is populated in nodes
+        [*] relevant nodes enriched
+        [*] where data not found: nodes not enriched
+        [*] where data there already: node left pristine
+        [*] where node is not vfmodule: node left pristine
+
+         */
+
+        String modelInvariantId = "modelInvariantId";
+
+        ///////////// HELPERS
+
+        Creator<Integer, AAITreeNode> nodeMissingData = n -> {
+            AAITreeNode node = new AAITreeNode();
+            node.setType(NodeType.VF_MODULE);
+            node.setModelCustomizationId("model-customization-id-" + n);
+            return node;
+        };
+
+        Creator<Integer, AAITreeNode> nodeWithData = n -> {
+            AAITreeNode node = nodeMissingData.by(n);
+            node.setModelCustomizationName("modelCustomizationName-" + n);
+            node.setKeyInModel("modelKey-" + n);
+            return node;
+        };
+
+        Creator<Integer, ImmutableMap<String, Names>> namesMap = n -> ImmutableMap.of(
+            "model-customization-id-" + n,
+            new Names("modelCustomizationName-" + n, "modelKey-" + n)
+        );
+
+        Creator<Integer, ModelVer> modelVer = n -> {
+            ModelVer m = new ModelVer();
+            m.setModelVersion("model-version-" + n);
+            m.setModelVersionId("model-version-id-" + n);
+            return m;
+        };
+
+        ///////////// SET-UP
+
+        when(featureManager.isActive(Features.FLAG_EXP_TOPOLOGY_TREE_VFMODULE_NAMES_FROM_OTHER_TOSCA_VERSIONS))
+            .thenReturn(true);
+
+        /*
+        +-------------+----------+----------+----------+-----------+-----------+
+        |             | model v1 | model v2 | model v3 | model v77 | model v99 |
+        +-------------+----------+----------+----------+-----------+-----------+
+        | in AAI list |   v      |   v      |   v      |   v       |           |
+        | in SDC      |   v      |   v      |   v      |           |           |
+        | in nodes    |   v      |          |   v      |   v       |   v       |
+        +-------------+----------+----------+----------+-----------+-----------+
+         */
+        when(aaiClient.getSortedVersionsByInvariantId(modelInvariantId))
+            .thenReturn(ImmutableList.of(modelVer.by(3), modelVer.by(77), modelVer.by(2), modelVer.by(1)));
+
+        ServiceModel serviceModel_1 = mock(ServiceModel.class);
+        when(sdcService.getServiceModelOrThrow("model-version-id-1")).thenReturn(serviceModel_1);
+        when(serviceModelInflator.toNamesByCustomizationId(serviceModel_1)).thenReturn(namesMap.by(1));
+
+        ServiceModel serviceModel_2 = mock(ServiceModel.class);
+        when(sdcService.getServiceModelOrThrow("model-version-id-2")).thenReturn(serviceModel_2);
+        when(serviceModelInflator.toNamesByCustomizationId(serviceModel_2)).thenReturn(namesMap.by(2));
+
+        ServiceModel serviceModel_3 = mock(ServiceModel.class);
+        when(sdcService.getServiceModelOrThrow("model-version-id-3")).thenReturn(serviceModel_3);
+        when(serviceModelInflator.toNamesByCustomizationId(serviceModel_3)).thenReturn(namesMap.by(3));
+
+        when(sdcService.getServiceModelOrThrow("model-version-id-77")).thenThrow(GenericUncheckedException.class);
+
+        AAITreeNode nodeWithDataAlready = nodeMissingData.by(1);
+        nodeWithDataAlready.setModelCustomizationName("significant-customization-name");
+        nodeWithDataAlready.setKeyInModel("significant-key-in-model");
+
+        AAITreeNode nodeNotVfModule = nodeMissingData.by(1);
+        nodeNotVfModule.setType(NodeType.GENERIC_VNF);
+
+        Collection<AAITreeNode> nodes = ImmutableList.of(
+            nodeMissingData.by(1), nodeMissingData.by(77),
+            nodeMissingData.by(3), nodeMissingData.by(99),
+            nodeWithDataAlready, nodeNotVfModule
+        );
+
+        ///////////// TEST
+
+        aaiTreeNodesEnricher.enrichVfModulesWithModelCustomizationNameFromOtherVersions(nodes, modelInvariantId);
+
+        assertThat(nodes, jsonEquals(ImmutableList.of(
+            nodeWithData.by(1),
+            nodeWithData.by(3),
+            nodeMissingData.by(77), // not in sdcService
+            nodeMissingData.by(99), // not in aaiClient
+            nodeWithDataAlready, // pristine
+            nodeNotVfModule // pristine
+        )).when(Option.IGNORING_ARRAY_ORDER));
+    }
 
 
     @NotNull