diff options
author | DylanB95EST <dylan.byrne@est.tech> | 2021-09-29 13:44:53 +0100 |
---|---|---|
committer | DylanB95EST <dylan.byrne@est.tech> | 2021-09-29 13:44:53 +0100 |
commit | 8bf710c85d26ddd4586554868473b070ca68a70c (patch) | |
tree | 28e9dd1dce1410e46618a7e7379f948bee3084c7 /cps-ri/src | |
parent | fdcb36fa9db642df3c47637d32f4e7ac5dd347e8 (diff) |
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 <luke.gleeson@est.tech>
Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
Change-Id: Idbf580fab05581513b52327d6895b9e39b4cf470
Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
Diffstat (limited to 'cps-ri/src')
3 files changed, 167 insertions, 43 deletions
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 c57723d322..f924c70453 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<FragmentEntity> 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<DataNode> 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<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set<String> 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<String, Object> 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<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments() .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); - final var updatedChildFragments = new HashSet<FragmentEntity>(); + final Collection updatedChildFragments = new HashSet<FragmentEntity>(); 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<DataNode> 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<FragmentEntity> childFragmentEntities = dataNodes.stream().map( - dataNode -> convertToFragmentWithAllDescendants( - parentEntity.getDataspace(), parentEntity.getAnchor(), dataNode) - ).collect(Collectors.toUnmodifiableSet()); - parentEntity.getChildFragments().addAll(childFragmentEntities); + final Collection<DataNode> replacementDataNodes) { + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String listNodeXpathPrefix = getListNodeXpathPrefix(replacementDataNodes); + final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath = + extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listNodeXpathPrefix); + removeExistingListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); + final Set<FragmentEntity> 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<FragmentEntity> fragmentEntities, + final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) { + fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); + } + + private static String getListNodeXpathPrefix(final Collection<DataNode> 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<String, FragmentEntity> 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<String, FragmentEntity> extractListElementFragmentEntitiesByXPath( + final Set<FragmentEntity> 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 e2316e8636..144b18b535 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,19 +389,79 @@ 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' def listNodeCollection = buildDataNodeCollection(listNodeXpaths) diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 886e6e1751..f6e887e017 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 |