4d75848cf792d73273ba09144d4ea3fda9c1ccdb
[cps.git] / cps-rest / src / test / groovy / org / onap / cps / rest / controller / DataRestControllerSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 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
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.rest.controller
24
25 import com.fasterxml.jackson.databind.ObjectMapper
26 import org.onap.cps.api.CpsDataService
27 import org.onap.cps.spi.model.DataNode
28 import org.onap.cps.spi.model.DataNodeBuilder
29 import org.onap.cps.utils.DateTimeUtility
30 import org.onap.cps.utils.JsonObjectMapper
31 import org.spockframework.spring.SpringBean
32 import org.springframework.beans.factory.annotation.Autowired
33 import org.springframework.beans.factory.annotation.Value
34 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
35 import org.springframework.http.HttpStatus
36 import org.springframework.http.MediaType
37 import org.springframework.test.web.servlet.MockMvc
38 import spock.lang.Shared
39 import spock.lang.Specification
40
41 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
42 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
43 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
44 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
45 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
46 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
47 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
48
49 @WebMvcTest(DataRestController)
50 class DataRestControllerSpec extends Specification {
51
52     @SpringBean
53     CpsDataService mockCpsDataService = Mock()
54
55     @SpringBean
56     JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
57
58     @Autowired
59     MockMvc mvc
60
61     @Value('${rest.api.cps-base-path}')
62     def basePath
63
64     def dataNodeBaseEndpoint
65     def dataspaceName = 'my_dataspace'
66     def anchorName = 'my_anchor'
67     def noTimestamp = null
68     def requestBody = '{"some-key" : "some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
69     def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}'
70
71     @Shared
72     static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath')
73         .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
74
75     @Shared
76     static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
77         .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
78
79     def setup() {
80         dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName"
81     }
82
83     def 'Create a node: #scenario.'() {
84         given: 'endpoint to create a node'
85             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
86         when: 'post is invoked with datanode endpoint and json'
87             def response =
88                 mvc.perform(
89                     post(endpoint)
90                         .contentType(MediaType.APPLICATION_JSON)
91                         .param('xpath', parentNodeXpath)
92                         .content(requestBody)
93                 ).andReturn().response
94         then: 'a created response is returned'
95             response.status == HttpStatus.CREATED.value()
96         then: 'the java API was called with the correct parameters'
97             1 * mockCpsDataService.saveData(dataspaceName, anchorName, expectedJsonData, noTimestamp)
98         where: 'following xpath parameters are are used'
99             scenario                     | parentNodeXpath
100             'no xpath parameter'         | ''
101             'xpath parameter point root' | '/'
102     }
103
104     def 'Create a node with observed-timestamp'() {
105         given: 'endpoint to create a node'
106             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
107         when: 'post is invoked with datanode endpoint and json'
108             def response =
109                 mvc.perform(
110                     post(endpoint)
111                         .contentType(MediaType.APPLICATION_JSON)
112                         .param('xpath', '')
113                         .param('observed-timestamp', observedTimestamp)
114                         .content(requestBody)
115                 ).andReturn().response
116         then: 'a created response is returned'
117             response.status == expectedHttpStatus.value()
118         then: 'the java API was called with the correct parameters'
119             expectedApiCount * mockCpsDataService.saveData(dataspaceName, anchorName, expectedJsonData,
120                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
121         where:
122             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
123             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.CREATED
124             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
125     }
126
127     def 'Create a child node'() {
128         given: 'endpoint to create a node'
129             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
130         and: 'parent node xpath'
131             def parentNodeXpath = 'some xpath'
132         when: 'post is invoked with datanode endpoint and json'
133             def postRequestBuilder = post(endpoint)
134                 .contentType(MediaType.APPLICATION_JSON)
135                 .param('xpath', parentNodeXpath)
136                 .content(requestBody)
137             if (observedTimestamp != null)
138                 postRequestBuilder.param('observed-timestamp', observedTimestamp)
139             def response =
140                 mvc.perform(postRequestBuilder).andReturn().response
141         then: 'a created response is returned'
142             response.status == HttpStatus.CREATED.value()
143         then: 'the java API was called with the correct parameters'
144             1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, expectedJsonData,
145                 DateTimeUtility.toOffsetDateTime(observedTimestamp))
146         where:
147             scenario                     | observedTimestamp
148             'with observed-timestamp'    | '2021-03-03T23:59:59.999-0400'
149             'without observed-timestamp' | null
150     }
151
152     def 'Save list elements #scenario.'() {
153         given: 'parent node xpath '
154             def parentNodeXpath = 'parent node xpath'
155         when: 'list-node endpoint is invoked with post (create) operation'
156             def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
157                 .contentType(MediaType.APPLICATION_JSON)
158                 .param('xpath', parentNodeXpath)
159                 .content(requestBody)
160             if (observedTimestamp != null)
161                 postRequestBuilder.param('observed-timestamp', observedTimestamp)
162             def response = mvc.perform(postRequestBuilder).andReturn().response
163         then: 'a created response is returned'
164             response.status == expectedHttpStatus.value()
165         then: 'the java API was called with the correct parameters'
166             expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedJsonData,
167                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
168         where:
169             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
170             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.CREATED
171             'without observed-timestamp'      | null                           || 1                | HttpStatus.CREATED
172             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
173     }
174
175     def 'Get data node with leaves'() {
176         given: 'the service returns data node leaves'
177             def xpath = 'some xPath'
178             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
179             mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren
180         when: 'get request is performed through REST API'
181             def response =
182                 mvc.perform(get(endpoint).param('xpath', xpath))
183                     .andReturn().response
184         then: 'a success response is returned'
185             response.status == HttpStatus.OK.value()
186         and: 'response contains expected leaf and value'
187             response.contentAsString.contains('"leaf":"value"')
188         and: 'response contains expected leaf-list and values'
189             response.contentAsString.contains('"leafList":["leaveListElement1","leaveListElement2"]')
190     }
191
192     def 'Get data node with #scenario.'() {
193         given: 'the service returns data node with #scenario'
194             def xpath = 'some xPath'
195             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
196             mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode
197         when: 'get request is performed through REST API'
198             def response =
199                 mvc.perform(
200                     get(endpoint)
201                         .param('xpath', xpath)
202                         .param('include-descendants', includeDescendantsOption))
203                     .andReturn().response
204         then: 'a success response is returned'
205             response.status == HttpStatus.OK.value()
206         and: 'the response contains child is #expectChildInResponse'
207             response.contentAsString.contains('"child"') == expectChildInResponse
208         where:
209             scenario                    | dataNode                     | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse
210             'no descendants by default' | dataNodeWithLeavesNoChildren | ''                       || OMIT_DESCENDANTS             | false
211             'no descendant explicitly'  | dataNodeWithLeavesNoChildren | 'false'                  || OMIT_DESCENDANTS             | false
212             'with descendants'          | dataNodeWithChild            | 'true'                   || INCLUDE_ALL_DESCENDANTS      | true
213     }
214
215     def 'Update data node leaves: #scenario.'() {
216         given: 'endpoint to update a node '
217             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
218         when: 'patch request is performed'
219             def response =
220                 mvc.perform(
221                     patch(endpoint)
222                         .contentType(MediaType.APPLICATION_JSON)
223                         .content(requestBody)
224                         .param('xpath', inputXpath)
225                 ).andReturn().response
226         then: 'the service method is invoked with expected parameters'
227             1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, null)
228         and: 'response status indicates success'
229             response.status == HttpStatus.OK.value()
230         where:
231             scenario               | inputXpath    || xpathServiceParameter
232             'root node by default' | ''            || '/'
233             'root node by choice'  | '/'           || '/'
234             'some xpath by parent' | '/some/xpath' || '/some/xpath'
235     }
236
237     def 'Update data node leaves with observedTimestamp'() {
238         given: 'endpoint to update a node leaves '
239             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
240         when: 'patch request is performed'
241             def response =
242                 mvc.perform(
243                     patch(endpoint)
244                         .contentType(MediaType.APPLICATION_JSON)
245                         .content(requestBody)
246                         .param('xpath', '/')
247                         .param('observed-timestamp', observedTimestamp)
248                 ).andReturn().response
249         then: 'the service method is invoked with expected parameters'
250             expectedApiCount * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, '/', expectedJsonData,
251                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
252         and: 'response status indicates success'
253             response.status == expectedHttpStatus.value()
254         where:
255             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
256             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.OK
257             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
258     }
259
260     def 'Replace data node tree: #scenario.'() {
261         given: 'endpoint to replace node'
262             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
263         when: 'put request is performed'
264             def response =
265                 mvc.perform(
266                     put(endpoint)
267                         .contentType(MediaType.APPLICATION_JSON)
268                         .content(requestBody)
269                         .param('xpath', inputXpath))
270                     .andReturn().response
271         then: 'the service method is invoked with expected parameters'
272             1 * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, xpathServiceParameter, expectedJsonData, noTimestamp)
273         and: 'response status indicates success'
274             response.status == HttpStatus.OK.value()
275         where:
276             scenario               | inputXpath    || xpathServiceParameter
277             'root node by default' | ''            || '/'
278             'root node by choice'  | '/'           || '/'
279             'some xpath by parent' | '/some/xpath' || '/some/xpath'
280     }
281
282     def 'Replace data node tree with observedTimestamp.'() {
283         given: 'endpoint to replace node'
284             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
285         when: 'put request is performed'
286             def response =
287                 mvc.perform(
288                     put(endpoint)
289                         .contentType(MediaType.APPLICATION_JSON)
290                         .content(requestBody)
291                         .param('xpath', '')
292                         .param('observed-timestamp', observedTimestamp))
293                     .andReturn().response
294         then: 'the service method is invoked with expected parameters'
295             expectedApiCount * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, '/', expectedJsonData,
296                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
297         and: 'response status indicates success'
298             response.status == expectedHttpStatus.value()
299         where:
300             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
301             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.OK
302             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
303     }
304
305     def 'Replace list content #scenario.'() {
306         when: 'list-nodes endpoint is invoked with put (update) operation'
307             def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
308                 .contentType(MediaType.APPLICATION_JSON)
309                 .param('xpath', 'parent xpath')
310                 .content(requestBody)
311             if (observedTimestamp != null)
312                 putRequestBuilder.param('observed-timestamp', observedTimestamp)
313             def response = mvc.perform(putRequestBuilder).andReturn().response
314         then: 'a success response is returned'
315             response.status == expectedHttpStatus.value()
316         and: 'the java API was called with the correct parameters'
317             expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData,
318                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
319         where:
320             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
321             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.OK
322             'without observed-timestamp'      | null                           || 1                | HttpStatus.OK
323             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
324     }
325
326     def 'Delete list element #scenario.'() {
327         when: 'list-nodes endpoint is invoked with delete operation'
328             def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
329                 .param('xpath', 'list element xpath')
330             if (observedTimestamp != null)
331                 deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
332             def response = mvc.perform(deleteRequestBuilder).andReturn().response
333         then: 'a success response is returned'
334             response.status == expectedHttpStatus.value()
335         and: 'the java API was called with the correct parameters'
336             expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath',
337                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
338         where:
339             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
340             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.NO_CONTENT
341             'without observed-timestamp'      | null                           || 1                | HttpStatus.NO_CONTENT
342             'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
343     }
344
345     def 'Delete data node #scenario.'() {
346         given: 'data node xpath'
347             def dataNodeXpath = '/dataNodeXpath'
348         when: 'delete data node endpoint is invoked'
349             def deleteDataNodeRequest = delete( "$dataNodeBaseEndpoint/anchors/$anchorName/nodes")
350                 .param('xpath', dataNodeXpath)
351         and: 'observed timestamp is added to the parameters'
352             if (observedTimestamp != null)
353                 deleteDataNodeRequest.param('observed-timestamp', observedTimestamp)
354             def response = mvc.perform(deleteDataNodeRequest).andReturn().response
355         then: 'a successful response is returned'
356             response.status == expectedHttpStatus.value()
357         and: 'the api is called with the correct parameters'
358             expectedApiCount * mockCpsDataService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath,
359                 { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
360         where:
361             scenario                            | observedTimestamp                 || expectedApiCount | expectedHttpStatus
362             'with observed timestamp'           | '2021-03-03T23:59:59.999-0400'    || 1                | HttpStatus.NO_CONTENT
363             'without observed timestamp'        | null                              || 1                | HttpStatus.NO_CONTENT
364             'with invalid observed timestamp'   | 'invalid'                         || 0                | HttpStatus.BAD_REQUEST
365     }
366 }