Remove CpsDataService dependency from DeltaReportExecutor 25/141625/7
authorArpit Singh <AS00745003@techmahindra.com>
Mon, 25 Aug 2025 14:26:02 +0000 (19:56 +0530)
committerArpit Singh <AS00745003@techmahindra.com>
Wed, 17 Sep 2025 13:17:46 +0000 (18:47 +0530)
CpsDataService is injected as bean in DeltaReportExecutor, This is
causing a cyclic dependency impacting CPS-2523(in progress).

The cyclic dependency forming is as follows:
CpsDataServiceImpl->CpsDeltaServiceImpl->DeltaReportExecutor->CpsDataService

The solution implemented is for DeltaReportExecutor to directly call
CpsDataPersistenceService

Since the cps delta service does not depend on CpsDataService, the
data validation steps which were present in CpsDataService have been
implemented in CpsDeltaServiceImpl and DeltaReportExecutor.

Issue-ID: CPS-2964
Change-Id: I1eecee22f67139eda111948d8aafad62c17340e2
Signed-off-by: Arpit Singh <AS00745003@techmahindra.com>
cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java
cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportExecutor.java
cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/deltareport/DeltaReportExecutorSpec.groovy

index 9d3d38b..e9f5137 100644 (file)
@@ -38,6 +38,7 @@ import org.onap.cps.api.model.Anchor;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.model.DeltaReport;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.utils.DataMapper;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.onap.cps.utils.deltareport.DeltaReportExecutor;
@@ -52,6 +53,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
 
     private final DeltaReportExecutor deltaReportExecutor;
     private final CpsAnchorService cpsAnchorService;
+    private final CpsValidator cpsValidator;
     private final CpsDataService cpsDataService;
     private final DataNodeFactory dataNodeFactory;
     private final DataMapper dataMapper;
@@ -107,6 +109,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
     @Override
     public void applyChangesInDeltaReport(final String dataspaceName, final String anchorName,
                                           final String deltaReportAsJsonString) {
+        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         deltaReportExecutor.applyChangesInDeltaReport(dataspaceName, anchorName, deltaReportAsJsonString);
     }
 
index d066a82..8e9a820 100644 (file)
@@ -20,6 +20,7 @@
 
 package org.onap.cps.utils.deltareport;
 
+import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH;
 import static org.onap.cps.utils.ContentType.JSON;
 
 import java.time.OffsetDateTime;
@@ -28,12 +29,12 @@ import java.util.List;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAnchorService;
-import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.DataNodeFactory;
 import org.onap.cps.api.model.Anchor;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.model.DeltaReport;
 import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -46,7 +47,7 @@ public class DeltaReportExecutor {
     private static final OffsetDateTime NO_TIMESTAMP = null;
 
     private final CpsAnchorService cpsAnchorService;
-    private final CpsDataService cpsDataService;
+    private final CpsDataPersistenceService cpsDataPersistenceService;
     private final DataNodeFactory dataNodeFactory;
     private final JsonObjectMapper jsonObjectMapper;
 
@@ -80,26 +81,37 @@ public class DeltaReportExecutor {
 
     private void updateDataNodes(final String dataspaceName, final String anchorName, final String xpath,
                                  final String updatedData) {
-        cpsDataService.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName,
-            CpsPathUtil.getNormalizedParentXpath(xpath), updatedData, NO_TIMESTAMP);
+        final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
+        final Collection<DataNode> dataNodesToUpdate =
+            createDataNodes(dataspaceName, anchorName, parentNodeXpath, updatedData);
+        cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodesToUpdate);
     }
 
     private void deleteDataNodesUsingDelta(final String dataspaceName, final String anchorName, final String xpath,
                                            final String dataToDelete) {
-        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        final Collection<DataNode> dataNodesToDelete =
-            dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor, xpath, dataToDelete, JSON);
+        final Collection<DataNode> dataNodesToDelete = createDataNodes(dataspaceName, anchorName, xpath, dataToDelete);
         final Collection<String> xpathsToDelete = dataNodesToDelete.stream().map(DataNode::getXpath).toList();
-        cpsDataService.deleteDataNodes(dataspaceName, anchorName, xpathsToDelete, NO_TIMESTAMP);
+        cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, xpathsToDelete);
     }
 
     private void addDataNodesUsingDelta(final String dataspaceName, final String anchorName, final String xpath,
                                         final String dataToAdd) {
         final String xpathToAdd = isRootListNodeXpath(xpath) ? CpsPathUtil.ROOT_NODE_XPATH : xpath;
-        cpsDataService.saveListElements(dataspaceName, anchorName, xpathToAdd, dataToAdd, NO_TIMESTAMP, JSON);
+        final Collection<DataNode> dataNodesToAdd = createDataNodes(dataspaceName, anchorName, xpathToAdd, dataToAdd);
+        if (ROOT_NODE_XPATH.equals(xpathToAdd)) {
+            cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodesToAdd);
+        } else {
+            cpsDataPersistenceService.addListElements(dataspaceName, anchorName, xpathToAdd, dataNodesToAdd);
+        }
     }
 
     private boolean isRootListNodeXpath(final String xpath) {
         return CpsPathUtil.getNormalizedParentXpath(xpath).isEmpty() && CpsPathUtil.isPathToListElement(xpath);
     }
+
+    private Collection<DataNode> createDataNodes(final String datasapceName, final String anchorName,
+                                                 final String xpath, final String nodeData) {
+        final Anchor anchor = cpsAnchorService.getAnchor(datasapceName, anchorName);
+        return dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor, xpath, nodeData, JSON);
+    }
 }
index 7aaeedb..9f5bd4b 100644 (file)
@@ -31,6 +31,7 @@ import org.onap.cps.api.exceptions.DataValidationException
 import org.onap.cps.api.model.Anchor
 import org.onap.cps.api.model.DataNode
 import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
 import org.onap.cps.utils.DataMapper
 import org.onap.cps.utils.JsonObjectMapper
 import org.onap.cps.utils.PrefixResolver
@@ -55,6 +56,7 @@ class CpsDeltaServiceImplSpec extends Specification {
 
     def mockCpsAnchorService = Mock(CpsAnchorService)
     def mockCpsDataService = Mock(CpsDataService)
+    def mockCpsValidator = Mock(CpsValidator)
     def mockDeltaReportExecutor = Mock(DeltaReportExecutor)
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
     def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
@@ -66,7 +68,7 @@ class CpsDeltaServiceImplSpec extends Specification {
     def deltaReportHelper = new DeltaReportHelper()
     def deltaReportGenerator = new DeltaReportGenerator(deltaReportHelper)
     def groupedDeltaReportGenerator = new GroupedDeltaReportGenerator(deltaReportHelper)
-    def objectUnderTest = new CpsDeltaServiceImpl(mockDeltaReportExecutor, mockCpsAnchorService, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper, deltaReportGenerator, groupedDeltaReportGenerator)
+    def objectUnderTest = new CpsDeltaServiceImpl(mockDeltaReportExecutor, mockCpsAnchorService, mockCpsValidator, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper, deltaReportGenerator, groupedDeltaReportGenerator)
 
     static def bookstoreDataNodeWithParentXpath = [new DataNode(xpath: '/bookstore', leaves: ['bookstore-name': 'Easons'])]
     static def bookstoreDataNodeWithChildXpath = [new DataNode(xpath: '/bookstore/categories[@code=\'02\']', leaves: ['code': '02', 'name': 'Kids'])]
index 7c27efc..867cb2f 100644 (file)
 
 package org.onap.cps.utils.deltareport
 
-
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.TestUtils
 import org.onap.cps.api.CpsAnchorService
-import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.exceptions.DataValidationException
 import org.onap.cps.api.model.Anchor
 import org.onap.cps.api.model.DataNode
@@ -32,6 +30,7 @@ import org.onap.cps.cpspath.parser.CpsPathUtil
 import org.onap.cps.impl.DataNodeFactoryImpl
 import org.onap.cps.impl.DeltaReportBuilder
 import org.onap.cps.impl.YangTextSchemaSourceSetCache
+import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import org.onap.cps.utils.YangParser
@@ -45,18 +44,17 @@ import spock.lang.Specification
 class DeltaReportExecutorSpec extends Specification {
 
     def mockCpsAnchorService = Mock(CpsAnchorService)
-    def mockCpsDataService = Mock(CpsDataService)
+    def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
     def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
     def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
     def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
     def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def objectUnderTest = new DeltaReportExecutor(mockCpsAnchorService, mockCpsDataService, dataNodeFactory, jsonObjectMapper)
+    def objectUnderTest = new DeltaReportExecutor(mockCpsAnchorService, mockCpsDataPersistenceService, dataNodeFactory, jsonObjectMapper)
 
     @Shared
     static def ANCHOR_NAME_1 = 'some-anchor-1'
     static def ANCHOR_NAME_2 = 'some-anchor-2'
-    static def NO_TIMESTAMP = null
     def dataspaceName = 'some-dataspace'
     def schemaSetName = 'some-schema-set'
     def anchor1 = Anchor.builder().name(ANCHOR_NAME_1).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
@@ -68,14 +66,14 @@ class DeltaReportExecutorSpec extends Specification {
     }
 
     def 'Perform delete operation on existing data under an anchor using delta report'() {
-        given: 'schema mocks and delta report in JSON format'
+        given: 'schema mocks and delta report as JSON'
             setupSchemaSetMocks('bookstore.yang')
             def deltaReportJson = '[{"action":"remove","xpath":"/bookstore","sourceData":{"categories":[{"code":"1","name":"Children","books":[{"title":"Matilda"}]}]}}]'
         and: 'delta report constructed from JSON'
             def deltaReport = new DeltaReportBuilder().actionRemove().withXpath('/bookstore').withSourceData('categories': [['code': '1', 'name': 'Children', 'books': [['title': 'Matilda']]]]).build()
         and: 'source data as JSON string from delta report'
             def sourceData = jsonObjectMapper.asJsonString(deltaReport.getSourceData())
-        and: 'expected data nodes with child nodes to delete'
+        and: 'expected data nodes to delete'
             def dataNodes = [new DataNode(xpath: '/bookstore/categories[@code=\'1\']', childDataNodes: [new DataNode(xpath: '/bookstore/categories[@code=\'1\']/books[@title=\'Matilda\']')])]
             def xpathsToDelete = dataNodes*.xpath
         when: 'attempt to apply delta using the delta report'
@@ -85,51 +83,91 @@ class DeltaReportExecutorSpec extends Specification {
         and: 'data nodes are built from the source data of delta report'
             dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, deltaReport.getXpath(), sourceData, ContentType.JSON) >> [dataNodes]
         and: 'appropriate cps data service method is invoked with expected parameters to delete data nodes'
-            1 * mockCpsDataService.deleteDataNodes(dataspaceName, ANCHOR_NAME_1, xpathsToDelete, NO_TIMESTAMP)
+            1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, ANCHOR_NAME_1, xpathsToDelete)
     }
 
     def 'Perform create operation on existing data under an anchor using delta report to add a node with #scenario'() {
-        given: 'schema mocks and delta report in JSON format'
+        given: 'schema mocks and test data'
             setupSchemaSetMocks('bookstore.yang')
+            def testData = setupTestData(scenario)
+        and: 'delta report as JSON'
+            def deltaReportJson = testData.deltaReportJson
+        and: 'delta report constructed from JSON'
+            def deltaReport = testData.deltaReport
+        and: 'data nodes to be created'
+            def dataNodes = testData.dataNodes
         and: 'target data as JSON string from delta report'
             def targetData = jsonObjectMapper.asJsonString(deltaReport.getTargetData())
         when: 'attempt to apply delta using the delta report'
             objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson)
-        then: 'the delta report in JSON format is converted to a list of DeltaReport objects'
+        then: 'the delta report as JSON string is converted to a list of DeltaReport objects'
             jsonObjectMapper.convertToJsonArray(deltaReportJson, DeltaReport) >> [deltaReport]
+        and: 'data nodes are built from the target data of delta report'
+            dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, expectedXpath, targetData, ContentType.JSON) >> dataNodes
         and: 'appropriate cps data service method is invoked with expected parameters to create data nodes'
-            1 * mockCpsDataService.saveListElements(dataspaceName, ANCHOR_NAME_1, expectedXpath, targetData, NO_TIMESTAMP, ContentType.JSON)
+            1 * mockCpsDataPersistenceService.addListElements(dataspaceName, ANCHOR_NAME_1, expectedXpath, { it*.xpath == dataNodes*.xpath && it*.leaves == dataNodes*.leaves })
         where:
-            scenario                 | deltaReportJson                                                                                                                    | deltaReport                                                                                                                                                        || expectedXpath
-            'xpath'                  | '[{"action":"create","xpath":"/bookstore","targetData":{"categories":[{"code":"1","name":"Children"}]}}]'                          | new DeltaReportBuilder().actionCreate().withXpath('/bookstore').withTargetData(['categories': [['code': '1', 'name': 'Children']]]).build()                        || '/bookstore'
-            'list node xpath'        | '[{"action":"create","xpath":"/bookstore/categories[@code=\'1\']","targetData":{"books":[{"price":20,"title":"Matilda"}]}}]'       | new DeltaReportBuilder().actionCreate().withXpath('/bookstore/categories[@code=\'1\']').withTargetData(['books': [['price': 20, 'title': 'Matilda']]]).build()     || '/bookstore/categories[@code=\'1\']'
-            'parent list node xpath' | '[{"action":"create","xpath":"/bookstore-address[@bookstore-name=\'Easons\']","targetData":{"address":{"street":"Main Street"}}}]' | new DeltaReportBuilder().actionCreate().withXpath('/bookstore-address[@bookstore-name=\'Easons\']').withTargetData(['address': ['street': 'Main Street']]).build() || '/'
+            scenario          || expectedXpath
+            'xpath'           || '/bookstore'
+            'list node xpath' || '/bookstore/categories[@code=\'1\']'
     }
 
-    def 'Perform replace operation on existing data under an anchor using delta report'() {
+    def 'Perform create operation on existing data under an anchor using delta report to add a parent list node'() {
         given: 'schema mocks and delta report in JSON format'
             setupSchemaSetMocks('bookstore.yang')
-            def deltaReportJson = '[{"action":"replace","xpath":"/bookstore/categories[@code=\'1\']","sourceData":{"books":[{"price":20,"title":"Matilda"}]},"targetData":{"books":[{"price":30,"title":"Matilda"}]}}]'
+        and: 'delta report as JSON'
+            def deltaReportJson = '[{"action":"create","xpath":"/bookstore-address[@bookstore-name=\'Easons\']","targetData":{"bookstore-address":[{"bookstore-name":"Easons"}]}}]'
+        and: 'delta report constructed from JSON'
+            def deltaReport = new DeltaReportBuilder().actionCreate().withXpath('/bookstore-address[@bookstore-name=\'Easons\']').withTargetData(['bookstore-address': [['bookstore-name': 'Easons']]]).build()
+        and: 'target data as JSON string from delta report'
+            def targetData = jsonObjectMapper.asJsonString(deltaReport.getTargetData())
+        and: 'data nodes created from delta report'
+            def dataNodesCreatedFromDeltaReport = [new DataNode(xpath: '/bookstore-address[@bookstore-name=\'Easons\']', leaves: ['bookstore-name':'Easons'])]
+        when: 'attempt to apply delta using the delta report'
+            objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson)
+        then: 'the delta report as JSON string is converted to a list of DeltaReport objects'
+            jsonObjectMapper.convertToJsonArray(deltaReportJson, DeltaReport) >> [deltaReport]
+        and: 'data nodes are built from the target data of delta report'
+            dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, '/', targetData, ContentType.JSON) >> dataNodesCreatedFromDeltaReport
+        and: 'appropriate cps data service method is invoked with expected parameters to create data nodes'
+            1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, ANCHOR_NAME_1, dataNodesToBeStored ->
+            { dataNodesToBeStored*.xpath == dataNodesCreatedFromDeltaReport*.xpath &&
+                dataNodesToBeStored*.leaves == dataNodesCreatedFromDeltaReport*.leaves })
+        }
+
+    def 'Perform replace operation on existing data under an anchor using delta report'() {
+        given: 'schema mocks'
+            setupSchemaSetMocks('bookstore.yang')
+        and: 'delta report as JSON string with parent and child data nodes'
+            def deltaReportJson = '[{"action":"replace","xpath":"/bookstore/categories[@code=\'1\']","sourceData":{"categories":[{"code":"1","name":"Children","books":[{"title":"Matilda","price":20}]}]},"targetData":{"categories":[{"code":"1","name":"Children","books":[{"title":"Matilda","price":30}]}]}}]'
         and: 'delta report constructed from JSON'
-            def deltaReport = new DeltaReportBuilder().actionReplace().withXpath('/bookstore/categories[@code=\'1\']').withSourceData(['books': [['price': 20, 'title': 'Matilda']]]).withTargetData(['books': [['price': 30, 'title': 'Matilda']]]).build()
+            def deltaReport = new DeltaReportBuilder().actionReplace().withXpath('/bookstore/categories[@code=\'1\']').withSourceData(['categories': [['code': '1', 'name': 'Children', 'books': [['title': 'Matilda', 'price': 20]]]]]).withTargetData(['categories': [['code': '1', 'name': 'Children', 'books': [['title': 'Matilda', 'price': 30]]]]]).build()
         and: 'the parent node xpath is fetched from delta report'
             def parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(deltaReport.getXpath())
-        and: 'target data as JSON is fetched from delta report'
+        and: 'target data as JSON string is fetched from delta report'
             def targetData = jsonObjectMapper.asJsonString(deltaReport.getTargetData())
+        and: 'parent and child nodes to be updated'
+         def dataNodesCreatedFromDeltaReport = [new DataNode(xpath: '/bookstore/categories[@code=\'1\']', leaves: ['code': '1', 'name': 'Children'], childDataNodes: [new DataNode(xpath: '/bookstore/categories[@code=\'1\']/books[@title=\'Matilda\']', leaves: ['price': 30, 'title': 'Matilda'])])]
         when: 'attempt to apply delta using the delta report'
             objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson)
-        then: 'the delta report in JSON format is converted to a list of DeltaReport objects'
+        then: 'the delta report as JSON string is converted to a list of DeltaReport objects'
             jsonObjectMapper.convertToJsonArray(deltaReportJson, DeltaReport) >> [deltaReport]
-        and: 'cps data service is invoked with expected parameters to delete data nodes by using their xpaths'
-            1 * mockCpsDataService.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, ANCHOR_NAME_1, parentNodeXpath, targetData, NO_TIMESTAMP)
+        and: 'data nodes are built from the target data of delta report'
+            dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, parentNodeXpath, targetData, ContentType.JSON) >> dataNodesCreatedFromDeltaReport
+        and: 'cps data service is invoked with expected parameters to update data nodes'
+            1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, ANCHOR_NAME_1, dataNodesToBeStored ->
+            { dataNodesToBeStored*.xpath == dataNodesCreatedFromDeltaReport*.xpath &&
+                dataNodesToBeStored*.leaves == dataNodesCreatedFromDeltaReport*.leaves })
     }
 
     def 'Batch operation using delta report rolls back in case of a semantically invalid Delta Report'() {
-        given: 'schema mocks and a semantically invalid delta report in JSON format'
+        given: 'schema mocks and a semantically invalid delta report as JSON string (code: xxx is invalid value for key code)'
             setupSchemaSetMocks('bookstore.yang')
-            def deltaReportJson = '[{"action":"create","xpath":"/bookstore/categories[@code=\'100\']","targetData":{"categories":[{"code":"100","name":"Funny"}]}},{"action":"remove","xpath":"/bookstore/categories[@code=\'4\']","sourceData":{"categorie":[{"code":"4","name":"Computing"}]}}]'
-        and: 'delta report object with invalid data for remove operation'
-            def deltaReport = [new DeltaReportBuilder().actionCreate().withXpath('/bookstore/categories[@code=\'100\']').withTargetData(['categories': [['code': '100', 'name': 'Funny']]]).build(), new DeltaReportBuilder().actionRemove().withXpath('/bookstore/categories[@code=\'4\'').withSourceData(['categorie': ['code': '4', 'name': 'Computing']]).build()]
+            def deltaReportJson = '[{"action":"create","xpath":"/bookstore","targetData":{"categories":[{"code":"xxx","name":"Funny"}]}},{"action":"remove","xpath":"/bookstore","sourceData":{"categorie":[{"code":"4","name":"Computing"}]}}]'
+        and: 'delta report object with invalid data for remove operation (code: xxx is invalid value for key code)'
+            def deltaReport = [new DeltaReportBuilder().actionCreate().withXpath('/bookstore').withTargetData(['categories': [['code': 'xxx', 'name': 'Funny']]]).build(), new DeltaReportBuilder().actionRemove().withXpath('/bookstore').withSourceData(['categorie': ['code': '4', 'name': 'Computing']]).build()]
+        and: 'data nodes to be created'
+            def dataNodesToAdd = [new DataNode(xpath: '/bookstore/categories[@code=\'100\']', leaves: ['code': '100', 'name': 'Funny'])]
         and: 'appropriate data is fetched from delta report'
             def xpathForCreateOperation = deltaReport[0].xpath
             def targetDataForCreateOperation = jsonObjectMapper.asJsonString(deltaReport[0].targetData)
@@ -138,12 +176,14 @@ class DeltaReportExecutorSpec extends Specification {
         when: 'attempt to apply delta using the delta report'
             objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson)
         then: 'the delta report in JSON format is converted to DeltaReport objects'
-            jsonObjectMapper.convertToJsonArray(deltaReportJson, DeltaReport) >> [deltaReport]
+            jsonObjectMapper.convertToJsonArray(deltaReportJson, DeltaReport) >> deltaReport
+        and: 'data nodes are built from the target data of create operation in delta report'
+            dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, xpathForCreateOperation, targetDataForCreateOperation, ContentType.JSON) >> dataNodesToAdd
         and: 'the create operation is attempted and succeeds'
-            1 * mockCpsDataService.saveListElements(dataspaceName, ANCHOR_NAME_1, xpathForCreateOperation, targetDataForCreateOperation, NO_TIMESTAMP, ContentType.JSON)
+           1 * mockCpsDataPersistenceService.addListElements(dataspaceName, ANCHOR_NAME_1, xpathForCreateOperation, { it })
         and: 'the remove operation fails due to invalid data, causing rollback'
             dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor1, xpathForDeleteOperation, sourceDataForDeleteOperation, ContentType.JSON) >> {throw new DataValidationException('Data Validation Failed')}
-        then: 'a DataValidationException is thrown'
+        and: 'a DataValidationException is thrown'
             thrown(DataValidationException)
     }
 
@@ -154,4 +194,19 @@ class DeltaReportExecutorSpec extends Specification {
         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
     }
+
+    def setupTestData(scenario) {
+        if (scenario == 'xpath') {
+            return [
+                deltaReportJson: '[{"action":"create","xpath":"/bookstore","targetData":{"categories":[{"code":"1","name":"Children"}]}}]',
+                deltaReport: new DeltaReportBuilder().actionCreate().withXpath('/bookstore').withTargetData(['categories': [['code': '1', 'name': 'Children']]]).build(),
+                dataNodes: [new DataNode(xpath: '/bookstore/categories[@code=\'1\']', leaves: ['code': '1', 'name': 'Children'])]
+            ]
+        }
+        return [
+            deltaReportJson: '[{"action":"create","xpath":"/bookstore/categories[@code=\'1\']","targetData":{"books":[{"price":20,"title":"Matilda"}]}}]',
+            deltaReport: new DeltaReportBuilder().actionCreate().withXpath('/bookstore/categories[@code=\'1\']').withTargetData(['books': [['price': 20, 'title': 'Matilda']]]).build(),
+            dataNodes: [new DataNode(xpath: '/bookstore/categories[@code=\'1\']/books[@title=\'Matilda\']', leaves: ['price':20, 'title':'Matilda'])]
+        ]
+    }
 }