From: leventecsanyi Date: Wed, 3 Dec 2025 12:21:56 +0000 (+0100) Subject: Update Patch Operation to make multiple calls to Policy Executor X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F34%2F142634%2F4;p=cps.git Update Patch Operation to make multiple calls to Policy Executor - refatored OperationDetailsFactory - updated testware Issue-ID: CPS-3071 Change-Id: Ic078c7c8125dc1424e3c9e379fc617b07b8f6fec Signed-off-by: leventecsanyi --- diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java index 1bad56bb8e..47a7a73f18 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java @@ -114,13 +114,8 @@ public class ProvMnsController implements ProvMnS { alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId( requestPathParameters.toAlternateId(), "/")); checkTarget(yangModelCmHandle); - policyExecutor.checkPermission(yangModelCmHandle, - OperationType.CREATE, - NO_AUTHORIZATION, - requestPathParameters.toAlternateId(), - jsonObjectMapper.asJsonString(operationDetailsFactory.buildPatchOperationDetails(requestPathParameters, - patchItems)) - ); + operationDetailsFactory + .checkPermissionForEachPatchItem(requestPathParameters, patchItems, yangModelCmHandle); final UrlTemplateParameters urlTemplateParameters = parametersBuilder.createUrlTemplateParametersForWrite(yangModelCmHandle, requestPathParameters); return dmiRestClient.synchronousPatchOperation(RequiredDmiService.DATA, patchItems, diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy index 929185504b..b028aa8fc7 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy @@ -201,8 +201,8 @@ class ProvMnsControllerSpec extends Specification { alternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId('/someUriLdnFirstPart/someClassName=someId', "/") >> 'cm-1' and: 'persistence service returns valid yangModelCmHandle' inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY)) - and: 'policy executor throws exception (denied)' - policyExecutor.checkPermission(*_) >> {throw new RuntimeException()} + and: 'the permission is denied (Policy Executor throws an exception)' + operationDetailsFactory.checkPermissionForEachPatchItem(*_) >> {throw new RuntimeException()} when: 'patch data resource request is performed' def response = mvc.perform(patch(url) .contentType(new MediaType('application', 'json-patch+json')) diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/CreateOperationDetails.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/CreateOperationDetails.java index 60a944db4c..8f85eaedc3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/CreateOperationDetails.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/CreateOperationDetails.java @@ -27,4 +27,4 @@ import java.util.Map; @JsonInclude(JsonInclude.Include.NON_NULL) public record CreateOperationDetails(String operation, String targetIdentifier, - Map> changeRequest) {} + Map> changeRequest) implements OperationDetails {} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/DeleteOperationDetails.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/DeleteOperationDetails.java index b24583dcf9..62547b59fb 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/DeleteOperationDetails.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/DeleteOperationDetails.java @@ -20,4 +20,8 @@ package org.onap.cps.ncmp.impl.data.policyexecutor; -public record DeleteOperationDetails(String operationType, String targetIdentifier) {} \ No newline at end of file +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record DeleteOperationDetails(String operation, + String targetIdentifier) implements OperationDetails {} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PatchOperationsDetails.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetails.java similarity index 87% rename from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PatchOperationsDetails.java rename to cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetails.java index d456507571..bb0a3faf14 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PatchOperationsDetails.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetails.java @@ -20,6 +20,6 @@ package org.onap.cps.ncmp.impl.data.policyexecutor; -import java.util.List; - -public record PatchOperationsDetails (String permissionId, String changeRequestFormat, List operations) {} +public interface OperationDetails { + String operation(); +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactory.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactory.java index bdb96c0c93..72c177bacf 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactory.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactory.java @@ -24,7 +24,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,6 +31,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.OperationType; import org.onap.cps.ncmp.api.exceptions.ProvMnSException; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.provmns.RequestPathParameters; import org.onap.cps.ncmp.impl.provmns.model.PatchItem; import org.onap.cps.utils.JsonObjectMapper; @@ -44,33 +44,42 @@ public class OperationDetailsFactory { private static final String ATTRIBUTE_NAME_SEPARATOR = "/"; private static final String REGEX_FOR_LEADING_AND_TRAILING_SEPARATORS = "(^/)|(/$)"; + private static final String NO_AUTHORIZATION = null; + private static final String UNSUPPORTED_OPERATION = "UNSUPPORTED_OP"; private final JsonObjectMapper jsonObjectMapper; private final ObjectMapper objectMapper; + private final PolicyExecutor policyExecutor; /** - * Build a PatchOperationDetails object from ProvMnS request details. + * Build an operation details object from ProvMnS request details and send it to Policy Executor. * * @param requestPathParameters request parameters including uri-ldn-first-part, className and id * @param patchItems provided request list of patch Items - * @return CreateOperationDetails object + * @param yangModelCmHandle representation of the cm handle to check */ - public PatchOperationsDetails buildPatchOperationDetails(final RequestPathParameters requestPathParameters, - final List patchItems) { - final List operations = new ArrayList<>(patchItems.size()); + public void checkPermissionForEachPatchItem(final RequestPathParameters requestPathParameters, + final List patchItems, + final YangModelCmHandle yangModelCmHandle) { + OperationDetails operationDetails; for (final PatchItem patchItem : patchItems) { switch (patchItem.getOp()) { - case ADD -> operations.add(buildCreateOperationDetails(OperationType.CREATE, requestPathParameters, - patchItem.getValue())); - case REPLACE -> operations.add(buildCreateOperationDetailsForUpdate(OperationType.UPDATE, - requestPathParameters, - patchItem)); - case REMOVE -> operations.add(buildDeleteOperationDetails(requestPathParameters.toAlternateId())); - default -> throw new ProvMnSException("UNSUPPORTED_OP", - "Unsupported Patch Operation Type: " + patchItem.getOp().getValue()); + case ADD -> operationDetails = buildCreateOperationDetails(OperationType.CREATE, requestPathParameters, + patchItem.getValue()); + case REPLACE -> operationDetails = buildCreateOperationDetailsForUpdate(OperationType.UPDATE, + requestPathParameters, + patchItem); + case REMOVE -> operationDetails = buildDeleteOperationDetails(requestPathParameters.toAlternateId()); + default -> throw new ProvMnSException(UNSUPPORTED_OPERATION, + "Unsupported Patch Operation Type: " + patchItem.getOp().getValue()); } + policyExecutor.checkPermission(yangModelCmHandle, + OperationType.fromOperationName(operationDetails.operation()), + NO_AUTHORIZATION, + requestPathParameters.toAlternateId(), + jsonObjectMapper.asJsonString(operationDetails) + ); } - return new PatchOperationsDetails("Some Permission Id", "cm-legacy", operations); } /** diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactorySpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactorySpec.groovy index 61a3ceed35..89fe5ec0fc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactorySpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/OperationDetailsFactorySpec.groovy @@ -23,7 +23,8 @@ package org.onap.cps.ncmp.impl.data.policyexecutor import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.exceptions.NcmpException -import org.onap.cps.ncmp.api.exceptions.ProvMnSException; +import org.onap.cps.ncmp.api.exceptions.ProvMnSException +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.provmns.RequestPathParameters; import org.onap.cps.ncmp.impl.provmns.model.PatchItem; import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf @@ -31,61 +32,121 @@ import org.onap.cps.utils.JsonObjectMapper; import spock.lang.Specification; import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE; +import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE; +import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE; class OperationDetailsFactorySpec extends Specification { def spiedObjectMapper = Spy(ObjectMapper) def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper) + def policyExecutor = Mock(PolicyExecutor) - OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper) + OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper, policyExecutor) static def complexValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['myAttribute1:myValue1', 'myAttribute2:myValue2'], objectClass: 'myClassName') static def simpleValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['simpleAttribute:1'], objectClass: 'myClassName') + static def yangModelCmHandle = new YangModelCmHandle(id: 'someId') + def 'Build create operation details with all properties.'() { + given: 'request parameters and resource' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'class in uri', id: 'my id') + def resource = new ResourceOneOf(id: 'some resource id', objectClass: 'class in resource') + when: 'create operation details are built' + def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource) + then: 'all details are correct' + assert result.targetIdentifier == 'my uri' + assert result.changeRequest.keySet()[0] == 'class in resource' + assert result.changeRequest['class in resource'][0].id == 'my id' + } - def 'Build policy executor patch operation details from ProvMnS request parameters where #scenario.'() { - given: 'a provMnsRequestParameter and a patchItem list' - def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'classNameInUri', id: 'myId') - def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: classNameInBody) - def patchItemsList = [new PatchItem(op: 'ADD', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REPLACE', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REMOVE', 'path':'myUriLdnFirstPart'),] - when: 'patch operation details are created' - def result = objectUnderTest.buildPatchOperationDetails(path, patchItemsList) - then: 'the result contain 3 operations of the correct types in the correct order' - result.operations.size() == 3 - and: 'note that Add and Replace both are defined using Create Operation Details' - assert result.operations[0] instanceof CreateOperationDetails - assert result.operations[1] instanceof CreateOperationDetails - assert result.operations[2] instanceof DeleteOperationDetails - and: 'the add operation target identifier is just the uri first part' - assert result.operations[0]['targetIdentifier'] == 'myUriLdnFirstPart' - and: 'the replace operation target identifier is just the uri first part' - assert result.operations[1]['targetIdentifier'] == 'myUriLdnFirstPart' - and: 'the replace change request has the correct class name' - assert result.operations[1].changeRequest.keySet()[0] == expectedChangeRequestKey - and: 'the delete operation target identifier includes the target class and id' - assert result.operations[2]['targetIdentifier'] == 'myUriLdnFirstPart/classNameInUri=myId' - where: 'the following class names are used in the body' - scenario | classNameInBody || expectedChangeRequestKey - 'class name in body is populated' | 'myClass' || 'myClass' - 'class name in body is empty' | '' || 'classNameInUri' - 'class name in body is null' | null || 'classNameInUri' + def 'Build replace operation details with all properties where class name in body is #scenario.'() { + given: 'request parameters and resource' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'class in uri', id: 'some id') + def resource = new ResourceOneOf(id: 'some resource id', objectClass: classNameInBody) + when: 'replace operation details are built' + def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource) + then: 'all details are correct' + assert result.targetIdentifier == 'my uri' + assert result.changeRequest.keySet()[0] == expectedChangeRequestKey + where: + scenario | classNameInBody || expectedChangeRequestKey + 'populated' | 'class in body' || 'class in body' + 'empty' | '' || 'class in uri' + 'null' | null || 'class in uri' + } + + def 'Build delete operation details with all properties'() { + given: 'request parameters' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'classNameInUri', id: 'myId') + when: 'delete operation details are built' + def result = objectUnderTest.buildDeleteOperationDetails(requestPathParameters.toAlternateId()) + then: 'all details are correct' + assert result.targetIdentifier == 'my uri/classNameInUri=myId' + } + + def 'Single patch operation with #patchOperationType checks correct operation type.'() { + given: 'request parameters and single patch item' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class') + def resource = new ResourceOneOf(id: 'some resource id') + def patchItem = new PatchItem(op: patchOperationType, 'path':'some uri', value: resource) + when: 'patch operation is processed' + objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, [patchItem], yangModelCmHandle) + then: 'policy executor is called with correct operation type' + 1 * policyExecutor.checkPermission(yangModelCmHandle, expectedPolicyExecutorOperationType, _, _, _) + where: 'following operations are used' + patchOperationType | expectedPolicyExecutorOperationType + 'ADD' | CREATE + 'REPLACE' | UPDATE + } + + def 'Single patch operation with REMOVE checks correct operation type.'() { + given: 'request parameters and single remove patch item' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri') + def patchItem = new PatchItem(op: 'REMOVE') + when: 'patch operation is processed' + objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, [patchItem], yangModelCmHandle) + then: 'policy executor is called with DELETE operation type' + 1 * policyExecutor.checkPermission(yangModelCmHandle, DELETE, _, _, _) + } + + def 'Multiple patch operations invoke policy executor correct number of times in order.'() { + given: 'request parameters and multiple patch items' + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri') + def resource = new ResourceOneOf(id: 'some resource id', objectClass: 'some class') + def patchItemsList = [ + new PatchItem(op: 'ADD', 'path':'some uri', value: resource), + new PatchItem(op: 'REPLACE', 'path':'some uri', value: resource), + new PatchItem(op: 'REMOVE', 'path':'some uri') + ] + when: 'patch operations are processed' + objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, patchItemsList, yangModelCmHandle) + then: 'policy executor is checked for create first' + 1 * policyExecutor.checkPermission(yangModelCmHandle, CREATE, _, _, _) + then: 'update is next' + 1 * policyExecutor.checkPermission(yangModelCmHandle, UPDATE, _, _, _) + then: 'and finally delete' + 1 * policyExecutor.checkPermission(yangModelCmHandle, DELETE, _, _, _) } def 'Build policy executor patch operation details with single replace operation and #scenario.'() { given: 'a requestParameter and a patchItem list' - def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'myClassName', id: 'myId') - def pathItems = [new PatchItem(op: 'REPLACE', 'path':"myUriLdnFirstPart${suffix}", value: value)] - when: 'patch operation details are created' - def result = objectUnderTest.buildPatchOperationDetails(path, pathItems) - then: 'the result has the correct type' - assert result instanceof PatchOperationsDetails - and: 'the change request contains the correct attributes value' - assert result.operations[0]['changeRequest']['myClassName'][0]['attributes'].toString() == attributesValueInOperation + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class') + def pathItems = [new PatchItem(op: 'REPLACE', 'path':"some uri${suffix}", value: value)] + when: 'patch operation details are checked' + objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, pathItems, yangModelCmHandle) + then: 'policyExecutor is called with correct payload' + 1 * policyExecutor.checkPermission( + yangModelCmHandle, + UPDATE, + null, + requestPathParameters.toAlternateId(), + { json -> assert json.contains(attributesValueInOperation) } // check for more details eg. verify type + ) where: 'attributes are set using # or resource' scenario | suffix | value || attributesValueInOperation - 'set simple value using #' | '#/attributes/simpleAttribute' | 1 || '[simpleAttribute:1]' - 'set simple value using resource' | '' | simpleValueAsResource || '[simpleAttribute:1]' - 'set complex value using resource' | '' | complexValueAsResource || '[myAttribute1:myValue1, myAttribute2:myValue2]' + 'set simple value using #' | '#/attributes/simpleAttribute' | 1 || '{"simpleAttribute":1}' + 'set simple value using resource' | '' | simpleValueAsResource || '["simpleAttribute:1"]' + 'set complex value using resource' | '' | complexValueAsResource || '["myAttribute1:myValue1","myAttribute2:myValue2"]' } def 'Build an attribute map with different depths of hierarchy with #scenario.'() { @@ -104,43 +165,42 @@ class OperationDetailsFactorySpec extends Specification { def 'Build policy executor patch operation details from ProvMnS request parameters with invalid op.'() { given: 'a provMnsRequestParameter and a patchItem list' - def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId') - def patchItemsList = [new PatchItem(op: 'TEST', 'path':'myUriLdnFirstPart')] + def path = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class') + def patchItemsList = [new PatchItem(op: 'TEST', 'path':'some uri')] when: 'a build is attempted with an invalid op' - objectUnderTest.buildPatchOperationDetails(path, patchItemsList) + objectUnderTest.checkPermissionForEachPatchItem(path, patchItemsList, yangModelCmHandle) then: 'the result is as expected (exception thrown)' thrown(ProvMnSException) } - def 'Build policy executor create operation details from ProvMnS request parameters where #scenario.'() { + def 'Build policy executor create operation details from ProvMnS request parameters where objectClass in resource #scenario.'() { given: 'a provMnsRequestParameter and a resource' - def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId') - def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: objectClass) + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'class in uri', id:'my id') + def resource = new ResourceOneOf(id: 'some resource id', objectClass: objectInResouce) when: 'a configurationManagementOperation is created and converted to JSON' - def result = objectUnderTest.buildCreateOperationDetails(CREATE, path, resource) + def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource) then: 'the result is as expected (using json to compare)' - String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"myUriLdnFirstPart","changeRequest":{"' + changeRequestClassReference + '":[{"id":"someId","attributes":["someAttribute1:someValue1","someAttribute2:someValue2"]}]}}' + String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"some uri","changeRequest":{"' + changeRequestClassReference + '":[{"id":"my id","attributes":null}]}}' assert jsonObjectMapper.asJsonString(result) == expectedJsonString where: - scenario | objectClass || changeRequestClassReference - 'objectClass is populated' | 'someObjectClass' || 'someObjectClass' - 'objectClass is empty' | '' || 'someClassName' - 'objectClass is null' | null || 'someClassName' + scenario | objectInResouce || changeRequestClassReference + 'populated' | 'class in resource' || 'class in resource' + 'empty' | '' || 'class in uri' + 'null' | null || 'class in uri' } - def 'Build Policy Executor Operation Details with a exception during conversion'() { + def 'Build Policy Executor Operation Details with a exception during conversion.'() { given: 'a provMnsRequestParameter and a resource' - def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId') - def resource = new ResourceOneOf(id: 'myResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2']) + def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class') + def resource = new ResourceOneOf(id: 'some resource id') and: 'json object mapper throws an exception' - def originalException = new JsonProcessingException('some-exception') - spiedObjectMapper.readValue(*_) >> {throw originalException} + spiedObjectMapper.readValue(*_) >> { throw new JsonProcessingException('original exception message') } when: 'a configurationManagementOperation is created and converted to JSON' - objectUnderTest.buildCreateOperationDetails(CREATE, path, resource) - then: 'the expected exception is throw and matches the original' + objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource) + then: 'the expected exception is throw and contains the original message' def thrown = thrown(NcmpException) assert thrown.message.contains('Cannot convert Resource Object') - assert thrown.details.contains('some-exception') + assert thrown.details.contains('original exception message') } }