summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordanielhanrahan <daniel.hanrahan@est.tech>2024-02-08 15:04:05 +0000
committerDaniel Hanrahan <daniel.hanrahan@est.tech>2024-02-12 14:56:01 +0000
commite2d88379376923fbdddc78b59f74d75fd8040ec6 (patch)
tree338da3402e62f1aa0497cebb7282ae469be12031
parentc72a0132b024141716b35550fd7204338b2fc673 (diff)
Fix test failure by ordering leaf-lists
YANG specifies two ways that leaf-lists can be ordered: - ordered-by user: original order in JSON is preserved - ordered-by system (default): it is up to the system how to order For leaf-lists to preserve same order as the JSON, the Yang module must specify 'ordered-by user'. To ensure consistent behaviour even when system ordering is used, the leaf-list is sorted during parsing. - Add 'ordered-by user' to authors field in bookstore.yang - Sort leaf-list during parsing when using 'ordered-by system' - Add new tests to verify ordering Issue-ID: CPS-2057 Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech> Change-Id: I6ab688ec2fa4a22182e853d1a8b26642f278c40a
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java13
-rw-r--r--csit/tests/cps-data/cps-data.robot5
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy34
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy10
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstore.yang6
5 files changed, 51 insertions, 17 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
index b040af5bb4..9859acdf0e 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
@@ -2,7 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Bell Canada. All rights reserved.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2023 Nordix Foundation.
+ * Modifications Copyright (C) 2022-2024 Nordix Foundation.
* Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,6 +34,7 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.utils.YangUtils;
+import org.opendaylight.yangtools.yang.common.Ordering;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@@ -242,10 +243,14 @@ public class DataNodeBuilder {
private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) {
final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName();
- final List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
+ List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
.stream()
- .map(normalizedNode -> (normalizedNode).body())
- .collect(Collectors.toUnmodifiableList());
+ .map(NormalizedNode::body)
+ .collect(Collectors.toList());
+ if (leafSetNode.ordering() == Ordering.SYSTEM) {
+ leafListValues.sort(null);
+ }
+ leafListValues = Collections.unmodifiableList(leafListValues);
addYangLeaf(currentDataNode, leafListName, (Serializable) leafListValues);
}
diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot
index f506b28011..e83857caea 100644
--- a/csit/tests/cps-data/cps-data.robot
+++ b/csit/tests/cps-data/cps-data.robot
@@ -59,10 +59,7 @@ Get Updated Data Node by XPath
Should Be Equal As Strings ${responseJson['name']} Bigger
${length_birds}= Get Length ${responseJson['birds']}
Should Be Equal As Integers ${length_birds} 3
- ${expected_list}= Create List Pigeon Falcon Eagle
- FOR ${item_to_check} IN @{expected_list}
- Should Contain ${responseJson['birds']} ${item_to_check}
- END
+ Should Be Equal As Strings ${responseJson['birds']} ['Eagle', 'Falcon', 'Pigeon']
Get Data Node by XPath
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors/${anchorName}/node
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 64996536e6..f967c62037 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
@@ -423,8 +423,34 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
then: 'the updated data nodes are retrieved'
def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
and: 'the leaf values are updated as expected'
- assert result.leaves['lang'] == ['English/French']
- assert result.leaves['price'] == [100]
+ assert result[0].leaves['lang'] == 'English/French'
+ assert result[0].leaves['price'] == 100
+ cleanup:
+ restoreBookstoreDataAnchor(2)
+ }
+
+ def 'Order of leaf-list elements is preserved when "ordered-by user" is set in the YANG model.'() {
+ given: 'Updated json for bookstore data'
+ def jsonData = "{'book-store:books':{'title':'Matilda', 'authors': ['beta', 'alpha', 'gamma', 'delta']}}"
+ when: 'update is performed for leaves'
+ objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+ and: 'the updated data nodes are retrieved'
+ def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
+ then: 'the leaf-list values have expected order'
+ assert result[0].leaves['authors'] == ['beta', 'alpha', 'gamma', 'delta']
+ cleanup:
+ restoreBookstoreDataAnchor(2)
+ }
+
+ def 'Leaf-list elements are sorted when "ordered-by user" is not set in the YANG model.'() {
+ given: 'Updated json for bookstore data'
+ def jsonData = "{'book-store:books':{'title':'Matilda', 'editions': [2011, 1988, 2001, 2022, 2025]}}"
+ when: 'update is performed for leaves'
+ objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonData, now)
+ and: 'the updated data nodes are retrieved'
+ def result = cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code=1]/books[@title='Matilda']", INCLUDE_ALL_DESCENDANTS)
+ then: 'the leaf-list values have natural order'
+ assert result[0].leaves['editions'] == [1988, 2001, 2011, 2022, 2025]
cleanup:
restoreBookstoreDataAnchor(2)
}
@@ -540,7 +566,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
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]]]
+ def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[1988, 2000, 2023]]]
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)
@@ -555,7 +581,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
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) CPS-2057
+ assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
}
def getDeltaReportEntities(List<DeltaReport> deltaReport) {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
index 3807a14bc5..b7b6fa11a7 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy
@@ -36,8 +36,8 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
CpsModuleService objectUnderTest
private static def originalNumberOfModuleReferences = 2 // bookstore has two modules
- private static def bookStoreModuleReference = new ModuleReference('stores','2024-01-30')
- private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-01-30', 'org:onap:cps:sample')
+ private static def bookStoreModuleReference = new ModuleReference('stores','2024-02-08')
+ private static def bookStoreModuleReferenceWithNamespace = new ModuleReference('stores','2024-02-08', 'org:onap:cps:sample')
private static def bookStoreTypesModuleReference = new ModuleReference('bookstore-types','2024-01-30')
private static def bookStoreTypesModuleReferenceWithNamespace = new ModuleReference('bookstore-types','2024-01-30', 'org:onap:cps:types:sample')
static def NEW_RESOURCE_REVISION = '2023-05-10'
@@ -155,7 +155,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
def result = objectUnderTest.getModuleDefinitionsByAnchorName(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1)
then: 'the correct module definitions are returned'
assert result.size() == 2
- assert result.contains(new ModuleDefinition('stores','2024-01-30',bookstoreModelFileContent))
+ assert result.contains(new ModuleDefinition('stores','2024-02-08',bookstoreModelFileContent))
assert result.contains(new ModuleDefinition('bookstore-types','2024-01-30', bookstoreTypesFileContent))
}
@@ -165,12 +165,12 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
then: 'the correct module definitions are returned'
if (expectedNumberOfDefinitions > 0) {
assert result.size() == expectedNumberOfDefinitions
- def expectedModuleDefinition = new ModuleDefinition('stores', '2024-01-30', bookstoreModelFileContent)
+ def expectedModuleDefinition = new ModuleDefinition('stores', '2024-02-08', bookstoreModelFileContent)
assert result[0] == expectedModuleDefinition
}
where: 'following parameters are used'
scenarios | moduleName | moduleRevision || expectedNumberOfDefinitions
- 'correct module name and revision' | 'stores' | '2024-01-30' || 1
+ 'correct module name and revision' | 'stores' | '2024-02-08' || 1
'correct module name' | 'stores' | null || 1
'incorrect module name' | 'other' | null || 0
'incorrect revision' | 'stores' | '2025-11-22' || 0
diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang
index 2abde656d4..0d093ea36c 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstore.yang
+++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang
@@ -9,6 +9,11 @@ module stores {
revision-date 2024-01-30;
}
+ revision "2024-02-08" {
+ description
+ "Order of book authors is preserved";
+ }
+
revision "2024-01-30" {
description
"Extracted bookstore types";
@@ -107,6 +112,7 @@ module stores {
type string;
}
leaf-list authors {
+ ordered-by user;
type string;
}
leaf-list editions {