*/
package org.onap.cps.spi.impl
-import org.onap.cps.spi.exceptions.ConcurrencyException
-
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-
import com.google.common.collect.ImmutableSet
import com.google.gson.Gson
import com.google.gson.GsonBuilder
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
class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100'
static final long UPDATE_DATA_NODE_FRAGMENT_ID = 4202L
static final long UPDATE_DATA_NODE_SUB_FRAGMENT_ID = 4203L
- static final long LIST_DATA_NODE_PARENT_FRAGMENT_ID = 4206L
+ 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
static final DataNode newDataNode = new DataNodeBuilder().build()
static DataNode existingDataNode
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Add list-node fragment with multiple elements.'() {
- given: 'list node data fragment as a collection of data nodes'
+ def 'Add list-node fragment with multiple elements including an element with a child datanode.'() {
+ given: 'two new data nodes for an existing list'
def listNodeXpaths = ['/parent-201/child-204[@key="B"]', '/parent-201/child-204[@key="C"]']
def listNodeCollection = buildDataNodeCollection(listNodeXpaths)
- when: 'list-node elements added to existing parent node'
+ and: 'a child node for one of the new data nodes'
+ def childDataNode = buildDataNode('/parent-201/child-204[@key="C"]/grand-child-204[@key2="Z"]', [leave:'value'], [])
+ listNodeCollection.iterator().next().childDataNodes = [childDataNode]
+ when: 'the data nodes (list elements) are added to existing parent node'
objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection)
then: 'new entries successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
- def parentFragment = fragmentRepository.getOne(LIST_DATA_NODE_PARENT_FRAGMENT_ID)
+ def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
assert allChildXpaths.size() == 5
assert allChildXpaths.containsAll(listNodeXpaths)
+ and: 'the child node of the new list entry is also present'
+ def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
+ def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3)
+ def listElementChild = fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, childDataNode.xpath)
+ assert listElementChild.isPresent()
}
@Sql([CLEAR_DATA, SET_DATA])
objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
"/parent-200/child-201", ['leaf-value': 'new'])
then: 'leaves are updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
when: 'replace data node tree is performed'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
def 'Replace data node tree with descendants.'() {
given: 'data node object with leaves updated, having child with old content'
def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [
- buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'original'], [])
+ buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'original'], [])
])
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
def submittedDataNode = buildDataNode(xpath, ['leaf-name': 'leaf-value'], [])
when: 'attempt to update data node for #scenario'
objectUnderTest.replaceDataNodeTree(dataspaceName, anchorName, submittedDataNode)
- then: 'a #expectedException is thrown'
+ then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'the following data is used'
scenario | dataspaceName | anchorName | xpath || expectedException
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.getOne(LIST_DATA_NODE_PARENT_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 |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 || 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 | 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'
'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException
}
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Delete list-node content of #scenario.'() {
+ given: 'list node data fragments are present in database'
+ when: 'list-node elements deleted within the existing parent node'
+ objectUnderTest.deleteListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, listNodeXpaths)
+ then: 'child list elements are removed as expected, non-list element remains as is'
+ 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 | listNodeFragmentID || expectedChildXpaths
+ 'existing list-node with key' | '/parent-203/child-204[@key="A"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]']
+ 'existing list-node with key' | '/parent-203/child-204[@key="X"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="A"]']
+ 'existing grand-child list node with keys' | '/parent-203/child-204[@key="X"]/grand-child-204[@key2="Y"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]', '/parent-203/child-204[@key="A"]']
+ 'existing list-node with combined keys' | '/parent-202/child-205[@key="A" and @key2="B"]' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]']
+ 'existing node with list node variants to delete' | '/parent-203/child-204' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203']
+ 'existing grandchild list-node' | '/parent-200/child-202/grand-child-202[@key="D"]' | LIST_DATA_NODE_CHILD202_FRAGMENT_ID || []
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Delete list-node fragment error scenario: #scenario.'() {
+ given: 'list node data fragments are present in database'
+ when: 'list-node elements are deleted under existing parent node'
+ objectUnderTest.deleteListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, listNodeXpaths)
+ then: 'a #expectedException is thrown'
+ thrown(expectedException)
+ where: 'following parameters were used'
+ scenario | listNodeXpaths || expectedException
+ 'list parent node does not exist' | '/unknown/unknown' || DataNodeNotFoundException
+ 'list child nodes do not exist' | '/parent-200/unknown' || DataNodeNotFoundException
+ 'list child nodes with key does not exist' | '/parent-200/unknown[@key="C"]' || DataNodeNotFoundException
+ 'list grandchild nodes parent does not exist' | '/parent-200/unknown/unknown' || DataNodeNotFoundException
+ 'non-existing parent with existing list-node' | '/unknown/child-204' || DataNodeNotFoundException
+ 'non-existing parent with existing list-node & key' | '/unknown/child-204[@key="A"]' || DataNodeNotFoundException
+ 'valid with non existing key' | '/parent-200/child-202/grand-child-202[@key="A"]' || DataNodeNotFoundException
+ 'child list node without key' | '/parent-200/child-204/grand-child-204' || DataNodeNotFoundException
+ 'valid list node with invalid key' | '/parent-203/child-204[@key="C"]' || DataNodeNotFoundException
+
+ }
+
static Collection<DataNode> buildDataNodeCollection(xpaths) {
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}