CPS-635 - Module Resource call does not include body 64/123964/7
authorJosephKeenan <joseph.keenan@est.tech>
Tue, 7 Sep 2021 09:31:37 +0000 (10:31 +0100)
committerJosephKeenan <joseph.keenan@est.tech>
Fri, 10 Sep 2021 08:43:57 +0000 (09:43 +0100)
Issue-ID: CPS-635

Change-Id: Ie7a783ec1cd5107cef19a128439532bb520e3e89
Signed-off-by: JosephKeenan <joseph.keenan@est.tech>
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/JsonUtils.java [new file with mode: 0644]
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/client/DmiRestClient.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/PersistenceCmHandlesList.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/JsonUtilsSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy

diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/JsonUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/JsonUtils.java
new file mode 100644 (file)
index 0000000..9ce32e3
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 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;
+
+public class JsonUtils {
+
+    private static final String BACK_SLASH = "\\";
+    private static final String NEW_LINE = "\n";
+    private static final String QUOTE = "\"";
+
+
+    /**
+     * Remove redundant beginning and end characters.
+     * @param input string to format
+     * @return formatted string
+     */
+    public static String removeWrappingTokens(final String input) {
+        return input.substring(1, input.length() - 1);
+    }
+
+    /**
+     * Remove redundant escape characters.
+     * @param input string to format
+     * @return formatted string
+     */
+    public static String removeRedundantEscapeCharacters(final String input) {
+        return input.replace(BACK_SLASH + "n", NEW_LINE)
+            .replace(BACK_SLASH + QUOTE, QUOTE)
+            .replace(BACK_SLASH + BACK_SLASH + QUOTE, BACK_SLASH + QUOTE);
+    }
+}
index 871d880..d536464 100755 (executable)
@@ -215,7 +215,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                 .cmHandleProperties(cmHandlePropertiesMap)
                 .build();
         final var dmiRequestBody = prepareOperationBody(dmiRequestBodyObject);
-        final ResponseEntity<Void> responseEntity = dmiOperations
+        final ResponseEntity<String> responseEntity = dmiOperations
                 .createResourceDataPassThroughRunningFromDmi(dmiServiceName,
                         cmHandle,
                         resourceIdentifier,
@@ -246,7 +246,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private Map<String, String> getCmHandlePropertiesAsMap(final Collection<DataNode> cmHandlePropertiesList) {
+    private static Map<String, String> getCmHandlePropertiesAsMap(final Collection<DataNode> cmHandlePropertiesList) {
         if (cmHandlePropertiesList == null || cmHandlePropertiesList.isEmpty()) {
             return null;
         }
@@ -258,7 +258,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         return cmHandlePropertiesMap;
     }
 
-    private Object handleResponse(final @NotNull ResponseEntity<Object> responseEntity) {
+    private static Object handleResponse(final @NotNull ResponseEntity<Object> responseEntity) {
         if (responseEntity.getStatusCode() == HttpStatus.OK) {
             return responseEntity.getBody();
         } else {
@@ -268,8 +268,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private void handleResponseForPost(final @NotNull ResponseEntity<Void> responseEntity) {
-        if (responseEntity.getStatusCode() != HttpStatus.CREATED) {
+    private static void handleResponseForPost(final @NotNull ResponseEntity<String> responseEntity) {
+        if (responseEntity.getStatusCode() != HttpStatus.OK) {
             throw new NcmpException("Not able to create resource data.",
                     "DMI status code: " + responseEntity.getStatusCodeValue()
                             + ", DMI response body: " + responseEntity.getBody());
@@ -314,11 +314,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                 final PersistenceCmHandle persistenceCmHandle =
                     toPersistenceCmHandle(dmiPluginRegistration.getDmiPlugin(), cmHandle);
                 persistenceCmHandlesList.add(persistenceCmHandle);
-                createAnchorAndSyncModel(persistenceCmHandle);
             }
             final String cmHandleJsonData = objectMapper.writeValueAsString(persistenceCmHandlesList);
-            cpsDataService.saveListNodeData(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry",
-                cmHandleJsonData, NO_TIMESTAMP);
+
+            registerAndSyncNode(dmiPluginRegistration, persistenceCmHandlesList, cmHandleJsonData);
         } catch (final JsonProcessingException e) {
             log.error("Parsing error occurred while converting Object to JSON for DMI Registry.");
             throw new DataValidationException(
@@ -327,8 +326,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private PersistenceCmHandle toPersistenceCmHandle(final String dmiPluginService,
-                                                      final CmHandle cmHandle) {
+    private void registerAndSyncNode(final DmiPluginRegistration dmiPluginRegistration,
+                                     final PersistenceCmHandlesList persistenceCmHandlesList,
+                                     final String cmHandleJsonData) {
+        cpsDataService.saveListNodeData(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry",
+            cmHandleJsonData, NO_TIMESTAMP);
+
+        for (final PersistenceCmHandle persistenceCmHandle : persistenceCmHandlesList.getPersistenceCmHandles()) {
+            createAnchorAndSyncModel(persistenceCmHandle);
+        }
+    }
+
+    private static PersistenceCmHandle toPersistenceCmHandle(final String dmiPluginService,
+                                                             final CmHandle cmHandle) {
         final PersistenceCmHandle persistenceCmHandle = new PersistenceCmHandle();
         persistenceCmHandle.setDmiServiceName(dmiPluginService);
         persistenceCmHandle.setId(cmHandle.getCmHandleID());
@@ -359,15 +369,20 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         final var knownModuleReferencesInCps =
             cpsModuleService.getYangResourceModuleReferences(NF_PROXY_DATASPACE_NAME);
         final List<ModuleReference> existingModuleReferences = new ArrayList<>();
-        for (final ModuleReference moduleReferenceFromDmiForCmHandle :
-            moduleReferencesFromDmiForCmHandle) {
+
+        final List<ModuleReference> unknownModuleReferences = new ArrayList<>();
+        for (final ModuleReference moduleReferenceFromDmiForCmHandle : moduleReferencesFromDmiForCmHandle) {
             if (knownModuleReferencesInCps.contains(moduleReferenceFromDmiForCmHandle)) {
                 existingModuleReferences.add(moduleReferenceFromDmiForCmHandle);
+            } else {
+                unknownModuleReferences.add(moduleReferenceFromDmiForCmHandle);
             }
         }
 
+        final JsonObject requestBodyAsJson = getRequestBodyAsJson(unknownModuleReferences);
+
         final Map<String, String> newYangResourcesModuleNameToContentMap =
-            getNewYangResources(cmHandle);
+            getNewYangResources(cmHandle, requestBodyAsJson.toString());
 
         cpsModuleService.createSchemaSetFromModules(NCMP_DATASPACE_NAME, cmHandle.getId(),
             newYangResourcesModuleNameToContentMap, existingModuleReferences);
@@ -375,10 +390,38 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         cpsAdminService.createAnchor(NCMP_DATASPACE_NAME, cmHandle.getId(), cmHandle.getId());
     }
 
-    private Map<String, String> getNewYangResources(final PersistenceCmHandle cmHandle) {
-        final var moduleResourcesAsJsonString =  dmiOperations.getResourceFromDmi(
-            cmHandle.getDmiServiceName(), cmHandle.getId(), "moduleResources");
-        final JsonArray moduleResources = new Gson().fromJson(moduleResourcesAsJsonString.getBody(), JsonArray.class);
+    private JsonObject getRequestBodyAsJson(final List<ModuleReference> unknownModuleReferences) {
+
+        final JsonObject requestBodyAsJson = new JsonObject();
+        requestBodyAsJson.addProperty("operation", "read");
+
+        final JsonArray moduleReferencesAsJson = getModuleReferencesAsJson(unknownModuleReferences);
+
+        final JsonObject data = new JsonObject();
+        data.add("modules", moduleReferencesAsJson);
+        requestBodyAsJson.add("data", data);
+
+        return requestBodyAsJson;
+    }
+
+    private JsonArray getModuleReferencesAsJson(final List<ModuleReference> unknownModuleReferences) {
+        final JsonArray moduleReferences = new JsonArray();
+
+        for (final ModuleReference moduleReference : unknownModuleReferences) {
+            final JsonObject moduleReferenceAsJson = new JsonObject();
+            moduleReferenceAsJson.addProperty("name", moduleReference.getModuleName());
+            moduleReferenceAsJson.addProperty("revision", moduleReference.getRevision());
+            moduleReferences.add(moduleReferenceAsJson);
+        }
+        return moduleReferences;
+    }
+
+    private Map<String, String> getNewYangResources(final PersistenceCmHandle cmHandle, final String jsonData) {
+        final var moduleResourcesAsJsonString =  dmiOperations.getResourceFromDmiWithJsonData(
+            cmHandle.getDmiServiceName(), jsonData, cmHandle.getId(), "moduleResources");
+
+        final JsonArray moduleResources = new Gson().fromJson(moduleResourcesAsJsonString.getBody(),
+            JsonArray.class);
         final Map<String, String> newYangResourcesModuleNameToContentMap = new HashMap<>();
 
         for (final JsonElement moduleResource : moduleResources) {
@@ -388,11 +431,16 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         return newYangResourcesModuleNameToContentMap;
     }
 
-    private YangResource toYangResource(final JsonObject yangResourceAsJson) {
+    private static YangResource toYangResource(final JsonObject yangResourceAsJson) {
         final YangResource yangResource = new YangResource();
         yangResource.setModuleName(yangResourceAsJson.get("moduleName").getAsString());
         yangResource.setRevision(yangResourceAsJson.get("revision").getAsString());
-        yangResource.setYangSource(yangResourceAsJson.get("yangSource").getAsString());
+        final String yangSourceJson = yangResourceAsJson.get("yangSource").getAsString();
+
+        String yangSource = JsonUtils.removeWrappingTokens(yangSourceJson);
+        yangSource = JsonUtils.removeRedundantEscapeCharacters(yangSource);
+        yangResource.setYangSource(yangSource);
+
         return yangResource;
     }
 
index af691f6..fc70708 100644 (file)
@@ -46,10 +46,18 @@ public class DmiRestClient {
         return restTemplate.exchange(dmiResourceUrl, HttpMethod.PUT, httpEntity, Object.class);
     }
 
-    public ResponseEntity<Void> postOperationWithJsonData(final String dmiResourceUrl,
-                                                            final String jsonData, final HttpHeaders headers) {
-        final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(headers));
-        return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Void.class);
+    /**
+     * Sends POST operation to DMI with json body containing module references.
+     * @param dmiResourceUrl dmi resource url
+     * @param jsonData json data body
+     * @param httpHeaders http headers
+     * @return response entity of type String
+     */
+    public ResponseEntity<String> postOperationWithJsonData(final String dmiResourceUrl,
+                                                            final String jsonData,
+                                                            final HttpHeaders httpHeaders) {
+        final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(httpHeaders));
+        return restTemplate.postForEntity(dmiResourceUrl, httpEntity, String.class);
     }
 
     private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders) {
@@ -58,6 +66,12 @@ public class DmiRestClient {
         return httpHeaders;
     }
 
+    /**
+     * Sends POST operation to DMI.
+     * @param dmiResourceUrl dmi resource url
+     * @param httpHeaders http headers
+     * @return response entity of type String
+     */
     public ResponseEntity<String> postOperation(final String dmiResourceUrl, final HttpHeaders httpHeaders) {
         final var httpEntity = new HttpEntity<>(configureHttpHeaders(httpHeaders));
         return restTemplate.exchange(dmiResourceUrl, HttpMethod.POST, httpEntity, String.class);
index d6feaf3..71af3d4 100644 (file)
@@ -76,7 +76,23 @@ public class DmiOperations {
         final var dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
         final var httpHeaders = new HttpHeaders();
         return dmiRestClient.postOperation(dmiResourceDataUrl, httpHeaders);
+    }
 
+    /**
+     * Get resources from DMI for modules.
+     *
+     * @param dmiServiceName dmi service name
+     * @param jsonData module names and revisions as JSON
+     * @param cmHandle cmHandle
+     * @param resourceName name of the resource(s)
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<String> getResourceFromDmiWithJsonData(final String dmiServiceName,
+                                                               final String jsonData,
+                                                               final String cmHandle,
+                                                               final String resourceName) {
+        final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonData, new HttpHeaders());
     }
 
     /**
@@ -141,7 +157,7 @@ public class DmiOperations {
      * @param jsonBody    json body for put operation
      * @return {@code ResponseEntity} response entity
      */
-    public ResponseEntity<Void> createResourceDataPassThroughRunningFromDmi(final String dmiServiceName,
+    public ResponseEntity<String> createResourceDataPassThroughRunningFromDmi(final String dmiServiceName,
                                                                             final String cmHandle,
                                                                             final String resourceId,
                                                                             final String jsonBody) {
index beeb00f..f35abf6 100644 (file)
@@ -23,11 +23,13 @@ package org.onap.cps.ncmp.api.models;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
 
+@Getter
 public class PersistenceCmHandlesList {
 
     @JsonProperty("cm-handles")
-    private List<PersistenceCmHandle> persistenceCmHandles;
+    private List<PersistenceCmHandle> persistenceCmHandles = new ArrayList<>();
 
     /**
      * Add a persistenceCmHandle.
@@ -35,10 +37,6 @@ public class PersistenceCmHandlesList {
      * @param persistenceCmHandle the persistenceCmHandle to add
      */
     public void add(final PersistenceCmHandle persistenceCmHandle) {
-        if (persistenceCmHandles == null) {
-            persistenceCmHandles = new ArrayList<>();
-        }
         persistenceCmHandles.add(persistenceCmHandle);
     }
-
 }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/JsonUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/JsonUtilsSpec.groovy
new file mode 100644 (file)
index 0000000..2b3d998
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 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
+
+import spock.lang.Specification
+
+class JsonUtilsSpec extends Specification  {
+    def 'Remove redundant escape characters.'() {
+        expect: 'removing redundant escape characters returns the correct output for #scenario'
+            JsonUtils.removeRedundantEscapeCharacters(input) == expectedOutput
+        where: 'the following input is used'
+            scenario                               | input                                      || expectedOutput
+            'two lines'                            | 'line1\\nline2'                            || 'line1\nline2'
+            'a string inside quotes'               | 'a \\"word in quotes\\"'                   || 'a "word in quotes"'
+            'quotes inside quotes (double escape)' | '\\"quotes \\\\\\"inside\\\\\\" quotes\\"' || '"quotes \\"inside\\" quotes"'  // human readable:  "quotes \"inside\" quotes"
+    }
+    def 'Remove wrapping tokens.'() {
+        expect: 'removing wrapping tokens returns the correct output for #scenario'
+            JsonUtils.removeWrappingTokens(input) == expectedOutput
+        where: 'the following input is used'
+            scenario                           | input    || expectedOutput
+            'a string in quotes'               | '"abc"'  || 'abc'
+            'a string in apostrophes'          | "'abc'"  || 'abc'
+            'a string inside any other tokens' | 'abcde'  || 'bcd'
+    }
+}
+
index 795eeef..98a3aae 100644 (file)
@@ -34,6 +34,7 @@ import org.onap.cps.ncmp.api.impl.operation.DmiOperations
 import org.onap.cps.ncmp.api.models.CmHandle
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.PersistenceCmHandle
+import org.onap.cps.ncmp.api.models.PersistenceCmHandlesList
 import org.onap.cps.ncmp.utils.TestUtils
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
@@ -65,7 +66,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
     def cmHandleForModelSync = new PersistenceCmHandle(id:'some cm handle', dmiServiceName: 'some service name')
     def expectedDataspaceNameForModleSync = 'NCMP-Admin'
-    def NO_NAMESPACE = null
 
     def expectedDataspaceName = 'NFP-Operational'
     def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
@@ -333,7 +333,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 'testCmHandle',
                 'testResourceId',
                 '{"operation":"create","dataType":"application/json","data":"{some-json}","cmHandleProperties":{"testName":"testValue"}}')
-                >> { new ResponseEntity<>(HttpStatus.CREATED) }
+                >> { new ResponseEntity<>(HttpStatus.OK) }
     }
 
     def 'Write resource data for pass-through running from dmi using POST "not found" response (from DMI).'() {
@@ -362,7 +362,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             mockCpsModuleService.getYangResourceModuleReferences(_) >> [knownModule1, knownOtherModule]
         and: 'DMI-Plugin returns resource(s) for "new" module(s)'
             def moduleResources = new ResponseEntity<String>(sdncReponseBody, HttpStatus.OK)
-            mockDmiOperations.getResourceFromDmi(_, cmHandleForModelSync.getId(), 'moduleResources') >> moduleResources
+            mockDmiOperations.getResourceFromDmiWithJsonData(_, _, _, 'moduleResources') >> moduleResources
         when: 'module Sync is triggered'
             objectUnderTest.createAnchorAndSyncModel(cmHandleForModelSync)
         then: 'the CPS module service is called once with the correct parameters'
@@ -370,9 +370,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
         and: 'admin service create anchor method has been called with correct parameters'
             1 * mockCpsAdminService.createAnchor(expectedDataspaceNameForModleSync, cmHandleForModelSync.getId(), cmHandleForModelSync.getId())
         where: 'the following responses are recieved from SDNC'
-            scenario             | sdncReponseBody                                                                  || expectedYangResourceToContentMap
-            'one unknown module' | '[{"moduleName" : "someModule", "revision" : "1","yangSource": "someResource"}]' || [someModule: 'someResource']
-            'no unknown module'  | '[]'                                                                             || [:]
+            scenario             | sdncReponseBody                                                                        || expectedYangResourceToContentMap
+            'one unknown module' | '[{"moduleName" : "someModule", "revision" : "1","yangSource": "[some yang source]"}]' || [someModule: 'some yang source']
+            'no unknown module'  | '[]'                                                                                   || [:]
     }
 
     def getModulesForCmHandle() {
index 879c73c..809c48a 100644 (file)
@@ -55,7 +55,7 @@ class DmiRestClientSpec extends Specification {
             setupTestConfigurationData()
         and: 'the rest template returns a valid response entity'
             def mockResponseEntity = Mock(ResponseEntity)
-            mockRestTemplate.postForEntity(getResourceDataUrl, _ as HttpEntity, Void.class) >> mockResponseEntity
+            mockRestTemplate.postForEntity(getResourceDataUrl, _ as HttpEntity, String.class) >> mockResponseEntity
         when: 'POST operation is invoked'
             def result = objectUnderTest.postOperationWithJsonData(getResourceDataUrl, 'json-data', new HttpHeaders())
         then: 'the output of the method is equal to the output from the test template'
index 987ab2b..6a1ce1a 100644 (file)
@@ -92,4 +92,18 @@ class DmiOperationsSpec extends Specification {
         then: 'the post operation is executed with the correct URL'
             1 * mockDmiRestClient.postOperation(expectedUrl, _ as HttpHeaders)
     }
+
+    def 'Call get resource from dmi with json data.'() {
+        given: 'expected url & json data'
+            def requestBody = 'some json'
+            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmHandle/modules'
+            def expectedHttpHeaders = new HttpHeaders()
+        when: 'get resource data is called to dmi'
+            objectUnderTest.getResourceFromDmiWithJsonData('testDmiBasePath',
+                    requestBody,
+                    'testCmHandle',
+                    'modules')
+        then: 'the post operation is executed with the correct URL and json data'
+            1 * mockDmiRestClient.postOperationWithJsonData(expectedUrl, requestBody, expectedHttpHeaders)
+    }
 }
\ No newline at end of file