2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Pantheon.tech
4 * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.rest.controller
23 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
24 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
25 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
26 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
27 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
29 import org.modelmapper.ModelMapper
30 import org.onap.cps.api.CpsAdminService
31 import org.onap.cps.api.CpsDataService
32 import org.onap.cps.api.CpsModuleService
33 import org.onap.cps.spi.exceptions.DataspaceAlreadyDefinedException
34 import org.onap.cps.spi.exceptions.SchemaSetInUseException
35 import org.onap.cps.spi.model.Anchor
36 import org.onap.cps.spi.model.SchemaSet
37 import org.spockframework.spring.SpringBean
38 import org.springframework.beans.factory.annotation.Autowired
39 import org.springframework.beans.factory.annotation.Value
40 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
41 import org.springframework.http.HttpStatus
42 import org.springframework.http.MediaType
43 import org.springframework.mock.web.MockMultipartFile
44 import org.springframework.test.web.servlet.MockMvc
45 import org.springframework.util.LinkedMultiValueMap
46 import org.springframework.util.MultiValueMap
47 import spock.lang.Specification
48 import spock.lang.Unroll
51 class AdminRestControllerSpec extends Specification {
54 CpsModuleService mockCpsModuleService = Mock()
57 CpsAdminService mockCpsAdminService = Mock()
60 CpsDataService mockCpsDataService = Mock()
63 ModelMapper modelMapper = Mock()
68 @Value('${rest.api.cps-base-path}')
71 def dataspaceName = 'my_dataspace'
72 def anchor = new Anchor(name: 'my_anchor')
73 def anchorList = [anchor]
74 def anchorName = 'my_anchor'
75 def schemaSetName = 'my_schema_set'
77 def 'Create new dataspace.'() {
79 def createDataspaceEndpoint = "$basePath/v1/dataspaces";
80 when: 'post is invoked'
81 def response = mvc.perform(
82 post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
83 then: 'service method is invoked with expected parameters'
84 1 * mockCpsAdminService.createDataspace(dataspaceName)
85 and: 'dataspace is create successfully'
86 response.status == HttpStatus.CREATED.value()
89 def 'Create dataspace over existing with same name.'() {
91 def createDataspaceEndpoint = "$basePath/v1/dataspaces";
92 and: 'the service method throws an exception indicating the dataspace is already defined'
93 def thrownException = new DataspaceAlreadyDefinedException("", new RuntimeException())
94 mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException }
95 when: 'post is invoked'
96 def response = mvc.perform(post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
97 then: 'dataspace creation fails'
98 response.status == HttpStatus.BAD_REQUEST.value()
101 def 'Create schema set from yang file.'() {
102 def yangResourceMapCapture
103 given: 'single yang file'
104 def multipartFile = createMultipartFile("filename.yang", "content")
106 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
107 when: 'file uploaded with schema set create request'
108 def response = mvc.perform(multipart(schemaSetEndpoint)
109 .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
110 then: 'associated service method is invoked with expected parameters'
111 1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
112 { args -> yangResourceMapCapture = args[2] }
113 yangResourceMapCapture['filename.yang'] == 'content'
114 and: 'response code indicates success'
115 response.status == HttpStatus.CREATED.value()
118 def 'Create schema set from zip archive.'() {
119 def yangResourceMapCapture
120 given: 'zip archive with multiple .yang files inside'
121 def multipartFile = createZipMultipartFileFromResource("/yang-files-set.zip")
123 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
124 when: 'file uploaded with schema set create request'
125 def response = mvc.perform(multipart(schemaSetEndpoint)
126 .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
127 then: 'associated service method is invoked with expected parameters'
128 1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
129 { args -> yangResourceMapCapture = args[2] }
130 yangResourceMapCapture['assembly.yang'] == "fake assembly content 1\n"
131 yangResourceMapCapture['component.yang'] == "fake component content 1\n"
132 and: 'response code indicates success'
133 response.status == HttpStatus.CREATED.value()
137 def 'Create schema set from zip archive having #caseDescriptor.'() {
139 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
140 when: 'zip archive having #caseDescriptor is uploaded with create schema set request'
141 def response = mvc.perform(multipart(schemaSetEndpoint)
142 .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
143 then: 'create schema set rejected'
144 response.status == HttpStatus.BAD_REQUEST.value()
145 where: 'following cases are tested'
146 caseDescriptor | multipartFile
147 'no .yang files inside' | createZipMultipartFileFromResource("/no-yang-files.zip")
148 'multiple .yang files with same name' | createZipMultipartFileFromResource("/yang-files-multiple-sets.zip")
151 def 'Create schema set from file with unsupported filename extension.'() {
152 given: 'file with unsupported filename extension (.doc)'
153 def multipartFile = createMultipartFile("filename.doc", "content")
155 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
156 when: 'file uploaded with schema set create request'
157 def response = mvc.perform(multipart(schemaSetEndpoint)
158 .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
159 then: 'create schema set rejected'
160 response.status == HttpStatus.BAD_REQUEST.value()
164 def 'Create schema set from #fileType file with IOException occurrence on processing.'() {
166 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
167 when: 'file uploaded with schema set create request'
168 def multipartFile = createMultipartFileForIOException(fileType)
169 def response = mvc.perform(multipart(schemaSetEndpoint)
170 .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
171 then: 'the error response returned indicating internal server error occurrence'
172 response.status == HttpStatus.INTERNAL_SERVER_ERROR.value()
173 where: 'following file types are used'
174 fileType << ['YANG', 'ZIP']
177 def 'Delete schema set.'() {
179 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
180 when: 'delete schema set endpoint is invoked'
181 def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
182 then: 'associated service method is invoked with expected parameters'
183 1 * mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED)
184 and: 'response code indicates success'
185 response.status == HttpStatus.NO_CONTENT.value()
188 def 'Delete schema set which is in use.'() {
189 given: 'service method throws an exception indicating the schema set is in use'
190 def thrownException = new SchemaSetInUseException(dataspaceName, schemaSetName)
191 mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED) >>
192 { throw thrownException }
194 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
195 when: 'delete schema set endpoint is invoked'
196 def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
197 then: 'schema set deletion fails with conflict response code'
198 response.status == HttpStatus.CONFLICT.value()
201 def 'Get existing schema set.'() {
202 given: 'service method returns a new schema set'
203 mockCpsModuleService.getSchemaSet(dataspaceName, schemaSetName) >>
204 new SchemaSet(name: schemaSetName, dataspaceName: dataspaceName)
206 def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
207 when: 'get schema set API is invoked'
208 def response = mvc.perform(get(schemaSetEndpoint)).andReturn().response
209 then: 'the correct schema set is returned'
210 response.status == HttpStatus.OK.value()
211 response.getContentAsString().contains(schemaSetName)
214 def 'Create Anchor.'() {
215 given: 'request parameters'
216 def requestParams = new LinkedMultiValueMap<>()
217 requestParams.add('schema-set-name', schemaSetName)
218 requestParams.add('anchor-name', anchorName)
220 def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
221 when: 'post is invoked'
222 def response = mvc.perform(post(anchorEndpoint).contentType(MediaType.APPLICATION_JSON)
223 .params(requestParams as MultiValueMap)).andReturn().response
224 then: 'anchor is created successfully'
225 1 * mockCpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName)
226 response.status == HttpStatus.CREATED.value()
227 response.getContentAsString().contains(anchorName)
230 def 'Get existing anchor.'() {
231 given: 'service method returns a list of anchors'
232 mockCpsAdminService.getAnchors(dataspaceName) >> anchorList
234 def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
235 when: 'get all anchors API is invoked'
236 def response = mvc.perform(get(anchorEndpoint)).andReturn().response
237 then: 'the correct anchor is returned'
238 response.status == HttpStatus.OK.value()
239 response.getContentAsString().contains(anchorName)
242 def 'Get existing anchor by dataspace and anchor name.'() {
243 given: 'service method returns an anchor'
244 mockCpsAdminService.getAnchor(dataspaceName,anchorName) >> new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName:schemaSetName)
246 def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName"
247 when: 'get anchor API is invoked'
248 def response = mvc.perform(get(anchorEndpoint))
249 .andReturn().response
250 def responseContent = response.getContentAsString()
251 then: 'the correct anchor is returned'
252 response.status == HttpStatus.OK.value()
253 responseContent.contains(anchorName)
254 responseContent.contains(dataspaceName)
255 responseContent.contains(schemaSetName)
258 def createMultipartFile(filename, content) {
259 return new MockMultipartFile("file", filename, "text/plain", content.getBytes())
262 def createZipMultipartFileFromResource(resourcePath) {
263 return new MockMultipartFile("file", "test.zip", "application/zip",
264 getClass().getResource(resourcePath).getBytes())
267 def createMultipartFileForIOException(extension) {
268 def multipartFile = Mock(MockMultipartFile)
269 multipartFile.getOriginalFilename() >> "TEST." + extension
270 multipartFile.getBytes() >> { throw new IOException() }
271 multipartFile.getInputStream() >> { throw new IOException() }