Merge "Cm Subscription: Predicates optional now"
[cps.git] / cps-rest / src / test / groovy / org / onap / cps / rest / controller / AdminRestControllerSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2020-2021 Pantheon.tech
4  *  Modifications Copyright (C) 2020-2021 Bell Canada.
5  *  Modifications Copyright (C) 2021-2022 Nordix Foundation
6  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
7  *  ================================================================================
8  *  Licensed under the Apache License, Version 2.0 (the "License");
9  *  you may not use this file except in compliance with the License.
10  *  You may obtain a copy of the License at
11  *
12  *        http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *  Unless required by applicable law or agreed to in writing, software
15  *  distributed under the License is distributed on an "AS IS" BASIS,
16  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  *  See the License for the specific language governing permissions and
18  *  limitations under the License.
19  *
20  *  SPDX-License-Identifier: Apache-2.0
21  *  ============LICENSE_END=========================================================
22  */
23
24 package org.onap.cps.rest.controller
25
26 import org.onap.cps.api.CpsAnchorService
27
28 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
29 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
30 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
31 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
32 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
33
34 import org.mapstruct.factory.Mappers
35 import org.onap.cps.api.CpsDataspaceService
36 import org.onap.cps.api.CpsModuleService
37 import org.onap.cps.spi.exceptions.AlreadyDefinedException
38 import org.onap.cps.spi.exceptions.SchemaSetInUseException
39 import org.onap.cps.spi.model.Anchor
40 import org.onap.cps.spi.model.Dataspace
41 import org.onap.cps.spi.model.SchemaSet
42 import org.spockframework.spring.SpringBean
43 import org.springframework.beans.factory.annotation.Autowired
44 import org.springframework.beans.factory.annotation.Value
45 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
46 import org.springframework.http.HttpStatus
47 import org.springframework.http.MediaType
48 import org.springframework.mock.web.MockMultipartFile
49 import org.springframework.test.web.servlet.MockMvc
50 import org.springframework.util.LinkedMultiValueMap
51 import org.springframework.util.MultiValueMap
52 import spock.lang.Specification
53
54 @WebMvcTest(AdminRestController)
55 class AdminRestControllerSpec extends Specification {
56
57     @SpringBean
58     CpsModuleService mockCpsModuleService = Mock()
59
60     @SpringBean
61     CpsDataspaceService mockCpsDataspaceService = Mock()
62
63     @SpringBean
64     CpsAnchorService mockCpsAnchorService = Mock()
65
66     @SpringBean
67     CpsRestInputMapper cpsRestInputMapper = Mappers.getMapper(CpsRestInputMapper)
68
69     @Autowired
70     MockMvc mvc
71
72     @Value('${rest.api.cps-base-path}')
73     def basePath
74
75     def dataspaceName = 'my_dataspace'
76     def anchorName = 'my_anchor'
77     def schemaSetName = 'my_schema_set'
78     def anchor = new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName: schemaSetName)
79     def dataspace = new Dataspace(name: dataspaceName)
80
81     def 'Create new dataspace with #scenario.'() {
82         when: 'post is invoked'
83             def response =
84                 mvc.perform(
85                     post("/cps/api/${apiVersion}/dataspaces")
86                         .param('dataspace-name', dataspaceName))
87                     .andReturn().response
88         then: 'service method is invoked with expected parameters'
89             1 * mockCpsDataspaceService.createDataspace(dataspaceName)
90         and: 'dataspace is create successfully'
91             response.status == HttpStatus.CREATED.value()
92             assert response.getContentAsString() == expectedResponseBody
93         where: 'following cases are tested'
94             scenario | apiVersion || expectedResponseBody
95             'V1 API' | 'v1'       || 'my_dataspace'
96             'V2 API' | 'v2'       || ''
97     }
98
99     def 'Create dataspace over existing with same name.'() {
100         given: 'an endpoint'
101             def createDataspaceEndpoint = "$basePath/v1/dataspaces"
102         and: 'the service method throws an exception indicating the dataspace is already defined'
103             def thrownException = new AlreadyDefinedException(dataspaceName, new RuntimeException())
104             mockCpsDataspaceService.createDataspace(dataspaceName) >> { throw thrownException }
105         when: 'post is invoked'
106             def response =
107                     mvc.perform(
108                             post(createDataspaceEndpoint)
109                                     .param('dataspace-name', dataspaceName))
110                             .andReturn().response
111         then: 'dataspace creation fails'
112             response.status == HttpStatus.CONFLICT.value()
113     }
114
115     def 'Get a dataspace.'() {
116         given: 'service method returns a dataspace'
117             mockCpsDataspaceService.getDataspace(dataspaceName) >> dataspace
118         and: 'an endpoint'
119             def getDataspaceEndpoint = "$basePath/v1/admin/dataspaces/$dataspaceName"
120         when: 'get dataspace API is invoked'
121             def response = mvc.perform(get(getDataspaceEndpoint)).andReturn().response
122         then: 'the correct dataspace is returned'
123             response.status == HttpStatus.OK.value()
124             response.getContentAsString().contains(dataspaceName)
125     }
126
127     def 'Get all dataspaces.'() {
128         given: 'service method returns all dataspace'
129             mockCpsDataspaceService.getAllDataspaces() >> [dataspace, new Dataspace(name: "dataspace-test2")]
130         and: 'an endpoint'
131             def getAllDataspaceEndpoint = "$basePath/v1/admin/dataspaces"
132         when: 'get all dataspace API is invoked'
133             def response = mvc.perform(get(getAllDataspaceEndpoint)).andReturn().response
134         then: 'the correct dataspace is returned'
135             response.status == HttpStatus.OK.value()
136             response.getContentAsString().contains(dataspaceName)
137             response.getContentAsString().contains("dataspace-test2")
138     }
139
140     def 'Create schema set from yang file with #scenario.'() {
141         def yangResourceMapCapture
142         given: 'single yang file'
143             def multipartFile = createMultipartFile("filename.yang", "content")
144         when: 'file uploaded with schema set create request'
145             def response =
146                     mvc.perform(
147                             multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets")
148                                     .file(multipartFile)
149                                     .param('schema-set-name', schemaSetName))
150                             .andReturn().response
151         then: 'associated service method is invoked with expected parameters'
152             1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
153                     { args -> yangResourceMapCapture = args[2] }
154             yangResourceMapCapture['filename.yang'] == 'content'
155         and: 'response code indicates success'
156             assert response.status == HttpStatus.CREATED.value()
157             assert response.getContentAsString() == expectedResponseBody
158         where: 'following cases are tested'
159             scenario | apiVersion || expectedResponseBody
160             'V1 API' | 'v1'       || 'my_schema_set'
161             'V2 API' | 'v2'       || ''
162     }
163
164     def 'Create schema set from zip archive with #scenario.'() {
165         def yangResourceMapCapture
166         given: 'zip archive with multiple .yang files inside'
167             def multipartFile = createZipMultipartFileFromResource("/yang-files-set.zip")
168         when: 'file uploaded with schema set create request'
169             def response =
170                     mvc.perform(
171                             multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets")
172                                     .file(multipartFile)
173                                     .param('schema-set-name', schemaSetName))
174                             .andReturn().response
175         then: 'associated service method is invoked with expected parameters'
176             1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
177                     { args -> yangResourceMapCapture = args[2] }
178             yangResourceMapCapture['assembly.yang'] == "fake assembly content 1\n"
179             yangResourceMapCapture['component.yang'] == "fake component content 1\n"
180         and: 'response code indicates success'
181             assert response.status == HttpStatus.CREATED.value()
182             assert response.getContentAsString() == expectedResponseBody
183         where: 'following cases are tested'
184             scenario | apiVersion || expectedResponseBody
185             'V1 API' | 'v1'       || 'my_schema_set'
186             'V2 API' | 'v2'       || ''
187     }
188
189     def 'Create a schema set from a yang file that is greater than 1MB #scenario.'() {
190         given: 'a yang file greater than 1MB'
191             def multipartFile = createMultipartFileFromResource("/model-over-1mb.yang")
192         when: 'a file is uploaded to the create schema set endpoint'
193             def response =
194                     mvc.perform(
195                             multipart("/cps/api/${apiVersion}/dataspaces/my_dataspace/schema-sets")
196                                     .file(multipartFile)
197                                     .param('schema-set-name', schemaSetName))
198                             .andReturn().response
199         then: 'the associated service method is invoked'
200             1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _)
201         and: 'the response code indicates success'
202             assert response.status == HttpStatus.CREATED.value()
203             assert response.getContentAsString() == expectedResponseBody
204         where: 'following cases are tested'
205             scenario | apiVersion || expectedResponseBody
206             'V1 API' | 'v1'       || 'my_schema_set'
207             'V2 API' | 'v2'       || ''
208     }
209
210     def 'Create schema set from zip archive having #caseDescriptor.'() {
211         given: 'an endpoint'
212             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
213         when: 'zip archive having #caseDescriptor is uploaded with create schema set request'
214             def response =
215                     mvc.perform(
216                             multipart(schemaSetEndpoint)
217                                     .file(multipartFile)
218                                     .param('schema-set-name', schemaSetName))
219                             .andReturn().response
220         then: 'create schema set rejected'
221             response.status == HttpStatus.BAD_REQUEST.value()
222         where: 'following cases are tested'
223             caseDescriptor                        | multipartFile
224             'no .yang files inside'               | createZipMultipartFileFromResource("/no-yang-files.zip")
225             'multiple .yang files with same name' | createZipMultipartFileFromResource("/yang-files-multiple-sets.zip")
226     }
227
228     def 'Create schema set from file with unsupported filename extension.'() {
229         given: 'file with unsupported filename extension (.doc)'
230             def multipartFile = createMultipartFile("filename.doc", "content")
231         and: 'an endpoint'
232             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
233         when: 'file uploaded with schema set create request'
234             def response =
235                     mvc.perform(
236                             multipart(schemaSetEndpoint)
237                                     .file(multipartFile)
238                                     .param('schema-set-name', schemaSetName))
239                             .andReturn().response
240         then: 'create schema set rejected'
241             response.status == HttpStatus.BAD_REQUEST.value()
242     }
243
244     def 'Create schema set from #fileType file with IOException occurrence on processing.'() {
245         given: 'an endpoint'
246             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
247         when: 'file uploaded with schema set create request'
248             def multipartFile = createMultipartFileForIOException(fileType)
249             def response =
250                     mvc.perform(
251                             multipart(schemaSetEndpoint)
252                                     .file(multipartFile)
253                                     .param('schema-set-name', schemaSetName))
254                             .andReturn().response
255         then: 'the error response returned indicating internal server error occurrence'
256             response.status == HttpStatus.INTERNAL_SERVER_ERROR.value()
257         where: 'following file types are used'
258             fileType << ['YANG', 'ZIP']
259     }
260
261     def 'Delete schema set.'() {
262         given: 'an endpoint'
263             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
264         when: 'delete schema set endpoint is invoked'
265             def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
266         then: 'associated service method is invoked with expected parameters'
267             1 * mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED)
268         and: 'response code indicates success'
269             response.status == HttpStatus.NO_CONTENT.value()
270     }
271
272     def 'Delete schema set which is in use.'() {
273         given: 'service method throws an exception indicating the schema set is in use'
274             def thrownException = new SchemaSetInUseException(dataspaceName, schemaSetName)
275             mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED) >>
276                     { throw thrownException }
277         and: 'an endpoint'
278             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
279         when: 'delete schema set endpoint is invoked'
280             def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
281         then: 'schema set deletion fails with conflict response code'
282             response.status == HttpStatus.CONFLICT.value()
283     }
284
285     def 'Get existing schema set.'() {
286         given: 'service method returns a new schema set'
287             mockCpsModuleService.getSchemaSet(dataspaceName, schemaSetName) >>
288                     new SchemaSet(name: schemaSetName, dataspaceName: dataspaceName)
289         and: 'an endpoint'
290             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
291         when: 'get schema set API is invoked'
292             def response = mvc.perform(get(schemaSetEndpoint)).andReturn().response
293         then: 'the correct schema set is returned'
294             response.status == HttpStatus.OK.value()
295             response.getContentAsString().contains(schemaSetName)
296     }
297
298     def 'Get all schema sets for a given dataspace name.'() {
299         given: 'service method returns all schema sets for a dataspace'
300             mockCpsModuleService.getSchemaSets(dataspaceName) >>
301                 [new SchemaSet(name: schemaSetName, dataspaceName: dataspaceName),
302                 new SchemaSet(name: "test-schemaset", dataspaceName: dataspaceName)]
303         and: 'an endpoint'
304             def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
305         when: 'get schema sets API is invoked'
306             def response = mvc.perform(get(schemaSetEndpoint)).andReturn().response
307         then: 'the correct schema sets is returned'
308             assert response.status == HttpStatus.OK.value()
309             assert response.getContentAsString() == '[{"dataspaceName":"my_dataspace","moduleReferences":[],"name":' +
310                    '"my_schema_set"},{"dataspaceName":"my_dataspace","moduleReferences":[],"name":"test-schemaset"}]'
311     }
312
313     def 'Create Anchor with #scenario.'() {
314         given: 'request parameters'
315             def requestParams = new LinkedMultiValueMap<>()
316             requestParams.add('schema-set-name', schemaSetName)
317             requestParams.add('anchor-name', anchorName)
318         when: 'post is invoked'
319             def response =
320                     mvc.perform(
321                             post("/cps/api/${apiVersion}/dataspaces/my_dataspace/anchors")
322                                     .contentType(MediaType.APPLICATION_JSON)
323                                     .params(requestParams as MultiValueMap))
324                                     .andReturn().response
325         then: 'anchor is created successfully'
326             1 * mockCpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName)
327             assert response.status == HttpStatus.CREATED.value()
328             assert response.getContentAsString() == expectedResponseBody
329         where: 'following cases are tested'
330             scenario | apiVersion || expectedResponseBody
331             'V1 API' | 'v1'       || 'my_anchor'
332             'V2 API' | 'v2'       || ''
333     }
334
335     def 'Get existing anchor.'() {
336         given: 'service method returns a list of anchors'
337             mockCpsAnchorService.getAnchors(dataspaceName) >> [anchor]
338         and: 'an endpoint'
339             def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
340         when: 'get all anchors API is invoked'
341             def response = mvc.perform(get(anchorEndpoint)).andReturn().response
342         then: 'the correct anchor is returned'
343             response.status == HttpStatus.OK.value()
344             response.getContentAsString().contains(anchorName)
345     }
346
347     def 'Get existing anchor by dataspace and anchor name.'() {
348         given: 'service method returns an anchor'
349             mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >>
350                     new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName: schemaSetName)
351         and: 'an endpoint'
352             def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName"
353         when: 'get anchor API is invoked'
354             def response = mvc.perform(get(anchorEndpoint)).andReturn().response
355             def responseContent = response.getContentAsString()
356         then: 'the correct anchor is returned'
357             response.status == HttpStatus.OK.value()
358             responseContent.contains(anchorName)
359             responseContent.contains(dataspaceName)
360             responseContent.contains(schemaSetName)
361     }
362
363     def 'Delete anchor.'() {
364         given: 'an endpoint'
365             def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName"
366         when: 'delete method is invoked on anchor endpoint'
367             def response = mvc.perform(delete(anchorEndpoint)).andReturn().response
368         then: 'associated service method is invoked with expected parameters'
369             1 * mockCpsAnchorService.deleteAnchor(dataspaceName, anchorName)
370         and: 'response code indicates success'
371             response.status == HttpStatus.NO_CONTENT.value()
372     }
373
374     def 'Delete dataspace.'() {
375         given: 'an endpoint'
376             def dataspaceEndpoint = "$basePath/v1/dataspaces"
377         when: 'delete dataspace endpoint is invoked'
378             def response = mvc.perform(delete(dataspaceEndpoint)
379                 .param('dataspace-name', dataspaceName))
380                 .andReturn().response
381         then: 'associated service method is invoked with expected parameter'
382             1 * mockCpsDataspaceService.deleteDataspace(dataspaceName)
383         and: 'response code indicates success'
384             response.status == HttpStatus.NO_CONTENT.value()
385     }
386
387     def createMultipartFile(filename, content) {
388         return new MockMultipartFile("file", filename, "text/plain", content.getBytes())
389     }
390
391     def createZipMultipartFileFromResource(resourcePath) {
392         return new MockMultipartFile("file", "test.zip", "application/zip",
393                 getClass().getResource(resourcePath).getBytes())
394     }
395
396     def createMultipartFileFromResource(resourcePath) {
397         return new MockMultipartFile("file", "test.yang", "application/text",
398                 getClass().getResource(resourcePath).getBytes())
399     }
400
401     def createMultipartFileForIOException(extension) {
402         def multipartFile = Mock(MockMultipartFile)
403         multipartFile.getOriginalFilename() >> "TEST." + extension
404         multipartFile.getBytes() >> { throw new IOException() }
405         multipartFile.getInputStream() >> { throw new IOException() }
406         return multipartFile
407     }
408
409 }