From: Toine Siebelink Date: Thu, 18 Jan 2024 16:51:48 +0000 (+0000) Subject: Merge "CPS Delta API: Update action for delta service" X-Git-Tag: 3.4.3~26 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=83433140bac9358aef50036081d1f7079ac10c01;hp=5ee1836bd9a2336afad291623db5b265f27d801a;p=cps.git Merge "CPS Delta API: Update action for delta service" --- 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 683ddce3d..1e1fe819a 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 xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes); final Map 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 getRemovedDeltaReports( - final Map xpathToSourceDataNodes, - final Map xpathToTargetDataNodes) { - - final List removedDeltaReportEntries = new ArrayList<>(); + private static List getRemovedAndUpdatedDeltaReports( + final Map xpathToSourceDataNodes, + final Map xpathToTargetDataNodes) { + final List removedAndUpdatedDeltaReportEntries = new ArrayList<>(); for (final Map.Entry entry: xpathToSourceDataNodes.entrySet()) { final String xpath = entry.getKey(); final DataNode sourceDataNode = entry.getValue(); final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath); - + final List deltaReports; if (targetDataNode == null) { - final Map 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 getRemovedDeltaReports(final String xpath, final DataNode sourceDataNode) { + final List removedDeltaReportEntries = new ArrayList<>(); + final Map sourceDataNodeLeaves = sourceDataNode.getLeaves(); + final DeltaReport removedDeltaReportEntry = new DeltaReportBuilder().actionRemove().withXpath(xpath) + .withSourceData(sourceDataNodeLeaves).build(); + removedDeltaReportEntries.add(removedDeltaReportEntry); return removedDeltaReportEntries; } + private static List getUpdatedDeltaReports(final String xpath, final DataNode sourceDataNode, + final DataNode targetDataNode) { + final List updatedDeltaReportEntries = new ArrayList<>(); + final Map, Map> updatedLeavesAsSourceDataToTargetData = + getUpdatedLeavesBetweenSourceAndTargetDataNode(sourceDataNode.getLeaves(), targetDataNode.getLeaves()); + addUpdatedLeavesToDeltaReport(xpath, updatedLeavesAsSourceDataToTargetData, updatedDeltaReportEntries); + return updatedDeltaReportEntries; + } + + private static Map, + Map> getUpdatedLeavesBetweenSourceAndTargetDataNode( + final Map leavesOfSourceDataNode, + final Map leavesOfTargetDataNode) { + final Map, Map> updatedLeavesAsSourceDataToTargetData = + new LinkedHashMap<>(); + final Map sourceDataInDeltaReport = new LinkedHashMap<>(); + final Map 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 leavesOfSourceDataNode, + final Map leavesOfTargetDataNode, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + for (final Map.Entry 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 leavesOfSourceDataNode, + final Map leavesOfTargetDataNode, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + final Map uniqueLeavesOfTargetDataNode = + new LinkedHashMap<>(leavesOfTargetDataNode); + uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet()); + for (final Map.Entry 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 sourceDataInDeltaReport, + final Map 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> updatedLeavesAsSourceDataToTargetData, + final List updatedDeltaReportEntries) { + for (final Map.Entry, Map> entry: + updatedLeavesAsSourceDataToTargetData.entrySet()) { + final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate() + .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); + updatedDeltaReportEntries.add(updatedDataForDeltaReport); + } + + } + private static List getAddedDeltaReports(final Map xpathToSourceDataNodes, final Map 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 b9c05dcf0..fb9c1971b 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 cef6ca3fa..1e151eeb2 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 a4f433973..e21c6f0e2 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, 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 e14309994..3843a9f1b 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) { 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 73b84fc98..1dd6c0d41 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",