CPS-Core: Unable to parse JSON input with space for POST endpoint 83/126483/3
authorsourabh_sourabh <sourabh.sourabh@est.tech>
Wed, 5 Jan 2022 18:20:20 +0000 (23:50 +0530)
committerSourabh Sourabh <sourabh.sourabh@est.tech>
Fri, 7 Jan 2022 10:29:38 +0000 (10:29 +0000)
Issue-ID: CPS-831

Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
Change-Id: If2f5f7034f05763990001c9dd8ccd9d8dc0099cf

cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
csit/data/test-tree.json

index 73c2c27..a55b1ba 100755 (executable)
 
 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<String> 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<String> 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<Object> 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<Object> 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<Object> 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);
     }
index 445b2a2..fbb5636 100755 (executable)
@@ -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
index cca2ac6..25b90d7 100644 (file)
@@ -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()
+    }
+
 }
index bc9cbd7..89d6784 100644 (file)
@@ -17,7 +17,7 @@
         "nest": {
           "name": "Big",
           "birds": [
-            "Owl",
+            "Night Owl",
             "Raven",
             "Crow"
           ]