Registration Response for Create cmhandles operations 06/128106/2
authorRenu Kumari <renu.kumari@bell.ca>
Wed, 23 Mar 2022 20:50:14 +0000 (16:50 -0400)
committerRenu Kumari <renu.kumari@bell.ca>
Mon, 28 Mar 2022 03:40:18 +0000 (23:40 -0400)
- Changed implementation to register each cm-handle at a time
  instead of registering all at once
- Removed YangCMHandleList class and using String format to
  generated expected JSON
- Response of all three operation types is returned in the
  updateDmiRegistrationAndSyncModule method
- Changed parseAndRemoveCmHandlesInDmiRegistration to protected
  to keep in sync with create flow
- Refactored existing create CMHandle test cases

Issue-ID: CPS-896
Signed-off-by: Renu Kumari <renu.kumari@bell.ca>
Change-Id: I5b0c01b3b8e31ca7c257b1e04069e35268be1132

cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy

index 576c45c..c3369d8 100755 (executable)
@@ -31,7 +31,6 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMES
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -54,14 +53,13 @@ import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
 import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
 import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandlesList;
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
-import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.utils.JsonObjectMapper;
@@ -101,22 +99,16 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         final DmiPluginRegistration dmiPluginRegistration) {
         dmiPluginRegistration.validateDmiPluginRegistration();
         final var dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
-        try {
-            dmiPluginRegistrationResponse.setRemovedCmHandles(
-                parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles()));
-            if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
-                parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration);
-            }
-            if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
-                dmiPluginRegistrationResponse.setUpdatedCmHandles(
-                    networkCmProxyDataServicePropertyHandler
-                        .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
-            }
-        } catch (final JsonProcessingException | DataNodeNotFoundException e) {
-            final String errorMessage = String.format(
-                    "Error occurred while processing the CM-handle registration request, caused by : [%s]",
-                    e.getMessage());
-            throw new DataValidationException(errorMessage, e.getMessage(), e);
+        dmiPluginRegistrationResponse.setRemovedCmHandles(
+            parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles()));
+        if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setCreatedCmHandles(
+                parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration));
+        }
+        if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setUpdatedCmHandles(
+                networkCmProxyDataServicePropertyHandler
+                    .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
         }
         return dmiPluginRegistrationResponse;
     }
@@ -215,14 +207,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * THis method registers a cm handle and initiates modules sync.
      *
      * @param dmiPluginRegistration dmi plugin registration information.
-     * @throws JsonProcessingException thrown if json is malformed or missing.
+     * @return cm-handle registration response for create cm-handle requests.
      */
-    public void parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
-        final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException {
-        final YangModelCmHandlesList createdYangModelCmHandlesList =
-            getUpdatedYangModelCmHandlesList(dmiPluginRegistration,
-                dmiPluginRegistration.getCreatedCmHandles());
-        registerAndSyncNewCmHandles(createdYangModelCmHandlesList);
+    public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
+        final DmiPluginRegistration dmiPluginRegistration) {
+        return dmiPluginRegistration.getCreatedCmHandles().stream()
+            .map(cmHandle ->
+                YangModelCmHandle.toYangModelCmHandle(
+                    dmiPluginRegistration.getDmiPlugin(),
+                    dmiPluginRegistration.getDmiDataPlugin(),
+                    dmiPluginRegistration.getDmiModelPlugin(), cmHandle)
+            )
+            .map(this::registerAndSyncNewCmHandle)
+            .collect(Collectors.toList());
     }
 
     private static Object handleResponse(final ResponseEntity<?> responseEntity,
@@ -236,23 +233,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private YangModelCmHandlesList getUpdatedYangModelCmHandlesList(
-        final DmiPluginRegistration dmiPluginRegistration,
-        final List<NcmpServiceCmHandle> updatedCmHandles) {
-        return YangModelCmHandlesList.toYangModelCmHandlesList(
-            dmiPluginRegistration.getDmiPlugin(),
-            dmiPluginRegistration.getDmiDataPlugin(),
-            dmiPluginRegistration.getDmiModelPlugin(),
-            updatedCmHandles);
-    }
-
-    private void registerAndSyncNewCmHandles(final YangModelCmHandlesList yangModelCmHandlesList) {
-        final String cmHandleJsonData = jsonObjectMapper.asJsonString(yangModelCmHandlesList);
-        cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+    private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) {
+        try {
+            final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
+                jsonObjectMapper.asJsonString(yangModelCmHandle));
+            cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
                 cmHandleJsonData, NO_TIMESTAMP);
-
-        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandlesList.getYangModelCmHandles()) {
             syncModulesAndCreateAnchor(yangModelCmHandle);
+            return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId());
+        } catch (final AlreadyDefinedException alreadyDefinedException) {
+            return CmHandleRegistrationResponse.createFailureResponse(
+                yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST);
+        } catch (final Exception exception) {
+            return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception);
         }
     }
 
@@ -261,7 +254,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         createAnchor(yangModelCmHandle);
     }
 
-    private List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration(
+    protected List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration(
         final List<String> tobeRemovedCmHandles) {
         final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
             new ArrayList<>(tobeRemovedCmHandles.size());
index 47062b3..e46b9e3 100644 (file)
@@ -21,6 +21,8 @@
 
 package org.onap.cps.ncmp.api.impl.yangmodels;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Strings;
 import java.util.ArrayList;
@@ -41,6 +43,7 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 @Getter
 @Setter
 @NoArgsConstructor
+@JsonInclude(Include.NON_NULL)
 public class YangModelCmHandle {
 
     private String id;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java
deleted file mode 100644 (file)
index 261a018..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
- *  ================================================================================
- *  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.
- *
- *  SPDX-License-Identifier: Apache-2.0
- *  ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.ncmp.api.impl.yangmodels;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import lombok.Getter;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-
-@Getter
-public class YangModelCmHandlesList {
-
-    @JsonProperty("cm-handles")
-    private final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>();
-
-    /**
-     * Create a YangModelCmHandleList given all service names and a collection of cmHandles.
-     * @param dmiServiceName the dmi service name
-     * @param dmiDataServiceName the dmi data service name
-     * @param dmiModelServiceName the dmi model service name
-     * @param ncmpServiceCmHandles cm handles rest model
-     * @return instance of YangModelCmHandleList
-     */
-    public static YangModelCmHandlesList toYangModelCmHandlesList(final String dmiServiceName,
-                                                                  final String dmiDataServiceName,
-                                                                  final String dmiModelServiceName,
-                                                                  final Collection<NcmpServiceCmHandle>
-                                                            ncmpServiceCmHandles) {
-        final YangModelCmHandlesList yangModelCmHandlesList = new YangModelCmHandlesList();
-        for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
-            final YangModelCmHandle yangModelCmHandle =
-                YangModelCmHandle.toYangModelCmHandle(
-                    dmiServiceName,
-                    dmiDataServiceName,
-                    dmiModelServiceName,
-                    ncmpServiceCmHandle);
-            yangModelCmHandlesList.add(yangModelCmHandle);
-        }
-        return yangModelCmHandlesList;
-    }
-
-    /**
-     * Add a yangModelCmHandle.
-     *
-     * @param yangModelCmHandle the yangModelCmHandle to add
-     */
-    public void add(final YangModelCmHandle yangModelCmHandle) {
-        yangModelCmHandles.add(yangModelCmHandle);
-    }
-}
index 23d2438..e7c1d05 100644 (file)
@@ -21,9 +21,8 @@
 
 package org.onap.cps.ncmp.api.impl
 
-import com.fasterxml.jackson.core.JsonProcessingException
 import com.fasterxml.jackson.databind.ObjectMapper
-import java.util.function.Predicate
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
@@ -34,15 +33,17 @@ import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
-import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
 import spock.lang.Specification
 
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
 
 class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
@@ -75,73 +76,189 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
             // Spock validated invocation order between multiple then blocks
         then: 'cm-handles are removed first'
-            1 * mockCpsDataService.deleteListOrListElement(*_)
+            1 * objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_)
         then: 'cm-handles are created'
-            1 * mockCpsDataService.saveListElements(*_)
+            1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_)
         then: 'cm-handles are updated'
             1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_)
     }
 
-    def 'Register or re-register a DMI Plugin for the given cm-handle(s) with #scenario process.'() {
-        given: 'a registration'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
-            ncmpServiceCmHandle.cmHandleID = '123'
-            ncmpServiceCmHandle.dmiProperties = [dmiProp1: 'dmiValue1', dmiProp2: 'dmiValue2']
-            ncmpServiceCmHandle.publicProperties = [publicProp1: 'publicValue1', publicProp2: 'publicValue2']
-            dmiPluginRegistration.createdCmHandles = createdCmHandles
-            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
-            dmiPluginRegistration.removedCmHandles = removedCmHandles
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,' +
-                '"additional-properties":[{"name":"dmiProp1","value":"dmiValue1"},{"name":"dmiProp2","value":"dmiValue2"}],' +
-                '"public-properties":[{"name":"publicProp1","value":"publicValue1"},{"name":"publicProp2","value":"publicValue2"}]' +
-                '}]}'
-        when: 'registration is updated and modules are synced'
+    def 'DMI Registration: Response from all operations types are in response'() {
+        given: 'a registration with operations of all three types'
+            def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
+        and: 'update cm-handles can be processed successfully'
+            def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')]
+            mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> updateResponses
+        and: 'create cm-handles can be processed successfully'
+            def createdResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-1')]
+            objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) >> createdResponses
+        and: 'delete cm-handles can be processed successfully'
+            def removeResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-3')]
+            objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) >> removeResponses
+        when: 'registration is processed'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
+        then: 'response has values from all operations'
+            response.getRemovedCmHandles() == removeResponses
+            response.getCreatedCmHandles() == createdResponses
+            response.getUpdatedCmHandles() == updateResponses
+
+
+    }
+
+    def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
+        given: 'a registration '
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+                dmiDataPlugin: dmiDataPlugin)
+            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
+        when: 'update registration and sync module is called with correct DMI plugin information'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'create cm handles registration and sync modules is called with the correct plugin information'
+            1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
+        where:
+            scenario                          | dmiPlugin  | dmiModelPlugin | dmiDataPlugin
+            'combined DMI plugin'             | 'service1' | ''             | ''
+            'data & model DMI plugins'        | ''         | 'service1'     | 'service2'
+            'data & model using same service' | ''         | 'service1'     | 'service1'
+    }
+
+    def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() {
+        given: 'a registration '
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+                dmiDataPlugin: dmiDataPlugin)
+            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
+        when: 'registration is called with incorrect DMI plugin information'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'save list elements is invoked with the expected parameters'
-            expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry', expectedJsonData, noTimestamp)
-        and: 'update data node leaves is called with correct parameters'
-            expectedCallsToUpdateCmHandleProperty * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(updatedCmHandles)
-        and: 'delete schema set is invoked with the correct parameters'
-            expectedCallsToDeleteSchemaSetAndListElement * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'cmHandle001', CASCADE_DELETE_ALLOWED)
-        and: 'delete list or list element is invoked with the correct parameters'
-            expectedCallsToDeleteSchemaSetAndListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
-                'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
+        then: 'a DMI Request Exception is thrown with correct message details'
+            def exceptionThrown = thrown(DmiRequestException.class)
+            assert exceptionThrown.getMessage().contains(expectedMessageDetails)
+        and: 'registration is not called'
+            0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
         where:
-            scenario                    | createdCmHandles      | updatedCmHandles      | removedCmHandles || expectedCallsToSaveNode | expectedCallsToDeleteSchemaSetAndListElement | expectedCallsToUpdateCmHandleProperty
-            'create'                    | [ncmpServiceCmHandle] | []                    | []               || 1                       | 0                                            | 0
-            'update'                    | []                    | [ncmpServiceCmHandle] | []               || 0                       | 0                                            | 1
-            'delete'                    | []                    | []                    | cmHandlesArray   || 0                       | 1                                            | 0
-            'create, update and delete' | [ncmpServiceCmHandle] | [ncmpServiceCmHandle] | cmHandlesArray   || 1                       | 1                                            | 1
-            'no valid data'             | []                    | []                    | []               || 0                       | 0                                            | 0
+            scenario                         | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
+            'empty DMI plugins'              | ''         | ''             | ''            || 'No DMI plugin service names'
+            'blank DMI plugins'              | ' '        | ' '            | ' '           || 'No DMI plugin service names'
+            'null DMI plugins'               | null       | null           | null          || 'No DMI plugin service names'
+            'all DMI plugins'                | 'service1' | 'service2'     | 'service3'    || 'Cannot register combined plugin service name and other service names'
+            '(combined)DMI and Data Plugin'  | 'service1' | ''             | 'service2'    || 'Cannot register combined plugin service name and other service names'
+            '(combined)DMI and model Plugin' | 'service1' | 'service2'     | ''            || 'Cannot register combined plugin service name and other service names'
+            'only model DMI plugin'          | ''         | 'service1'     | ''            || 'Cannot register just a Data or Model plugin service name'
+            'only data DMI plugin'           | ''         | ''             | 'service1'    || 'Cannot register just a Data or Model plugin service name'
     }
 
-    def 'Create CM-Handle: Register a DMI Plugin for the given cm-handle(s) without DMI properties.'() {
+    def 'Create CM-Handle Successfully: #scenario.'() {
         given: 'a registration without cm-handle properties'
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
-            ncmpServiceCmHandle.cmHandleID = '123'
-            ncmpServiceCmHandle.dmiProperties = Collections.emptyMap()
-            ncmpServiceCmHandle.publicProperties = Collections.emptyMap()
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[],"public-properties":[]}]}'
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)]
         when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'save list elements is invoked with the expected parameters'
-            1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry', expectedJsonData, noTimestamp)
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a successful response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle'
+            }
+        and: 'save list elements is invoked with the expected parameters'
+            interaction {
+                def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}"""
+                1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry', expectedJsonData, noTimestamp)
+            }
+        then: 'model sync is invoked with expected parameters'
+            1 * objectUnderTest.syncModulesAndCreateAnchor(_) >> { YangModelCmHandle yangModelCmHandle ->
+                {
+                    assert yangModelCmHandle.id == 'cmhandle'
+                    assert yangModelCmHandle.dmiServiceName == 'my-server'
+                    assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getPublicProperties()) == expectedPublicProperties
+                    assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getDmiProperties()) == expectedDmiProperties
+
+                }
+            }
+        where:
+            scenario                          | dmiProperties            | publicProperties               || expectedDmiProperties                      | expectedPublicProperties
+            'with dmi & public properties'    | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]'
+            'with only public properties'     | [:]                      | ['public-key': 'public-value'] || '[]'                                       | '[{"name":"public-key","value":"public-value"}]'
+            'with only dmi properties'        | ['dmi-key': 'dmi-value'] | [:]                            || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]'
+            'without dmi & public properties' | [:]                      | [:]                            || '[]'                                       | '[]'
+
     }
 
-    def 'Create CM-Handle: Register a DMI Plugin for a given cm-handle(s) with JSON processing errors during process.'() {
-        given: 'a registration without cm-handle properties '
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'some-plugin')
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-        and: 'an json processing exception occurs'
-            spiedJsonObjectMapper.asJsonString(_) >> { throw (new JsonProcessingException('')) }
-        when: 'registration is updated and modules are synced'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'a data validation exception is thrown'
-            thrown(DataValidationException)
+    def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() {
+        given: 'a registration with three cm-handles to be created'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                createdCmHandles: [new NcmpServiceCmHandle(cmHandleID: 'cmhandle1'),
+                                   new NcmpServiceCmHandle(cmHandleID: 'cmhandle2'),
+                                   new NcmpServiceCmHandle(cmHandleID: 'cmhandle3')])
+        and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
+            mockCpsDataService.saveListElements(_, _, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {}
+        when: 'registration is updated to create cm-handles'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a response is received for all cm-handles'
+            response.getCreatedCmHandles().size() == 3
+        and: '1st and 3rd cm-handle are created successfully'
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle1'
+            }
+            with(response.getCreatedCmHandles().get(2)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle3'
+            }
+        and: '2nd cm-handle creation fails'
+            with(response.getCreatedCmHandles().get(1)) {
+                assert it.status == Status.FAILURE
+                assert it.registrationError == UNKNOWN_ERROR
+                assert it.errorText == 'Failed'
+                assert it.cmHandle == 'cmhandle2'
+            }
+    }
+
+    def 'Create CM-Handle Error Handling: Registration fails: #scenario'() {
+        given: 'a registration without cm-handle properties'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+        and: 'cm-handler registration fails: #scenario'
+            mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception }
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a failure response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.registrationError == expectedError
+                assert it.errorText == expectedErrorText
+            }
+        and: 'model-sync is not invoked'
+            0 * objectUnderTest.syncModulesAndCreateAnchor(_)
+        where:
+            scenario                                        | exception                                               || expectedError           | expectedErrorText
+            'cm-handle already exist'                       | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+            'unknown exception while registering cm-handle' | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
+    }
+
+    def 'Create CM-Handle Error Handling: Model Sync fails'() {
+        given: 'objects under test without disabled model sync'
+            def objectUnderTest = getObjectUnderTest()
+        and: 'a registration without cm-handle properties'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+        and: 'cm-handler models sync fails'
+            objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') }
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a failure response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.registrationError == UNKNOWN_ERROR
+                assert it.errorText == 'Model-Sync failed'
+            }
+        and: 'cm-handle is registered'
+            1 * mockCpsDataService.saveListElements(*_)
     }
 
     def 'Update CM-Handle: Update Operation Response is added to the response'() {
@@ -174,7 +291,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         and: 'successful response is received'
             assert response.getRemovedCmHandles().size() == 1
             with(response.getRemovedCmHandles().get(0)) {
-                assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+                assert it.status == Status.SUCCESS
                 assert it.cmHandle == 'cmhandle'
             }
         where:
@@ -195,16 +312,19 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             response.getRemovedCmHandles().size() == 3
         and: '1st and 3rd cm-handle deletes successfully'
             with(response.getRemovedCmHandles().get(0)) {
-                assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle1'
             }
             with(response.getRemovedCmHandles().get(2)) {
-                assert it.status == CmHandleRegistrationResponse.Status.SUCCESS
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle3'
             }
-        and: '2nd cmhandle deletion fails'
+        and: '2nd cm-handle deletion fails'
             with(response.getRemovedCmHandles().get(1)) {
-                assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+                assert it.status == Status.FAILURE
                 assert it.registrationError == UNKNOWN_ERROR
                 assert it.errorText == 'Failed'
+                assert it.cmHandle == 'cmhandle2'
             }
     }
 
@@ -223,7 +343,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         and: 'a failure response is received'
             assert response.getRemovedCmHandles().size() == 1
             with(response.getRemovedCmHandles().get(0)) {
-                assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+                assert it.status == Status.FAILURE
                 assert it.cmHandle == 'cmhandle'
                 assert it.errorText == 'Failed'
                 assert it.registrationError == UNKNOWN_ERROR
@@ -243,7 +363,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         and: 'a failure response is received'
             assert response.getRemovedCmHandles().size() == 1
             with(response.getRemovedCmHandles().get(0)) {
-                assert it.status == CmHandleRegistrationResponse.Status.FAILURE
+                assert it.status == Status.FAILURE
                 assert it.cmHandle == 'cmhandle'
                 assert it.registrationError == expectedError
                 assert it.errorText == expectedErrorText
@@ -254,50 +374,14 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             'an unexpected exception'  | new RuntimeException("Failed")            | UNKNOWN_ERROR            | 'Failed'
     }
 
-    def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
-        given: 'a registration '
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
-                dmiDataPlugin: dmiDataPlugin)
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-        when: 'update registration and sync module is called with correct DMI plugin information'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'create cm handles registration and sync modules is called with the correct plugin information'
-            1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
-        where:
-            scenario                          | dmiPlugin  | dmiModelPlugin | dmiDataPlugin
-            'combined DMI plugin'             | 'service1' | ''             | ''
-            'data & model DMI plugins'        | ''         | 'service1'     | 'service2'
-            'data & model using same service' | ''         | 'service1'     | 'service1'
-    }
-
-    def 'Create CM-handle Error Handling: Invalid DMI plugin service name with #scenario'() {
-        given: 'a registration '
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
-                dmiDataPlugin: dmiDataPlugin)
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-        when: 'registration is called with incorrect DMI plugin information'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'a DMI Request Exception is thrown with correct message details'
-            def exceptionThrown = thrown(DmiRequestException.class)
-            assert exceptionThrown.getMessage().contains(expectedMessageDetails)
-        and: 'registration is not called'
-            0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
-        where:
-            scenario                         | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
-            'empty DMI plugins'              | ''         | ''             | ''            || 'No DMI plugin service names'
-            'blank DMI plugins'              | ' '        | ' '            | ' '           || 'No DMI plugin service names'
-            'null DMI plugins'               | null       | null           | null          || 'No DMI plugin service names'
-            'all DMI plugins'                | 'service1' | 'service2'     | 'service3'    || 'Cannot register combined plugin service name and other service names'
-            '(combined)DMI and Data Plugin'  | 'service1' | ''             | 'service2'    || 'Cannot register combined plugin service name and other service names'
-            '(combined)DMI and model Plugin' | 'service1' | 'service2'     | ''            || 'Cannot register combined plugin service name and other service names'
-            'only model DMI plugin'          | ''         | 'service1'     | ''            || 'Cannot register just a Data or Model plugin service name'
-            'only data DMI plugin'           | ''         | ''             | 'service1'    || 'Cannot register just a Data or Model plugin service name'
-    }
-
     def getObjectUnderTestWithModelSyncDisabled() {
-        def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
-            mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever))
+        def objectUnderTest = getObjectUnderTest()
         objectUnderTest.syncModulesAndCreateAnchor(*_) >> null
         return objectUnderTest
     }
+
+    def getObjectUnderTest() {
+        return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
+            mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever))
+    }
 }