Persisting a list element to a parent list (ep2)

Post List Element does not allow for create List Element, only appends onto existing node as children

-Add a check in saveListElements to see if the parent xpath is a root path ("/").If root node store list element as top node. Else add passed list element to parent xpath node.
-Add test for scenario for above
-Add test scenario Saving list element data fragment under Root node
-Add Integration Tests Add and Delete top-level list (element) data nodes with root node
-Update bookstore model with TopLevelList datanode

Issue-ID: CPS-1586
Change-Id: Iaa7f59fbeebb03703626132c6d5c2afde0e7ab4b
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
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 6e7c164..7db87e8 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 @@
         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 @@
             .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 db86640..ba43849 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 @@
             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 459908b..4b8ed3d 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 2179fb9..b7a52e2 100644
--- a/cps-service/src/test/resources/bookstore.yang
+++ b/cps-service/src/test/resources/bookstore.yang
@@ -15,6 +15,34 @@
         }
     }
 
+    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 {