From 6f494f46266e8709d6b61530aa48fe76adacf965 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Wed, 5 Jan 2022 23:50:20 +0530 Subject: [PATCH] CPS-Core: Unable to parse JSON input with space for POST endpoint Issue-ID: CPS-831 Signed-off-by: sourabh_sourabh Change-Id: If2f5f7034f05763990001c9dd8ccd9d8dc0099cf --- .../cps/rest/controller/DataRestController.java | 20 ++++++----- .../rest/controller/DataRestControllerSpec.groovy | 41 +++++++++++----------- .../groovy/org/onap/cps/utils/YangUtilsSpec.groovy | 27 ++++++++++++++ csit/data/test-tree.json | 2 +- 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index 73c2c27c9..a55b1ba7a 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -22,15 +22,16 @@ package org.onap.cps.rest.controller; +import com.google.gson.Gson; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import javax.validation.ValidationException; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsDataService; import org.onap.cps.rest.api.CpsDataApi; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.utils.DataMapUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,23 +39,24 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("${rest.api.cps-base-path}") +@RequiredArgsConstructor public class DataRestController implements CpsDataApi { private static final String ROOT_XPATH = "/"; private static final String ISO_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; private static final DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_FORMAT); - @Autowired - private CpsDataService cpsDataService; + private final CpsDataService cpsDataService; + private final Gson gson; @Override public ResponseEntity createNode(final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { if (isRootXpath(parentNodeXpath)) { - cpsDataService.saveData(dataspaceName, anchorName, jsonData.toString(), + cpsDataService.saveData(dataspaceName, anchorName, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); } else { - cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, jsonData.toString(), + cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); } return new ResponseEntity<>(HttpStatus.CREATED); @@ -71,7 +73,7 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity addListElements(final String parentNodeXpath, final String dataspaceName, final String anchorName, final Object jsonData, final String observedTimestamp) { - cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, jsonData.toString(), + cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); return new ResponseEntity<>(HttpStatus.CREATED); } @@ -89,7 +91,7 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity updateNodeLeaves(final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { - cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData.toString(), + cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); return new ResponseEntity<>(HttpStatus.OK); } @@ -98,7 +100,7 @@ public class DataRestController implements CpsDataApi { public ResponseEntity replaceNode(final String dataspaceName, final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { cpsDataService - .replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData.toString(), + .replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); return new ResponseEntity<>(HttpStatus.OK); } @@ -107,7 +109,7 @@ public class DataRestController implements CpsDataApi { public ResponseEntity replaceListContent(final String parentNodeXpath, final String dataspaceName, final String anchorName, final Object jsonData, final String observedTimestamp) { - cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, jsonData.toString(), + cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, gson.toJson(jsonData), toOffsetDateTime(observedTimestamp)); return new ResponseEntity<>(HttpStatus.OK); } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index 445b2a2bd..fbb563656 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -60,8 +60,8 @@ class DataRestControllerSpec extends Specification { def dataspaceName = 'my_dataspace' def anchorName = 'my_anchor' def noTimestamp = null - def jsonString = '{"some-key" : "some-value"}' - def jsonObject + def requestBody = '{"some-key" : "some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' + def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' @Shared static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath') @@ -73,7 +73,6 @@ class DataRestControllerSpec extends Specification { def setup() { dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName" - jsonObject = groovy.json.JsonOutput.toJson(jsonString); } def 'Create a node: #scenario.'() { @@ -85,12 +84,12 @@ class DataRestControllerSpec extends Specification { post(endpoint) .contentType(MediaType.APPLICATION_JSON) .param('xpath', parentNodeXpath) - .content(jsonObject) + .content(requestBody) ).andReturn().response then: 'a created response is returned' response.status == HttpStatus.CREATED.value() then: 'the java API was called with the correct parameters' - 1 * mockCpsDataService.saveData(dataspaceName, anchorName, jsonString, noTimestamp) + 1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedJsonData, noTimestamp) where: 'following xpath parameters are are used' scenario | parentNodeXpath 'no xpath parameter' | '' @@ -107,12 +106,12 @@ class DataRestControllerSpec extends Specification { .contentType(MediaType.APPLICATION_JSON) .param('xpath', '') .param('observed-timestamp', observedTimestamp) - .content(jsonObject) + .content(requestBody) ).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() then: 'the java API was called with the correct parameters' - expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, jsonString, + expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) where: scenario | observedTimestamp || expectedApiCount | expectedHttpStatus @@ -129,7 +128,7 @@ class DataRestControllerSpec extends Specification { def postRequestBuilder = post(endpoint) .contentType(MediaType.APPLICATION_JSON) .param('xpath', parentNodeXpath) - .content(jsonObject) + .content(requestBody) if (observedTimestamp != null) postRequestBuilder.param('observed-timestamp', observedTimestamp) def response = @@ -137,7 +136,7 @@ class DataRestControllerSpec extends Specification { then: 'a created response is returned' response.status == HttpStatus.CREATED.value() then: 'the java API was called with the correct parameters' - 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, jsonString, + 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedJsonData, DateTimeUtility.toOffsetDateTime(observedTimestamp)) where: scenario | observedTimestamp @@ -152,14 +151,14 @@ class DataRestControllerSpec extends Specification { def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', parentNodeXpath) - .content(jsonObject) + .content(requestBody) if (observedTimestamp != null) postRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(postRequestBuilder).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() then: 'the java API was called with the correct parameters' - expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, jsonString, + expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) where: scenario | observedTimestamp || expectedApiCount | expectedHttpStatus @@ -216,11 +215,11 @@ class DataRestControllerSpec extends Specification { mvc.perform( patch(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(jsonObject) + .content(requestBody) .param('xpath', inputXpath) ).andReturn().response then: 'the service method is invoked with expected parameters' - 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, jsonString, null) + 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, null) and: 'response status indicates success' response.status == HttpStatus.OK.value() where: @@ -238,12 +237,12 @@ class DataRestControllerSpec extends Specification { mvc.perform( patch(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(jsonObject) + .content(requestBody) .param('xpath', '/') .param('observed-timestamp', observedTimestamp) ).andReturn().response then: 'the service method is invoked with expected parameters' - expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', jsonString, + expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) and: 'response status indicates success' response.status == expectedHttpStatus.value() @@ -261,11 +260,11 @@ class DataRestControllerSpec extends Specification { mvc.perform( put(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(jsonObject) + .content(requestBody) .param('xpath', inputXpath)) .andReturn().response then: 'the service method is invoked with expected parameters' - 1 * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, xpathServiceParameter, jsonString, noTimestamp) + 1 * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, noTimestamp) and: 'response status indicates success' response.status == HttpStatus.OK.value() where: @@ -283,12 +282,12 @@ class DataRestControllerSpec extends Specification { mvc.perform( put(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(jsonObject) + .content(requestBody) .param('xpath', '') .param('observed-timestamp', observedTimestamp)) .andReturn().response then: 'the service method is invoked with expected parameters' - expectedApiCount * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, '/', jsonString, + expectedApiCount * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, '/', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) and: 'response status indicates success' response.status == expectedHttpStatus.value() @@ -303,14 +302,14 @@ class DataRestControllerSpec extends Specification { def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', 'parent xpath') - .content(jsonObject) + .content(requestBody) if (observedTimestamp != null) putRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(putRequestBuilder).andReturn().response then: 'a success response is returned' response.status == expectedHttpStatus.value() and: 'the java API was called with the correct parameters' - expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', jsonString, + expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData, { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) where: scenario | observedTimestamp || expectedApiCount | expectedHttpStatus 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 cca2ac621..25b90d702 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 @@ -88,4 +88,31 @@ class YangUtilsSpec extends Specification { '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() + } + } diff --git a/csit/data/test-tree.json b/csit/data/test-tree.json index bc9cbd7ce..89d678427 100644 --- a/csit/data/test-tree.json +++ b/csit/data/test-tree.json @@ -17,7 +17,7 @@ "nest": { "name": "Big", "birds": [ - "Owl", + "Night Owl", "Raven", "Crow" ] -- 2.16.6