aboutsummaryrefslogtreecommitdiffstats
path: root/cps-service/src/test/groovy/org/onap
diff options
context:
space:
mode:
authorToineSiebelink <toine.siebelink@est.tech>2024-01-16 08:40:51 +0000
committerToineSiebelink <toine.siebelink@est.tech>2024-01-17 14:56:53 +0000
commit6229cfeafade160ed281fc410454c7498b8a21dc (patch)
tree714247ebb871c00fce2b60fd5f0dcbdcebf1a233 /cps-service/src/test/groovy/org/onap
parent2dce7ab2fd627450550666e3993e28e10f6a8548 (diff)
Clear instance based Schema Context Cache upon validation errors
- retry yang parser exceptions one time by clearing cache - split yangUtils into YangParserHelper (instance based) and YangUtils for non-parsering static methods - removed public methods only used from test. Refactored to use variation with (empty) parent path - removed use of optional for parentPath, easier to handle with just an empty string! - make methods no longer used in production code in YangUtils are private - udpate testware to use proper public methods instead of private methods of yang utils / parser (helper) Issue-ID:CPS-2000 Signed-off-by: ToineSiebelink <toine.siebelink@est.tech> Change-Id: I0c7590a5e1495d047006e7136f1bd873be37f7b0
Diffstat (limited to 'cps-service/src/test/groovy/org/onap')
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy10
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy15
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy18
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy28
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy166
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy85
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy138
8 files changed, 298 insertions, 166 deletions
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index 322d2c915..b2b2d7d44 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -34,28 +34,26 @@ import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.exceptions.SessionManagerException
import org.onap.cps.spi.exceptions.SessionTimeoutException
import org.onap.cps.spi.model.Anchor
-import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
import org.onap.cps.spi.utils.CpsValidator
import org.onap.cps.utils.ContentType
-import org.onap.cps.utils.TimedYangParser
+import org.onap.cps.utils.YangParser
+import org.onap.cps.utils.YangParserHelper
import org.onap.cps.yang.YangTextSchemaSourceSet
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Shared
import spock.lang.Specification
import java.time.OffsetDateTime
-import java.util.stream.Collectors
class CpsDataServiceImplSpec extends Specification {
def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
def mockCpsAnchorService = Mock(CpsAnchorService)
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
- def timedYangParser = new TimedYangParser()
+ def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache)
def mockCpsDeltaService = Mock(CpsDeltaService);
- def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService,
- mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService)
+ def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
def setup() {
mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
index 4782468f1..140dfaac9 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation.
+ * Copyright (C) 2021-2024 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
@@ -27,12 +27,12 @@ import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
import org.onap.cps.spi.CpsDataPersistenceService
-import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
import org.onap.cps.spi.model.Anchor
import org.onap.cps.spi.utils.CpsValidator
-import org.onap.cps.utils.TimedYangParser
-import org.onap.cps.utils.YangUtils
+import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.YangParser
+import org.onap.cps.utils.YangParserHelper
import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Specification
@@ -44,14 +44,13 @@ class E2ENetworkSliceSpec extends Specification {
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
- def timedYangParser = new TimedYangParser()
+ def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache)
def mockCpsDeltaService = Mock(CpsDeltaService)
def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService,
- mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService, mockCpsValidator, yangParser, mockCpsDeltaService)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
@@ -165,6 +164,6 @@ class E2ENetworkSliceSpec extends Specification {
expect: 'schema context is built with no exception indicating the schema set being valid '
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
and: 'data is parsed with no exception indicating the model match'
- YangUtils.parseJsonData(jsonData, schemaContext) != null
+ new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
index fcbae628e..e305abee8 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2021-2023 Nordix Foundation.
+ * Modifications Copyright (C) 2021-2024 Nordix Foundation.
* Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,8 +23,9 @@ package org.onap.cps.spi.model
import org.onap.cps.TestUtils
import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.utils.ContentType
import org.onap.cps.utils.DataMapUtils
-import org.onap.cps.utils.YangUtils
+import org.onap.cps.utils.YangParserHelper
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode
@@ -33,6 +34,7 @@ import spock.lang.Specification
class DataNodeBuilderSpec extends Specification {
def objectUnderTest = new DataNodeBuilder()
+ def yangParserHelper = new YangParserHelper()
def expectedLeavesByXpathMap = [
'/test-tree' : [],
@@ -58,7 +60,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('test-tree.json')
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -78,7 +80,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }'
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree")
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree')
when: 'the container node is converted to a data node with parent node xpath defined'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -94,7 +96,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json')
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
when: 'the container node is converted to a data node '
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -127,7 +129,7 @@ class DataNodeBuilderSpec extends Specification {
def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']"
and: 'the json data fragment parsed into container node object for given parent node xpath'
def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}'
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath)
when: 'the container node is converted to a data node with given parent node xpath'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build()
then: 'the resulting data node represents a child of augmentation node'
@@ -142,7 +144,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data fragment parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json')
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -160,7 +162,7 @@ class DataNodeBuilderSpec extends Specification {
and: 'parent node xpath referencing parent of list element'
def parentNodeXpath = '/test-tree'
and: 'the json data fragment (list element) parsed into container node object'
- def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath)
when: 'the container node is converted to a data node collection'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection()
def resultXpaths = result.collect { it.getXpath() }
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy
index c100ea31d..7e211deb7 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/GsonSpec.groovy
@@ -1,13 +1,32 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * 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.utils
import com.google.gson.stream.JsonReader
import org.onap.cps.TestUtils
import spock.lang.Specification
+class GsonSpec extends Specification {
-class GsonSpec extends Specification{
-
- def 'Iterate over JSON data with gson JsonReader'(){
+ def 'Iterate over JSON data with gson JsonReader'() {
given: 'json data with two objects and JSON reader'
def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json')
def objectUnderTest = new JsonReader(new StringReader(jsonData));
@@ -17,7 +36,7 @@ class GsonSpec extends Specification{
noExceptionThrown()
}
- def iterateWithJsonReader(JsonReader jsonReader){
+ def iterateWithJsonReader(JsonReader jsonReader) {
switch(jsonReader.peek()) {
case "STRING":
print(jsonReader.nextString() + " ")
@@ -36,5 +55,4 @@ class GsonSpec extends Specification{
}
}
-
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
index 3864a5253..dc6027de2 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Deutsche Telekom AG
- * Modifications Copyright (c) 2023 Nordix Foundation
+ * Modifications Copyright (c) 2023-2024 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ class XmlFileUtilsSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
and: 'Parent schema node by xPath'
- def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get("dataSchemaNode")
+ def parentSchemaNode = YangParserHelper.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext).get('dataSchemaNode')
when: 'the XML data is parsed'
def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, xPath)
then: 'the result XML is wrapped by xPath defined parent root node'
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
new file mode 100644
index 000000000..073383113
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
@@ -0,0 +1,166 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * 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.utils
+
+import org.onap.cps.TestUtils
+import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import org.opendaylight.yangtools.yang.common.QName
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
+import spock.lang.Specification
+
+class YangParserHelperSpec extends Specification {
+
+ def objectUnderTest = new YangParserHelper()
+
+ def 'Parsing a valid multicontainer Json String.'() {
+ given: 'a yang model (file)'
+ def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json')
+ and: 'a model for that data'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the json data is parsed'
+ def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ then: 'a ContainerNode holding collection of normalized nodes is returned'
+ result.body().getAt(index) instanceof NormalizedNode == true
+ then: 'qualified name of children created is as expected'
+ result.body().getAt(index).getIdentifier().nodeType == QName.create('org:onap:ccsdk:multiDataTree', '2020-09-15', nodeName)
+ where:
+ index | nodeName
+ 0 | 'first-container'
+ 1 | 'last-container'
+ }
+
+ def 'Parsing a valid #scenario String.'() {
+ given: 'a yang model (file)'
+ def fileData = TestUtils.getResourceFileContent(contentFile)
+ and: 'a model for that data'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the data is parsed'
+ NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '')
+ then: 'the result is a normalized node of the correct type'
+ if (revision) {
+ result.identifier.nodeType == QName.create(namespace, revision, localName)
+ } else {
+ result.identifier.nodeType == QName.create(namespace, localName)
+ }
+ where:
+ scenario | contentFile | contentType | namespace | revision | localName
+ 'JSON' | 'bookstore.json' | ContentType.JSON | 'org:onap:ccsdk:sample' | '2020-09-15' | 'bookstore'
+ 'XML' | 'bookstore.xml' | ContentType.XML | 'urn:ietf:params:xml:ns:netconf:base:1.0' | '' | 'bookstore'
+ }
+
+ def 'Parsing invalid data: #description.'() {
+ given: 'a yang model (file)'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'invalid data is parsed'
+ objectUnderTest.parseData(contentType, invalidData, schemaContext, '')
+ then: 'an exception is thrown'
+ thrown(DataValidationException)
+ where: 'the following invalid data is provided'
+ invalidData | contentType | description
+ '{incomplete json' | ContentType.JSON | 'incomplete json'
+ '{"test:bookstore": {"address": "Parnell st." }}' | ContentType.JSON | 'json with un-modelled data'
+ '{" }' | ContentType.JSON | 'json with syntax exception'
+ '<data>' | ContentType.XML | 'incomplete xml'
+ '<data><bookstore><bookstore-anything>blabla</bookstore-anything></bookstore</data>' | ContentType.XML | 'xml with invalid model'
+ '' | ContentType.XML | 'empty xml'
+ }
+
+ def 'Parsing data fragment by xpath for #scenario.'() {
+ given: 'schema context'
+ def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
+ when: 'json string is parsed'
+ def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath)
+ then: 'a ContainerNode holding collection of normalized nodes is returned'
+ result.body().getAt(0) instanceof NormalizedNode == true
+ then: 'result represents a node of expected type'
+ result.body().getAt(0).getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName)
+ where:
+ scenario | contentType | nodeData | parentNodeXpath || nodeName
+ 'JSON list element as container' | ContentType.JSON | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch'
+ 'JSON list element within list' | ContentType.JSON | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch'
+ 'JSON container element' | ContentType.JSON | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
+ 'XML element test tree' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>' | '/test-tree' || 'branch'
+ 'XML element branch xpath' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Robin</birds></nest></branch>' | '/test-tree' || 'branch'
+ 'XML container element' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
+ }
+
+ def 'Parsing json data fragment by xpath error scenario: #scenario.'() {
+ given: 'schema context'
+ def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
+ when: 'json string is parsed'
+ objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath)
+ then: 'expected exception is thrown'
+ thrown(DataValidationException)
+ where:
+ scenario | parentNodeXpath
+ 'xpath has no identifiers' | '/'
+ 'xpath has no valid identifiers' | '/[@name=\'Name\']'
+ 'invalid parent path' | '/test-bush'
+ 'another invalid parent path' | '/test-tree/branch[@name=\'Branch\']/nest/name/last-name'
+ 'fragment does not belong to parent' | '/test-tree/'
+ }
+
+ def 'Parsing json data with invalid json string: #description.'() {
+ given: 'schema context'
+ def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
+ when: 'malformed json string is parsed'
+ objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '')
+ then: 'an exception is thrown'
+ thrown(DataValidationException)
+ where: 'the following malformed json is provided'
+ description | invalidJson
+ 'malformed json string with unterminated array data' | '{bookstore={categories=[{books=[{authors=[Iain M. Banks]}]}]}}'
+ 'incorrect json' | '{" }'
+ }
+
+ def 'Parsing json data with space.'() {
+ given: 'schema context'
+ def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
+ and: 'some json data with space in the array elements'
+ def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json')
+ when: 'that json data is parsed'
+ objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '')
+ then: 'no exception thrown'
+ noExceptionThrown()
+ }
+
+ def 'Converting xPath to nodeId for #scenario.'() {
+ when: 'xPath is parsed'
+ def result = objectUnderTest.xpathToNodeIdSequence(xPath)
+ then: 'result represents an array of expected identifiers'
+ assert result == expectedNodeIdentifier
+ where: 'the following parameters are used'
+ scenario | xPath || expectedNodeIdentifier
+ 'container xpath' | '/test-tree' || ['test-tree']
+ 'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch']
+ 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories']
+ }
+
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
new file mode 100644
index 000000000..99070fe72
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
@@ -0,0 +1,85 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * 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.utils
+
+import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.spi.model.Anchor
+import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
+import org.opendaylight.yangtools.yang.model.api.SchemaContext
+import spock.lang.Specification
+import org.onap.cps.api.impl.YangTextSchemaSourceSetCache
+
+class YangParserSpec extends Specification {
+
+ def mockYangParserHelper = Mock(YangParserHelper)
+ def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+
+ def objectUnderTest = new YangParser(mockYangParserHelper, mockYangTextSchemaSourceSetCache)
+
+ def anchor = new Anchor(dataspaceName: 'my dataspace', schemaSetName: 'my schema')
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+ def mockSchemaContext = Mock(SchemaContext)
+ def containerNodeFromYangUtils = Mock(ContainerNode)
+
+ def noParent = ''
+
+ def setup() {
+ mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
+ mockYangTextSchemaSourceSet.getSchemaContext() >> mockSchemaContext
+ }
+
+ def 'Parsing data.'() {
+ given: 'the yang parser (utility) always returns a container node'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+ when: 'parsing some json data'
+ def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'the schema source set for the correct dataspace and schema set is retrieved form the cache'
+ 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
+ and: 'the result is the same container node as return from yang utils'
+ assert result == containerNodeFromYangUtils
+ and: 'nothing is removed from the cache'
+ 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_)
+ }
+
+ def 'Parsing data with exception on first attempt.'() {
+ given: 'the yang parser throws an exception on the first attempt only'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils
+ when: 'attempt to parse some data'
+ def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'the cache is cleared for the correct dataspace and schema'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ and: 'the result is the same container node as return from yang utils (no exception thrown!)'
+ assert result == containerNodeFromYangUtils
+ }
+
+ def 'Parsing data with exception on all attempts.'() {
+ given: 'the yang parser always throws an exception'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) }
+ when: 'attempt to parse some data'
+ objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'a data validation exception is thrown'
+ thrown(DataValidationException)
+ and: 'the cache is cleared for the correct dataspace and schema (but that did not help)'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
index e6344d303..3852bae57 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2023 Nordix Foundation
+ * Copyright (C) 2020-2024 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
@@ -23,146 +23,10 @@
package org.onap.cps.utils
-import org.onap.cps.TestUtils
-import org.onap.cps.spi.exceptions.DataValidationException
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
-import org.opendaylight.yangtools.yang.common.QName
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
import spock.lang.Specification
class YangUtilsSpec extends Specification {
- def 'Parsing a valid multicontainer Json String.'() {
- given: 'a yang model (file)'
- def jsonData = org.onap.cps.TestUtils.getResourceFileContent('multiple-object-data.json')
- and: 'a model for that data'
- def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
- when: 'the json data is parsed'
- def result = YangUtils.parseJsonData(jsonData, schemaContext)
- then: 'a ContainerNode holding collection of normalized nodes is returned'
- result.body().getAt(index) instanceof NormalizedNode == true
- then: 'qualified name of children created is as expected'
- result.body().getAt(index).getIdentifier().nodeType == QName.create('org:onap:ccsdk:multiDataTree', '2020-09-15', nodeName)
- where:
- index | nodeName
- 0 | 'first-container'
- 1 | 'last-container'
- }
-
- def 'Parsing a valid #scenario String.'() {
- given: 'a yang model (file)'
- def fileData = org.onap.cps.TestUtils.getResourceFileContent(contentFile)
- and: 'a model for that data'
- def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
- when: 'the data is parsed'
- NormalizedNode result = YangUtils.parseData(contentType, fileData, schemaContext)
- then: 'the result is a normalized node of the correct type'
- if (revision) {
- result.identifier.nodeType == QName.create(namespace, revision, localName)
- } else {
- result.identifier.nodeType == QName.create(namespace, localName)
- }
- where:
- scenario | contentFile | contentType | namespace | revision | localName
- 'JSON' | 'bookstore.json' | ContentType.JSON | 'org:onap:ccsdk:sample' | '2020-09-15' | 'bookstore'
- 'XML' | 'bookstore.xml' | ContentType.XML | 'urn:ietf:params:xml:ns:netconf:base:1.0' | '' | 'bookstore'
- }
-
- def 'Parsing invalid data: #description.'() {
- given: 'a yang model (file)'
- def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
- when: 'invalid data is parsed'
- YangUtils.parseData(contentType, invalidData, schemaContext)
- then: 'an exception is thrown'
- thrown(DataValidationException)
- where: 'the following invalid data is provided'
- invalidData | contentType | description
- '{incomplete json' | ContentType.JSON | 'incomplete json'
- '{"test:bookstore": {"address": "Parnell st." }}' | ContentType.JSON | 'json with un-modelled data'
- '{" }' | ContentType.JSON | 'json with syntax exception'
- '<data>' | ContentType.XML | 'incomplete xml'
- '<data><bookstore><bookstore-anything>blabla</bookstore-anything></bookstore</data>' | ContentType.XML | 'xml with invalid model'
- '' | ContentType.XML | 'empty xml'
- }
-
- def 'Parsing data fragment by xpath for #scenario.'() {
- given: 'schema context'
- def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
- when: 'json string is parsed'
- def result = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath)
- then: 'a ContainerNode holding collection of normalized nodes is returned'
- result.body().getAt(0) instanceof NormalizedNode == true
- then: 'result represents a node of expected type'
- result.body().getAt(0).getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName)
- where:
- scenario | contentType | nodeData | parentNodeXpath || nodeName
- 'JSON list element as container' | ContentType.JSON | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch'
- 'JSON list element within list' | ContentType.JSON | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch'
- 'JSON container element' | ContentType.JSON | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
- 'XML element test tree' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>' | '/test-tree' || 'branch'
- 'XML element branch xpath' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Robin</birds></nest></branch>' | '/test-tree' || 'branch'
- 'XML container element' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
- }
-
- def 'Parsing json data fragment by xpath error scenario: #scenario.'() {
- given: 'schema context'
- def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
- when: 'json string is parsed'
- YangUtils.parseJsonData('{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext,
- parentNodeXpath)
- then: 'expected exception is thrown'
- thrown(DataValidationException)
- where:
- scenario | parentNodeXpath
- 'xpath has no identifiers' | '/'
- 'xpath has no valid identifiers' | '/[@name=\'Name\']'
- 'invalid parent path' | '/test-bush'
- 'another invalid parent path' | '/test-tree/branch[@name=\'Branch\']/nest/name/last-name'
- 'fragment does not belong to parent' | '/test-tree/'
- }
-
- def 'Parsing json data with invalid json string: #description.'() {
- given: 'schema context'
- def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
- when: 'malformed json string is parsed'
- YangUtils.parseJsonData(invalidJson, schemaContext)
- then: 'an exception is thrown'
- thrown(DataValidationException)
- where: 'the following malformed json is provided'
- description | invalidJson
- 'malformed json string with unterminated array data' | '{bookstore={categories=[{books=[{authors=[Iain M. Banks]}]}]}}'
- 'incorrect json' | '{" }'
- }
-
- def 'Parsing json data with space.'() {
- given: 'schema context'
- def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
- def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
- and: 'some json data with space in the array elements'
- def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json')
- when: 'that json data is parsed'
- YangUtils.parseJsonData(jsonDataWithSpacesInArrayElement, schemaContext)
- then: 'no exception thrown'
- noExceptionThrown()
- }
-
- def 'Parsing xPath to nodeId for #scenario.'() {
- when: 'xPath is parsed'
- def result = YangUtils.xpathToNodeIdSequence(xPath)
- then: 'result represents an array of expected identifiers'
- assert result == expectedNodeIdentifier
- where: 'the following parameters are used'
- scenario | xPath || expectedNodeIdentifier
- 'container xpath' | '/test-tree' || ['test-tree']
- 'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch']
- 'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories']
- }
def 'Get key attribute statement without key attributes'() {
given: 'a path argument without key attributes'