summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy23
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java12
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy22
-rw-r--r--cps-service/src/test/resources/bookstore.json8
-rw-r--r--cps-service/src/test/resources/bookstore.yang28
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy25
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstore.yang28
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstoreData.json8
8 files changed, 150 insertions, 4 deletions
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index d88a9cdf0b..81262c80c4 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -179,6 +179,29 @@ class DataRestControllerSpec extends Specification {
'without observed-timestamp XML' | null | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
}
+ def 'save list elements under root node #scenario.'() {
+ given: 'root node xpath '
+ def rootNodeXpath = '/'
+ when: 'list-node endpoint is invoked with post (create) operation'
+ def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
+ .contentType(MediaType.APPLICATION_JSON)
+ .param('xpath', rootNodeXpath )
+ .content(requestBodyJson)
+ if (observedTimestamp != null)
+ postRequestBuilder.param('observed-timestamp', observedTimestamp)
+ def response = mvc.perform(postRequestBuilder).andReturn().response
+ then: 'a created response is returned'
+ response.status == expectedHttpStatus.value()
+ then: 'the java API was called with the correct parameters'
+ expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedJsonData,
+ { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+ where:
+ scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
+ 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
+ 'without observed-timestamp' | null || 1 | HttpStatus.CREATED
+ 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
+ }
+
def 'Save list elements #scenario.'() {
given: 'parent node xpath '
def parentNodeXpath = 'parent node xpath'
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 6e7c1649d7..7db87e87ea 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -116,8 +116,12 @@ public class CpsDataServiceImpl implements CpsDataService {
final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<DataNode> listElementDataNodeCollection =
buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
- cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
- listElementDataNodeCollection);
+ if (isRootNodeXpath(parentNodeXpath)) {
+ cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
+ } else {
+ cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
+ listElementDataNodeCollection);
+ }
processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@@ -391,6 +395,10 @@ public class CpsDataServiceImpl implements CpsDataService {
.get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
}
+ private static boolean isRootNodeXpath(final String xpath) {
+ return ROOT_NODE_XPATH.equals(xpath);
+ }
+
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index db8664042a..ba438496fd 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -110,6 +110,28 @@ class CpsDataServiceImplSpec extends Specification {
noExceptionThrown()
}
+ def 'Saving list element data fragment under Root node.'() {
+ given: 'schema set for given anchor and dataspace references bookstore model'
+ setupSchemaSetMocks('bookstore.yang')
+ when: 'save data method is invoked with list element json data'
+ def jsonData = '{"multiple-data-tree:invoice": [{"ProductID": "2","ProductName": "Banana","price": "100","stock": True}]}'
+ objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
+ then: 'the persistence service method is invoked with correct parameters'
+ 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
+ { dataNodeCollection ->
+ {
+ assert dataNodeCollection.size() == 1
+ assert dataNodeCollection.collect { it.getXpath() }
+ .containsAll(['/invoice[@ProductID=\'2\']'])
+ }
+ }
+ )
+ and: 'the CpsValidator is called on the dataspaceName and AnchorName'
+ 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+ and: 'data updated event is sent to notification service'
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.UPDATE, observedTimestamp)
+ }
+
def 'Saving child data fragment under existing node.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
diff --git a/cps-service/src/test/resources/bookstore.json b/cps-service/src/test/resources/bookstore.json
index 459908bd63..4b8ed3dab1 100644
--- a/cps-service/src/test/resources/bookstore.json
+++ b/cps-service/src/test/resources/bookstore.json
@@ -1,4 +1,12 @@
{
+ "multiple-data-tree:invoice": [
+ {
+ "ProductID": "1",
+ "ProductName": "Apple",
+ "price": "100",
+ "stock": false
+ }
+ ],
"test:bookstore":{
"bookstore-name": "Chapters/Easons",
"categories": [
diff --git a/cps-service/src/test/resources/bookstore.yang b/cps-service/src/test/resources/bookstore.yang
index 2179fb93d9..b7a52e2c8c 100644
--- a/cps-service/src/test/resources/bookstore.yang
+++ b/cps-service/src/test/resources/bookstore.yang
@@ -15,6 +15,34 @@ module stores {
}
}
+ list invoice {
+ key "ProductID";
+ leaf ProductID {
+ type uint64;
+ mandatory "true";
+ description
+ "Unique product ID. Example: 001";
+ }
+ leaf ProductName {
+ type string;
+ mandatory "true";
+ description
+ "Name of the Product";
+ }
+ leaf price {
+ type uint64;
+ mandatory "true";
+ description
+ "Price of book";
+ }
+ leaf stock {
+ type boolean;
+ default "false";
+ description
+ "Book in stock or not. Example value: true";
+ }
+ }
+
container bookstore {
leaf bookstore-name {
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 750deb1b36..a3f14397c2 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
@@ -43,11 +43,13 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
CpsDataService objectUnderTest
def originalCountBookstoreChildNodes
+ def originalCountBookstoreTopLevelListNodes
def now = OffsetDateTime.now()
def setup() {
objectUnderTest = cpsDataService
originalCountBookstoreChildNodes = countDataNodesInBookstore()
+ originalCountBookstoreTopLevelListNodes = countTopLevelListDataNodesInBookstore()
}
def 'Read bookstore top-level container(s) using #fetchDescendantsOption.'() {
@@ -73,9 +75,9 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
when: 'get data nodes for bookstore container'
def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, root, OMIT_DESCENDANTS)
then: 'the tree consist ouf of one data node'
- assert countDataNodesInTree(result) == 1
+ assert countDataNodesInTree(result) == 2
and: 'the top level data node has the expected attribute and value'
- assert result.leaves['bookstore-name'] == ['Easons']
+ assert result.leaves.size() == 2
where: 'the following variations of "root" are used'
root << [ '/', '' ]
}
@@ -179,6 +181,21 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
thrown(DataNodeNotFoundExceptionBatch)
}
+ def 'Add and Delete top-level list (element) data nodes with root node.'() {
+ given: 'a new (multiple-data-tree:invoice) datanodes'
+ def json = '{"multiple-data-tree:invoice": [{"ProductID": "2","ProductName": "Mango","price": "150","stock": true}]}'
+ when: 'the new list elements are saved'
+ objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now)
+ then: 'they can be retrieved by their xpaths'
+ objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/invoice[@ProductID ="2"]', INCLUDE_ALL_DESCENDANTS)
+ and: 'there is one extra datanode'
+ assert originalCountBookstoreTopLevelListNodes + 1 == countTopLevelListDataNodesInBookstore()
+ when: 'the new elements are deleted'
+ objectUnderTest.deleteDataNode(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/invoice[@ProductID ="2"]', now)
+ then: 'the original number of datanodes is restored'
+ assert originalCountBookstoreTopLevelListNodes == countTopLevelListDataNodesInBookstore()
+ }
+
def 'Add and Delete list (element) data nodes.'() {
given: 'two new (categories) data nodes'
def json = '{"categories": [ {"code":"new1"}, {"code":"new2" } ] }'
@@ -368,4 +385,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
def countDataNodesInBookstore() {
return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
}
+
+ def countTopLevelListDataNodesInBookstore() {
+ return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', INCLUDE_ALL_DESCENDANTS))
+ }
}
diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang
index e592a9c5ce..ab384de1c4 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstore.yang
+++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang
@@ -15,6 +15,34 @@ module stores {
}
}
+ list invoice {
+ key "ProductID";
+ leaf ProductID {
+ type uint64;
+ mandatory "true";
+ description
+ "Unique product ID. Example: 001";
+ }
+ leaf ProductName {
+ type string;
+ mandatory "true";
+ description
+ "Name of the Product";
+ }
+ leaf price {
+ type uint64;
+ mandatory "true";
+ description
+ "Price of book";
+ }
+ leaf stock {
+ type boolean;
+ default "false";
+ description
+ "Book in stock or not. Example value: true";
+ }
+ }
+
container bookstore {
leaf bookstore-name {
diff --git a/integration-test/src/test/resources/data/bookstore/bookstoreData.json b/integration-test/src/test/resources/data/bookstore/bookstoreData.json
index 5d22f6d675..5f66a1d002 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstoreData.json
+++ b/integration-test/src/test/resources/data/bookstore/bookstoreData.json
@@ -1,4 +1,12 @@
{
+ "multiple-data-tree:invoice": [
+ {
+ "ProductID": "1",
+ "ProductName": "Apple",
+ "price": "100",
+ "stock": false
+ }
+ ],
"bookstore": {
"bookstore-name": "Easons",
"premises": {