From: DylanB95EST Date: Wed, 29 Sep 2021 12:44:53 +0000 (+0100) Subject: CPS-594: Exception when updating list node PATCH X-Git-Tag: 2.0.1~17^2 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=cps.git;a=commitdiff_plain;h=8bf710c85d26ddd4586554868473b070ca68a70c CPS-594: Exception when updating list node PATCH SQL ConstraintViolationException updating the list node element using PATCH List node API - Took advantage of replaceDataNodeTree to replace list node children recursively - Added functionality to exclude the updated dataNodes when removing the list node descendants Issue-ID: CPS-594 Signed-off-by: lukegleeson Signed-off-by: DylanB95EST Change-Id: Idbf580fab05581513b52327d6895b9e39b4cf470 Signed-off-by: DylanB95EST --- diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index c57723d32..f924c7045 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -86,7 +86,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService this.dataspaceRepository = dataspaceRepository; this.anchorRepository = anchorRepository; this.fragmentRepository = fragmentRepository; - this.objectMapper = new ObjectMapper(); + objectMapper = new ObjectMapper(); } private static final Gson GSON = new GsonBuilder().create(); @@ -97,7 +97,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath, final DataNode dataNode) { final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath); - final var fragmentEntity = + final FragmentEntity fragmentEntity = toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode); parentFragment.getChildFragments().add(fragmentEntity); try { @@ -131,9 +131,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - final var fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, dataNode); try { fragmentRepository.save(fragmentEntity); @@ -153,7 +153,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService */ private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { - final var parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); + final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { final FragmentEntity childFragment = @@ -189,14 +189,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath, final FetchDescendantsOption fetchDescendantsOption) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); return toDataNode(fragmentEntity, fetchDescendantsOption); } private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName, final String xpath) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); if (isRootXpath(xpath)) { return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); } else { @@ -208,8 +208,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public List queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final CpsPathQuery cpsPathQuery; try { cpsPathQuery = CpsPathQuery.createFrom(cpsPath); @@ -231,11 +231,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private static Set processAncestorXpath(final List fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); - final var pattern = + final Pattern pattern = Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*"); for (final FragmentEntity fragmentEntity : fragmentEntities) { - final var matcher = pattern.matcher(fragmentEntity.getXpath()); + final Matcher matcher = pattern.matcher(fragmentEntity.getXpath()); if (matcher.matches()) { ancestorXpath.add(matcher.group(1)); } @@ -276,15 +276,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, final Map leaves) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); fragmentEntity.setAttributes(GSON.toJson(leaves)); fragmentRepository.save(fragmentEntity); } @Override - public void replaceDataNodeTree(final String dataspaceName, final String anchorName, - final DataNode dataNode) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); + public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) { + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); replaceDataNodeTree(fragmentEntity, dataNode); try { fragmentRepository.save(fragmentEntity); @@ -296,51 +295,105 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } } - private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, final DataNode submittedDataNode) { + private static void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, + final DataNode submittedDataNode) { existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves())); final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments() .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); - final var updatedChildFragments = new HashSet(); + final Collection updatedChildFragments = new HashSet(); for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) { final FragmentEntity childFragment; - if (existingChildrenByXpath.containsKey(submittedChildDataNode.getXpath())) { - childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath()); - replaceDataNodeTree(childFragment, submittedChildDataNode); - } else { + if (isNewDataNode(submittedChildDataNode, existingChildrenByXpath)) { childFragment = convertToFragmentWithAllDescendants( existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode); + } else { + childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath()); + replaceDataNodeTree(childFragment, submittedChildDataNode); } updatedChildFragments.add(childFragment); } - existingFragmentEntity.setChildFragments(updatedChildFragments); + existingFragmentEntity.getChildFragments().clear(); + existingFragmentEntity.getChildFragments().addAll(updatedChildFragments); } @Override @Transactional public void replaceListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection dataNodes) { - final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); - final var firstChildNodeXpath = dataNodes.iterator().next().getXpath(); - final var listNodeXpath = firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[")); - removeListNodeDescendants(parentEntity, listNodeXpath); - final Set childFragmentEntities = dataNodes.stream().map( - dataNode -> convertToFragmentWithAllDescendants( - parentEntity.getDataspace(), parentEntity.getAnchor(), dataNode) - ).collect(Collectors.toUnmodifiableSet()); - parentEntity.getChildFragments().addAll(childFragmentEntities); + final Collection replacementDataNodes) { + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String listNodeXpathPrefix = getListNodeXpathPrefix(replacementDataNodes); + final Map existingListElementFragmentEntitiesByXPath = + extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listNodeXpathPrefix); + removeExistingListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); + final Set updatedChildFragmentEntities = new HashSet<>(); + for (final DataNode replacementDataNode : replacementDataNodes) { + final FragmentEntity existingListNodeElementEntity = + existingListElementFragmentEntitiesByXPath.get(replacementDataNode.getXpath()); + final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, replacementDataNode, + existingListNodeElementEntity); + + updatedChildFragmentEntities.add(entityToBeAdded); + } + parentEntity.getChildFragments().addAll(updatedChildFragmentEntities); fragmentRepository.save(parentEntity); } + private static void removeExistingListElements( + final Collection fragmentEntities, + final Map existingListElementFragmentEntitiesByXPath) { + fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); + } + + private static String getListNodeXpathPrefix(final Collection replacementDataNodes) { + final String firstChildNodeXpath = replacementDataNodes.iterator().next().getXpath(); + return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1); + } + + private static FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity, + final DataNode replacementDataNode, + final FragmentEntity existingListNodeElementEntity) { + if (existingListNodeElementEntity == null) { + return convertToFragmentWithAllDescendants( + parentEntity.getDataspace(), parentEntity.getAnchor(), replacementDataNode); + } + if (replacementDataNode.getChildDataNodes().isEmpty()) { + copyAttributesFromReplacementDataNode(existingListNodeElementEntity, replacementDataNode); + existingListNodeElementEntity.getChildFragments().clear(); + } else { + replaceDataNodeTree(existingListNodeElementEntity, replacementDataNode); + } + return existingListNodeElementEntity; + } + + private static boolean isNewDataNode(final DataNode replacementDataNode, + final Map existingListNodeElementsByXpath) { + return !existingListNodeElementsByXpath.containsKey(replacementDataNode.getXpath()); + } + + private static void copyAttributesFromReplacementDataNode(final FragmentEntity existingListNodeElementEntity, + final DataNode replacementDataNode) { + final FragmentEntity replacementFragmentEntity = + FragmentEntity.builder().attributes(GSON.toJson(replacementDataNode.getLeaves())).build(); + existingListNodeElementEntity.setAttributes(replacementFragmentEntity.getAttributes()); + } + + private static Map extractListElementFragmentEntitiesByXPath( + final Set childEntities, final String listNodeXpathPrefix) { + return childEntities.stream() + .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listNodeXpathPrefix)) + .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); + } + @Override @Transactional public void deleteListDataNodes(final String dataspaceName, final String anchorName, final String listNodeXpath) { - final var parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/')); - final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); - final var descendantNode = listNodeXpath.substring(listNodeXpath.lastIndexOf('/')); + final String parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/')); + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String descendantNode = listNodeXpath.substring(listNodeXpath.lastIndexOf('/')); final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(descendantNode); final boolean xpathPointsToAValidChildNodeWithKey = parentEntity.getChildFragments().stream().anyMatch( diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index e2316e863..144b18b53 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -32,10 +32,12 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder +import org.spockframework.util.CollectionUtil import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql import javax.validation.ConstraintViolationException +import java.util.stream.Collectors import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS @@ -55,6 +57,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { static final long UPDATE_DATA_NODE_SUB_FRAGMENT_ID = 4203L static final long LIST_DATA_NODE_PARENT201_FRAGMENT_ID = 4206L static final long LIST_DATA_NODE_PARENT203_FRAGMENT_ID = 4214L + static final long LIST_DATA_NODE_PARENT204_FRAGMENT_ID = 4219L + static final long LIST_DATA_NODE_PARENT205_FRAGMENT_ID = 4221L static final long LIST_DATA_NODE_CHILD202_FRAGMENT_ID = 4204L static final long LIST_DATA_NODE_PARENT202_FRAGMENT_ID = 4211L @@ -385,18 +389,78 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { given: 'list node data fragment as a collection of data nodes' def listNodeCollection = buildDataNodeCollection(listNodeXpaths) when: 'list-node elements replaced within the existing parent node' - objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection) + objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, listNodeCollection) then: 'child list elements are updated as expected, non-list element remains as is' - def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID) + def parentFragment = fragmentRepository.getById(listNodeFragmentID) def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() } assert allChildXpaths.size() == expectedChildXpaths.size() assert allChildXpaths.containsAll(expectedChildXpaths) where: 'following parameters were used' - scenario | listNodeXpaths || expectedChildXpaths - 'existing list-node' | ['/parent-201/child-204[@key="B"]'] || ['/parent-201/child-203', '/parent-201/child-204[@key="B"]'] - 'non-existing list-node' | ['/parent-201/child-205[@key="1"]'] || ['/parent-201/child-203', '/parent-201/child-204[@key="A"]', '/parent-201/child-204[@key="X"]', '/parent-201/child-205[@key="1"]'] + scenario | listNodeXpaths |parentXpath |listNodeFragmentID || expectedChildXpaths + 'existing list node with non existing key' | ['/parent-201/child-204[@key="B"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="B"]'] + 'non existing list node with non existing key' | ['/parent-201/child-205[@key="1"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="A"]', '/parent-201/child-204[@key="X"]', '/parent-201/child-205[@key="1"]'] + 'existing list node with 1 existing key' | ['/parent-201/child-204[@key="X"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="X"]'] + 'existing list-node with combined keys' | ['/parent-202/child-205[@key="A"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A"]'] + 'existing grandchild list-node' | ['/parent-200/child-202/grand-child-202[@key="E"]'] | '/parent-200/child-202' | LIST_DATA_NODE_CHILD202_FRAGMENT_ID || ['/parent-200/child-202/grand-child-202[@key="E"]'] + 'existing list node with two list nodes' | ['/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]'] + 'existing list node with compounded list node' | ['/parent-202/child-205[@key="A" and @key2="B"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A" and @key2="B"]'] + 'existing list node with list node with parent with key value' | ['/parent-204[@key="L"]/child-210[@key="N"]'] | '/parent-204[@key="L"]' | LIST_DATA_NODE_PARENT204_FRAGMENT_ID || ['/parent-204[@key="L"]/child-210[@key="N"]'] } + @Sql([CLEAR_DATA, SET_DATA]) + def 'Replace list-node that has children with #scenario'() { + given: 'list node data fragment with child data node fragments' + def grandChildDataNodes = buildDataNodeCollection(grandChildXpaths) + def listNode = new DataNodeBuilder().withXpath(childXpath).withChildDataNodes(grandChildDataNodes).build() + when: 'list-node elements replaced within the existing parent node' + objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, [ listNode ]) + then: 'child list elements are updated as expected with non-list elements remaining as is' + def parentFragment = fragmentRepository.getById(listNodeFragmentId) + def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() } + assert allChildXpaths.size() == expectedChildXpaths.size() + assert allChildXpaths.containsAll(expectedChildXpaths) + and: 'grandchild list elements are updated as expected' + def allGrandChildXpaths = parentFragment.getChildFragments().collect(){ + it.getChildFragments().collect(){ + it.getXpath()}} + allGrandChildXpaths.removeIf(list -> list.isEmpty()) + def grandChildXpathsToList = allGrandChildXpaths.stream().flatMap(List::stream).collect(Collectors.toList()) + def expectedGrandChildXpaths = grandChildXpaths + assert grandChildXpathsToList.size() == expectedGrandChildXpaths.size() + assert grandChildXpathsToList.containsAll(expectedGrandChildXpaths) + where: 'the following parameters are used' + scenario | parentXpath | childXpath | grandChildXpaths | expectedChildXpaths | listNodeFragmentId + 'existing grandchild of list node' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]'] | ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID + 'existing grandchild of list node with two new nodes' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]' , '/parent-203/child-204/grandchild[@key="3"]'] | ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID + 'existing grandchild with compound list node' | '/parent-205' | '/parent-205/child-205[@key="X"]' | ['/parent-205/child-205/grand-child-206[@key="Y" and @key2="Z"]'] | ['/parent-205/child-205', '/parent-205/child-205[@key="X"]'] | LIST_DATA_NODE_PARENT205_FRAGMENT_ID + 'two existing list node with a new node' | '/parent-205' | '/parent-205/child-205[@key="X"]' | ['/parent-205/child-205/grandchild[@key="A"]'] | ['/parent-205/child-205', '/parent-205/child-205[@key="X"]'] | LIST_DATA_NODE_PARENT205_FRAGMENT_ID + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Replace list-node content of #scenario with grandchildren.'() { + given: 'list node data fragment as a collection of data nodes' + def listNodeCollection = buildDataNodeCollection(listNodeXpaths) + when: 'list-node elements replaced within the existing parent node' + objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, listNodeCollection) + then: 'child list elements are updated as expected with non-list elements remaining as is' + def parentFragment = fragmentRepository.getById(listNodeFragmentID) + def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() } + assert allChildXpaths.size() == expectedChildXpaths.size() + assert allChildXpaths.containsAll(expectedChildXpaths) + and: 'grandchild list elements are updated as expected' + def allGrandChildXpaths = parentFragment.getChildFragments().collect { + it.getChildFragments().collect { + it.getXpath()}} + allGrandChildXpaths.removeIf(list -> list.isEmpty()) + assert allGrandChildXpaths.size() == expectedGrandChildXpaths.size() + assert allGrandChildXpaths.containsAll(expectedGrandChildXpaths) + where: 'following parameters were used' + scenario | listNodeXpaths | parentXpath | listNodeFragmentID || expectedChildXpaths | expectedGrandChildXpaths + 'existing list node with existing keys' | ['/parent-203/child-204[@key="X"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | [] + 'non existing list node with existing keys' | ['/parent-203/child-204[@key="V"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="V"]'] | [] + } + + @Sql([CLEAR_DATA, SET_DATA]) def 'Replace list-node fragment error scenario: #scenario.'() { given: 'list node data fragment as a collection of data nodes' diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 886e6e175..f6e887e01 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -61,4 +61,11 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) (4215, 1001, 3003, 4214, '/parent-203/child-203', '{}'), (4216, 1001, 3003, 4214, '/parent-203/child-204[@key="A"]', '{"key": "A"}'), (4217, 1001, 3003, 4214, '/parent-203/child-204[@key="X"]', '{"key": "X"}'), - (4218, 1001, 3003, 4217, '/parent-203/child-204[@key="X"]/grand-child-204[@key2="Y"]', '{"key": "X", "key2": "Y"}'); \ No newline at end of file + (4218, 1001, 3003, 4217, '/parent-203/child-204[@key="X"]/grand-child-204[@key2="Y"]', '{"key": "X", "key2": "Y"}'), + (4219, 1001, 3003, null, '/parent-204[@key="L"]', '{"key": "L"}'), + (4220, 1001, 3003, 4219, '/parent-204[@key="L"]/child-210[@key="M"]', '{"key": "M"}'), + (4221, 1001, 3003, null, '/parent-205', '{"leaf-value": "original"}'), + (4222, 1001, 3003, 4221, '/parent-205/child-205', '{}'), + (4223, 1001, 3003, 4221, '/parent-205/child-205[@key="X"]', '{"key": "X"}'), + (4224, 1001, 3003, 4223, '/parent-205/child-205[@key="X"]/grand-child-206[@key="Y"]', '{"key": "Y", "key2": "Z"}'), + (4225, 1001, 3003, 4223, '/parent-205/child-205[@key="X"]/grand-child-206[@key="Y" and @key2="Z"]', '{"key": "Y", "key2": "Z"}'); \ No newline at end of file