From: Arpit Singh Date: Thu, 11 Sep 2025 06:46:46 +0000 (+0530) Subject: Bug: Update operation executes on a data node that is not present in the database X-Git-Tag: 3.7.1~12^2 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=0ac47ab5fa485a327e6fdb0775c6431c047661a5;p=cps.git Bug: Update operation executes on a data node that is not present in the database 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 --- diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java index 72a70362ff..5d2e46e059 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java @@ -134,7 +134,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final Collection xpathsOfUpdatedLeaves = updatedLeavesPerXPath.keySet(); final Collection fragmentEntities = getFragmentEntities(anchorEntity, xpathsOfUpdatedLeaves); - + if (fragmentEntities.isEmpty()) { + throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, xpathsOfUpdatedLeaves); + } for (final FragmentEntity fragmentEntity : fragmentEntities) { final Map updatedLeaves = updatedLeavesPerXPath.get(fragmentEntity.getXpath()); final String mergedLeaves = mergeLeaves(updatedLeaves, fragmentEntity.getAttributes()); diff --git a/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy index 9303275a03..86097be516 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy @@ -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([ diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy index c7f7b7d3df..802438f8e3 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy @@ -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)) }