diff options
Diffstat (limited to 'cps-rest')
5 files changed, 92 insertions, 48 deletions
diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index 4f138fc898..e700da6ea1 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -2,6 +2,7 @@ # Copyright (c) 2021-2022 Bell Canada. # Modifications Copyright (C) 2021-2022 Nordix Foundation # Modifications Copyright (C) 2022 TechMahindra Ltd. +# Modifications Copyright (C) 2022 Deutsche Telekom AG # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -106,6 +107,17 @@ components: name: SciFi - code: 02 name: kids + dataSampleXml: + value: + <stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <bookstore xmlns="org:onap:ccsdk:sample"> + <bookstore-name>Chapters</bookstore-name> + <categories> + <code>1</code> + <name>SciFi</name> + </categories> + </bookstore> + </stores> parameters: dataspaceNameInQuery: @@ -220,6 +232,14 @@ components: type: string enum: [v1, v2] default: v2 + contentTypeHeader: + name: Content-Type + in: header + description: Content type header + schema: + type: string + example: 'application/json' + required: true responses: NotFound: diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml index 9d940c3f83..0dc388706c 100644 --- a/cps-rest/docs/openapi/cpsData.yml +++ b/cps-rest/docs/openapi/cpsData.yml @@ -2,6 +2,7 @@ # Copyright (c) 2021-2022 Bell Canada. # Modifications Copyright (C) 2021-2022 Nordix Foundation # Modifications Copyright (C) 2022 TechMahindra Ltd. +# Modifications Copyright (C) 2022 Deutsche Telekom AG # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -130,15 +131,25 @@ nodesByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeHeader' requestBody: required: true content: application/json: schema: - type: object + type: string examples: dataSample: $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object # Workaround to show example + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' + responses: '201': $ref: 'components.yml#/components/responses/Created' 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 c7d44b67b3..30bed12775 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 @@ -4,6 +4,7 @@ * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,11 +33,14 @@ import org.onap.cps.api.CpsDataService; import org.onap.cps.rest.api.CpsDataApi; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; +import org.onap.cps.utils.ContentType; import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.JsonObjectMapper; import org.onap.cps.utils.PrefixResolver; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -54,16 +58,19 @@ public class DataRestController implements CpsDataApi { private final PrefixResolver prefixResolver; @Override - public ResponseEntity<String> createNode(final String apiVersion, - final String dataspaceName, final String anchorName, - final Object jsonData, final String parentNodeXpath, final String observedTimestamp) { - final String jsonDataAsString = jsonObjectMapper.asJsonString(jsonData); + public ResponseEntity<String> createNode(@RequestHeader(value = "Content-Type") final String contentTypeHeader, + final String apiVersion, + final String dataspaceName, final String anchorName, + final String nodeData, final String parentNodeXpath, + final String observedTimestamp) { + final ContentType contentType = contentTypeHeader.contains(MediaType.APPLICATION_XML_VALUE) ? ContentType.XML + : ContentType.JSON; if (isRootXpath(parentNodeXpath)) { - cpsDataService.saveData(dataspaceName, anchorName, jsonDataAsString, - toOffsetDateTime(observedTimestamp)); + cpsDataService.saveData(dataspaceName, anchorName, nodeData, + toOffsetDateTime(observedTimestamp), contentType); } else { cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, - jsonDataAsString, toOffsetDateTime(observedTimestamp)); + nodeData, toOffsetDateTime(observedTimestamp), contentType); } return new ResponseEntity<>(HttpStatus.CREATED); } 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 53da3e6599..94f62f8c22 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 @@ -3,6 +3,7 @@ * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada. + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.ContentType import org.onap.cps.utils.DateTimeUtility import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.utils.PrefixResolver @@ -69,10 +71,20 @@ class DataRestControllerSpec extends Specification { def dataspaceName = 'my_dataspace' def anchorName = 'my_anchor' def noTimestamp = null - def requestBody = '{"some-key" : "some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' + + @Shared + def requestBodyJson = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' + + @Shared def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' @Shared + def requestBodyXml = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>' + + @Shared + def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>' + + @Shared static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath') .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() @@ -91,18 +103,20 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform( post(endpoint) - .contentType(MediaType.APPLICATION_JSON) + .contentType(contentType) .param('xpath', parentNodeXpath) .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, expectedJsonData, noTimestamp) + 1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, noTimestamp, expectedContentType) where: 'following xpath parameters are are used' - scenario | parentNodeXpath - 'no xpath parameter' | '' - 'xpath parameter point root' | '/' + scenario | parentNodeXpath | contentType | expectedContentType | requestBody | expectedData + 'JSON content: no xpath parameter' | '' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData + 'JSON content: xpath parameter point root' | '/' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData + 'XML content: no xpath parameter' | '' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData + 'XML content: xpath parameter point root' | '/' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData } def 'Create a node with observed-timestamp'() { @@ -112,30 +126,31 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform( post(endpoint) - .contentType(MediaType.APPLICATION_JSON) + .contentType(contentType) .param('xpath', '') .param('observed-timestamp', observedTimestamp) - .content(requestBody) + .content(content) ).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, expectedJsonData, - { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) + expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, + { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType) where: - scenario | observedTimestamp || expectedApiCount | expectedHttpStatus - 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED - 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST + scenario | observedTimestamp | contentType | content || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType + 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON + 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML + 'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON } - def 'Create a child node'() { + def 'Create a child node #scenario'() { given: 'endpoint to create a node' def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" and: 'parent node xpath' def parentNodeXpath = 'some xpath' when: 'post is invoked with datanode endpoint and json' def postRequestBuilder = post(endpoint) - .contentType(MediaType.APPLICATION_JSON) + .contentType(contentType) .param('xpath', parentNodeXpath) .content(requestBody) if (observedTimestamp != null) @@ -145,12 +160,14 @@ 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, expectedJsonData, - DateTimeUtility.toOffsetDateTime(observedTimestamp)) + 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedData, + DateTimeUtility.toOffsetDateTime(observedTimestamp), expectedContentType) where: - scenario | observedTimestamp - 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' - 'without observed-timestamp' | null + scenario | observedTimestamp | contentType | requestBody | expectedData | expectedContentType + 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON + 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML + 'without observed-timestamp JSON' | null | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON + 'without observed-timestamp XML' | null | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML } def 'Save list elements #scenario.'() { @@ -160,7 +177,7 @@ class DataRestControllerSpec extends Specification { def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', parentNodeXpath) - .content(requestBody) + .content(requestBodyJson) if (observedTimestamp != null) postRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(postRequestBuilder).andReturn().response @@ -228,7 +245,7 @@ class DataRestControllerSpec extends Specification { mvc.perform( patch(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(requestBody) + .content(requestBodyJson) .param('xpath', inputXpath) ).andReturn().response then: 'the service method is invoked with expected parameters' @@ -250,7 +267,7 @@ class DataRestControllerSpec extends Specification { mvc.perform( patch(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(requestBody) + .content(requestBodyJson) .param('xpath', '/') .param('observed-timestamp', observedTimestamp) ).andReturn().response @@ -273,7 +290,7 @@ class DataRestControllerSpec extends Specification { mvc.perform( put(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(requestBody) + .content(requestBodyJson) .param('xpath', inputXpath)) .andReturn().response then: 'the service method is invoked with expected parameters' @@ -295,7 +312,7 @@ class DataRestControllerSpec extends Specification { mvc.perform( put(endpoint) .contentType(MediaType.APPLICATION_JSON) - .content(requestBody) + .content(requestBodyJson) .param('xpath', '') .param('observed-timestamp', observedTimestamp)) .andReturn().response @@ -315,7 +332,7 @@ class DataRestControllerSpec extends Specification { def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', 'parent xpath') - .content(requestBody) + .content(requestBodyJson) if (observedTimestamp != null) putRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(putRequestBuilder).andReturn().response diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy index ece3507f2d..0821b6bebc 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy @@ -4,6 +4,7 @@ * Modifications Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -167,13 +168,13 @@ class CpsRestExceptionHandlerSpec extends Specification { def 'Post request with #exceptionThrown.class.simpleName returns HTTP Status Bad Request.'() { given: '#exception is thrown the service indicating data is not found' - mockCpsDataService.saveData(_, _, _, _, _) >> { throw exceptionThrown } + mockCpsDataService.saveData(*_) >> { throw exceptionThrown } when: 'data update request is performed' def response = mvc.perform( post("$basePath/v1/dataspaces/dataspace-name/anchors/anchor-name/nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', 'parent node xpath') - .content(groovy.json.JsonOutput.toJson('{"some-key" : "some-value"}')) + .content('{"some-key" : "some-value"}') ).andReturn().response then: 'response code indicates bad input parameters' response.status == BAD_REQUEST.value() @@ -181,18 +182,6 @@ class CpsRestExceptionHandlerSpec extends Specification { exceptionThrown << [new DataNodeNotFoundException('', ''), new NotFoundInDataspaceException('', '')] } - def 'Post request with invalid JSON payload returns HTTP Status Bad Request.'() { - when: 'data post request is performed' - def response = mvc.perform( - post("$basePath/v1/dataspaces/dataspace-name/anchors/anchor-name/nodes") - .contentType(MediaType.APPLICATION_JSON) - .param('xpath', 'parent node xpath') - .content('{') - ).andReturn().response - then: 'response code indicates bad input parameters' - response.status == BAD_REQUEST.value() - } - /* * NB. The test uses 'get anchors' endpoint and associated service method invocation * to test the exception handling. The endpoint chosen is not a subject of test. |