2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2025 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 Bell Canada.
6 * Modifications Copyright (C) 2022 Deutsche Telekom AG
7 * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
21 * SPDX-License-Identifier: Apache-2.0
22 * ============LICENSE_END=========================================================
25 package org.onap.cps.rest.controller
27 import com.fasterxml.jackson.databind.ObjectMapper
28 import org.onap.cps.api.CpsDataService
29 import org.onap.cps.api.CpsFacade
30 import org.onap.cps.utils.ContentType
31 import org.onap.cps.utils.DateTimeUtility
32 import org.onap.cps.utils.JsonObjectMapper
33 import org.spockframework.spring.SpringBean
34 import org.springframework.beans.factory.annotation.Autowired
35 import org.springframework.beans.factory.annotation.Value
36 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
37 import org.springframework.http.HttpStatus
38 import org.springframework.http.MediaType
39 import org.springframework.test.web.servlet.MockMvc
40 import spock.lang.Shared
41 import spock.lang.Specification
43 import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
44 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
45 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
46 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
47 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
48 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
49 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
51 @WebMvcTest(DataRestController)
52 class DataRestControllerSpec extends Specification {
55 CpsFacade mockCpsFacade = Mock()
58 CpsDataService mockCpsDataService = Mock()
61 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
66 @Value('${rest.api.cps-base-path}')
69 def dataNodeBaseEndpointV1
70 def dataNodeBaseEndpointV2
71 def dataNodeBaseEndpointV3
72 def dataspaceName = 'my_dataspace'
73 def anchorName = 'my_anchor'
74 def noTimestamp = null
77 def requestBodyJson = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
80 def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
83 def requestBodyXml = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
86 def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
89 dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
90 dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
91 dataNodeBaseEndpointV3 = "$basePath/v3/dataspaces/$dataspaceName"
94 def 'Create a node: #scenario.'() {
95 given: 'endpoint to create a node'
96 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
97 when: 'post is invoked with datanode endpoint and json'
101 .contentType(contentType)
102 .param('xpath', parentNodeXpath)
103 .content(requestBody)
104 ).andReturn().response
105 then: 'a created response is returned'
106 response.status == HttpStatus.CREATED.value()
107 then: 'the cps data service was called with the correct parameters'
108 1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, noTimestamp, expectedContentType)
109 where: 'following xpath parameters are are used'
110 scenario | parentNodeXpath | contentType | expectedContentType | requestBody | expectedData
111 'JSON content: no xpath parameter' | '' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData
112 'JSON content: xpath parameter point root' | '/' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData
113 'XML content: no xpath parameter' | '' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData
114 'XML content: xpath parameter point root' | '/' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData
117 def 'Create a node with observed-timestamp.'() {
118 given: 'endpoint to create a node'
119 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
120 when: 'post is invoked with datanode endpoint and json'
124 .contentType(contentType)
126 .param('observed-timestamp', observedTimestamp)
128 ).andReturn().response
129 then: 'a created response is returned'
130 response.status == expectedHttpStatus.value()
131 then: 'the cps data service was called with the correct parameters'
132 expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData,
133 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
135 scenario | observedTimestamp | contentType | content || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType
136 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
137 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
138 'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
141 def 'Validate data using create a node API.'() {
142 given: 'an endpoint to create a node'
143 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
144 def parentNodeXpath = '/'
145 and: 'dryRunEnabled flag is set to true'
146 def dryRunEnabled = 'true'
147 when: 'post is invoked with json data and dry-run flag enabled'
151 .contentType(MediaType.APPLICATION_JSON)
152 .param('xpath', parentNodeXpath)
153 .param('dry-run', dryRunEnabled)
154 .content(requestBodyJson)
155 ).andReturn().response
156 then: 'a 200 OK response is returned'
157 response.status == HttpStatus.OK.value()
158 then: 'the cps data service was called with correct parameters'
159 1 * mockCpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, requestBodyJson, ContentType.JSON)
162 def 'Create a child node #scenario.'() {
163 given: 'endpoint to create a node'
164 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
165 and: 'parent node xpath'
166 def parentNodeXpath = 'some xpath'
167 when: 'post is invoked with datanode endpoint and json'
168 def postRequestBuilder = post(endpoint)
169 .contentType(contentType)
170 .param('xpath', parentNodeXpath)
171 .content(requestBody)
172 if (observedTimestamp != null)
173 postRequestBuilder.param('observed-timestamp', observedTimestamp)
175 mvc.perform(postRequestBuilder).andReturn().response
176 then: 'a created response is returned'
177 response.status == HttpStatus.CREATED.value()
178 then: 'the cps data service was called with the correct parameters'
179 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedData,
180 DateTimeUtility.toOffsetDateTime(observedTimestamp), expectedContentType)
182 scenario | observedTimestamp | contentType | requestBody | expectedData | expectedContentType
183 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON
184 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
185 'without observed-timestamp JSON' | null | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON
186 'without observed-timestamp XML' | null | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
189 def 'save list elements under root node #scenario.'() {
190 given: 'root node xpath '
191 def rootNodeXpath = '/'
192 when: 'list-node endpoint is invoked with post (create) operation'
193 def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
194 .contentType(contentType)
195 .param('xpath', rootNodeXpath )
196 .content(requestBody)
197 if (observedTimestamp != null)
198 postRequestBuilder.param('observed-timestamp', observedTimestamp)
199 def response = mvc.perform(postRequestBuilder).andReturn().response
200 then: 'a created response is returned'
201 response.status == expectedHttpStatus.value()
202 then: 'the java API was called with the correct parameters'
203 expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedData,
204 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
206 scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType
207 'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
208 'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
209 'Content type JSON with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
210 'Content type XML with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
211 'Content type XML without observed-timestamp' | null | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
212 'Content type XML with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_XML | requestBodyXml || 0 | HttpStatus.BAD_REQUEST | expectedXmlData | ContentType.XML
215 def 'Save list elements #scenario.'() {
216 given: 'parent node xpath '
217 def parentNodeXpath = 'parent node xpath'
218 when: 'list-node endpoint is invoked with post (create) operation'
219 def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
220 .contentType(contentType)
221 .param('xpath', parentNodeXpath)
222 .content(requestBody)
223 if (observedTimestamp != null)
224 postRequestBuilder.param('observed-timestamp', observedTimestamp)
225 def response = mvc.perform(postRequestBuilder).andReturn().response
226 then: 'a created response is returned'
227 response.status == expectedHttpStatus.value()
228 then: 'the cps data service was called with the correct parameters when needed'
229 expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedData,
230 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
231 where: 'the following parameters are used'
232 scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType
233 'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
234 'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
235 'Content type JSON with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
236 'Content type XML with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
237 'Content type XML without observed-timestamp' | null | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
238 'Content type XML with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_XML | requestBodyXml || 0 | HttpStatus.BAD_REQUEST | expectedXmlData | ContentType.XML
241 def 'Validate data using Save list elements API'() {
242 given: 'endpoint to save list elements'
243 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes"
244 and: 'dryRunEnabled flag is set to true'
245 def dryRunEnabled = 'true'
246 when: 'post request is performed'
250 .contentType(MediaType.APPLICATION_JSON)
252 .content(requestBodyJson)
253 .param('dry-run', dryRunEnabled)
254 ).andReturn().response
255 then: 'a 200 OK response is returned'
256 response.status == HttpStatus.OK.value()
257 then: 'the cps data service was called with correct parameters'
258 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
261 def 'Get data nodes [V1] with #scenario.'() {
262 given: 'the service returns data node with #scenario'
263 def xpath = 'my/path'
264 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
265 when: 'get request is performed through REST API'
269 .param('xpath', xpath)
270 .param('include-descendants', includeDescendantsOption))
271 .andReturn().response
272 then: 'the cps facade is called with the correct parameters'
273 1 * mockCpsFacade.getFirstDataNodeByAnchor(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [mocked:'result']
274 then: 'a success response is returned'
275 response.status == HttpStatus.OK.value()
276 and: 'the response contains the facade result in json format'
277 response.getContentAsString() == '{"mocked":"result"}'
278 where: 'the following parameters are used'
279 scenario | includeDescendantsOption || expectedCpsDataServiceOption
280 'no descendants (default) ' | '' || OMIT_DESCENDANTS
281 'with descendants' | 'true' || INCLUDE_ALL_DESCENDANTS
284 def 'Get data node with #scenario using V2. output type #scenario.'() {
285 given: 'the service returns data nodes with #scenario'
286 def xpath = 'some xPath'
287 def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
288 when: 'V2 of get request is performed through REST API'
290 mvc.perform(get(endpoint)
291 .contentType(contentType)
292 .param('xpath', xpath)
293 .param('descendants', 'all'))
294 .andReturn().response
295 then: 'the cps service facade is called with the correct parameters and returns some data'
296 1 * mockCpsFacade.getDataNodesByAnchor(dataspaceName, anchorName, xpath, INCLUDE_ALL_DESCENDANTS) >> [[mocked:'result1'], [mocked:'result2']]
297 and: 'a success response is returned'
298 assert response.status == HttpStatus.OK.value()
299 and: 'the response is in the expected format'
300 assert response.contentAsString == expectedResult
301 where: 'the following content types are used'
302 scenario | contentType || expectedResult
303 'XML' | MediaType.APPLICATION_XML || '<mocked>result1</mocked><mocked>result2</mocked>'
304 'JSON' | MediaType.APPLICATION_JSON || '[{"mocked":"result1"},{"mocked":"result2"}]'
307 def 'Get data node with #scenario using V3. output type #scenario.'() {
308 given: 'the service returns data nodes with #scenario'
309 def xpath = 'some xPath'
310 def endpoint = "$dataNodeBaseEndpointV3/anchors/$anchorName/node"
311 when: 'V3 of get request is performed through REST API'
313 mvc.perform(get(endpoint)
314 .contentType(contentType)
315 .param('xpath', xpath)
316 .param('descendants', 'all'))
317 .andReturn().response
318 then: 'the cps service facade is called with the correct parameters and returns some data'
319 1 * mockCpsFacade.getDataNodesByAnchorV3(dataspaceName, anchorName, xpath, INCLUDE_ALL_DESCENDANTS) >> [books: [[title: 'Book 1'], [title: 'Book 2']]]
320 and: 'a success response is returned'
321 assert response.status == HttpStatus.OK.value()
322 and: 'the response is in the expected format'
323 assert response.contentAsString == expectedResult
324 where: 'the following content types are used'
325 scenario | contentType || expectedResult
326 'XML' | MediaType.APPLICATION_XML || '<books><title>Book 1</title></books><books><title>Book 2</title></books>'
327 'JSON' | MediaType.APPLICATION_JSON || '{"books":[{"title":"Book 1"},{"title":"Book 2"}]}'
330 def 'Update data node leaves: #scenario.'() {
331 given: 'endpoint to update a node '
332 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
333 when: 'patch request is performed'
337 .contentType(contentType)
338 .content(requestBody)
339 .param('xpath', inputXpath)
340 ).andReturn().response
341 then: 'the cps data service method is invoked with expected parameters'
342 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedData, null, expectedContentType)
343 and: 'response status indicates success'
344 response.status == HttpStatus.OK.value()
346 scenario | inputXpath | contentType || xpathServiceParameter | requestBody | expectedData | expectedContentType
347 'JSON content: root node by default' | '' | MediaType.APPLICATION_JSON || '/' | requestBodyJson | expectedJsonData | ContentType.JSON
348 'JSON content: root node by choice' | '/' | MediaType.APPLICATION_JSON || '/' | requestBodyJson | expectedJsonData | ContentType.JSON
349 'JSON content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_JSON || '/some/xpath' | requestBodyJson | expectedJsonData | ContentType.JSON
350 'XML content: root node by default' | '' | MediaType.APPLICATION_XML || '/' | requestBodyXml | expectedXmlData | ContentType.XML
351 'XML content: root node by choice' | '/' | MediaType.APPLICATION_XML || '/' | requestBodyXml | expectedXmlData | ContentType.XML
352 'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML
355 def 'Update data node leaves with observedTimestamp.'() {
356 given: 'endpoint to update a node leaves '
357 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
358 when: 'patch request is performed'
362 .contentType(MediaType.APPLICATION_JSON)
363 .content(requestBodyJson)
365 .param('observed-timestamp', observedTimestamp)
366 ).andReturn().response
367 then: 'the cps data service method is invoked with expected parameters'
368 expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData,
369 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
370 and: 'response status indicates success'
371 response.status == expectedHttpStatus.value()
373 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
374 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
375 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
378 def 'Validate data using Update a node API.'() {
379 given: 'endpoint to update a node leaves'
380 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
381 and: 'dryRunEnabled flag is set to true'
382 def dryRunEnabled = 'true'
383 when: 'patch request is performed'
387 .contentType(MediaType.APPLICATION_JSON)
388 .content(requestBodyJson)
390 .param('dry-run', dryRunEnabled)
391 ).andReturn().response
392 then: 'a 200 OK response is returned'
393 response.status == HttpStatus.OK.value()
394 then: 'the cps data service was called with correct parameters'
395 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
398 def 'Replace data node tree: #scenario.'() {
399 given: 'endpoint to replace node'
400 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
401 when: 'put request is performed'
405 .contentType(contentType)
406 .content(requestBody)
407 .param('xpath', inputXpath))
408 .andReturn().response
409 then: 'the cps data service method is invoked with expected parameters'
410 1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedData, noTimestamp, expectedContentType)
411 and: 'response status indicates success'
412 response.status == HttpStatus.OK.value()
414 scenario | inputXpath | contentType || xpathServiceParameter | requestBody | expectedData | expectedContentType
415 'JSON content: root node by default' | '' | MediaType.APPLICATION_JSON || '/' | requestBodyJson | expectedJsonData | ContentType.JSON
416 'JSON content: root node by choice' | '/' | MediaType.APPLICATION_JSON || '/' | requestBodyJson | expectedJsonData | ContentType.JSON
417 'JSON content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_JSON || '/some/xpath' | requestBodyJson | expectedJsonData | ContentType.JSON
418 'XML content: root node by default' | '' | MediaType.APPLICATION_XML || '/' | requestBodyXml | expectedXmlData | ContentType.XML
419 'XML content: root node by choice' | '/' | MediaType.APPLICATION_XML || '/' | requestBodyXml | expectedXmlData | ContentType.XML
420 'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML
423 def 'Validate data using Replace data node API.'() {
424 given: 'endpoint to replace node'
425 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
426 and: 'dryRunEnabled flag is set to true'
427 def dryRunEnabled = 'true'
428 when: 'put request is performed'
432 .contentType(MediaType.APPLICATION_JSON)
433 .content(requestBodyJson)
435 .param('dry-run', dryRunEnabled)
436 ).andReturn().response
437 then: 'a 200 OK response is returned'
438 response.status == HttpStatus.OK.value()
439 then: 'the cps data service was called with correct parameters'
440 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
443 def 'Update data node and descendants with observedTimestamp.'() {
444 given: 'endpoint to replace node'
445 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
446 when: 'put request is performed'
450 .contentType(MediaType.APPLICATION_JSON)
451 .content(requestBodyJson)
453 .param('observed-timestamp', observedTimestamp))
454 .andReturn().response
455 then: 'the cps data service method is invoked with expected parameters'
456 expectedApiCount * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', expectedJsonData,
457 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
458 and: 'response status indicates success'
459 response.status == expectedHttpStatus.value()
461 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
462 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
463 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
466 def 'Replace list content #scenario.'() {
467 when: 'list-nodes endpoint is invoked with put (update) operation'
468 def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
469 .contentType(MediaType.APPLICATION_JSON)
470 .param('xpath', 'parent xpath')
471 .content(requestBodyJson)
472 if (observedTimestamp != null)
473 putRequestBuilder.param('observed-timestamp', observedTimestamp)
474 def response = mvc.perform(putRequestBuilder).andReturn().response
475 then: 'a success response is returned'
476 response.status == expectedHttpStatus.value()
477 and: 'the cps data service was called with the correct parameters'
478 expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData,
479 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
481 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
482 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
483 'without observed-timestamp' | null || 1 | HttpStatus.OK
484 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
487 def 'Replace list XML content #scenario.'() {
488 when: 'list-nodes endpoint is invoked with put (update) operation'
489 def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
490 .contentType(MediaType.APPLICATION_XML)
491 .param('xpath', 'parent xpath')
492 .content(requestBodyXml)
493 if (observedTimestamp != null)
494 putRequestBuilder.param('observed-timestamp', observedTimestamp)
495 def response = mvc.perform(putRequestBuilder).andReturn().response
496 then: 'a success response is returned'
497 response.status == expectedHttpStatus.value()
498 and: 'the cps data service was called with the correct parameters'
499 expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedXmlData,
500 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.XML)
502 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
503 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
504 'without observed-timestamp' | null || 1 | HttpStatus.OK
505 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
508 def 'Validate data using Replace list content API.'() {
509 given: 'endpoint to replace list-nodes'
510 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes"
511 and: 'dryRunEnabled flag is set to true'
512 def dryRunEnabled = 'true'
513 when: 'put request is performed'
517 .contentType(MediaType.APPLICATION_JSON)
519 .content(requestBodyJson)
520 .param('dry-run', dryRunEnabled)
521 ).andReturn().response
522 then: 'a 200 OK response is returned'
523 response.status == HttpStatus.OK.value()
524 then: 'the cps data service was called with correct parameters'
525 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON)
528 def 'Delete list element #scenario.'() {
529 when: 'list-nodes endpoint is invoked with delete operation'
530 def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
531 .param('xpath', 'list element xpath')
532 if (observedTimestamp != null)
533 deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
534 def response = mvc.perform(deleteRequestBuilder).andReturn().response
535 then: 'a success response is returned'
536 response.status == expectedHttpStatus.value()
537 and: 'the cps data service was called with the correct parameters'
538 expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath',
539 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
541 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
542 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.NO_CONTENT
543 'without observed-timestamp' | null || 1 | HttpStatus.NO_CONTENT
544 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
547 def 'Delete data node #scenario.'() {
548 given: 'data node xpath'
549 def dataNodeXpath = '/dataNodeXpath'
550 when: 'delete data node endpoint is invoked'
551 def deleteDataNodeRequest = delete( "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes")
552 .param('xpath', dataNodeXpath)
553 and: 'observed timestamp is added to the parameters'
554 if (observedTimestamp != null)
555 deleteDataNodeRequest.param('observed-timestamp', observedTimestamp)
556 def response = mvc.perform(deleteDataNodeRequest).andReturn().response
557 then: 'a successful response is returned'
558 response.status == expectedHttpStatus.value()
559 and: 'the cps data service is called with the correct parameters'
560 expectedApiCount * mockCpsDataService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath,
561 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
563 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
564 'with observed timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.NO_CONTENT
565 'without observed timestamp' | null || 1 | HttpStatus.NO_CONTENT
566 'with invalid observed timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST