import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.cpspath.parser.CpsPathQuery;
import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.DataMapper;
final List<DeltaReport> deltaReport = new ArrayList<>();
if (groupDataNodes) {
- deltaReport.addAll(getCondensedAddedDeltaReports(sourceDataNodes, targetDataNodes));
+ deltaReport.addAll(getCondensedDeltaReports(sourceDataNodes, targetDataNodes));
} else {
final Map<String, DataNode> xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes);
final Map<String, DataNode> xpathToTargetDataNodes = convertToXPathToDataNodesMap(targetDataNodes);
final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath);
final List<DeltaReport> deltaReports;
if (targetDataNode == null) {
- deltaReports = getRemovedDeltaReports(xpath, sourceDataNode);
+ deltaReports = getDeltaReportsForRemove(xpath, sourceDataNode);
} else {
- deltaReports = getUpdatedDeltaReports(xpath, sourceDataNode, targetDataNode);
+ deltaReports = getDeltaReportsForUpdates(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<>();
+ private static List<DeltaReport> getDeltaReportsForRemove(final String xpath, final DataNode sourceDataNode) {
+ final List<DeltaReport> deltaReportEntriesForRemove = 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;
+ deltaReportEntriesForRemove.add(removedDeltaReportEntry);
+ return deltaReportEntriesForRemove;
}
- 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 List<DeltaReport> getDeltaReportsForUpdates(final String xpath, final DataNode sourceDataNode,
+ final DataNode targetDataNode) {
+ final List<DeltaReport> deltaReportEntriesForUpdates = new ArrayList<>();
+ final Map<Map<String, Serializable>, Map<String, Serializable>> updatedSourceDataToTargetData =
+ getUpdatedSourceAndTargetDataNode(sourceDataNode, targetDataNode);
+ if (!updatedSourceDataToTargetData.isEmpty()) {
+ addUpdatedDataToDeltaReport(xpath, updatedSourceDataToTargetData, deltaReportEntriesForUpdates);
+ }
+ return deltaReportEntriesForUpdates;
}
- 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);
+ private static Map<Map<String, Serializable>, Map<String, Serializable>> getUpdatedSourceAndTargetDataNode(
+ final DataNode sourceDataNode,
+ final DataNode targetDataNode) {
+ final Map<String, Serializable> updatedLeavesInSourceData = new HashMap<>();
+ final Map<String, Serializable> updatedLeavesInTargetData = new HashMap<>();
+ processSourceAndTargetDataNode(sourceDataNode, targetDataNode,
+ updatedLeavesInSourceData, updatedLeavesInTargetData);
+ processUniqueDataInTargetDataNode(sourceDataNode, targetDataNode,
+ updatedLeavesInSourceData, updatedLeavesInTargetData);
+ final Map<String, Serializable> updatedSourceData =
+ getUpdatedNodeData(sourceDataNode, updatedLeavesInSourceData);
+ final Map<String, Serializable> updatedTargetData =
+ getUpdatedNodeData(targetDataNode, updatedLeavesInTargetData);
+ if (updatedSourceData.isEmpty() && updatedTargetData.isEmpty()) {
+ return Collections.emptyMap();
}
- return updatedLeavesAsSourceDataToTargetData;
+ return Collections.singletonMap(updatedSourceData, updatedTargetData);
}
- private static void processLeavesPresentInSourceAndTargetDataNode(
- final Map<String, Serializable> leavesOfSourceDataNode,
- final Map<String, Serializable> leavesOfTargetDataNode,
+ private static void processSourceAndTargetDataNode(
+ final DataNode sourceDataNode,
+ final DataNode targetDataNode,
final Map<String, Serializable> sourceDataInDeltaReport,
final Map<String, Serializable> targetDataInDeltaReport) {
+ final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
+ final Map<String, Serializable> leavesOfTargetDataNode = targetDataNode.getLeaves();
for (final Map.Entry<String, Serializable> entry: leavesOfSourceDataNode.entrySet()) {
final String key = entry.getKey();
final Serializable sourceLeaf = entry.getValue();
}
}
- private static void processLeavesUniqueInTargetDataNode(
- final Map<String, Serializable> leavesOfSourceDataNode,
- final Map<String, Serializable> leavesOfTargetDataNode,
+ private static void processUniqueDataInTargetDataNode(
+ final DataNode sourceDataNode,
+ final DataNode targetDataNode,
final Map<String, Serializable> sourceDataInDeltaReport,
final Map<String, Serializable> targetDataInDeltaReport) {
+ final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
final Map<String, Serializable> uniqueLeavesOfTargetDataNode =
- new LinkedHashMap<>(leavesOfTargetDataNode);
+ new HashMap<>(targetDataNode.getLeaves());
uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet());
for (final Map.Entry<String, Serializable> entry: uniqueLeavesOfTargetDataNode.entrySet()) {
final String key = entry.getKey();
}
}
- private static void addUpdatedLeavesToDeltaReport(final String xpath,
- final Map<Map<String, Serializable>, Map<String,
- Serializable>> updatedLeavesAsSourceDataToTargetData,
- final List<DeltaReport> updatedDeltaReportEntries) {
+ private static Map<String, Serializable> getUpdatedNodeData(final DataNode dataNode,
+ final Map<String, Serializable> updatedLeaves) {
+ final Map<String, Serializable> updatedSourceData = new HashMap<>();
+ if (!updatedLeaves.isEmpty()) {
+ final String xpath = dataNode.getXpath();
+ if (CpsPathUtil.isPathToListElement(xpath)) {
+ addKeyLeavesToUpdatedData(xpath, updatedLeaves);
+ }
+ final Collection<DataNode> updatedDataNode = buildUpdatedDataNode(dataNode, updatedLeaves);
+ updatedSourceData.putAll(getCondensedDataForDeltaReport(updatedDataNode));
+ }
+ return updatedSourceData;
+ }
+
+ private static void addKeyLeavesToUpdatedData(final String xpath,
+ final Map<String, Serializable> updatedLeaves) {
+ final Map<String, Serializable> keyLeaves = new HashMap<>();
+ final List<CpsPathQuery.LeafCondition> leafConditions = CpsPathUtil.getCpsPathQuery(xpath).getLeafConditions();
+ for (final CpsPathQuery.LeafCondition leafCondition: leafConditions) {
+ final String leafName = leafCondition.name();
+ final Serializable leafValue = (Serializable) leafCondition.value();
+ keyLeaves.put(leafName, leafValue);
+ }
+ updatedLeaves.putAll(keyLeaves);
+ }
+
+ private static Collection<DataNode> buildUpdatedDataNode(final DataNode dataNode,
+ final Map<String, Serializable> updatedLeaves) {
+ final DataNode updatedDataNode = new DataNodeBuilder()
+ .withXpath(dataNode.getXpath())
+ .withModuleNamePrefix(dataNode.getModuleNamePrefix())
+ .withLeaves(updatedLeaves)
+ .build();
+ return Collections.singletonList(updatedDataNode);
+ }
+
+ private static void addUpdatedDataToDeltaReport(final String xpath,
+ final Map<Map<String, Serializable>, Map<String, Serializable>> updatedSourceDataToTargetData,
+ final List<DeltaReport> deltaReportEntriesForUpdates) {
for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry:
- updatedLeavesAsSourceDataToTargetData.entrySet()) {
- final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace()
- .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
- updatedDeltaReportEntries.add(updatedDataForDeltaReport);
+ updatedSourceDataToTargetData.entrySet()) {
+ final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace().withXpath(xpath)
+ .withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
+ deltaReportEntriesForUpdates.add(updatedDataForDeltaReport);
}
}
}
}
+ private static List<DeltaReport> getCondensedDeltaReports(final Collection<DataNode> sourceDataNodes,
+ final Collection<DataNode> targetDataNodes) {
+
+ final List<DeltaReport> deltaReportEntries = new ArrayList<>();
+ final Map<String, DataNode> xpathToTargetDataNodes = flattenToXpathToFirstLevelDataNodeMap(targetDataNodes);
+ deltaReportEntries.addAll(getCondensedRemovedDeltaReports(sourceDataNodes, xpathToTargetDataNodes));
+ deltaReportEntries.addAll(getCondensedUpdatedDeltaReports(sourceDataNodes, xpathToTargetDataNodes));
+ deltaReportEntries.addAll(getCondensedAddedDeltaReports(sourceDataNodes, targetDataNodes));
+ return deltaReportEntries;
+ }
+
+ private static List<DeltaReport> getCondensedRemovedDeltaReports(final Collection<DataNode> sourceDataNodes,
+ final Map<String, DataNode> xpathToTargetDataNodes) {
+
+ final List<DeltaReport> deltaReportEntriesForRemove = new ArrayList<>();
+ final Collection<DataNode> removedDataNodes =
+ getDataNodesForDeltaReport(sourceDataNodes, xpathToTargetDataNodes);
+ if (!removedDataNodes.isEmpty()) {
+ final String xpath = getXpathForDeltaReport(removedDataNodes);
+ deltaReportEntriesForRemove.add(new DeltaReportBuilder().actionRemove().withXpath(xpath)
+ .withSourceData(getCondensedDataForDeltaReport(removedDataNodes)).build());
+ }
+ return deltaReportEntriesForRemove;
+ }
+
+ private static List<DeltaReport> getCondensedUpdatedDeltaReports(final Collection<DataNode> sourceDataNodes,
+ final Map<String, DataNode> xpathToTargetDataNodes) {
+ final List<DeltaReport> deltaReportEntriesForUpdates = new ArrayList<>();
+ for (final DataNode sourceDataNode : sourceDataNodes) {
+ final String xpath = sourceDataNode.getXpath();
+ if (xpathToTargetDataNodes.containsKey(xpath)) {
+ final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath);
+ deltaReportEntriesForUpdates.addAll(getDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode));
+ getCondensedDeltaReportsForChildDataNodes(sourceDataNode, targetDataNode, deltaReportEntriesForUpdates);
+ }
+ }
+ return deltaReportEntriesForUpdates;
+ }
+
+ private static void getCondensedDeltaReportsForChildDataNodes(final DataNode sourceDataNode,
+ final DataNode targetDataNode,
+ final List<DeltaReport> deltaReportEntries) {
+ final Collection<DataNode> childrenOfSourceDataNodes = sourceDataNode.getChildDataNodes();
+ final Collection<DataNode> childrenOfTargetDataNodes = targetDataNode.getChildDataNodes();
+ if (!childrenOfSourceDataNodes.isEmpty() || !childrenOfTargetDataNodes.isEmpty()) {
+ deltaReportEntries.addAll(getCondensedDeltaReports(childrenOfSourceDataNodes, childrenOfTargetDataNodes));
+ }
+ }
+
private static List<DeltaReport> getCondensedAddedDeltaReports(final Collection<DataNode> sourceDataNodes,
final Collection<DataNode> targetDataNodes) {
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'])]
+ static def bookstoreDataNodesWithChildXpathAndNoLeaves = [new DataNode(xpath: '/bookstore/categories[@code=\'02\']')]
static def bookstoreDataAsMapForParentNode = [bookstore: ['bookstore-name': 'Easons']]
static def bookstoreDataAsMapForChildNode = [categories: ['code': '02', 'name': 'Kids']]
static def bookstoreJsonForParentNode = '{"bookstore":{"bookstore-name":"My Store"}}'
static def sourceDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')]
static def targetDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-leaf-as-target-data'])]
static def targetDataNodeWithChild = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-leaf-as-target-data'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-leaf-as-target-data'])])]
- static def targetDataNodeWithXpath = [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-leaf-as-target-data'])]
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'])]
+ static def expectedParentSourceData = ['parent':['parent-leaf':'parent-leaf-as-source-data']]
+ static def expectedParentTargetData = ['parent':['parent-leaf':'parent-leaf-as-target-data']]
def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
def loggingListAppender
def schemaSetName = 'some-schema-set'
def anchor1 = Anchor.builder().name(ANCHOR_NAME_1).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
def anchor2 = Anchor.builder().name(ANCHOR_NAME_2).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
- def GROUPING_ENABLED = true
- def GROUPING_DISABLED = false
+ static def GROUPING_ENABLED = true
+ static def GROUPING_DISABLED = false
def setup() {
mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1
applicationContext.close()
}
- def 'Get Delta between 2 anchors for #scenario with grouping of data nodes disabled'() {
+ def 'Get Delta between 2 anchors where data node is #scenario'() {
given: 'xpath to get delta'
def xpath = '/'
when: 'attempt to get delta between 2 anchors'
- def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, OMIT_DESCENDANTS, GROUPING_DISABLED)
+ def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, OMIT_DESCENDANTS, groupDataNodes)
then: 'cps data service is invoked and returns source data nodes'
mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], OMIT_DESCENDANTS) >> sourceDataNodes
and: 'cps data service is invoked again to return target data nodes'
deltaReport[0].sourceData == expectedSourceData
deltaReport[0].targetData == expectedTargetData
where: 'following data was used'
- scenario | sourceDataNodes | targetDataNodes || expectedAction | expectedSourceData | expectedTargetData
- 'Data node is added' | [] | targetDataNode || 'create' | null | ['parent-leaf': 'parent-leaf-as-target-data']
- 'Data node is removed' | sourceDataNode | [] || 'remove' | ['parent-leaf': 'parent-leaf-as-source-data'] | null
- 'Data node is updated' | sourceDataNode | targetDataNode || 'replace' | ['parent-leaf': 'parent-leaf-as-source-data'] |['parent-leaf': 'parent-leaf-as-target-data']
+ scenario | sourceDataNodes | targetDataNodes | groupDataNodes || expectedAction | expectedSourceData | expectedTargetData
+ 'added with grouping disabled' | [] | targetDataNode | GROUPING_DISABLED || 'create' | null | ['parent-leaf':'parent-leaf-as-target-data']
+ 'removed with grouping disabled' | sourceDataNode | [] | GROUPING_DISABLED || 'remove' | ['parent-leaf':'parent-leaf-as-source-data'] | null
+ 'updated with grouping disabled' | sourceDataNode | targetDataNode | GROUPING_DISABLED || 'replace' | ['parent':['parent-leaf':'parent-leaf-as-source-data']] | ['parent':['parent-leaf':'parent-leaf-as-target-data']]
+ 'added with grouping enabled' | [] | targetDataNodeWithChild | GROUPING_ENABLED || 'create' | null | ['parent':['parent-leaf': 'parent-leaf-as-target-data', 'child':['child-leaf': 'child-leaf-as-target-data']]]
+ 'removed with grouping enabled' | sourceDataNodeWithChild | [] | GROUPING_ENABLED || 'remove' | ['parent':['parent-leaf': 'parent-leaf-as-source-data', 'child':['child-leaf': 'child-leaf-as-source-data']]] | null
+ 'updated with grouping enabled' | sourceDataNode | targetDataNode | GROUPING_ENABLED || 'replace' | ['parent':['parent-leaf': 'parent-leaf-as-source-data']] | ['parent':['parent-leaf': 'parent-leaf-as-target-data']]
}
- def 'Delta Report between parent nodes containing child nodes where #scenario with grouping of data nodes disabled'() {
- given: 'root node xpath'
+ def 'Delta Report between parent nodes with children where data node is #scenario without grouping of data nodes'() {
+ given: 'root node xpath and expected source and target data'
def xpath = '/'
when: 'attempt to get delta between 2 anchors'
def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, INCLUDE_ALL_DESCENDANTS, GROUPING_DISABLED)
assert deltaReport[1].sourceData == expectedSourceDataForChild
assert deltaReport[1].targetData == expectedTargetDataForChild
where: 'the following data is used'
- scenario | sourceDataNodes | targetDataNodes || expectedAction | expectedSourceDataForParent | expectedTargetDataForParent | expectedSourceDataForChild | expectedTargetDataForChild
- 'Data node is added' | [] | targetDataNodeWithChild || 'create' | null | ['parent-leaf': 'parent-leaf-as-target-data'] | null | ['child-leaf': 'child-leaf-as-target-data']
- 'Data node is removed' | sourceDataNodeWithChild | [] || 'remove' | ['parent-leaf': 'parent-leaf-as-source-data'] | null | ['child-leaf': 'child-leaf-as-source-data'] | null
- 'Data node is updated' | sourceDataNodeWithChild | targetDataNodeWithChild || 'replace' | ['parent-leaf': 'parent-leaf-as-source-data'] | ['parent-leaf': 'parent-leaf-as-target-data'] | ['child-leaf': 'child-leaf-as-source-data'] | ['child-leaf': 'child-leaf-as-target-data']
+ scenario | sourceDataNodes | targetDataNodes || expectedAction | expectedSourceDataForParent | expectedTargetDataForParent | expectedSourceDataForChild | expectedTargetDataForChild
+ 'added' | [] | targetDataNodeWithChild || 'create' | null | ['parent-leaf': 'parent-leaf-as-target-data'] | null | ['child-leaf': 'child-leaf-as-target-data']
+ 'removed' | sourceDataNodeWithChild | [] || 'remove' | ['parent-leaf': 'parent-leaf-as-source-data'] | null | ['child-leaf': 'child-leaf-as-source-data'] | null
+ 'updated' | sourceDataNodeWithChild | targetDataNodeWithChild || 'replace' | expectedParentSourceData | expectedParentTargetData | ['child':['child-leaf': 'child-leaf-as-source-data']] | ['child':['child-leaf': 'child-leaf-as-target-data']]
+ }
+
+ def 'Delta Report between parent nodes with children where parent is updated and child node is #scenario with grouping of data nodes'() {
+ given: 'root node xpath and expected source and target data'
+ def xpath = '/'
+ when: 'attempt to get delta between 2 anchors'
+ def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, INCLUDE_ALL_DESCENDANTS, GROUPING_ENABLED)
+ then: 'cps data service is invoked and returns source data nodes'
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
+ and: 'cps data service is invoked again to return target data nodes'
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
+ and: 'delta report contains correct number of entries'
+ deltaReport.size() == 2
+ and: 'the delta report contains expected details for parent node'
+ assert deltaReport[0].action == 'replace'
+ assert deltaReport[0].xpath == '/parent'
+ assert deltaReport[0].sourceData == expectedParentSourceData
+ assert deltaReport[0].targetData == expectedParentTargetData
+ and: 'the delta report contains expected details for child node'
+ assert deltaReport[1].action == expectedChildAction
+ assert deltaReport[1].xpath == expectedChildXpath
+ assert deltaReport[1].sourceData == expectedSourceDataForChild
+ assert deltaReport[1].targetData == expectedTargetDataForChild
+ where: 'the following data is used'
+ scenario | sourceDataNodes | targetDataNodes || expectedChildAction | expectedChildXpath | expectedSourceDataForChild | expectedTargetDataForChild
+ 'added' | sourceDataNode | targetDataNodeWithChild || 'create' | '/parent' | null | ['child':['child-leaf': 'child-leaf-as-target-data']]
+ 'removed' | sourceDataNodeWithChild | targetDataNode || 'remove' | '/parent' | ['child':['child-leaf':'child-leaf-as-source-data']] | null
+ 'updated' | sourceDataNodeWithChild | targetDataNodeWithChild || 'replace' | '/parent/child' | ['child':['child-leaf': 'child-leaf-as-source-data']] | ['child':['child-leaf': 'child-leaf-as-target-data']]
}
def 'Delta report between leaves, #scenario'() {
assert deltaReport[0].sourceData == expectedSourceData
assert deltaReport[0].targetData == expectedTargetData
where: 'the following data was used'
- scenario | sourceDataNodes | targetDataNodes || expectedSourceData | expectedTargetData
- 'source and target data nodes have leaves' | sourceDataNode | targetDataNode || ['parent-leaf': 'parent-leaf-as-source-data'] | ['parent-leaf': 'parent-leaf-as-target-data']
- 'only source data node has leaves' | sourceDataNode | targetDataNodeWithoutLeafData || ['parent-leaf': 'parent-leaf-as-source-data'] | null
- 'only target data node has leaves' | sourceDataNodeWithoutLeafData | targetDataNode || null | ['parent-leaf': 'parent-leaf-as-target-data']
- 'source and target data 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']
+ scenario | sourceDataNodes | targetDataNodes || expectedSourceData | expectedTargetData
+ 'source and target data nodes have leaves' | sourceDataNode | targetDataNode || ['parent':['parent-leaf':'parent-leaf-as-source-data']] | ['parent':['parent-leaf':'parent-leaf-as-target-data']]
+ 'only source data node has leaves' | sourceDataNode | targetDataNodeWithoutLeafData || ['parent':['parent-leaf':'parent-leaf-as-source-data']] | null
+ 'only target data node has leaves' | sourceDataNodeWithoutLeafData | targetDataNode || null | ['parent':['parent-leaf':'parent-leaf-as-target-data']]
+ 'source and target data node with multiple leaves' | sourceDataNodeWithMultipleLeaves | targetDataNodeWithMultipleLeaves || ['parent':['leaf-1':'leaf-1-in-source', 'leaf-2':'leaf-2-in-source']] | ['parent':['leaf-1':'leaf-1-in-target', 'leaf-2':'leaf-2-in-target']]
}
def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() {
deltaReport[0].getSourceData().equals(expectedSourceData)
deltaReport[0].getTargetData().equals(expectedTargetData)
where: 'following data was used'
- scenario | xpath | sourceDataNodes | sourceDataNodesAsMap | jsonData || expectedNodeXpath | expectedSourceData | expectedTargetData
- 'root node xpath' | '/' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore-name':'Easons'] | ['bookstore-name':'My Store']
- 'parent xpath' | '/bookstore' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore-name':'Easons'] | ['bookstore-name':'My Store']
- 'non-root xpath' | '/bookstore/categories[@code=\'02\']' | bookstoreDataNodeWithChildXpath | bookstoreDataAsMapForChildNode | bookstoreJsonForChildNode || '/bookstore/categories[@code=\'02\']'| ['name':'Kids'] | ['name':'Child']
+ scenario | xpath | sourceDataNodes | sourceDataNodesAsMap | jsonData || expectedNodeXpath | expectedSourceData | expectedTargetData
+ 'root node xpath' | '/' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore':['bookstore-name':'Easons']] | ['bookstore':['bookstore-name':'My Store']]
+ 'parent xpath' | '/bookstore' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore':['bookstore-name':'Easons']] | ['bookstore':['bookstore-name':'My Store']]
+ 'non-root xpath' | '/bookstore/categories[@code=\'02\']' | bookstoreDataNodeWithChildXpath | bookstoreDataAsMapForChildNode | bookstoreJsonForChildNode || '/bookstore/categories[@code=\'02\']'| ['categories':[['code':'02', 'name':'Kids']]] | ['categories':[['code':'02', 'name':'Child']]]
}
def 'Get delta between anchor and payload by using schema from anchor #scenario'() {
deltaReport[0].getSourceData().equals(expectedSourceData)
deltaReport[0].getTargetData().equals(expectedTargetData)
where: 'following data was used'
- scenario | xpath | sourceDataNodes | sourceDataNodesAsMap | jsonData || expectedNodeXpath | expectedSourceData | expectedTargetData
- 'root node xpath' | '/' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore-name':'Easons'] | ['bookstore-name':'My Store']
- 'parent xpath' | '/bookstore' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore-name':'Easons'] | ['bookstore-name':'My Store']
- 'non-root xpath' | '/bookstore/categories[@code=\'02\']' | bookstoreDataNodeWithChildXpath | bookstoreDataAsMapForChildNode | bookstoreJsonForChildNode || '/bookstore/categories[@code=\'02\']' | ['name':'Kids'] | ['name':'Child']
+ scenario | xpath | sourceDataNodes | sourceDataNodesAsMap | jsonData || expectedNodeXpath | expectedSourceData | expectedTargetData
+ 'root node xpath' | '/' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore':['bookstore-name':'Easons']] | ['bookstore':['bookstore-name':'My Store']]
+ 'parent xpath' | '/bookstore' | bookstoreDataNodeWithParentXpath | bookstoreDataAsMapForParentNode | bookstoreJsonForParentNode || '/bookstore' | ['bookstore':['bookstore-name':'Easons']] | ['bookstore':['bookstore-name':'My Store']]
+ 'non-root xpath' | '/bookstore/categories[@code=\'02\']' | bookstoreDataNodeWithChildXpath | bookstoreDataAsMapForChildNode | bookstoreJsonForChildNode || '/bookstore/categories[@code=\'02\']' | ['categories':[['code':'02', 'name':'Kids']]] | ['categories':[['code':'02', 'name':'Child']]]
}
def 'Delta between anchor and payload error scenario #scenario'() {
'empty json data with xpath' | '/bookstore/categories[@code=\'02\']' | '{}'
}
- def 'Get Delta Report between anchors with grouping of data nodes enabled and data node with #scenario'() {
- given: 'xpath and source data node'
+ def 'Delta Report between identical nodes, with grouping of data nodes #scenario'() {
+ given: 'parent node xpath'
def xpath = '/parent'
- def sourceDataNodes = []
when: 'attempt to get delta between 2 anchors'
- def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, INCLUDE_ALL_DESCENDANTS, GROUPING_ENABLED)
+ def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, INCLUDE_ALL_DESCENDANTS, groupDataNodes)
then: 'cps data service is invoked and returns source data nodes'
- mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], INCLUDE_ALL_DESCENDANTS) >> sourceDataNodeWithoutLeafData
and: 'cps data service is invoked again to return target data nodes'
- mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
- and: 'the delta report contains expected "create" action'
- assert deltaReport[0].action == 'create'
- and: 'the delta report contains expected xpath'
- assert deltaReport[0].xpath == '/parent'
- and: 'the delta report does not contain any source data'
- assert deltaReport[0].sourceData == null
- and: 'the delta report contains expected target data, with child data node information included under same delta report entry'
- assert deltaReport[0].targetData == expectedTargetData
- where: 'following data was used'
- scenario | targetDataNodes || expectedTargetData
- 'parent node xpath' | targetDataNode || ['parent':['parent-leaf': 'parent-leaf-as-target-data']]
- 'xpath' | targetDataNodeWithXpath || ['child':['child-leaf': 'child-leaf-as-target-data']]
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], INCLUDE_ALL_DESCENDANTS) >> targetDataNodeWithoutLeafData
+ and: 'the delta report contains expected details for parent node and child node'
+ assert deltaReport.isEmpty()
+ where:
+ scenario | groupDataNodes
+ 'enabled' | GROUPING_ENABLED
+ 'disabled' | GROUPING_DISABLED
}
- def 'Delta Report between identical nodes, with grouping of data nodes enabled'() {
- given: 'parent node xpath'
- def xpath = '/parent'
+ def 'Delta Report between data nodes with list node xpath where leaf data is #scenario with grouping of data nodes enabled'() {
+ given: 'root node xpath'
+ def xpath = '/'
when: 'attempt to get delta between 2 anchors'
def deltaReport = objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, ANCHOR_NAME_1, ANCHOR_NAME_2, xpath, INCLUDE_ALL_DESCENDANTS, GROUPING_ENABLED)
then: 'cps data service is invoked and returns source data nodes'
- mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], INCLUDE_ALL_DESCENDANTS) >> sourceDataNodeWithoutLeafData
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_1, [xpath], INCLUDE_ALL_DESCENDANTS) >> sourceDataNodes
and: 'cps data service is invoked again to return target data nodes'
- mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], INCLUDE_ALL_DESCENDANTS) >> targetDataNodeWithoutLeafData
- and: 'the delta report contains expected details for parent node and child node'
- assert deltaReport.isEmpty()
+ mockCpsDataService.getDataNodesForMultipleXpaths(dataspaceName, ANCHOR_NAME_2, [xpath], INCLUDE_ALL_DESCENDANTS) >> targetDataNodes
+ and: 'delta report contains correct number of entries'
+ deltaReport.size() == 1
+ and: 'the delta report contains expected details for updated node'
+ assert deltaReport[0].action == 'replace'
+ assert deltaReport[0].xpath == "/bookstore/categories[@code='02']"
+ assert deltaReport[0].sourceData == expectedSourceData
+ assert deltaReport[0].targetData == expectedTargetData
+ where: 'the following data is used'
+ scenario | sourceDataNodes | targetDataNodes || expectedSourceData | expectedTargetData
+ 'added' | bookstoreDataNodeWithChildXpath | bookstoreDataNodesWithChildXpathAndNoLeaves || ['categories': [['code': '02', 'name': 'Kids']]] | null
+ 'removed' | bookstoreDataNodesWithChildXpathAndNoLeaves | bookstoreDataNodeWithChildXpath || null | ['categories': [['code': '02', 'name': 'Kids']]]
}
def setupSchemaSetMocks(String... yangResources) {
package org.onap.cps.integration.functional.cps
import org.onap.cps.api.CpsDataService
-import org.onap.cps.integration.base.FunctionalSpecBase
-import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.api.exceptions.AlreadyDefinedException
import org.onap.cps.api.exceptions.AnchorNotFoundException
import org.onap.cps.api.exceptions.CpsPathException
import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.exceptions.DataspaceNotFoundException
+import org.onap.cps.api.parameters.FetchDescendantsOption
+import org.onap.cps.integration.base.FunctionalSpecBase
import org.onap.cps.utils.ContentType
import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
and: 'the payload has expected leaf values'
def sourceData = result[0].getSourceData()
def targetData = result[0].getTargetData()
- assert sourceData == expectedSourceValue
- assert targetData == expectedTargetValue
+ assert sourceData.equals(expectedSourceValue)
+ assert targetData.equals(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]
+ scenario | sourceAnchor | targetAnchor | xpath || expectedSourceValue | expectedTargetValue
+ 'leaf is updated in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || ['bookstore':['bookstore-name':'Easons-1']] | ['bookstore':['bookstore-name': 'Crossword Bookstores']]
+ 'leaf is removed in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 1\']' || ['books':[['price':1, 'title':'Book 1']]] | null
+ 'leaf is added in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 1\']' || null | ['books':[['title':'Book 1', 'price':1]]]
}
def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() {
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':[1988, 2000, 2023]]]
+ def expectedSourceDataInParentNode = ['categories':[['code':'1', 'name':'Children']]]
+ def expectedTargetDataInParentNode = ['categories':[['code':'1', 'name':'Kids']]]
+ def expectedSourceDataInChildNode = [['books':[['lang':'English', 'title':'The Gruffalo']]], ['books':[['editions':[1988, 2000], 'price':20, 'title':'Matilda']]]]
+ def expectedTargetDataInChildNode = [['books':[['lang':'English/German', 'title':'The Gruffalo']]], ['books':[['price':200, 'editions':[1988, 2000, 2023], 'title':'Matilda']]]]
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, NO_GROUPING)
def deltaReportEntities = getDeltaReportEntities(result)