aboutsummaryrefslogtreecommitdiffstats
path: root/integration-test/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'integration-test/src/test')
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy4
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy187
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DeltaServiceIntegrationSpec.groovy245
3 files changed, 249 insertions, 187 deletions
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
index bb69f2f544..b432823158 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
@@ -28,6 +28,7 @@ import okhttp3.mockwebserver.MockWebServer
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsDataspaceService
+import org.onap.cps.api.CpsDeltaService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.CpsQueryService
import org.onap.cps.api.exceptions.DataspaceNotFoundException
@@ -93,6 +94,9 @@ abstract class CpsIntegrationSpecBase extends Specification {
CpsDataService cpsDataService
@Autowired
+ CpsDeltaService cpsDeltaService
+
+ @Autowired
CpsModuleService cpsModuleService
@Autowired
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 4823d58af9..6ecc3a509c 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
@@ -26,13 +26,11 @@ import org.onap.cps.integration.base.FunctionalSpecBase
import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.api.exceptions.AlreadyDefinedException
import org.onap.cps.api.exceptions.AnchorNotFoundException
-import org.onap.cps.api.exceptions.CpsAdminException
import org.onap.cps.api.exceptions.CpsPathException
import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.exceptions.DataspaceNotFoundException
-import org.onap.cps.api.model.DeltaReport
import org.onap.cps.utils.ContentType
import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
@@ -523,191 +521,6 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase {
restoreBookstoreDataAnchor(2)
}
- def 'Get delta between 2 anchors'() {
- when: 'attempt to get delta report between anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS)
- and: 'report is ordered based on xpath'
- result = result.toList().sort { it.xpath }
- then: 'delta report contains expected number of changes'
- result.size() == 3
- and: 'delta report contains REPLACE action with expected xpath'
- assert result[0].getAction() == 'replace'
- assert result[0].getXpath() == '/bookstore'
- and: 'delta report contains CREATE action with expected xpath'
- assert result[1].getAction() == 'create'
- assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
- and: 'delta report contains REMOVE action with expected xpath'
- assert result[2].getAction() == 'remove'
- assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
- }
-
- def 'Get delta between 2 anchors returns empty response when #scenario'() {
- when: 'attempt to get delta report between anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS)
- then: 'delta report is empty'
- assert result.isEmpty()
- where: 'following data was used'
- scenario | targetAnchor | xpath
- 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_4 | '/'
- 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | '/'
- 'non existing xpath' | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath'
- }
-
- def 'Get delta between anchors error scenario: #scenario'() {
- when: 'attempt to get delta between anchors'
- objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchor, targetAnchor, '/some-xpath', INCLUDE_ALL_DESCENDANTS)
- then: 'expected exception is thrown'
- thrown(expectedException)
- where: 'following data was used'
- scenario | dataspaceName | sourceAnchor | targetAnchor || expectedException
- 'invalid dataspace name' | 'Invalid dataspace' | 'not-relevant' | 'not-relevant' || DataValidationException
- 'invalid anchor 1 name' | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor' | 'not-relevant' || DataValidationException
- 'invalid anchor 2 name' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'invalid anchor' || DataValidationException
- 'non-existing dataspace' | 'non-existing' | 'not-relevant1' | 'not-relevant2' || DataspaceNotFoundException
- 'non-existing dataspace with same anchor name' | 'non-existing' | 'not-relevant' | 'not-relevant' || DataspaceNotFoundException
- 'non-existing anchor 1' | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | 'not-relevant' || AnchorNotFoundException
- 'non-existing anchor 2' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'non-existing-anchor' || AnchorNotFoundException
- }
-
- def 'Get delta between anchors for remove action, where source data node #scenario'() {
- when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_5, BOOKSTORE_ANCHOR_3, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
- then: 'expected action is present in delta report'
- assert result.get(0).getAction() == 'remove'
- where: 'following data was used'
- scenario | parentNodeXpath
- 'has leaves and child nodes' | "/bookstore/categories[@code='6']"
- 'has leaves only' | "/bookstore/categories[@code='5']/books[@title='Book 11']"
- 'has child data node only' | "/bookstore/support-info/contact-emails"
- 'is empty' | "/bookstore/container-without-leaves"
- }
-
- def 'Get delta between anchors for "create" action, where target data node #scenario'() {
- when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
- then: 'the expected action is present in delta report'
- result.get(0).getAction() == 'create'
- and: 'the expected xapth is present in delta report'
- result.get(0).getXpath() == parentNodeXpath
- where: 'following data was used'
- scenario | parentNodeXpath
- 'has leaves and child nodes' | "/bookstore/categories[@code='6']"
- 'has leaves only' | "/bookstore/categories[@code='5']/books[@title='Book 11']"
- 'has child data node only' | "/bookstore/support-info/contact-emails"
- 'is empty' | "/bookstore/container-without-leaves"
- }
-
- def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() {
- when: 'attempt to get delta between leaves of existing data nodes'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS)
- then: 'expected action is "replace"'
- assert result[0].getAction() == 'replace'
- and: 'the payload has expected leaf values'
- def sourceData = result[0].getSourceData()
- def targetData = result[0].getTargetData()
- assert sourceData == expectedSourceValue
- assert targetData == expectedTargetValue
- where: 'following data was used'
- scenario | sourceAnchor | targetAnchor | xpath || expectedSourceValue | expectedTargetValue
- 'leaf is updated in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || ['bookstore-name': 'Easons-1'] | ['bookstore-name': 'Crossword Bookstores']
- 'leaf is removed in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || [price:1] | null
- 'leaf is added in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | "/bookstore/categories[@code='5']/books[@title='Book 1']" || null | [price:1]
- }
-
- def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() {
- when: 'attempt to get delta between leaves of existing data nodes'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY)
- then: 'expected action is "replace"'
- assert result[0].getAction() == 'replace'
- and: 'the delta report has expected child node xpaths'
- def deltaReportEntities = getDeltaReportEntities(result)
- def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths')
- assert childNodeXpathsInDeltaReport.contains(expectedChildNodeXpath)
- where: 'following data was used'
- scenario | sourceAnchor | targetAnchor | xpath || expectedChildNodeXpath
- 'source and target anchors have child data nodes' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/premises' || '/bookstore/premises/addresses[@house-number=\'2\' and @street=\'Main Street\']'
- 'removed child data nodes in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore' || '/bookstore/support-info'
- 'added child data nodes in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || '/bookstore/support-info'
- }
-
- def 'Get delta between anchors where source and target data nodes have leaves and child data nodes'() {
- given: 'parent node xpath and expected data in delta report'
- def parentNodeXpath = "/bookstore/categories[@code='1']"
- def expectedSourceDataInParentNode = ['name':'Children']
- def expectedTargetDataInParentNode = ['name':'Kids']
- def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]]
- def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[1988, 2000, 2023]]]
- when: 'attempt to get delta between leaves of existing data nodes'
- def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
- def deltaReportEntities = getDeltaReportEntities(result)
- then: 'expected action is "replace"'
- assert result[0].getAction() == 'replace'
- and: 'the payload has expected parent node xpath'
- assert deltaReportEntities.get('xpaths').contains(parentNodeXpath)
- and: 'delta report has expected source and target data'
- assert deltaReportEntities.get('sourcePayload').contains(expectedSourceDataInParentNode)
- assert deltaReportEntities.get('targetPayload').contains(expectedTargetDataInParentNode)
- and: 'the delta report also has expected child node xpaths'
- assert deltaReportEntities.get('xpaths').containsAll(["/bookstore/categories[@code='1']/books[@title='The Gruffalo']", "/bookstore/categories[@code='1']/books[@title='Matilda']"])
- and: 'the delta report also has expected source and target data of child nodes'
- assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode)
- assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
- }
-
- def 'Get delta between anchor and JSON payload'() {
- when: 'attempt to get delta report between anchor and JSON payload'
- def jsonPayload = "{\"book-store:bookstore\":{\"bookstore-name\":\"Crossword Bookstores\"},\"book-store:bookstore-address\":{\"address\":\"Bangalore, India\",\"postal-code\":\"560062\",\"bookstore-name\":\"Crossword Bookstores\"}}"
- def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, OMIT_DESCENDANTS)
- then: 'delta report contains expected number of changes'
- result.size() == 3
- and: 'delta report contains "replace" action with expected xpath'
- assert result[0].getAction() == 'replace'
- assert result[0].getXpath() == '/bookstore'
- and: 'delta report contains "remove" action with expected xpath'
- assert result[1].getAction() == 'remove'
- assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']"
- and: 'delta report contains "create" action with expected xpath'
- assert result[2].getAction() == 'create'
- assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']"
- }
-
- def 'Get delta between anchor and payload returns empty response when JSON payload is identical to anchor data'() {
- when: 'attempt to get delta report between anchor and JSON payload (replacing the string Easons with Easons-1 because the data in JSON file is modified, to append anchor number, during the setup process of the integration tests)'
- def jsonPayload = readResourceDataFile('bookstore/bookstoreData.json').replace('Easons', 'Easons-1')
- def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
- then: 'delta report is empty'
- assert result.isEmpty()
- }
-
- def 'Get delta between anchor and payload error scenario: #scenario'() {
- when: 'attempt to get delta between anchor and json payload'
- objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchor, xpath, [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
- then: 'expected exception is thrown'
- thrown(expectedException)
- where: 'following data was used'
- scenario | dataspaceName | sourceAnchor | xpath | jsonPayload || expectedException
- 'invalid dataspace name' | 'Invalid dataspace' | 'not-relevant' | '/' | '{some-json}' || DataValidationException
- 'invalid anchor name' | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor' | '/' | '{some-json}' || DataValidationException
- 'non-existing dataspace' | 'non-existing' | 'not-relevant' | '/' | '{some-json}' || DataspaceNotFoundException
- 'non-existing anchor' | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | '/' | '{some-json}' || AnchorNotFoundException
- 'empty json payload with root node xpath' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | '/' | '' || DataValidationException
- 'empty json payload with non-root node xpath' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | '/bookstore' | '' || DataValidationException
- }
-
- def getDeltaReportEntities(List<DeltaReport> deltaReport) {
- def xpaths = []
- def action = []
- def sourcePayload = []
- def targetPayload = []
- deltaReport.each {
- delta -> xpaths.add(delta.getXpath())
- action.add(delta.getAction())
- sourcePayload.add(delta.getSourceData())
- targetPayload.add(delta.getTargetData())
- }
- return ['xpaths':xpaths, 'action':action, 'sourcePayload':sourcePayload, 'targetPayload':targetPayload]
- }
-
def countDataNodesInBookstore() {
return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DeltaServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DeltaServiceIntegrationSpec.groovy
new file mode 100644
index 0000000000..691e71427c
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DeltaServiceIntegrationSpec.groovy
@@ -0,0 +1,245 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 TechMahindra Ltd.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.integration.functional.cps
+
+import org.onap.cps.api.CpsDeltaService
+import org.onap.cps.api.exceptions.AnchorNotFoundException
+import org.onap.cps.api.exceptions.DataValidationException
+import org.onap.cps.api.exceptions.DataspaceNotFoundException
+import org.onap.cps.api.model.DeltaReport
+import org.onap.cps.api.parameters.FetchDescendantsOption
+import org.onap.cps.integration.base.FunctionalSpecBase
+
+class DeltaServiceIntegrationSpec extends FunctionalSpecBase {
+ CpsDeltaService objectUnderTest
+ def originalCountBookstoreChildNodes
+ def originalCountXmlBookstoreChildNodes
+ def originalCountBookstoreTopLevelListNodes
+
+ static def INCLUDE_ALL_DESCENDANTS = FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+ static def OMIT_DESCENDANTS = FetchDescendantsOption.OMIT_DESCENDANTS
+ static def DIRECT_CHILDREN_ONLY = FetchDescendantsOption.DIRECT_CHILDREN_ONLY
+
+ def setup() {
+ objectUnderTest = cpsDeltaService
+ originalCountBookstoreChildNodes = countDataNodesInBookstore()
+ originalCountBookstoreTopLevelListNodes = countTopLevelListDataNodesInBookstore()
+ originalCountXmlBookstoreChildNodes = countXmlDataNodesInBookstore()
+ }
+
+ def 'Get delta between 2 anchors'() {
+ when: 'attempt to get delta report between anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS)
+ and: 'report is ordered based on xpath'
+ result = result.toList().sort { it.xpath }
+ then: 'delta report contains expected number of changes'
+ result.size() == 3
+ and: 'delta report contains REPLACE action with expected xpath'
+ assert result[0].getAction() == 'replace'
+ assert result[0].getXpath() == '/bookstore'
+ and: 'delta report contains CREATE action with expected xpath'
+ assert result[1].getAction() == 'create'
+ assert result[1].getXpath() == '/bookstore-address[@bookstore-name=\'Crossword Bookstores\']'
+ and: 'delta report contains REMOVE action with expected xpath'
+ assert result[2].getAction() == 'remove'
+ assert result[2].getXpath() == '/bookstore-address[@bookstore-name=\'Easons-1\']'
+ }
+
+ def 'Get delta between 2 anchors returns empty response when #scenario'() {
+ when: 'attempt to get delta report between anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, targetAnchor, xpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'delta report is empty'
+ assert result.isEmpty()
+ where: 'following data was used'
+ scenario | targetAnchor | xpath
+ 'anchors with identical data are queried' | BOOKSTORE_ANCHOR_4 | '/'
+ 'same anchor name is passed as parameter' | BOOKSTORE_ANCHOR_3 | '/'
+ 'non existing xpath' | BOOKSTORE_ANCHOR_5 | '/non-existing-xpath'
+ }
+
+ def 'Get delta between anchors error scenario: #scenario'() {
+ when: 'attempt to get delta between anchors'
+ objectUnderTest.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchor, targetAnchor, '/some-xpath', INCLUDE_ALL_DESCENDANTS)
+ then: 'expected exception is thrown'
+ thrown(expectedException)
+ where: 'following data was used'
+ scenario | dataspaceName | sourceAnchor | targetAnchor || expectedException
+ 'invalid dataspace name' | 'Invalid dataspace' | 'not-relevant' | 'not-relevant' || DataValidationException
+ 'invalid anchor 1 name' | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor' | 'not-relevant' || DataValidationException
+ 'invalid anchor 2 name' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'invalid anchor' || DataValidationException
+ 'non-existing dataspace' | 'non-existing' | 'not-relevant1' | 'not-relevant2' || DataspaceNotFoundException
+ 'non-existing dataspace with same anchor name' | 'non-existing' | 'not-relevant' | 'not-relevant' || DataspaceNotFoundException
+ 'non-existing anchor 1' | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | 'not-relevant' || AnchorNotFoundException
+ 'non-existing anchor 2' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | 'non-existing-anchor' || AnchorNotFoundException
+ }
+
+ def 'Get delta between anchors for remove action, where source data node #scenario'() {
+ when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_5, BOOKSTORE_ANCHOR_3, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'expected action is present in delta report'
+ assert result.get(0).getAction() == 'remove'
+ where: 'following data was used'
+ scenario | parentNodeXpath
+ 'has leaves and child nodes' | '/bookstore/categories[@code=\'6\']'
+ 'has leaves only' | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 11\']'
+ 'has child data node only' | '/bookstore/support-info/contact-emails'
+ 'is empty' | '/bookstore/container-without-leaves'
+ }
+
+ def 'Get delta between anchors for "create" action, where target data node #scenario'() {
+ when: 'attempt to get delta between leaves of data nodes present in 2 anchors'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ then: 'the expected action is present in delta report'
+ result.get(0).getAction() == 'create'
+ and: 'the expected xapth is present in delta report'
+ result.get(0).getXpath() == parentNodeXpath
+ where: 'following data was used'
+ scenario | parentNodeXpath
+ 'has leaves and child nodes' | '/bookstore/categories[@code=\'6\']'
+ 'has leaves only' | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 11\']'
+ 'has child data node only' | '/bookstore/support-info/contact-emails'
+ 'is empty' | '/bookstore/container-without-leaves'
+ }
+
+ def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() {
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS)
+ then: 'expected action is "replace"'
+ assert result[0].getAction() == 'replace'
+ and: 'the payload has expected leaf values'
+ def sourceData = result[0].getSourceData()
+ def targetData = result[0].getTargetData()
+ assert sourceData == expectedSourceValue
+ assert targetData == expectedTargetValue
+ where: 'following data was used'
+ scenario | sourceAnchor | targetAnchor | xpath || expectedSourceValue | expectedTargetValue
+ 'leaf is updated in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || ['bookstore-name': 'Easons-1'] | ['bookstore-name': 'Crossword Bookstores']
+ 'leaf is removed in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 1\']' || [price:1] | null
+ 'leaf is added in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore/categories[@code=\'5\']/books[@title=\'Book 1\']' || null | [price:1]
+ }
+
+ def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() {
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY)
+ then: 'expected action is "replace"'
+ assert result[0].getAction() == 'replace'
+ and: 'the delta report has expected child node xpaths'
+ def deltaReportEntities = getDeltaReportEntities(result)
+ def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths')
+ assert childNodeXpathsInDeltaReport.contains(expectedChildNodeXpath)
+ where: 'following data was used'
+ scenario | sourceAnchor | targetAnchor | xpath || expectedChildNodeXpath
+ 'source and target anchors have child data nodes' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore/premises' || '/bookstore/premises/addresses[@house-number=\'2\' and @street=\'Main Street\']'
+ 'removed child data nodes in target anchor' | BOOKSTORE_ANCHOR_5 | BOOKSTORE_ANCHOR_3 | '/bookstore' || '/bookstore/support-info'
+ 'added child data nodes in target anchor' | BOOKSTORE_ANCHOR_3 | BOOKSTORE_ANCHOR_5 | '/bookstore' || '/bookstore/support-info'
+ }
+
+ def 'Get delta between anchors where source and target data nodes have leaves and child data nodes'() {
+ given: 'parent node xpath and expected data in delta report'
+ def parentNodeXpath = '/bookstore/categories[@code=\'1\']'
+ def expectedSourceDataInParentNode = ['name':'Children']
+ def expectedTargetDataInParentNode = ['name':'Kids']
+ def expectedSourceDataInChildNode = [['lang' : 'English'],['price':20, 'editions':[1988, 2000]]]
+ def expectedTargetDataInChildNode = [['lang':'English/German'], ['price':200, 'editions':[1988, 2000, 2023]]]
+ when: 'attempt to get delta between leaves of existing data nodes'
+ def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS)
+ def deltaReportEntities = getDeltaReportEntities(result)
+ then: 'expected action is "replace"'
+ assert result[0].getAction() == 'replace'
+ and: 'the payload has expected parent node xpath'
+ assert deltaReportEntities.get('xpaths').contains(parentNodeXpath)
+ and: 'delta report has expected source and target data'
+ assert deltaReportEntities.get('sourcePayload').contains(expectedSourceDataInParentNode)
+ assert deltaReportEntities.get('targetPayload').contains(expectedTargetDataInParentNode)
+ and: 'the delta report also has expected child node xpaths'
+ assert deltaReportEntities.get('xpaths').containsAll(['/bookstore/categories[@code=\'1\']/books[@title=\'The Gruffalo\']', '/bookstore/categories[@code=\'1\']/books[@title=\'Matilda\']'])
+ and: 'the delta report also has expected source and target data of child nodes'
+ assert deltaReportEntities.get('sourcePayload').containsAll(expectedSourceDataInChildNode)
+ assert deltaReportEntities.get('targetPayload').containsAll(expectedTargetDataInChildNode)
+ }
+
+ def 'Get delta between anchor and JSON payload'() {
+ when: 'attempt to get delta report between anchor and JSON payload'
+ def jsonPayload = '{\"book-store:bookstore\":{\"bookstore-name\":\"Crossword Bookstores\"},\"book-store:bookstore-address\":{\"address\":\"Bangalore, India\",\"postal-code\":\"560062\",\"bookstore-name\":\"Crossword Bookstores\"}}'
+ def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, OMIT_DESCENDANTS)
+ then: 'delta report contains expected number of changes'
+ result.size() == 3
+ and: 'delta report contains "replace" action with expected xpath'
+ assert result[0].getAction() == 'replace'
+ assert result[0].getXpath() == '/bookstore'
+ and: 'delta report contains "remove" action with expected xpath'
+ assert result[1].getAction() == 'remove'
+ assert result[1].getXpath() == '/bookstore-address[@bookstore-name=\'Easons-1\']'
+ and: 'delta report contains "create" action with expected xpath'
+ assert result[2].getAction() == 'create'
+ assert result[2].getXpath() == '/bookstore-address[@bookstore-name=\'Crossword Bookstores\']'
+ }
+
+ def 'Get delta between anchor and payload returns empty response when JSON payload is identical to anchor data'() {
+ when: 'attempt to get delta report between anchor and JSON payload (replacing the string Easons with Easons-1 because the data in JSON file is modified, to append anchor number, during the setup process of the integration tests)'
+ def jsonPayload = readResourceDataFile('bookstore/bookstoreData.json').replace('Easons', 'Easons-1')
+ def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
+ then: 'delta report is empty'
+ assert result.isEmpty()
+ }
+
+ def 'Get delta between anchor and payload error scenario: #scenario'() {
+ when: 'attempt to get delta between anchor and json payload'
+ objectUnderTest.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchor, xpath, [:], jsonPayload, INCLUDE_ALL_DESCENDANTS)
+ then: 'expected exception is thrown'
+ thrown(expectedException)
+ where: 'following data was used'
+ scenario | dataspaceName | sourceAnchor | xpath | jsonPayload || expectedException
+ 'invalid dataspace name' | 'Invalid dataspace' | 'not-relevant' | '/' | '{some-json}' || DataValidationException
+ 'invalid anchor name' | FUNCTIONAL_TEST_DATASPACE_3 | 'invalid anchor' | '/' | '{some-json}' || DataValidationException
+ 'non-existing dataspace' | 'non-existing' | 'not-relevant' | '/' | '{some-json}' || DataspaceNotFoundException
+ 'non-existing anchor' | FUNCTIONAL_TEST_DATASPACE_3 | 'non-existing-anchor' | '/' | '{some-json}' || AnchorNotFoundException
+ 'empty json payload with root node xpath' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | '/' | '' || DataValidationException
+ 'empty json payload with non-root node xpath' | FUNCTIONAL_TEST_DATASPACE_3 | BOOKSTORE_ANCHOR_3 | '/bookstore' | '' || DataValidationException
+ }
+
+ def getDeltaReportEntities(List<DeltaReport> deltaReport) {
+ def xpaths = []
+ def action = []
+ def sourcePayload = []
+ def targetPayload = []
+ deltaReport.each {
+ delta -> xpaths.add(delta.getXpath())
+ action.add(delta.getAction())
+ sourcePayload.add(delta.getSourceData())
+ targetPayload.add(delta.getTargetData())
+ }
+ return ['xpaths':xpaths, 'action':action, 'sourcePayload':sourcePayload, 'targetPayload':targetPayload]
+ }
+
+ def countDataNodesInBookstore() {
+ return countDataNodesInTree(cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
+ }
+
+ def countTopLevelListDataNodesInBookstore() {
+ return countDataNodesInTree(cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', INCLUDE_ALL_DESCENDANTS))
+ }
+
+ def countXmlDataNodesInBookstore() {
+ return countDataNodesInTree(cpsDataService.getDataNodes(FUNCTIONAL_TEST_DATASPACE_4, BOOKSTORE_ANCHOR_6, '/bookstore', INCLUDE_ALL_DESCENDANTS))
+ }
+
+}