ProvMnS correct parameters send to PolicyExecutor(class) 18/142918/5
authorseanbeirne <sean.beirne@est.tech>
Tue, 13 Jan 2026 11:37:30 +0000 (11:37 +0000)
committerseanbeirne <sean.beirne@est.tech>
Tue, 13 Jan 2026 18:10:57 +0000 (18:10 +0000)
- Improved Policy Executor unit test to include request body check
- remove AlternateId prefix from ResourceIdentifier
- remove wrapping of changeRequest

Issue-ID: CPS-3119
Change-Id: I4e0e159e180b09a82c9702035bdef625750f8521
Signed-off-by: seanbeirne <sean.beirne@est.tech>
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnSController.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnSControllerSpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/ClassInstance.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationEntry.java with 94% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetails.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactory.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactorySpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/provmns/ProvMnSRestApiSpec.groovy

index 881dfff..7de3891 100644 (file)
@@ -21,7 +21,6 @@
 package org.onap.cps.ncmp.rest.controller;
 
 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
-import static org.onap.cps.ncmp.impl.data.policyexecutor.OperationDetailsFactory.DELETE_OPERATION_DETAILS;
 import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA;
 import static org.onap.cps.ncmp.impl.provmns.ParameterHelper.NO_OP;
 import static org.springframework.http.HttpStatus.BAD_REQUEST;
@@ -35,15 +34,17 @@ import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
 
 import io.netty.handler.timeout.TimeoutException;
 import jakarta.servlet.http.HttpServletRequest;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.exceptions.DataValidationException;
-import org.onap.cps.ncmp.api.data.models.OperationType;
 import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
 import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState;
 import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException;
+import org.onap.cps.ncmp.impl.data.policyexecutor.ClassInstance;
 import org.onap.cps.ncmp.impl.data.policyexecutor.OperationDetails;
 import org.onap.cps.ncmp.impl.data.policyexecutor.OperationDetailsFactory;
 import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor;
@@ -134,7 +135,7 @@ public class ProvMnSController implements ProvMnS {
             final YangModelCmHandle yangModelCmHandle = getAndValidateYangModelCmHandle(requestParameters);
             final OperationDetails operationDetails =
                 operationDetailsFactory.buildOperationDetails(CREATE, requestParameters, resource);
-            checkPermission(yangModelCmHandle, requestParameters.fdn(), operationDetails);
+            checkPermission(yangModelCmHandle, operationDetails);
             final UrlTemplateParameters urlTemplateParameters =
                 parametersBuilder.createUrlTemplateParametersForWrite(yangModelCmHandle, requestParameters.fdn());
             return dmiRestClient.synchronousPutOperation(DATA, resource, urlTemplateParameters);
@@ -148,7 +149,9 @@ public class ProvMnSController implements ProvMnS {
         final RequestParameters requestParameters = ParameterHelper.extractRequestParameters(httpServletRequest);
         try {
             final YangModelCmHandle yangModelCmHandle = getAndValidateYangModelCmHandle(requestParameters);
-            checkPermission(yangModelCmHandle, requestParameters.fdn(), DELETE_OPERATION_DETAILS);
+            final OperationDetails operationDetails =
+                operationDetailsFactory.buildOperationDetailsForDelete(requestParameters.fdn());
+            checkPermission(yangModelCmHandle, operationDetails);
             final UrlTemplateParameters urlTemplateParameters =
                 parametersBuilder.createUrlTemplateParametersForWrite(yangModelCmHandle, requestParameters.fdn());
             return dmiRestClient.synchronousDeleteOperation(DATA, urlTemplateParameters);
@@ -179,12 +182,14 @@ public class ProvMnSController implements ProvMnS {
     }
 
     private void checkPermission(final YangModelCmHandle yangModelCmHandle,
-                                 final String resourceIdentifier,
                                  final OperationDetails operationDetails) {
-        final OperationType operationType = OperationType.fromOperationName(operationDetails.operation());
-        final String operationDetailsAsJson = jsonObjectMapper.asJsonString(operationDetails);
-        policyExecutor.checkPermission(yangModelCmHandle, operationType, NO_AUTHORIZATION, resourceIdentifier,
-            operationDetailsAsJson);
+        final Map<String, List<ClassInstance>> changeRequestAsMap = new HashMap<>(1);
+        changeRequestAsMap.put(operationDetails.className(), operationDetails.ClassInstances());
+        final String changeRequestAsJson = jsonObjectMapper.asJsonString(changeRequestAsMap);
+        final int index = yangModelCmHandle.getAlternateId().length();
+        final String resourceIdentifier = operationDetails.parentFdn().substring(index);
+        policyExecutor.checkPermission(yangModelCmHandle, operationDetails.operationType(),
+            NO_AUTHORIZATION, resourceIdentifier, changeRequestAsJson);
     }
 
     private void checkPermissionForEachPatchItem(final String baseFdn,
@@ -197,7 +202,7 @@ public class ProvMnSController implements ProvMnS {
             final OperationDetails operationDetails =
                 operationDetailsFactory.buildOperationDetails(requestParameters, patchItem);
             try {
-                checkPermission(yangModelCmHandle, requestParameters.fdn(), operationDetails);
+                checkPermission(yangModelCmHandle, operationDetails);
                 patchItemCounter++;
             } catch (final Exception exception) {
                 final String httpMethodName = "PATCH";
index f2edc9c..f4d2bcf 100644 (file)
@@ -94,17 +94,16 @@ class ProvMnSControllerSpec extends Specification {
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(objectMapper))
 
     static def resourceAsJson = '{"id":"test", "objectClass": "Test", "attributes": { "attr1": "value1"} }'
-    static def validCmHandle = new YangModelCmHandle(id:'ch-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
+    static def validCmHandle = new YangModelCmHandle(id:'ch-1', alternateId: '/managedElement=1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
     static def cmHandleWithoutDataProducer = new YangModelCmHandle(id:'ch-1', dmiServiceName: 'someDmiService', compositeState: new CompositeState(cmHandleState: READY))
     static def cmHandleNotReady            = new YangModelCmHandle(id:'ch-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: ADVISED))
 
     static def patchMediaType       = new MediaType('application', 'json-patch+json')
     static def patchMediaType3gpp   = new MediaType('application', '3gpp-json-patch+json')
-    static def patchJsonBody        = '[{"op":"replace","path":"/child=id2/attributes","value":{"attr1":"test"}}]'
+    static def patchJsonBody        = '[{"op":"replace","path":"/child=id2/attributes","value":{"id":"id1","attributes":{"attr1":"test"}}}]'
     static def patchJsonBody3gpp    = '[{"op":"replace","path":"/child=id2#/attributes/attr1","value":"test"}]'
-    static def patchJsonBodyInvalid = '[{"op":"replace","path":"/test","value":{}}]'
 
-    static def expectedDeleteOperationDetails = '{"operation":"delete","targetIdentifier":"","changeRequest":{}}'
+    static def expectedDeleteChangeRequest = '{"":[]}'
 
     @Value('${rest.api.provmns-base-path}')
     def provMnSBasePath
@@ -222,13 +221,13 @@ class ProvMnSControllerSpec extends Specification {
 
     def 'Patch request with #scenario.'() {
         given: 'provmns url'
-            def provmnsUrl = "$provMnSBasePath/v1/myClass=id1"
+            def provmnsUrl = "$provMnSBasePath/v1/managedElement=1/myClass=id1"
         and: 'alternate Id can be matched'
-            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1', "/") >> 'ch-1'
+            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/managedElement=1/myClass=id1', "/") >> 'ch-1'
         and: 'resource id for policy executor points to child node'
-            def expectedResourceIdForPolicyExecutor = '/myClass=id1/child=id2'
+            def expectedResourceIdForPolicyExecutor = '/myClass=id1'
         and: 'operation details has correct class and attributes, target identifier points to parent'
-            def expectedOperationDetails = '{"operation":"update","targetIdentifier":"/myClass=id1","changeRequest":{"child":[{"id":"id2","attributes":{"attr1":"test"}}]}}'
+            def expectedChangeRequest = '{"child":[{"id":"id2","attributes":{"attr1":"test"}}]}'
         and: 'persistence service returns yangModelCmHandle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> validCmHandle
         and: 'dmi provides a response'
@@ -243,7 +242,7 @@ class ProvMnSControllerSpec extends Specification {
         and: 'the response contains the expected content'
             assert response.contentAsString.contains('content from DMI')
         and: 'policy executor was invoked with the expected parameters'
-            1 * mockPolicyExecutor.checkPermission(_, OperationType.UPDATE, _, expectedResourceIdForPolicyExecutor, expectedOperationDetails)
+            1 * mockPolicyExecutor.checkPermission(validCmHandle, OperationType.UPDATE, _, expectedResourceIdForPolicyExecutor, expectedChangeRequest)
         where: 'following scenarios are applied'
             scenario          | contentMediaType   | jsonBody             | responseStatusFromDmi || expectedResponseStatusFromProvMnS
             'happy flow 3gpp' | patchMediaType3gpp | patchJsonBody3gpp    | OK                    || OK
@@ -292,12 +291,12 @@ class ProvMnSControllerSpec extends Specification {
 
     def 'Patch remove request.'() {
         given: 'resource data url'
-            def url = "$provMnSBasePath/v1/myClass=id1"
+            def url = "$provMnSBasePath/v1/managedElement=1/myClass=id1"
         and: 'alternate Id can be matched'
-            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1', "/") >> 'ch-1'
+            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/managedElement=1/myClass=id1', "/") >> 'ch-1'
         and: 'persistence service returns valid yangModelCmHandle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> validCmHandle
-            def expectedResourceIdentifier = '/myClass=id1/childClass=1/grandchildClass=1'
+            def expectedResourceIdentifier = '/myClass=id1/childClass=1'
         when: 'patch data resource request is performed'
             def response = mvc.perform(patch(url)
                 .contentType(patchMediaType)
@@ -306,14 +305,14 @@ class ProvMnSControllerSpec extends Specification {
         then: 'response status is OK'
             assert response.status == OK.value()
         and: 'Policy Executor was invoked with correct details'
-            1 * mockPolicyExecutor.checkPermission(_, OperationType.DELETE, _, expectedResourceIdentifier, expectedDeleteOperationDetails)
+            1 * mockPolicyExecutor.checkPermission(_, OperationType.DELETE, _, expectedResourceIdentifier, expectedDeleteChangeRequest)
     }
 
     def 'Patch request with no permission from Coordination Management (aka Policy Executor).'() {
         given: 'resource data url'
-            def url = "$provMnSBasePath/v1/myClass=id1"
+            def url = "$provMnSBasePath/v1/ManageElement=1/myClass=id1"
         and: 'alternate Id can be matched'
-            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1', "/") >> 'ch-1'
+            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/ManageElement=1/myClass=id1', "/") >> 'ch-1'
         and: 'persistence service returns valid yangModelCmHandle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> validCmHandle
         and: 'the permission is denied (Policy Executor throws an exception)'
@@ -374,17 +373,17 @@ class ProvMnSControllerSpec extends Specification {
 
     def 'Put resource data request with #scenario.'() {
         given: 'resource data url'
-            def putUrl = "$provMnSBasePath/v1/myClass=id1/childClass=1/grandChildClass=2"
+            def putUrl = "$provMnSBasePath/v1/ManagedElement=1/myClass=id1/childClass=1/grandChildClass=2"
         and: 'alternate Id can be matched'
-            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1/childClass=1/grandChildClass=2', "/") >> 'ch-1'
+            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/ManagedElement=1/myClass=id1/childClass=1/grandChildClass=2', "/") >> 'ch-1'
         and: 'persistence service returns yangModelCmHandle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> validCmHandle
         and: 'dmi provides a response'
             mockDmiRestClient.synchronousPutOperation(*_) >> new ResponseEntity<>(responseContentFromDmi, responseStatusFromDmi)
         and: 'The expected resource identifier for policy executor is the FDN to grandchild'
-            def expectedResourceIdentifier = '/myClass=id1/childClass=1/grandChildClass=2'
+            def expectedResourceIdentifier = '/myClass=id1/childClass=1'
         and: 'The change request target identifier is the FDN to parent and last class as object name in change request'
-            def expectedChangeRequest = '{"operation":"create","targetIdentifier":"/myClass=id1/childClass=1","changeRequest":{"grandChildClass":[{"id":"2","attributes":{"attr1":"value1"}}]}}'
+            def expectedChangeRequest = '{"grandChildClass":[{"id":"2","attributes":{"attr1":"value1"}}]}'
         when: 'put data resource request is performed'
             def response = mvc.perform(put(putUrl)
                     .contentType(MediaType.APPLICATION_JSON)
@@ -420,9 +419,9 @@ class ProvMnSControllerSpec extends Specification {
 
     def 'Delete resource data request with #scenario.'() {
         given: 'resource data url'
-            def deleteUrl = "$provMnSBasePath/v1/myClass=id1/childClass=1/grandChildClass=2"
+            def deleteUrl = "$provMnSBasePath/v1/ManagedElement=1/myClass=id1/childClass=1/grandChildClass=2"
         and: 'alternate Id can be matched'
-            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/myClass=id1/childClass=1/grandChildClass=2', "/") >> 'ch-1'
+            mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/ManagedElement=1/myClass=id1/childClass=1/grandChildClass=2', "/") >> 'ch-1'
         and: 'persistence service returns yangModelCmHandle'
             mockInventoryPersistence.getYangModelCmHandle('ch-1') >> validCmHandle
         and: 'dmi provides a response'
@@ -434,7 +433,7 @@ class ProvMnSControllerSpec extends Specification {
         and: 'the content is whatever the DMI returned'
             assert response.contentAsString == responseContentFromDmi
         and: 'Policy Executor was invoked with correct resource identifier and almost empty operation details (not used for delete!)'
-            1 * mockPolicyExecutor.checkPermission(_, OperationType.DELETE, _, '/myClass=id1/childClass=1/grandChildClass=2', expectedDeleteOperationDetails)
+            1 * mockPolicyExecutor.checkPermission(_, OperationType.DELETE, _, '/myClass=id1/childClass=1', expectedDeleteChangeRequest)
         where: 'following responses returned by DMI'
             scenario         | responseStatusFromDmi | responseContentFromDmi
             'happy flow'     | OK                    | 'content from DMI'
index 7c64283..23dfff1 100644 (file)
@@ -22,9 +22,10 @@ package org.onap.cps.ncmp.impl.data.policyexecutor;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import java.util.List;
-import java.util.Map;
+import org.onap.cps.ncmp.api.data.models.OperationType;
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
-public record OperationDetails(String operation,
-                               String targetIdentifier,
-                               Map<String, List<OperationEntry>> changeRequest) {}
+public record OperationDetails(OperationType operationType,
+                               String parentFdn,
+                               String className,
+                               List<ClassInstance> ClassInstances) {}
index 2ad53cb..5d5ef96 100644 (file)
@@ -20,7 +20,7 @@
 
 package org.onap.cps.ncmp.impl.data.policyexecutor;
 
-import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptyList;
 import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
 import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE;
 
@@ -43,8 +43,6 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class OperationDetailsFactory {
 
-    public static final OperationDetails DELETE_OPERATION_DETAILS  = new OperationDetails("delete", "", emptyMap());
-
     private static final String ATTRIBUTE_NAME_SEPARATOR = "/";
     private static final String REGEX_FOR_LEADING_AND_TRAILING_SEPARATORS = "(^/)|(/$)";
 
@@ -62,17 +60,17 @@ public class OperationDetailsFactory {
         final OperationDetails operationDetails;
         switch (patchItem.getOp()) {
             case ADD:
-                operationDetails = buildOperationDetailsForPatchItem(CREATE, requestParameters, patchItem);
+                operationDetails = buildOperationDetails(CREATE, requestParameters, patchItem.getValue());
                 break;
             case REPLACE:
                 if (patchItem.getPath().contains("#/attributes")) {
                     operationDetails = buildOperationDetailsForPatchItemWithHash(requestParameters, patchItem);
                 } else {
-                    operationDetails = buildOperationDetailsForPatchItem(UPDATE, requestParameters, patchItem);
+                    operationDetails = buildOperationDetails(UPDATE, requestParameters, patchItem.getValue());
                 }
                 break;
             case REMOVE:
-                operationDetails = DELETE_OPERATION_DETAILS;
+                operationDetails = buildOperationDetailsForDelete(requestParameters.fdn());
                 break;
             default:
                 throw new ProvMnSException("PATCH", HttpStatus.UNPROCESSABLE_ENTITY,
@@ -94,39 +92,30 @@ public class OperationDetailsFactory {
                                                   final Object resourceAsObject) {
         final ResourceObjectDetails resourceObjectDetails = createResourceObjectDetails(resourceAsObject,
             requestParameters);
-        final OperationEntry operationEntry = new OperationEntry(resourceObjectDetails.id(),
-            resourceObjectDetails.attributes());
-        final Map<String, List<OperationEntry>> changeRequestAsMap =
-            Map.of(resourceObjectDetails.objectClass(), List.of(operationEntry));
-        final String targetIdentifier = ParameterHelper.extractParentFdn(requestParameters.fdn());
-        return new OperationDetails(operationType.getOperationName(), targetIdentifier, changeRequestAsMap);
+        final String parentFdn = ParameterHelper.extractParentFdn(requestParameters.fdn());
+        final List<ClassInstance> classInstances
+            = List.of(new ClassInstance(resourceObjectDetails.id(), resourceObjectDetails.attributes()));
+        return new OperationDetails(operationType, parentFdn, resourceObjectDetails.objectClass(), classInstances);
     }
 
     /**
-     * Build OperationDetails for a specific patch item.
+     * Build a OperationDetails object from ProvMnS request details for delete.
      *
-     * @param operationType     the type of operation (CREATE, UPDATE)
-     * @param requestParameters request parameters including uri-ldn-first-part, className and id
-     * @param patchItem         the patch item containing operation details
-     * @return OperationDetails object for the patch item
+     * @param fdn    fdn to be deleted
+     * @return OperationDetails object
      */
-    public OperationDetails buildOperationDetailsForPatchItem(final OperationType operationType,
-                                                              final RequestParameters requestParameters,
-                                                              final PatchItem patchItem) {
-        final Map<String, Object> resourceAsObject = new HashMap<>(2);
-        resourceAsObject.put("id", requestParameters.id());
-        resourceAsObject.put("attributes", patchItem.getValue());
-        return buildOperationDetails(operationType, requestParameters, resourceAsObject);
+    public OperationDetails buildOperationDetailsForDelete(final String fdn) {
+        final String parentFdn = ParameterHelper.extractParentFdn(fdn);
+        return new OperationDetails(OperationType.DELETE, parentFdn, "", emptyList());
     }
 
     private OperationDetails buildOperationDetailsForPatchItemWithHash(final RequestParameters requestParameters,
                                                                        final PatchItem patchItem) {
         final Map<String, Object> attributeHierarchyAsMap = createNestedMap(patchItem);
-        final OperationEntry operationEntry = new OperationEntry(requestParameters.id(), attributeHierarchyAsMap);
-        final String targetIdentifier = ParameterHelper.extractParentFdn(requestParameters.fdn());
-        final Map<String, List<OperationEntry>> operationEntriesPerObjectClass = new HashMap<>();
-        operationEntriesPerObjectClass.put(requestParameters.className(), List.of(operationEntry));
-        return new OperationDetails(UPDATE.getOperationName(), targetIdentifier, operationEntriesPerObjectClass);
+        final String parentFdn = ParameterHelper.extractParentFdn(requestParameters.fdn());
+        final List<ClassInstance> classInstances
+            = List.of(new ClassInstance(requestParameters.id(), attributeHierarchyAsMap));
+        return new OperationDetails(UPDATE, parentFdn, requestParameters.className(), classInstances);
     }
 
     @SuppressWarnings("unchecked")
index 1c41b42..f9a9894 100644 (file)
@@ -35,29 +35,19 @@ import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
 class OperationDetailsFactorySpec extends Specification {
 
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def requestPathParameters = new RequestParameters('some method', '/parent=1/child=2','some uri', 'class in uri', 'id in uri')
+    def requestPathParameters = new RequestParameters('some method', '/parent=id1/someChild=someId','some uri', 'class from uri', 'id from uri')
 
     OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper)
 
-    def 'Build create operation details with all properties.'() {
-        given: 'a resource'
-            def resource = new ResourceOneOf(id: 'id in resource', objectClass: 'class in resource')
-        when: 'create operation details are built'
-            def result = objectUnderTest.buildOperationDetails(CREATE, requestPathParameters, resource)
-        then: 'all details are correct'
-            assert result.targetIdentifier == '/parent=1'
-            assert result.changeRequest.keySet()[0] == 'class in uri'
-            assert result.changeRequest['class in uri'][0].id == 'id in uri'
-    }
-
     def 'Build replace (~create) operation details with all properties where class name in body is #scenario.'() {
         given: 'a resource'
             def resource = new ResourceOneOf(id: 'some resource id', objectClass: classNameInBody)
         when: 'replace operation details are built'
             def result = objectUnderTest.buildOperationDetails(CREATE, requestPathParameters, resource)
         then: 'all details are correct'
-            assert result.targetIdentifier == '/parent=1'
-            assert result.changeRequest.keySet()[0] == 'class in uri'
+            assert result.parentFdn == '/parent=id1'
+            assert result.className == 'class from uri'
+            assert result.ClassInstances[0].id == 'id from uri'
         where:
             scenario    | classNameInBody
             'populated' | 'class in body'
@@ -72,7 +62,7 @@ class OperationDetailsFactorySpec extends Specification {
         when: 'operation details is created'
             def result = objectUnderTest.buildOperationDetails(requestPathParameters, patchItem)
         then: 'it has the correct operation type (for Policy Executor check)'
-            assert result.operation() == expectedPolicyExecutorOperationType.operationName
+            assert result.operationType == expectedPolicyExecutorOperationType
         where: 'following operations are used'
             patchOperationType | expectedPolicyExecutorOperationType
             'ADD'              | CREATE
@@ -85,12 +75,12 @@ class OperationDetailsFactorySpec extends Specification {
             def patchItem = new PatchItem(op: 'REPLACE', 'path':"some uri${suffix}", value: value)
         when: 'patch operation details are checked'
             def result = objectUnderTest.buildOperationDetails(requestPathParameters, patchItem)
-        then: 'Attribute Value in operation is correct'
-            result.changeRequest.values()[0].attributes[0] == expectedAttributesValueInOperation
+        then: 'Attribute value is correct'
+            result.ClassInstances[0].attributes == [attr1:456]
         where: 'attributes are set using # or resource'
-            scenario                            | suffix               | value                          || expectedAttributesValueInOperation
-            'set simple value using #'          | '#/attributes/attr1' | 1                              || [attr1:1]
-            'set complex value using resource'  | ''                   | '{"attr1":"abc","attr2":123}'  || '{"attr1":"abc","attr2":123}'
+            scenario                            | suffix               | value
+            'set simple value using #'          | '#/attributes/attr1' | 456
+            'set complex value using resource'  | ''                   | [id:'id1', attributes:[attr1:456]]
     }
 
     def 'Build an attribute map with different depths of hierarchy with #scenario.'() {
index 96330ab..c631202 100644 (file)
@@ -55,7 +55,11 @@ class PolicyExecutorSpec extends Specification {
 
     def logAppender = Spy(ListAppender<ILoggingEvent>)
 
-    def someValidJson = '{"Hello":"World"}'
+    def static someValidJson = '{"Hello":"World"}'
+    def static provMnSExampleJson = '{"grandChildClass":[{"id":"2","attributes":{"attr1":"value1"}}]}'
+    def static grandChildInstance = [id:'2', attributes:[attr1:'value1']]
+    def static provMnSExampleAsMap = [grandChildClass: [grandChildInstance]]
+    def static NO_CHANGE_REQUEST_FOR_DELETE = null
 
     def setup() {
         setupLogger()
@@ -77,13 +81,24 @@ class PolicyExecutorSpec extends Specification {
         given: 'allow response'
             mockResponse([permissionResult:'allow'], HttpStatus.OK)
         when: 'permission is checked for an operation'
-            objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson)
+            objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',provMnSExampleJson)
         then: 'system logs the operation is allowed'
             assert getLogEntry(4) == 'Operation allowed.'
+        and: 'the request body sent has expected operation and change request'
+            1 * mockRequestBodyUriSpec.body(*_) >> { args ->
+                def firstOperationAsMap = args[0].arg$1.get('operations')[0]
+                assert firstOperationAsMap.get('operation') == operationType.operationName
+                assert firstOperationAsMap.get('changeRequest') == expectedChangeRequest
+                return mockRequestBodyUriSpec
+            }
         and: 'no exception occurs'
             noExceptionThrown()
         where: 'all write operations are tested'
-            operationType << [ CREATE, DELETE, PATCH, UPDATE ]
+            operationType || expectedChangeRequest
+            CREATE        || provMnSExampleAsMap
+            DELETE        || NO_CHANGE_REQUEST_FOR_DELETE
+            PATCH         || provMnSExampleAsMap
+            UPDATE        || provMnSExampleAsMap
     }
 
     def 'Permission check with "other" decision (not allowed).'() {
index 78ae3d0..a46e84a 100644 (file)
@@ -47,7 +47,7 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{
     def 'Put Resource Data from provmns interface.'() {
         given: 'an example resource json body'
             dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
-            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2')
             def jsonBody = jsonObjectMapper.asJsonString(new ResourceOneOf('test'))
         expect: 'an OK response on PUT endpoint'
             mvc.perform(put("/ProvMnS/v1/A=1/B=2/C=3")
@@ -61,8 +61,8 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{
     def 'Patch Resource Data from provmns interface.'() {
         given: 'an example resource json body'
             dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
-            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
-            def jsonBody = jsonObjectMapper.asJsonString([new PatchItem(op: 'REMOVE', path: 'someUriLdnFirstPart')])
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2')
+            def jsonBody = jsonObjectMapper.asJsonString([new PatchItem(op: 'REMOVE', path: '/D=3/C=4')])
         expect: 'an OK response on PATCH endpoint'
             mvc.perform(patch("/ProvMnS/v1/A=1/B=2/C=3")
                     .contentType(new MediaType('application', 'json-patch+json'))
@@ -75,7 +75,7 @@ class ProvMnSRestApiSpec extends CpsIntegrationSpecBase{
     def 'Delete Resource Data from provmns interface.'() {
         given: 'a registered cm handle'
             dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
-            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2/C=3')
+            registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, '/A=1/B=2')
         expect: 'ok response on DELETE endpoint'
             mvc.perform(delete("/ProvMnS/v1/A=1/B=2/C=3")).andExpect(status().isOk())
         cleanup: 'deregister CM handles'