2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021 Bell Canada.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.cps.rest.controller
25 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
26 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
27 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
28 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
29 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
30 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
31 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
33 import org.onap.cps.api.CpsDataService
34 import org.onap.cps.spi.model.DataNode
35 import org.onap.cps.spi.model.DataNodeBuilder
36 import org.onap.cps.utils.DateTimeUtility
37 import org.spockframework.spring.SpringBean
38 import org.springframework.beans.factory.annotation.Autowired
39 import org.springframework.beans.factory.annotation.Value
40 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
41 import org.springframework.http.HttpStatus
42 import org.springframework.http.MediaType
43 import org.springframework.test.web.servlet.MockMvc
44 import spock.lang.Shared
45 import spock.lang.Specification
47 @WebMvcTest(DataRestController)
48 class DataRestControllerSpec extends Specification {
51 CpsDataService mockCpsDataService = Mock()
56 @Value('${rest.api.cps-base-path}')
59 def dataNodeBaseEndpoint
60 def dataspaceName = 'my_dataspace'
61 def anchorName = 'my_anchor'
62 def noTimestamp = null
65 static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath')
66 .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
69 static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
70 .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
73 dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName"
76 def 'Create a node: #scenario.'() {
77 given: 'some json to create a data node'
78 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
79 def json = 'some json (this is not validated)'
80 when: 'post is invoked with datanode endpoint and json'
84 .contentType(MediaType.APPLICATION_JSON)
85 .param('xpath', parentNodeXpath)
87 ).andReturn().response
88 then: 'a created response is returned'
89 response.status == HttpStatus.CREATED.value()
90 then: 'the java API was called with the correct parameters'
91 1 * mockCpsDataService.saveData(dataspaceName, anchorName, json, noTimestamp)
92 where: 'following xpath parameters are are used'
93 scenario | parentNodeXpath
94 'no xpath parameter' | ''
95 'xpath parameter point root' | '/'
98 def 'Create a node with observed-timestamp'() {
99 given: 'some json to create a data node'
100 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
101 def json = 'some json (this is not validated)'
102 when: 'post is invoked with datanode endpoint and json'
106 .contentType(MediaType.APPLICATION_JSON)
108 .param('observed-timestamp', observedTimestamp)
110 ).andReturn().response
111 then: 'a created response is returned'
112 response.status == expectedHttpStatus.value()
113 then: 'the java API was called with the correct parameters'
114 expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, json,
115 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
117 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
118 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
119 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
122 def 'Create a child node'() {
123 given: 'some json to create a data node'
124 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
125 def json = 'some json (this is not validated)'
126 and: 'parent node xpath'
127 def parentNodeXpath = 'some xpath'
128 when: 'post is invoked with datanode endpoint and json'
129 def postRequestBuilder = post(endpoint)
130 .contentType(MediaType.APPLICATION_JSON)
131 .param('xpath', parentNodeXpath)
133 if (observedTimestamp != null)
134 postRequestBuilder.param('observed-timestamp', observedTimestamp)
136 mvc.perform(postRequestBuilder).andReturn().response
137 then: 'a created response is returned'
138 response.status == HttpStatus.CREATED.value()
139 then: 'the java API was called with the correct parameters'
140 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, json,
141 DateTimeUtility.toOffsetDateTime(observedTimestamp))
143 scenario | observedTimestamp
144 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400'
145 'without observed-timestamp' | null
148 def 'Create list node child elements #scenario.'() {
149 given: 'parent node xpath and json data inputs'
150 def parentNodeXpath = 'parent node xpath'
151 def jsonData = 'json data'
152 when: 'post is invoked list-node endpoint'
153 def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
154 .contentType(MediaType.APPLICATION_JSON)
155 .param('xpath', parentNodeXpath)
157 if (observedTimestamp != null)
158 postRequestBuilder.param('observed-timestamp', observedTimestamp)
159 def response = mvc.perform(postRequestBuilder).andReturn().response
160 then: 'a created response is returned'
161 response.status == expectedHttpStatus.value()
162 then: 'the java API was called with the correct parameters'
163 expectedApiCount * mockCpsDataService.saveListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
164 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
166 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
167 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
168 'without observed-timestamp' | null || 1 | HttpStatus.CREATED
169 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
172 def 'Get data node with leaves'() {
173 given: 'the service returns data node leaves'
174 def xpath = 'some xPath'
175 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
176 mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren
177 when: 'get request is performed through REST API'
179 mvc.perform(get(endpoint).param('xpath', xpath))
180 .andReturn().response
181 then: 'a success response is returned'
182 response.status == HttpStatus.OK.value()
183 and: 'response contains expected leaf and value'
184 response.contentAsString.contains('"leaf":"value"')
185 and: 'response contains expected leaf-list and values'
186 response.contentAsString.contains('"leafList":["leaveListElement1","leaveListElement2"]')
189 def 'Get data node with #scenario.'() {
190 given: 'the service returns data node with #scenario'
191 def xpath = 'some xPath'
192 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
193 mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode
194 when: 'get request is performed through REST API'
198 .param('xpath', xpath)
199 .param('include-descendants', includeDescendantsOption))
200 .andReturn().response
201 then: 'a success response is returned'
202 response.status == HttpStatus.OK.value()
203 and: 'the response contains child is #expectChildInResponse'
204 response.contentAsString.contains('"child"') == expectChildInResponse
206 scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse
207 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false
208 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false
209 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true
212 def 'Update data node leaves: #scenario.'() {
214 def jsonData = 'json data'
215 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
216 when: 'patch request is performed'
220 .contentType(MediaType.APPLICATION_JSON)
222 .param('xpath', inputXpath)
223 ).andReturn().response
224 then: 'the service method is invoked with expected parameters'
225 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, jsonData, null)
226 and: 'response status indicates success'
227 response.status == HttpStatus.OK.value()
229 scenario | inputXpath || xpathServiceParameter
230 'root node by default' | '' || '/'
231 'root node by choice' | '/' || '/'
232 'some xpath by parent' | '/some/xpath' || '/some/xpath'
235 def 'Update data node leaves with observedTimestamp'() {
237 def jsonData = 'json data'
238 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
239 when: 'patch request is performed'
243 .contentType(MediaType.APPLICATION_JSON)
246 .param('observed-timestamp', observedTimestamp)
247 ).andReturn().response
248 then: 'the service method is invoked with expected parameters'
249 expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', jsonData,
250 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
251 and: 'response status indicates success'
252 response.status == expectedHttpStatus.value()
254 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
255 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
256 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
259 def 'Replace data node tree: #scenario.'() {
261 def jsonData = 'json data'
262 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
263 when: 'put request is performed'
267 .contentType(MediaType.APPLICATION_JSON)
269 .param('xpath', inputXpath))
270 .andReturn().response
271 then: 'the service method is invoked with expected parameters'
272 1 * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, xpathServiceParameter, jsonData, noTimestamp)
273 and: 'response status indicates success'
274 response.status == HttpStatus.OK.value()
276 scenario | inputXpath || xpathServiceParameter
277 'root node by default' | '' || '/'
278 'root node by choice' | '/' || '/'
279 'some xpath by parent' | '/some/xpath' || '/some/xpath'
282 def 'Replace data node tree with observedTimestamp.'() {
284 def jsonData = 'json data'
285 def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
286 when: 'put request is performed'
290 .contentType(MediaType.APPLICATION_JSON)
293 .param('observed-timestamp', observedTimestamp))
294 .andReturn().response
295 then: 'the service method is invoked with expected parameters'
296 expectedApiCount * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, '/', jsonData,
297 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
298 and: 'response status indicates success'
299 response.status == expectedHttpStatus.value()
301 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
302 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
303 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
306 def 'Replace list node child elements.'() {
307 given: 'parent node xpath and json data inputs'
308 def parentNodeXpath = 'parent node xpath'
309 def jsonData = 'json data'
310 when: 'put is invoked list-node endpoint'
311 def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
312 .contentType(MediaType.APPLICATION_JSON)
313 .param('xpath', parentNodeXpath)
315 if (observedTimestamp != null)
316 putRequestBuilder.param('observed-timestamp', observedTimestamp)
317 def response = mvc.perform(putRequestBuilder).andReturn().response
318 then: 'a success response is returned'
319 response.status == expectedHttpStatus.value()
320 and: 'the java API was called with the correct parameters'
321 expectedApiCount * mockCpsDataService.replaceListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
322 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
324 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
325 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
326 'without observed-timestamp' | null || 1 | HttpStatus.OK
327 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
330 def 'Delete list node child elements. #scenario'() {
331 given: 'list node xpath'
332 def listNodeXpath = 'list node xpath'
333 when: 'delete is invoked list-node endpoint'
334 def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
335 .param('xpath', listNodeXpath)
336 if (observedTimestamp != null)
337 deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
338 def response = mvc.perform(deleteRequestBuilder).andReturn().response
339 then: 'a success response is returned'
340 response.status == expectedHttpStatus.value()
341 and: 'the java API was called with the correct parameters'
342 expectedApiCount * mockCpsDataService.deleteListNodeData(dataspaceName, anchorName, listNodeXpath,
343 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
345 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
346 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.NO_CONTENT
347 'without observed-timestamp' | null || 1 | HttpStatus.NO_CONTENT
348 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST