summaryrefslogtreecommitdiffstats
path: root/cps-service
diff options
context:
space:
mode:
authorArpit Singh <as00745003@techmahindra.com>2024-08-09 12:23:49 +0530
committerArpit Singh <as00745003@techmahindra.com>2024-10-09 09:28:30 +0530
commit07acbb4ddd713f74406b156cbcac2507f96f3b08 (patch)
tree8f4122b9c3ecb994b9bb203cdd8b3fd1ad191f64 /cps-service
parente2517a8b993ed884edb251b91ce600d0a1a9fefe (diff)
Implementation of Data validation feature in Create a Node API
Added support to validate JSON/XML data without the need of persisting it in the databse. - added "dryRunInQuery" flag as a new query parameter - added new method as part of CpsDataService layer to perform data validation - added new method in yang parser "validateData" to validate data without persisting it Issue-ID: CPS-2361 Change-Id: I43dd33cc6120576d0fac606d5c4b0168d107311d Signed-off-by: Arpit Singh <as00745003@techmahindra.com>
Diffstat (limited to 'cps-service')
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java14
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java14
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangParser.java34
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java33
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy14
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy2
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy13
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy33
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy48
-rw-r--r--cps-service/src/test/resources/bookstore-categories-data.json49
-rw-r--r--cps-service/src/test/resources/bookstore-categories-data.xml14
11 files changed, 236 insertions, 32 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
index 68e1880d77..b3eff8eb26 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
@@ -322,4 +322,18 @@ public interface CpsDataService {
Map<String, String> yangResourcesNameToContentMap,
String targetData,
FetchDescendantsOption fetchDescendantsOption);
+
+
+ /**
+ * Validates JSON or XML data by parsing it using the schema associated to an anchor within the given dataspace.
+ * Validation is performed without persisting the data.
+ *
+ * @param dataspaceName the name of the dataspace where the anchor is located.
+ * @param anchorName the name of the anchor used to validate the data.
+ * @param parentNodeXpath the xpath of the parent node where the data is to be validated.
+ * @param nodeData the JSON or XML data to be validated.
+ * @param contentType the content type of the data (e.g., JSON or XML).
+ */
+ void validateData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+ ContentType contentType);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index eed4f09bf0..b1b545be68 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -64,6 +64,7 @@ import org.springframework.stereotype.Service;
public class CpsDataServiceImpl implements CpsDataService {
private static final String ROOT_NODE_XPATH = "/";
+ private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = "";
private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
private static final String NO_DATA_NODES = "No data nodes.";
@@ -358,6 +359,14 @@ public class CpsDataServiceImpl implements CpsDataService {
sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
}
+ @Override
+ public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final String nodeData, final ContentType contentType) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH :
+ CpsPathUtil.getNormalizedXpath(parentNodeXpath);
+ yangParser.validateData(contentType, nodeData, anchor, xpath);
+ }
private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
final Collection<DataNode> sourceDataNodes) {
@@ -422,7 +431,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final ContentType contentType) {
if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
+ final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
+ anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withContainerNode(containerNode)
.buildCollection();
@@ -450,7 +460,7 @@ public class CpsDataServiceImpl implements CpsDataService {
if (isRootNodeXpath(xpath)) {
final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- yangResourcesNameToContentMap, "");
+ yangResourcesNameToContentMap, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withContainerNode(containerNode)
.buildCollection();
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
index dc23c6bc4a..168e0999d5 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
@@ -21,6 +21,9 @@
package org.onap.cps.utils;
+import static org.onap.cps.utils.YangParserHelper.VALIDATE_AND_PARSE;
+import static org.onap.cps.utils.YangParserHelper.VALIDATE_ONLY;
+
import io.micrometer.core.annotation.Timed;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -57,11 +60,12 @@ public class YangParser {
final String parentNodeXpath) {
final SchemaContext schemaContext = getSchemaContext(anchor);
try {
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper
+ .parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
} catch (final DataValidationException e) {
invalidateCache(anchor);
}
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
}
/**
@@ -78,7 +82,31 @@ public class YangParser {
final Map<String, String> yangResourcesNameToContentMap,
final String parentNodeXpath) {
final SchemaContext schemaContext = getSchemaContext(yangResourcesNameToContentMap);
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
+ }
+
+ /**
+ * Parses data to validate it, using the schema context for given anchor.
+ *
+ * @param anchor the anchor used for node data validation
+ * @param parentNodeXpath the xpath of the parent node
+ * @param nodeData JSON or XML data string to validate
+ * @param contentType the content type of the data (e.g., JSON or XML)
+ * @throws DataValidationException if validation fails
+ */
+ public void validateData(final ContentType contentType,
+ final String nodeData,
+ final Anchor anchor,
+ final String parentNodeXpath) {
+ final SchemaContext schemaContext = getSchemaContext(anchor);
+ try {
+ yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY);
+ } catch (final DataValidationException e) {
+ invalidateCache(anchor);
+ log.error("Data validation failed for anchor: {}, xpath: {}, details: {}", anchor, parentNodeXpath,
+ e.getMessage());
+ }
+ yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY);
}
private SchemaContext getSchemaContext(final Anchor anchor) {
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
index d95aceaf79..5612945ea9 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
@@ -72,6 +72,9 @@ public class YangParserHelper {
static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
static final String DATA_ROOT_NODE_TAG_NAME = "data";
+ static final String DATA_VALIDATION_FAILURE_MESSAGE = "Data Validation Failed";
+ static final boolean VALIDATE_ONLY = true;
+ static final boolean VALIDATE_AND_PARSE = false;
/**
* Parses data into NormalizedNode according to given schema context.
@@ -85,11 +88,20 @@ public class YangParserHelper {
public ContainerNode parseData(final ContentType contentType,
final String nodeData,
final SchemaContext schemaContext,
- final String parentNodeXpath) {
+ final String parentNodeXpath,
+ final boolean validateOnly) {
if (contentType == ContentType.JSON) {
- return parseJsonData(nodeData, schemaContext, parentNodeXpath);
+ final ContainerNode validatedAndParsedJson = parseJsonData(nodeData, schemaContext, parentNodeXpath);
+ if (validateOnly) {
+ return null;
+ }
+ return validatedAndParsedJson;
+ }
+ final NormalizedNodeResult normalizedNodeResult = parseXmlData(nodeData, schemaContext, parentNodeXpath);
+ if (validateOnly) {
+ return null;
}
- return parseXmlData(nodeData, schemaContext, parentNodeXpath);
+ return buildContainerNodeFormNormalizedNodeResult(normalizedNodeResult);
}
private ContainerNode parseJsonData(final String jsonData,
@@ -124,13 +136,13 @@ public class YangParserHelper {
jsonParserStream.parse(jsonReader);
} catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) {
throw new DataValidationException(
- "Data Validation Failed", "Failed to parse json data. " + exception.getMessage(), exception);
+ DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse json data. " + exception.getMessage(), exception);
}
return dataContainerNodeBuilder.build();
}
@SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Problem originates in 3PP code")
- private ContainerNode parseXmlData(final String xmlData,
+ private NormalizedNodeResult parseXmlData(final String xmlData,
final SchemaContext schemaContext,
final String parentNodeXpath) {
final XMLInputFactory factory = XMLInputFactory.newInstance();
@@ -167,12 +179,17 @@ public class YangParserHelper {
} catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException
| ParserConfigurationException | TransformerException exception) {
throw new DataValidationException(
- "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception);
+ DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse xml data: " + exception.getMessage(), exception);
}
+ return normalizedNodeResult;
+ }
+
+ private ContainerNode buildContainerNodeFormNormalizedNodeResult(final NormalizedNodeResult normalizedNodeResult) {
+
final DataContainerChild dataContainerChild =
- (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
+ (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
final YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
- new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
+ new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build();
}
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 9846b30158..8c208a1cf8 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
@@ -546,6 +546,20 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor2, '/', DELETE, observedTimestamp)
}
+ def "Validating #scenario when dry run is enabled."() {
+ given: 'schema set for given anchors and dataspace references bookstore model'
+ setupSchemaSetMocks('bookstore.yang')
+ when: 'validating the data with the given parameters'
+ objectUnderTest.validateData(dataspaceName, anchorName, parentNodeXpath, data,contentType)
+ then: 'the appropriate yang parser method is invoked with correct parameters'
+ yangParser.validateData(contentType, data, anchor, xpath)
+ where: 'the following parameters were used'
+ scenario | parentNodeXpath | xpath | contentType | data
+ 'JSON data with root node xpath' | '/' | '' | ContentType.JSON | '{"bookstore":{"bookstore-name":"Easons"}}'
+ 'JSON data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.JSON | '{"bookstore-name":"Easons"}'
+ 'XML data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.XML | '<bookstore-name>Easons</bookstore-name>'
+ }
+
def 'Start session.'() {
when: 'start session method is called'
objectUnderTest.startSession()
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 05c8983fc2..9f3456280e 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
@@ -171,6 +171,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'
- new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null
+ new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '', false) != 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 e305abee86..f028d5d5d9 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
@@ -35,6 +35,7 @@ class DataNodeBuilderSpec extends Specification {
def objectUnderTest = new DataNodeBuilder()
def yangParserHelper = new YangParserHelper()
+ def validateAndParse = false
def expectedLeavesByXpathMap = [
'/test-tree' : [],
@@ -60,7 +61,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -80,7 +81,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse)
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)
@@ -96,7 +97,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node '
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -129,7 +130,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse)
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'
@@ -144,7 +145,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -162,7 +163,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 = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse)
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/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
index 073383113d..e1490c28ab 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
@@ -30,6 +30,8 @@ import spock.lang.Specification
class YangParserHelperSpec extends Specification {
def objectUnderTest = new YangParserHelper()
+ def validateOnly = true
+ def validateAndParse = false
def 'Parsing a valid multicontainer Json String.'() {
given: 'a yang model (file)'
@@ -38,7 +40,7 @@ class YangParserHelperSpec extends Specification {
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, '')
+ def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
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'
@@ -56,7 +58,7 @@ class YangParserHelperSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'the data is parsed'
- NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '')
+ NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '', validateAndParse)
then: 'the result is a normalized node of the correct type'
if (revision) {
result.identifier.nodeType == QName.create(namespace, revision, localName)
@@ -74,7 +76,7 @@ class YangParserHelperSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'invalid data is parsed'
- objectUnderTest.parseData(contentType, invalidData, schemaContext, '')
+ objectUnderTest.parseData(contentType, invalidData, schemaContext, '', validateAndParse)
then: 'an exception is thrown'
thrown(DataValidationException)
where: 'the following invalid data is provided'
@@ -92,7 +94,7 @@ class YangParserHelperSpec extends Specification {
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)
+ def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath, validateAndParse)
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'
@@ -112,7 +114,7 @@ class YangParserHelperSpec extends Specification {
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)
+ objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath, validateAndParse)
then: 'expected exception is thrown'
thrown(DataValidationException)
where:
@@ -129,7 +131,7 @@ class YangParserHelperSpec extends Specification {
def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
when: 'malformed json string is parsed'
- objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '')
+ objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '', validateAndParse)
then: 'an exception is thrown'
thrown(DataValidationException)
where: 'the following malformed json is provided'
@@ -145,7 +147,7 @@ class YangParserHelperSpec extends Specification {
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, '')
+ objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '', validateAndParse)
then: 'no exception thrown'
noExceptionThrown()
}
@@ -162,5 +164,22 @@ class YangParserHelperSpec extends Specification {
'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories']
}
+ def 'Validating #scenario xpath String.'() {
+ given: 'a data model (file) is provided'
+ def fileData = TestUtils.getResourceFileContent(contentFile)
+ and: 'the schema context is built for that data model'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the data is parsed to be validated'
+ objectUnderTest.parseData(contentType, fileData, schemaContext, parentNodeXpath, validateOnly)
+ then: 'no exception is thrown'
+ noExceptionThrown()
+ where:
+ scenario | parentNodeXpath | contentFile | contentType
+ 'JSON without parent node' | '' | 'bookstore.json' | ContentType.JSON
+ 'JSON with parent node' | '/bookstore' | 'bookstore-categories-data.json' | ContentType.JSON
+ 'XML without parent node' | '' | 'bookstore.xml' | ContentType.XML
+ 'XML with parent node' | '/bookstore' | 'bookstore-categories-data.xml' | ContentType.XML
+ }
}
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
index 18d0502e30..6c52becbe1 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
@@ -26,7 +26,6 @@ import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.Anchor
import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
import org.onap.cps.yang.YangTextSchemaSourceSet
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
import org.opendaylight.yangtools.yang.model.api.SchemaContext
import spock.lang.Specification
@@ -47,6 +46,8 @@ class YangParserSpec extends Specification {
def containerNodeFromYangUtils = Mock(ContainerNode)
def noParent = ''
+ def validateOnly = true
+ def validateAndParse = false
def setup() {
mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
@@ -55,7 +56,7 @@ class YangParserSpec extends Specification {
def 'Parsing data.'() {
given: 'the yang parser (utility) always returns a container node'
- mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> 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'
@@ -68,7 +69,7 @@ class YangParserSpec extends Specification {
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
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { 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'
@@ -79,7 +80,7 @@ class YangParserSpec extends Specification {
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) }
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { 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'
@@ -94,9 +95,46 @@ class YangParserSpec extends Specification {
when: 'parsing some json data'
def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent)
then: 'the yang parser helper always returns a container node'
- 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+ 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils
and: 'the result is the same container node as return from yang utils'
assert result == containerNodeFromYangUtils
}
+ def 'Validating #scenario data using Yang parser with cache retrieval.'() {
+ given: 'the yang parser (utility) is set up and schema context is available'
+ mockYangParserHelper.parseData(contentType, 'some json', mockSchemaContext, noParent, validateOnly)
+ when: 'attempt to parse data with no parent node xpath'
+ objectUnderTest.validateData(contentType, 'some json or xml data', anchor, noParent)
+ then: 'the correct schema set is retrieved from the cache for the dataspace and schema'
+ 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
+ and: 'no cache entries are removed during validation'
+ 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_)
+ where:
+ scenario | contentType
+ 'JSON' | ContentType.JSON
+ 'XML' | ContentType.XML
+ }
+
+ def 'Validating data when parsing fails on first attempt and recovers.'() {
+ given: 'the Yang parser throws an exception on the first attempt but succeeds on the second'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } >> null
+ when: 'attempting to parse JSON data'
+ objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'the cache is cleared for the correct dataspace and schema after the first failure'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ and: 'no exceptions are thrown after the second attempt'
+ noExceptionThrown()
+ }
+
+ def 'Validating data with repeated parsing failures leading to exception.'() {
+ given: 'the yang parser throws an exception on the first attempt only'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) }
+ when: 'attempting to parse JSON data'
+ objectUnderTest.validateData(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 after the failure'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ }
+
}
diff --git a/cps-service/src/test/resources/bookstore-categories-data.json b/cps-service/src/test/resources/bookstore-categories-data.json
new file mode 100644
index 0000000000..7dc22b17f7
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore-categories-data.json
@@ -0,0 +1,49 @@
+{
+ "categories": [
+ {
+ "code": "01/1",
+ "name": "SciFi",
+ "books": [
+ {
+ "authors": [
+ "Iain M. Banks"
+ ],
+ "lang": "en/it",
+ "price": "895",
+ "pub_year": "1994",
+ "title": "Feersum Endjinn/Endjinn Feersum"
+ },
+ {
+ "authors": [
+ "Ursula K. Le Guin",
+ "Joe Haldeman",
+ "Orson Scott Card",
+ "david Brin",
+ "Rober Silverberg",
+ "Dan Simmons",
+ "Greg Bear"
+ ],
+ "lang": "en",
+ "price": "1099",
+ "pub_year": "1999",
+ "title": "Far Horizons"
+ }
+ ]
+ },
+ {
+ "name": "kids",
+ "code": "02",
+ "books": [
+ {
+ "authors": [
+ "Philip Pullman"
+ ],
+ "lang": "en",
+ "price": "699",
+ "pub_year": "1995",
+ "title": "The Golden Compass"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/cps-service/src/test/resources/bookstore-categories-data.xml b/cps-service/src/test/resources/bookstore-categories-data.xml
new file mode 100644
index 0000000000..c8592c1f90
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore-categories-data.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<categories>
+ <code>1</code>
+ <name>SciFi</name>
+ <books>
+ <title>2001: A Space Odyssey</title>
+ <lang>en</lang>
+ <authors>
+ Iain M. Banks
+ </authors>
+ <pub_year>1994</pub_year>
+ <price>895</price>
+ </books>
+</categories> \ No newline at end of file