Bug: Update operation executes on a data node that is not present in the database 33/142033/8
authorArpit Singh <AS00745003@techmahindra.com>
Thu, 11 Sep 2025 06:46:46 +0000 (12:16 +0530)
committerArpit Singh <AS00745003@techmahindra.com>
Thu, 18 Sep 2025 07:48:55 +0000 (13:18 +0530)
Failing test where an attempt is made to update a data node that does not exist
in database.
The test shows that no exception is thrown when such datanode is
updated.

Issue-ID: CPS-2980
Change-Id: Ia42b66016be4e880e6207916c4f98b9aa3069dad
Signed-off-by: Arpit Singh <AS00745003@techmahindra.com>
cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy

index 72a7036..5d2e46e 100644 (file)
@@ -134,7 +134,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
         final Collection<String> xpathsOfUpdatedLeaves = updatedLeavesPerXPath.keySet();
         final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpathsOfUpdatedLeaves);
-
+        if (fragmentEntities.isEmpty()) {
+            throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, xpathsOfUpdatedLeaves);
+        }
         for (final FragmentEntity fragmentEntity : fragmentEntities) {
             final Map<String, Serializable> updatedLeaves = updatedLeavesPerXPath.get(fragmentEntity.getXpath());
             final String mergedLeaves = mergeLeaves(updatedLeaves, fragmentEntity.getAttributes());
index 9303275..86097be 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.ri
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.hibernate.StaleStateException
+import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch
 import org.onap.cps.ri.models.AnchorEntity
 import org.onap.cps.ri.models.DataspaceEntity
 import org.onap.cps.ri.models.FragmentEntity
@@ -95,7 +96,6 @@ class CpsDataPersistenceServiceImplSpec extends Specification {
 
     def 'Batch update data node leaves and descendants: #scenario'(){
         given: 'the fragment repository returns fragment entities related to the xpath inputs'
-            mockFragmentRepository.findByAnchorAndXpathIn(_, [] as Set) >> []
             mockFragmentRepository.findByAnchorAndXpathIn(_, ['/test/xpath'] as Set) >> [
                     new FragmentEntity(1, '/test/xpath', null, "{\"id\":\"testId\"}", anchorEntity, [] as Set)
             ]
@@ -113,11 +113,24 @@ class CpsDataPersistenceServiceImplSpec extends Specification {
             })
         where: 'the following Data Type is passed'
             scenario                         | dataNodes                                                                                                                              | expectedSize || expectedFragmentEntities
-            'empty data node list'           | []                                                                                                                                     | 0            || []
             'one data node in list'          | [new DataNode(xpath: '/test/xpath', leaves: ['id': 'testId'])]                                                                         | 1            || [new FragmentEntity(xpath: '/test/xpath', attributes: '{"id":"testId"}', anchor: anchorEntity)]
             'multiple data nodes'            | [new DataNode(xpath: '/test/xpath1', leaves: ['id': 'newTestId1']), new DataNode(xpath: '/test/xpath2', leaves: ['id': 'newTestId2'])] | 2            || [new FragmentEntity(xpath: '/test/xpath2', attributes: '{"id":"newTestId2"}', anchor: anchorEntity), new FragmentEntity(xpath: '/test/xpath1', attributes: '{"id":"newTestId1"}', anchor: anchorEntity)]
     }
 
+    def 'Batch update data node leaves and descendants fails when fragment entity is not found'() {
+        given: 'leaf nodes to update'
+            def leafNodes = ['/test/xpath': ['id': 'invalid-id']]
+        and: 'the fragment repository returns no fragment entities'
+            mockFragmentRepository.findByAnchorAndXpathIn(anchorEntity, ['/test/xpath'] as Set) >> []
+        and: 'the fragment repository returns no fragments for list nodes'
+            mockFragmentRepository.findListByAnchorAndXpath(anchorEntity, '/test/xpath') >> []
+        when: 'attempt to batch update data node leaves'
+            objectUnderTest.batchUpdateDataLeaves('dataspaceName', 'anchorName', leafNodes)
+        then: 'expected exception is thrown with correct message'
+            def thrown = thrown(DataNodeNotFoundExceptionBatch)
+            assert thrown.message == 'DataNode not found'
+    }
+
     def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
         given: 'the system can update one datanode and has two more datanodes that throw an exception while updating'
             def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([
index c7f7b7d..802438f 100644 (file)
@@ -521,6 +521,24 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase {
             restoreBookstoreDataAnchor(2)
     }
 
+    def 'Update leaves on non existing data node'() {
+        given: 'JSON string with data of non existent data node'
+            def jsonDataForNonExistingDataNode =  "{'books':{'lang':'English/French','price':100,'title':'Non-existing book'}}"
+        when: 'attempt to perform update'
+            objectUnderTest.updateNodeLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore/categories[@code='1']", jsonDataForNonExistingDataNode, now, ContentType.JSON)
+        then: 'expected exception is thrown'
+            thrown(DataNodeNotFoundExceptionBatch)
+    }
+
+    def 'Update leaves and descendants on non existing data node'() {
+        given: 'JSON string with data of non existent data node'
+            def jsonDataForNonExistingDataNodes =  "{'categories':{'code':'xxx','books':{'lang':'English/French','price':100,'title':'Non-existing book'}}}"
+        when: 'attempt to perform update'
+            objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_2, "/bookstore", jsonDataForNonExistingDataNodes, now)
+        then: 'expected exception is thrown'
+            thrown(DataNodeNotFoundExceptionBatch)
+    }
+
     def countDataNodesInBookstore() {
         return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
     }