summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToine Siebelink <toine.siebelink@est.tech>2024-01-18 16:51:48 +0000
committerGerrit Code Review <gerrit@onap.org>2024-01-18 16:51:48 +0000
commit83433140bac9358aef50036081d1f7079ac10c01 (patch)
tree39cd88b32ee3f3b45f02f1b1e5ce28ceeb548365
parent5ee1836bd9a2336afad291623db5b265f27d801a (diff)
parent885980d8ebf6bc8be9efad7ce9094deeb602a076 (diff)
Merge "CPS Delta API: Update action for delta service"
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java121
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java1
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java5
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy100
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy100
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json5
6 files changed, 261 insertions, 71 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
index 683ddce3d1..1e1fe819ac 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java
@@ -28,7 +28,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import lombok.NoArgsConstructor;
+import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsDeltaService;
import org.onap.cps.spi.model.DataNode;
@@ -38,7 +38,6 @@ import org.springframework.stereotype.Service;
@Slf4j
@Service
-@NoArgsConstructor
public class CpsDeltaServiceImpl implements CpsDeltaService {
@Override
@@ -50,7 +49,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
final Map<String, DataNode> xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes);
final Map<String, DataNode> xpathToTargetDataNodes = convertToXPathToDataNodesMap(targetDataNodes);
- deltaReport.addAll(getRemovedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes));
+ deltaReport.addAll(getRemovedAndUpdatedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes));
deltaReport.addAll(getAddedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes));
@@ -70,26 +69,122 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
return xpathToDataNode;
}
- private static List<DeltaReport> getRemovedDeltaReports(
- final Map<String, DataNode> xpathToSourceDataNodes,
- final Map<String, DataNode> xpathToTargetDataNodes) {
-
- final List<DeltaReport> removedDeltaReportEntries = new ArrayList<>();
+ private static List<DeltaReport> getRemovedAndUpdatedDeltaReports(
+ final Map<String, DataNode> xpathToSourceDataNodes,
+ final Map<String, DataNode> xpathToTargetDataNodes) {
+ final List<DeltaReport> removedAndUpdatedDeltaReportEntries = new ArrayList<>();
for (final Map.Entry<String, DataNode> entry: xpathToSourceDataNodes.entrySet()) {
final String xpath = entry.getKey();
final DataNode sourceDataNode = entry.getValue();
final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath);
-
+ final List<DeltaReport> deltaReports;
if (targetDataNode == null) {
- final Map<String, Serializable> sourceDataNodeLeaves = sourceDataNode.getLeaves();
- final DeltaReport removedData = new DeltaReportBuilder().actionRemove().withXpath(xpath)
- .withSourceData(sourceDataNodeLeaves).build();
- removedDeltaReportEntries.add(removedData);
+ deltaReports = getRemovedDeltaReports(xpath, sourceDataNode);
+ } else {
+ deltaReports = getUpdatedDeltaReports(xpath, sourceDataNode, targetDataNode);
}
+ removedAndUpdatedDeltaReportEntries.addAll(deltaReports);
}
+ return removedAndUpdatedDeltaReportEntries;
+ }
+
+ private static List<DeltaReport> getRemovedDeltaReports(final String xpath, final DataNode sourceDataNode) {
+ final List<DeltaReport> removedDeltaReportEntries = new ArrayList<>();
+ final Map<String, Serializable> sourceDataNodeLeaves = sourceDataNode.getLeaves();
+ final DeltaReport removedDeltaReportEntry = new DeltaReportBuilder().actionRemove().withXpath(xpath)
+ .withSourceData(sourceDataNodeLeaves).build();
+ removedDeltaReportEntries.add(removedDeltaReportEntry);
return removedDeltaReportEntries;
}
+ private static List<DeltaReport> getUpdatedDeltaReports(final String xpath, final DataNode sourceDataNode,
+ final DataNode targetDataNode) {
+ final List<DeltaReport> updatedDeltaReportEntries = new ArrayList<>();
+ final Map<Map<String, Serializable>, Map<String, Serializable>> updatedLeavesAsSourceDataToTargetData =
+ getUpdatedLeavesBetweenSourceAndTargetDataNode(sourceDataNode.getLeaves(), targetDataNode.getLeaves());
+ addUpdatedLeavesToDeltaReport(xpath, updatedLeavesAsSourceDataToTargetData, updatedDeltaReportEntries);
+ return updatedDeltaReportEntries;
+ }
+
+ private static Map<Map<String, Serializable>,
+ Map<String, Serializable>> getUpdatedLeavesBetweenSourceAndTargetDataNode(
+ final Map<String, Serializable> leavesOfSourceDataNode,
+ final Map<String, Serializable> leavesOfTargetDataNode) {
+ final Map<Map<String, Serializable>, Map<String, Serializable>> updatedLeavesAsSourceDataToTargetData =
+ new LinkedHashMap<>();
+ final Map<String, Serializable> sourceDataInDeltaReport = new LinkedHashMap<>();
+ final Map<String, Serializable> targetDataInDeltaReport = new LinkedHashMap<>();
+ processLeavesPresentInSourceAndTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode,
+ sourceDataInDeltaReport, targetDataInDeltaReport);
+ processLeavesUniqueInTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode,
+ sourceDataInDeltaReport, targetDataInDeltaReport);
+ final boolean isUpdatedDataInDeltaReport =
+ !sourceDataInDeltaReport.isEmpty() || !targetDataInDeltaReport.isEmpty();
+ if (isUpdatedDataInDeltaReport) {
+ updatedLeavesAsSourceDataToTargetData.put(sourceDataInDeltaReport, targetDataInDeltaReport);
+ }
+ return updatedLeavesAsSourceDataToTargetData;
+ }
+
+ private static void processLeavesPresentInSourceAndTargetDataNode(
+ final Map<String, Serializable> leavesOfSourceDataNode,
+ final Map<String, Serializable> leavesOfTargetDataNode,
+ final Map<String, Serializable> sourceDataInDeltaReport,
+ final Map<String, Serializable> targetDataInDeltaReport) {
+ for (final Map.Entry<String, Serializable> entry: leavesOfSourceDataNode.entrySet()) {
+ final String key = entry.getKey();
+ final Serializable sourceLeaf = entry.getValue();
+ final Serializable targetLeaf = leavesOfTargetDataNode.get(key);
+ compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport);
+ }
+ }
+
+ private static void processLeavesUniqueInTargetDataNode(
+ final Map<String, Serializable> leavesOfSourceDataNode,
+ final Map<String, Serializable> leavesOfTargetDataNode,
+ final Map<String, Serializable> sourceDataInDeltaReport,
+ final Map<String, Serializable> targetDataInDeltaReport) {
+ final Map<String, Serializable> uniqueLeavesOfTargetDataNode =
+ new LinkedHashMap<>(leavesOfTargetDataNode);
+ uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet());
+ for (final Map.Entry<String, Serializable> entry: uniqueLeavesOfTargetDataNode.entrySet()) {
+ final String key = entry.getKey();
+ final Serializable targetLeaf = entry.getValue();
+ final Serializable sourceLeaf = leavesOfSourceDataNode.get(key);
+ compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport);
+ }
+ }
+
+ private static void compareLeaves(final String key,
+ final Serializable sourceLeaf,
+ final Serializable targetLeaf,
+ final Map<String, Serializable> sourceDataInDeltaReport,
+ final Map<String, Serializable> targetDataInDeltaReport) {
+ if (sourceLeaf != null && targetLeaf != null) {
+ if (!Objects.equals(sourceLeaf, targetLeaf)) {
+ sourceDataInDeltaReport.put(key, sourceLeaf);
+ targetDataInDeltaReport.put(key, targetLeaf);
+ }
+ } else if (sourceLeaf != null) {
+ sourceDataInDeltaReport.put(key, sourceLeaf);
+ } else if (targetLeaf != null) {
+ targetDataInDeltaReport.put(key, targetLeaf);
+ }
+ }
+
+ private static void addUpdatedLeavesToDeltaReport(final String xpath,
+ final Map<Map<String, Serializable>, Map<String,
+ Serializable>> updatedLeavesAsSourceDataToTargetData,
+ final List<DeltaReport> updatedDeltaReportEntries) {
+ for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry:
+ updatedLeavesAsSourceDataToTargetData.entrySet()) {
+ final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate()
+ .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
+ updatedDeltaReportEntries.add(updatedDataForDeltaReport);
+ }
+
+ }
+
private static List<DeltaReport> getAddedDeltaReports(final Map<String, DataNode> xpathToSourceDataNodes,
final Map<String, DataNode> xpathToTargetDataNodes) {
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
index b9c05dcf02..fb9c1971b2 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java
@@ -32,6 +32,7 @@ public class DeltaReport {
public static final String ADD_ACTION = "add";
public static final String REMOVE_ACTION = "remove";
+ public static final String UPDATE_ACTION = "update";
DeltaReport() {}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
index cef6ca3fa2..1e151eeb2d 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java
@@ -58,6 +58,11 @@ public class DeltaReportBuilder {
return this;
}
+ public DeltaReportBuilder actionUpdate() {
+ this.action = DeltaReport.UPDATE_ACTION;
+ return this;
+ }
+
/**
* To create a single entry of {@link DeltaReport}.
*
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
index a4f4339737..e21c6f0e2f 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy
@@ -21,7 +21,6 @@
package org.onap.cps.api.impl
import org.onap.cps.spi.model.DataNode
-import org.onap.cps.spi.model.DataNodeBuilder
import spock.lang.Shared
import spock.lang.Specification
@@ -29,38 +28,81 @@ class CpsDeltaServiceImplSpec extends Specification{
def objectUnderTest = new CpsDeltaServiceImpl()
- @Shared
- def dataNodeWithLeafAndChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload'])
- .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").withLeaves('child-leaf': 'child-payload').build()]).build()]
- @Shared
- def dataNodeWithChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload'])
- .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()]
- @Shared
- def emptyDataNode = [new DataNodeBuilder().withXpath('/parent').build()]
- def 'Get delta between data nodes for removed data where source data node has #scenario'() {
+ static def sourceDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-source'])]
+ static def sourceDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')]
+ static def targetDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-target'])]
+ static def targetDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')]
+ static def sourceDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'])]
+ static def targetDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target'])]
+
+ def 'Get delta between data nodes for REMOVED data where source data node has #scenario'() {
+ when: 'attempt to get delta between 2 data nodes'
+ def result = objectUnderTest.getDeltaReports(sourceDataNodeWithLeafData, [])
+ then: 'the delta report contains expected "remove" action'
+ assert result[0].action.equals('remove')
+ and : 'the delta report contains the expected xpath'
+ assert result[0].xpath == '/parent'
+ and: 'the delta report contains expected source data'
+ assert result[0].sourceData == ['parent-leaf': 'parent-payload-in-source']
+ and: 'the delta report contains no target data'
+ assert result[0].targetData == null
+ }
+
+ def 'Get delta between data nodes with ADDED data where target data node has #scenario'() {
+ when: 'attempt to get delta between 2 data nodes'
+ def result = objectUnderTest.getDeltaReports([], targetDataNodeWithLeafData)
+ then: 'the delta report contains expected "add" action'
+ assert result[0].action.equals('add')
+ and: 'the delta report contains expected xpath'
+ assert result[0].xpath == '/parent'
+ and: 'the delta report contains no source data'
+ assert result[0].sourceData == null
+ and: 'the delta report contains expected target data'
+ assert result[0].targetData == ['parent-leaf': 'parent-payload-in-target']
+ }
+
+ def 'Delta Report between leaves for parent and child nodes, #scenario'() {
+ given: 'Two data nodes'
+ def sourceDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload'])])]
+ def targetDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-updated'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload-updated'])])]
+ when: 'attempt to get delta between 2 data nodes'
+ def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode)
+ then: 'the delta report contains expected "update" action'
+ assert result[index].action.equals('update')
+ and: 'the delta report contains expected xpath'
+ assert result[index].xpath == expectedXpath
+ and: 'the delta report contains expected source and target data'
+ assert result[index].sourceData == expectedSourceData
+ assert result[index].targetData == expectedTargetData
+ where: 'the following data was used'
+ scenario | index || expectedXpath | expectedSourceData | expectedTargetData
+ 'parent data node' | 0 || '/parent' | ['parent-leaf': 'parent-payload'] | ['parent-leaf': 'parent-payload-updated']
+ 'child data node' | 1 || '/parent/child' | ['child-leaf': 'child-payload'] | ['child-leaf': 'child-payload-updated']
+ }
+
+ def 'Delta report between leaves, #scenario'() {
when: 'attempt to get delta between 2 data nodes'
- def result = objectUnderTest.getDeltaReports(sourceDataNode as Collection<DataNode>, emptyDataNode)
- then: 'the delta report contains "remove" action with right data'
- assert result.first().action.equals("remove")
- assert result.first().xpath == "/parent/child"
- assert result.first().sourceData == expectedSourceData
- where: 'following data was used'
- scenario | sourceDataNode || expectedSourceData
- 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload']
- 'no leaf data' | dataNodeWithChildDataNode || null
+ def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode)
+ then: 'the delta report contains expected "update" action'
+ assert result[0].action.equals('update')
+ and: 'the delta report contains expected xpath'
+ assert result[0].xpath == '/parent'
+ and: 'the delta report contains expected source and target data'
+ assert result[0].sourceData == expectedSourceData
+ assert result[0].targetData == expectedTargetData
+ where: 'the following data was used'
+ scenario | sourceDataNode | targetDataNode || expectedSourceData | expectedTargetData
+ 'source and target data nodes have leaves' | sourceDataNodeWithLeafData | targetDataNodeWithLeafData || ['parent-leaf': 'parent-payload-in-source'] | ['parent-leaf': 'parent-payload-in-target']
+ 'only source data node has leaves' | sourceDataNodeWithLeafData | targetDataNodeWithoutLeafData || ['parent-leaf': 'parent-payload-in-source'] | null
+ 'only target data node has leaves' | sourceDataNodeWithoutLeafData | targetDataNodeWithLeafData || null | ['parent-leaf': 'parent-payload-in-target']
+ 'source and target dsta node with multiple leaves' | sourceDataNodeWithMultipleLeaves | targetDataNodeWithMultipleLeaves || ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'] | ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target']
}
- def 'Get delta between data nodes with new data where target data node has #scenario'() {
+ def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() {
when: 'attempt to get delta between 2 data nodes'
- def result = objectUnderTest.getDeltaReports(emptyDataNode, targetDataNode)
- then: 'the delta report contains "add" action with right data'
- assert result.first().action.equals("add")
- assert result.first().xpath == "/parent/child"
- assert result.first().targetData == expectedTargetData
- where: 'following data was used'
- scenario | targetDataNode || expectedTargetData
- 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload']
- 'no leaf data' | dataNodeWithChildDataNode || null
+ def result = objectUnderTest.getDeltaReports(sourceDataNodeWithoutLeafData, targetDataNodeWithoutLeafData)
+ then: 'the delta report contains "update" action with right data'
+ assert result.isEmpty()
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
index e143099943..3843a9f1bd 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
@@ -431,40 +431,30 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
def 'Get delta between 2 anchors for when #scenario'() {
when: 'attempt to get delta report between anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, xpath, fetchDescendantOption)
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS)
then: 'delta report contains expected number of changes'
- result.size() == 2
- and: 'delta report contains expected action'
- assert result.get(index).getAction() == expectedActions
- and: 'delta report contains expected xpath'
- assert result.get(index).getXpath() == expectedXpath
- where: 'following data was used'
- scenario | index | xpath || expectedActions || expectedXpath | fetchDescendantOption
- 'a node is removed' | 0 | '/' || 'remove' || "/bookstore-address[@bookstore-name='Easons-1']" | OMIT_DESCENDANTS
- 'a node is added' | 1 | '/' || 'add' || "/bookstore-address[@bookstore-name='Crossword Bookstores']" | OMIT_DESCENDANTS
- }
-
- def 'Get delta between 2 anchors where child nodes are added/removed but parent node remains unchanged'() {
- def parentNodeXpath = "/bookstore"
- when: 'attempt to get delta report between anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
- then: 'delta report contains expected number of changes'
- result.size() == 11
- and: 'the delta report does not contain parent node xpath'
- def xpaths = getDeltaReportEntities(result).get('xpaths')
- assert !(xpaths.contains(parentNodeXpath))
+ result.size() == 3
+ and: 'delta report contains UPDATE action with expected xpath'
+ assert result[0].getAction() == 'update'
+ assert result[0].getXpath() == '/bookstore'
+ and: 'delta report contains REMOVE action with expected xpath'
+ assert result[1].getAction() == 'remove'
+ assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
+ and: 'delta report contains ADD action with expected xpath'
+ assert result[2].getAction() == 'add'
+ assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
}
def 'Get delta between 2 anchors returns empty response when #scenario'() {
when: 'attempt to get delta report between anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS)
then: 'delta report is empty'
assert result.isEmpty()
where: 'following data was used'
- scenario | sourceAnchor | targetAnchor | xpath
- 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_4 | '/'
- 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_3 | '/'
- 'non existing xpath' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath'
+ scenario | targetAnchor | xpath
+ 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_4 | '/'
+ 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | '/'
+ 'non existing xpath' | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath'
}
def 'Get delta between anchors error scenario: #scenario'() {
@@ -511,6 +501,64 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
'is empty' | "/bookstore/container-without-leaves"
}
+ def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() {
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS)
+ then: 'expected action is update'
+ assert result[0].getAction() == 'update'
+ and: 'the payload has expected leaf values'
+ def sourceData = result[0].getSourceData()
+ def targetData = result[0].getTargetData()
+ assert sourceData == expectedSourceValue
+ assert targetData == expectedTargetValue
+ where: 'following data was used'
+ scenario | sourceAnchor | targetAnchor | xpath || expectedSourceValue | expectedTargetValue
+ 'leaf is updated in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || ['bookstore-name': 'Easons-1'] | ['bookstore-name': 'Crossword Bookstores']
+ 'leaf is removed in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || [price:1] | null
+ 'leaf is added in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || null | [price:1]
+ }
+
+ def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() {
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY)
+ then: 'expected action is update'
+ assert result[0].getAction() == 'update'
+ and: 'the delta report has expected child node xpaths'
+ def deltaReportEntities = getDeltaReportEntities(result)
+ def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths')
+ assert childNodeXpathsInDeltaReport.contains(expectedChildNodeXpath)
+ where: 'following data was used'
+ scenario | sourceAnchor | targetAnchor | xpath || expectedChildNodeXpath
+ 'source and target anchors have child data nodes' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/premises' || '/bookstore/premises/addresses[@house-number=\'2\' and @street=\'Main Street\']'
+ 'removed child data nodes in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore' || '/bookstore/support-info'
+ 'added child data nodes in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || '/bookstore/support-info'
+ }
+
+ def 'Get delta between anchors where source and target data nodes have leaves and child data nodes'() {
+ given: 'parent node xpath and expected data in delta report'
+ def parentNodeXpath = "/bookstore/categories[@code='1']"
+ def expectedSourceDataInParentNode = ['name':'Children']
+ def expectedTargetDataInParentNode = ['name':'Kids']
+ def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]]
+ def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[2023, 1988, 2000]]]
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ def deltaReportEntities = getDeltaReportEntities(result)
+ then: 'expected action is update'
+ assert result[0].getAction() == 'update'
+ and: 'the payload has expected parent node xpath'
+ assert deltaReportEntities.get('xpaths').contains(parentNodeXpath)
+ and: 'delta report has expected source and target data'
+ assert deltaReportEntities.get('sourcePayload').contains(expectedSourceDataInParentNode)
+ assert deltaReportEntities.get('targetPayload').contains(expectedTargetDataInParentNode)
+ and: 'the delta report also has expected child node xpaths'
+ assert deltaReportEntities.get('xpaths').containsAll(["/bookstore/categories[@code='1']/books[@title='The Gruffalo']", "/bookstore/categories[@code='1']/books[@title='Matilda']"])
+ and: 'the delta report also has expected source and target data of child nodes'
+ assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode)
+ assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
+
+ }
+
def getDeltaReportEntities(List<DeltaReport> deltaReport) {
def xpaths = []
def action = []
diff --git a/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json
index 73b84fc986..1dd6c0d419 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json
+++ b/integration-test/src/test/resources/data/bookstore/bookstoreDataForDeltaReport.json
@@ -7,7 +7,7 @@
}
],
"bookstore": {
- "bookstore-name": "Easons",
+ "bookstore-name": "Crossword Bookstores",
"premises": {
"addresses": [
{
@@ -96,8 +96,7 @@
"title": "Book 1",
"lang": "blah",
"authors": [],
- "editions": [],
- "price": 1
+ "editions": []
},
{
"title": "Book 2",