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