2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2022 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-2023 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 groovy.json.JsonSlurper
29 import org.onap.cps.api.CpsDataService
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.model.DataNode
32 import org.onap.cps.spi.model.DataNodeBuilder
33 import org.onap.cps.spi.model.DeltaReportBuilder
34 import org.onap.cps.utils.ContentType
35 import org.onap.cps.utils.DateTimeUtility
36 import org.onap.cps.utils.JsonObjectMapper
37 import org.onap.cps.utils.PrefixResolver
38 import org.spockframework.spring.SpringBean
39 import org.springframework.beans.factory.annotation.Autowired
40 import org.springframework.beans.factory.annotation.Value
41 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
42 import org.springframework.http.HttpStatus
43 import org.springframework.http.MediaType
44 import org.springframework.test.web.servlet.MockMvc
45 import spock.lang.Shared
46 import spock.lang.Specification
48 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
49 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
50 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
51 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
52 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
53 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
54 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
56 @WebMvcTest(DataRestController)
57 class DataRestControllerSpec extends Specification {
60 CpsDataService mockCpsDataService = Mock()
63 JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
66 PrefixResolver prefixResolver = Mock()
71 @Value('${rest.api.cps-base-path}')
74 def dataNodeBaseEndpointV1
75 def dataNodeBaseEndpointV2
76 def dataspaceName = 'my_dataspace'
77 def anchorName = 'my_anchor'
78 def noTimestamp = null
81 def requestBodyJson = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
84 def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
87 def requestBodyXml = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
90 def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
93 static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1')
94 .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
97 static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2')
98 .withLeaves([leaf: 'value']).build()
101 static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
102 .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
105 dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
106 dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
109 def 'Create a node: #scenario.'() {
110 given: 'endpoint to create a node'
111 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
112 when: 'post is invoked with datanode endpoint and json'
116 .contentType(contentType)
117 .param('xpath', parentNodeXpath)
118 .content(requestBody)
119 ).andReturn().response
120 then: 'a created response is returned'
121 response.status == HttpStatus.CREATED.value()
122 then: 'the java API was called with the correct parameters'
123 1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData, noTimestamp, expectedContentType)
124 where: 'following xpath parameters are are used'
125 scenario | parentNodeXpath | contentType | expectedContentType | requestBody | expectedData
126 'JSON content: no xpath parameter' | '' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData
127 'JSON content: xpath parameter point root' | '/' | MediaType.APPLICATION_JSON | ContentType.JSON | requestBodyJson | expectedJsonData
128 'XML content: no xpath parameter' | '' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData
129 'XML content: xpath parameter point root' | '/' | MediaType.APPLICATION_XML | ContentType.XML | requestBodyXml | expectedXmlData
132 def 'Create a node with observed-timestamp'() {
133 given: 'endpoint to create a node'
134 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
135 when: 'post is invoked with datanode endpoint and json'
139 .contentType(contentType)
141 .param('observed-timestamp', observedTimestamp)
143 ).andReturn().response
144 then: 'a created response is returned'
145 response.status == expectedHttpStatus.value()
146 then: 'the java API was called with the correct parameters'
147 expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedData,
148 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType)
150 scenario | observedTimestamp | contentType | content || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType
151 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON
152 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML
153 'with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON
156 def 'Create a child node #scenario'() {
157 given: 'endpoint to create a node'
158 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
159 and: 'parent node xpath'
160 def parentNodeXpath = 'some xpath'
161 when: 'post is invoked with datanode endpoint and json'
162 def postRequestBuilder = post(endpoint)
163 .contentType(contentType)
164 .param('xpath', parentNodeXpath)
165 .content(requestBody)
166 if (observedTimestamp != null)
167 postRequestBuilder.param('observed-timestamp', observedTimestamp)
169 mvc.perform(postRequestBuilder).andReturn().response
170 then: 'a created response is returned'
171 response.status == HttpStatus.CREATED.value()
172 then: 'the java API was called with the correct parameters'
173 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedData,
174 DateTimeUtility.toOffsetDateTime(observedTimestamp), expectedContentType)
176 scenario | observedTimestamp | contentType | requestBody | expectedData | expectedContentType
177 'with observed-timestamp JSON' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON
178 'with observed-timestamp XML' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
179 'without observed-timestamp JSON' | null | MediaType.APPLICATION_JSON | requestBodyJson | expectedJsonData | ContentType.JSON
180 'without observed-timestamp XML' | null | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
183 def 'save list elements under root node #scenario.'() {
184 given: 'root node xpath '
185 def rootNodeXpath = '/'
186 when: 'list-node endpoint is invoked with post (create) operation'
187 def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
188 .contentType(MediaType.APPLICATION_JSON)
189 .param('xpath', rootNodeXpath )
190 .content(requestBodyJson)
191 if (observedTimestamp != null)
192 postRequestBuilder.param('observed-timestamp', observedTimestamp)
193 def response = mvc.perform(postRequestBuilder).andReturn().response
194 then: 'a created response is returned'
195 response.status == expectedHttpStatus.value()
196 then: 'the java API was called with the correct parameters'
197 expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedJsonData,
198 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
200 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
201 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
202 'without observed-timestamp' | null || 1 | HttpStatus.CREATED
203 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
206 def 'Save list elements #scenario.'() {
207 given: 'parent node xpath '
208 def parentNodeXpath = 'parent node xpath'
209 when: 'list-node endpoint is invoked with post (create) operation'
210 def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
211 .contentType(MediaType.APPLICATION_JSON)
212 .param('xpath', parentNodeXpath)
213 .content(requestBodyJson)
214 if (observedTimestamp != null)
215 postRequestBuilder.param('observed-timestamp', observedTimestamp)
216 def response = mvc.perform(postRequestBuilder).andReturn().response
217 then: 'a created response is returned'
218 response.status == expectedHttpStatus.value()
219 then: 'the java API was called with the correct parameters'
220 expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedJsonData,
221 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
223 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
224 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
225 'without observed-timestamp' | null || 1 | HttpStatus.CREATED
226 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
229 def 'Get data node with leaves'() {
230 given: 'the service returns data node leaves'
231 def xpath = 'parent-1'
232 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
233 mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
234 when: 'get request is performed through REST API'
236 mvc.perform(get(endpoint).param('xpath', xpath))
237 .andReturn().response
238 then: 'a success response is returned'
239 response.status == HttpStatus.OK.value()
240 then: 'the response contains the the datanode in json format'
241 response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
242 and: 'response contains expected leaf and value'
243 response.contentAsString.contains('"leaf":"value"')
244 and: 'response contains expected leaf-list and values'
245 response.contentAsString.contains('"leafList":["leaveListElement1","leaveListElement2"]')
248 def 'Get data node with #scenario.'() {
249 given: 'the service returns data node with #scenario'
250 def xpath = 'some xPath'
251 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
252 mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
253 when: 'get request is performed through REST API'
257 .param('xpath', xpath)
258 .param('include-descendants', includeDescendantsOption))
259 .andReturn().response
260 then: 'a success response is returned'
261 response.status == HttpStatus.OK.value()
262 and: 'the response contains the root node identifier: #expectedRootidentifier'
263 response.contentAsString.contains(expectedRootidentifier)
264 and: 'the response contains child is #expectChildInResponse'
265 response.contentAsString.contains('"child"') == expectChildInResponse
267 scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
268 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
269 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'parent-1'
270 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
273 def 'Get all the data trees as json array with root node xPath using V2'() {
274 given: 'the service returns all data node leaves'
276 def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
277 mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
278 when: 'V2 of get request is performed through REST API'
280 mvc.perform(get(endpoint).param('xpath', xpath))
281 .andReturn().response
282 then: 'a success response is returned'
283 response.status == HttpStatus.OK.value()
284 and: 'the response contains the datanode in json array format'
285 response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' +
286 '{"parent-2":{"leaf":"value"}}]'
287 and: 'the json array contains expected number of data trees'
288 def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size()
289 assert numberOfDataTrees == 2
292 def 'Get data node with #scenario using V2.'() {
293 given: 'the service returns data nodes with #scenario'
294 def xpath = 'some xPath'
295 def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
296 mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
297 when: 'V2 of get request is performed through REST API'
301 .param('xpath', xpath)
302 .param('descendants', includeDescendantsOption))
303 .andReturn().response
304 then: 'a success response is returned'
305 response.status == HttpStatus.OK.value()
306 and: 'the response contains the root node identifier: #expectedRootidentifier'
307 response.contentAsString.contains(expectedRootidentifier)
308 and: 'the response contains child is #expectChildInResponse'
309 response.contentAsString.contains('"child"') == expectChildInResponse
311 scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
312 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
313 'no descendant explicitly' | dataNodeWithLeavesNoChildren | '0' || OMIT_DESCENDANTS | false | 'parent-1'
314 'with descendants' | dataNodeWithChild | '-1' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
317 def 'Get data node using v2 api'() {
318 given: 'the service returns data node'
319 def xpath = 'some xPath'
320 def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
321 mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> {
322 assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild]
323 when: 'get request is performed through REST API'
327 .param('xpath', xpath)
328 .param('descendants', '2'))
329 .andReturn().response
330 then: 'a success response is returned'
331 assert response.status == HttpStatus.OK.value()
332 and: 'the response contains the root node identifier'
333 assert response.contentAsString.contains('parent')
334 and: 'the response contains child is true'
335 assert response.contentAsString.contains('"child"')
338 def 'Get delta between two anchors'() {
339 given: 'the service returns a list containing delta reports'
340 def deltaReports = new DeltaReportBuilder().actionAdd().withXpath('/bookstore').withSourceData('bookstore-name': 'Easons').withTargetData('bookstore-name': 'Easons').build()
341 def xpath = 'some xpath'
342 def endpoint = "$dataNodeBaseEndpointV2/anchors/sourceAnchor/delta"
343 mockCpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, 'sourceAnchor', 'targetAnchor', xpath, OMIT_DESCENDANTS) >> [deltaReports]
344 when: 'get delta request is performed using REST API'
346 mvc.perform(get(endpoint)
347 .param('target-anchor-name', 'targetAnchor')
348 .param('xpath', xpath))
349 .andReturn().response
350 then: 'expected response code is returned'
351 assert response.status == HttpStatus.OK.value()
352 and: 'the response contains expected value'
353 assert response.contentAsString.contains("[{\"action\":\"add\",\"xpath\":\"/bookstore\",\"sourceData\":{\"bookstore-name\":\"Easons\"},\"targetData\":{\"bookstore-name\":\"Easons\"}}]")
356 def 'Update data node leaves: #scenario.'() {
357 given: 'endpoint to update a node '
358 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
359 when: 'patch request is performed'
363 .contentType(MediaType.APPLICATION_JSON)
364 .content(requestBodyJson)
365 .param('xpath', inputXpath)
366 ).andReturn().response
367 then: 'the service method is invoked with expected parameters'
368 1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, null)
369 and: 'response status indicates success'
370 response.status == HttpStatus.OK.value()
372 scenario | inputXpath || xpathServiceParameter
373 'root node by default' | '' || '/'
374 'root node by choice' | '/' || '/'
375 'some xpath by parent' | '/some/xpath' || '/some/xpath'
378 def 'Update data node leaves with observedTimestamp'() {
379 given: 'endpoint to update a node leaves '
380 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
381 when: 'patch request is performed'
385 .contentType(MediaType.APPLICATION_JSON)
386 .content(requestBodyJson)
388 .param('observed-timestamp', observedTimestamp)
389 ).andReturn().response
390 then: 'the service method is invoked with expected parameters'
391 expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData,
392 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
393 and: 'response status indicates success'
394 response.status == expectedHttpStatus.value()
396 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
397 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
398 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
401 def 'Replace data node tree: #scenario.'() {
402 given: 'endpoint to replace node'
403 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
404 when: 'put request is performed'
408 .contentType(MediaType.APPLICATION_JSON)
409 .content(requestBodyJson)
410 .param('xpath', inputXpath))
411 .andReturn().response
412 then: 'the service method is invoked with expected parameters'
413 1 * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, noTimestamp)
414 and: 'response status indicates success'
415 response.status == HttpStatus.OK.value()
417 scenario | inputXpath || xpathServiceParameter
418 'root node by default' | '' || '/'
419 'root node by choice' | '/' || '/'
420 'some xpath by parent' | '/some/xpath' || '/some/xpath'
423 def 'Update data node and descendants with observedTimestamp.'() {
424 given: 'endpoint to replace node'
425 def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
426 when: 'put request is performed'
430 .contentType(MediaType.APPLICATION_JSON)
431 .content(requestBodyJson)
433 .param('observed-timestamp', observedTimestamp))
434 .andReturn().response
435 then: 'the service method is invoked with expected parameters'
436 expectedApiCount * mockCpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, '/', expectedJsonData,
437 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
438 and: 'response status indicates success'
439 response.status == expectedHttpStatus.value()
441 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
442 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
443 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
446 def 'Replace list content #scenario.'() {
447 when: 'list-nodes endpoint is invoked with put (update) operation'
448 def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
449 .contentType(MediaType.APPLICATION_JSON)
450 .param('xpath', 'parent xpath')
451 .content(requestBodyJson)
452 if (observedTimestamp != null)
453 putRequestBuilder.param('observed-timestamp', observedTimestamp)
454 def response = mvc.perform(putRequestBuilder).andReturn().response
455 then: 'a success response is returned'
456 response.status == expectedHttpStatus.value()
457 and: 'the java API was called with the correct parameters'
458 expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData,
459 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
461 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
462 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.OK
463 'without observed-timestamp' | null || 1 | HttpStatus.OK
464 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
467 def 'Delete list element #scenario.'() {
468 when: 'list-nodes endpoint is invoked with delete operation'
469 def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
470 .param('xpath', 'list element xpath')
471 if (observedTimestamp != null)
472 deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
473 def response = mvc.perform(deleteRequestBuilder).andReturn().response
474 then: 'a success response is returned'
475 response.status == expectedHttpStatus.value()
476 and: 'the java API was called with the correct parameters'
477 expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath',
478 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
480 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
481 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.NO_CONTENT
482 'without observed-timestamp' | null || 1 | HttpStatus.NO_CONTENT
483 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
486 def 'Delete data node #scenario.'() {
487 given: 'data node xpath'
488 def dataNodeXpath = '/dataNodeXpath'
489 when: 'delete data node endpoint is invoked'
490 def deleteDataNodeRequest = delete( "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes")
491 .param('xpath', dataNodeXpath)
492 and: 'observed timestamp is added to the parameters'
493 if (observedTimestamp != null)
494 deleteDataNodeRequest.param('observed-timestamp', observedTimestamp)
495 def response = mvc.perform(deleteDataNodeRequest).andReturn().response
496 then: 'a successful response is returned'
497 response.status == expectedHttpStatus.value()
498 and: 'the api is called with the correct parameters'
499 expectedApiCount * mockCpsDataService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath,
500 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
502 scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
503 'with observed timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.NO_CONTENT
504 'without observed timestamp' | null || 1 | HttpStatus.NO_CONTENT
505 'with invalid observed timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST