Implementation of Data validation feature in CPS APIs 92/139592/4
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Mon, 2 Dec 2024 10:07:07 +0000 (15:37 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Thu, 12 Dec 2024 07:18:23 +0000 (07:18 +0000)
Added support to validate JSON/XML data without the need of persisting
it in the database.

 - added "dryRunInQuery" flag as a new query parameter in update/Replace/Add APIs
 - added new method as part of CpsDataService layer to perform data
   validation

Issue-ID: CPS-2516
Change-Id: I87bb33dd6021567d0fac606d5c4b0168d107311c
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
cps-rest/docs/openapi/components.yml
cps-rest/docs/openapi/cpsData.yml
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
docs/api/swagger/cps/openapi.yaml

index 1db4185..1a7e430 100644 (file)
@@ -326,7 +326,7 @@ components:
     dryRunInQuery:
       name: dry-run
       in: query
-      description: Boolean flag to validate data, without persisting it. Default value is set to false.
+      description: Boolean flag to validate data, without persisting it. Default value is false.
       required: false
       schema:
         type: boolean
index 36000fd..178a68f 100644 (file)
@@ -31,6 +31,7 @@ listElementByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/requiredXpathInQuery'
+      - $ref: 'components.yml#/components/parameters/dryRunInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
       - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
@@ -70,6 +71,7 @@ listElementByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/requiredXpathInQuery'
+      - $ref: 'components.yml#/components/parameters/dryRunInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
       - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
@@ -154,6 +156,7 @@ nodesByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
+      - $ref: 'components.yml#/components/parameters/dryRunInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
       - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
@@ -214,6 +217,7 @@ nodesByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/xpathInQuery'
+      - $ref: 'components.yml#/components/parameters/dryRunInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
       - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
index d460f52..be552ec 100755 (executable)
@@ -102,12 +102,17 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<String> addListElements(final String apiVersion, final String dataspaceName,
                                                   final String anchorName, final String parentNodeXpath,
-                                                  final String nodeData, final String observedTimestamp,
-                                                  final String contentTypeInHeader) {
+                                                  final String nodeData, final Boolean dryRunEnabled,
+                                                  final String observedTimestamp, final String contentTypeInHeader) {
         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
-        cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath,
-                nodeData, toOffsetDateTime(observedTimestamp), contentType);
-        return new ResponseEntity<>(HttpStatus.CREATED);
+        if (Boolean.TRUE.equals(dryRunEnabled)) {
+            cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
+            return ResponseEntity.ok().build();
+        } else {
+            cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath,
+                    nodeData, toOffsetDateTime(observedTimestamp), contentType);
+        }
+        return ResponseEntity.status(HttpStatus.CREATED).build();
     }
 
     @Override
@@ -151,34 +156,50 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName,
                                                    final String anchorName, final String nodeData,
-                                                   final String parentNodeXpath, final String observedTimestamp,
-                                                   final String contentTypeInHeader) {
+                                                   final String parentNodeXpath, final Boolean dryRunEnabled,
+                                                   final String observedTimestamp, final String contentTypeInHeader) {
         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
-        cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath,
-                nodeData, toOffsetDateTime(observedTimestamp), contentType);
-        return new ResponseEntity<>(HttpStatus.OK);
+        if (Boolean.TRUE.equals(dryRunEnabled)) {
+            cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
+            return ResponseEntity.ok().build();
+        } else {
+            cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath,
+                    nodeData, toOffsetDateTime(observedTimestamp), contentType);
+        }
+        return ResponseEntity.status(HttpStatus.OK).build();
     }
 
     @Override
     public ResponseEntity<Object> replaceNode(final String apiVersion, final String dataspaceName,
                                               final String anchorName, final String nodeData,
-                                              final String parentNodeXpath, final String observedTimestamp,
-                                              final String contentTypeInHeader) {
+                                              final String parentNodeXpath, final Boolean dryRunEnabled,
+                                              final String observedTimestamp, final String contentTypeInHeader) {
         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
-        cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
-                        nodeData, toOffsetDateTime(observedTimestamp), contentType);
-        return new ResponseEntity<>(HttpStatus.OK);
+        if (Boolean.TRUE.equals(dryRunEnabled)) {
+            cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
+            return ResponseEntity.ok().build();
+        } else {
+            cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
+                    nodeData, toOffsetDateTime(observedTimestamp), contentType);
+        }
+        return ResponseEntity.status(HttpStatus.OK).build();
     }
 
     @Override
     public ResponseEntity<Object> replaceListContent(final String apiVersion, final String dataspaceName,
                                                      final String anchorName, final String parentNodeXpath,
-                                                     final String nodeData, final String observedTimestamp,
-                                                     final String contentTypeInHeader) {
+                                                     final String nodeData, final Boolean dryRunEnabled,
+                                                     final String observedTimestamp, final String contentTypeInHeader) {
         final ContentType contentType = ContentType.fromString(contentTypeInHeader);
-        cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath,
-                nodeData, toOffsetDateTime(observedTimestamp), contentType);
-        return new ResponseEntity<>(HttpStatus.OK);
+        if (Boolean.TRUE.equals(dryRunEnabled)) {
+            cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData,
+                    ContentType.JSON);
+            return ResponseEntity.ok().build();
+        } else {
+            cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath,
+                    nodeData, toOffsetDateTime(observedTimestamp), contentType);
+        }
+        return ResponseEntity.status(HttpStatus.OK).build();
     }
 
     @Override
index 915fbde..ca89faf 100755 (executable)
@@ -168,16 +168,17 @@ class DataRestControllerSpec extends Specification {
         given: 'an endpoint to create a node'
             def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
             def parentNodeXpath = '/'
+        and: 'dryRunEnabled flag is set to true'
             def dryRunEnabled = 'true'
         when: 'post is invoked with json data and dry-run flag enabled'
             def response =
-                    mvc.perform(
-                            post(endpoint)
-                                    .contentType(MediaType.APPLICATION_JSON)
-                                    .param('xpath', parentNodeXpath)
-                                    .param('dry-run', dryRunEnabled)
-                                    .content(requestBodyJson)
-                    ).andReturn().response
+                mvc.perform(
+                    post(endpoint)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .param('xpath', parentNodeXpath)
+                        .param('dry-run', dryRunEnabled)
+                        .content(requestBodyJson)
+                ).andReturn().response
         then: 'a 200 OK response is returned'
             response.status == HttpStatus.OK.value()
         then: 'the service was called with correct parameters'
@@ -263,6 +264,26 @@ class DataRestControllerSpec extends Specification {
             'Content type XML with invalid observed-timestamp'  | 'invalid'                      | MediaType.APPLICATION_XML  | requestBodyXml  || 0                | HttpStatus.BAD_REQUEST | expectedXmlData  | ContentType.XML
     }
 
+    def 'Validate data using Save list elements API'() {
+        given: 'endpoint to save list elements'
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes"
+        and: 'dryRunEnabled flag is set to true'
+            def dryRunEnabled = 'true'
+        when: 'post request is performed'
+            def response =
+                mvc.perform(
+                    post(endpoint)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .param('xpath', '/')
+                        .content(requestBodyJson)
+                        .param('dry-run', dryRunEnabled)
+                ).andReturn().response
+        then: 'a 200 OK response is returned'
+            response.status == HttpStatus.OK.value()
+        then: 'the service was called with correct parameters'
+            1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
+    }
+
     def 'Get data node with leaves'() {
         given: 'the service returns data node leaves'
             def xpath = 'parent-1'
@@ -515,6 +536,26 @@ class DataRestControllerSpec extends Specification {
             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
     }
 
+    def 'Validate data using Update a node API'() {
+        given: 'endpoint to update a node leaves'
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
+        and: 'dryRunEnabled flag is set to true'
+            def dryRunEnabled = 'true'
+        when: 'patch request is performed'
+            def response =
+                mvc.perform(
+                    patch(endpoint)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(requestBodyJson)
+                        .param('xpath', '/')
+                        .param('dry-run', dryRunEnabled)
+                ).andReturn().response
+        then: 'a 200 OK response is returned'
+            response.status == HttpStatus.OK.value()
+        then: 'the service was called with correct parameters'
+            1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
+    }
+
     def 'Replace data node tree: #scenario.'() {
         given: 'endpoint to replace node'
             def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
@@ -540,6 +581,26 @@ class DataRestControllerSpec extends Specification {
             'XML content: some xpath by parent'  | '/some/xpath' | MediaType.APPLICATION_XML  || '/some/xpath'         | requestBodyXml  | expectedXmlData  | ContentType.XML
     }
 
+    def 'Validate data using Replace data node API'() {
+        given: 'endpoint to replace node'
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
+        and: 'dryRunEnabled flag is set to true'
+            def dryRunEnabled = 'true'
+        when: 'put request is performed'
+            def response =
+                mvc.perform(
+                    put(endpoint)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(requestBodyJson)
+                        .param('xpath', '/')
+                        .param('dry-run', dryRunEnabled)
+                ).andReturn().response
+        then: 'a 200 OK response is returned'
+            response.status == HttpStatus.OK.value()
+        then: 'the service was called with correct parameters'
+            1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
+    }
+
     def 'Update data node and descendants with observedTimestamp.'() {
         given: 'endpoint to replace node'
             def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
@@ -605,6 +666,26 @@ class DataRestControllerSpec extends Specification {
             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
     }
 
+    def 'Validate data using Replace list content API'() {
+        given: 'endpoint to replace list-nodes'
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes"
+        and: 'dryRunEnabled flag is set to true'
+            def dryRunEnabled = 'true'
+        when: 'put request is performed'
+            def response =
+                mvc.perform(
+                    put(endpoint)
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .param('xpath', '/')
+                        .content(requestBodyJson)
+                        .param('dry-run', dryRunEnabled)
+                ).andReturn().response
+        then: 'a 200 OK response is returned'
+            response.status == HttpStatus.OK.value()
+        then: 'the service was called with correct parameters'
+            1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
+    }
+
     def 'Delete list element #scenario.'() {
         when: 'list-nodes endpoint is invoked with delete operation'
             def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
index 7a30020..c84609b 100644 (file)
@@ -1354,6 +1354,15 @@ paths:
         schema:
           default: /
           type: string
+      - description: "Boolean flag to validate data, without persisting it. Default\
+          \ value is false."
+        in: query
+        name: dry-run
+        required: false
+        schema:
+          default: false
+          example: false
+          type: boolean
       - description: observed-timestamp
         in: query
         name: observed-timestamp
@@ -1474,7 +1483,7 @@ paths:
           default: /
           type: string
       - description: "Boolean flag to validate data, without persisting it. Default\
-          \ value is set to false."
+          \ value is false."
         in: query
         name: dry-run
         required: false
@@ -1610,6 +1619,15 @@ paths:
         schema:
           default: /
           type: string
+      - description: "Boolean flag to validate data, without persisting it. Default\
+          \ value is false."
+        in: query
+        name: dry-run
+        required: false
+        schema:
+          default: false
+          example: false
+          type: boolean
       - description: observed-timestamp
         in: query
         name: observed-timestamp
@@ -1804,6 +1822,15 @@ paths:
         required: true
         schema:
           type: string
+      - description: "Boolean flag to validate data, without persisting it. Default\
+          \ value is false."
+        in: query
+        name: dry-run
+        required: false
+        schema:
+          default: false
+          example: false
+          type: boolean
       - description: observed-timestamp
         in: query
         name: observed-timestamp
@@ -1920,6 +1947,15 @@ paths:
         required: true
         schema:
           type: string
+      - description: "Boolean flag to validate data, without persisting it. Default\
+          \ value is false."
+        in: query
+        name: dry-run
+        required: false
+        schema:
+          default: false
+          example: false
+          type: boolean
       - description: observed-timestamp
         in: query
         name: observed-timestamp
@@ -2623,17 +2659,9 @@ components:
         - application/json
         - application/xml
         type: string
-    observedTimestampInQuery:
-      description: observed-timestamp
-      in: query
-      name: observed-timestamp
-      required: false
-      schema:
-        example: 2021-03-21T00:10:34.030-0100
-        type: string
     dryRunInQuery:
       description: "Boolean flag to validate data, without persisting it. Default\
-        \ value is set to false."
+        \ value is false."
       in: query
       name: dry-run
       required: false
@@ -2641,6 +2669,14 @@ components:
         default: false
         example: false
         type: boolean
+    observedTimestampInQuery:
+      description: observed-timestamp
+      in: query
+      name: observed-timestamp
+      required: false
+      schema:
+        example: 2021-03-21T00:10:34.030-0100
+        type: string
     requiredXpathInQuery:
       description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html"
       examples: