Remove old subscription code and other fixes
[cps/ncmp-dmi-plugin.git] / src / test / groovy / org / onap / cps / ncmp / dmi / rest / controller / DmiRestControllerSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2021-2022 Bell Canada
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
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.ncmp.dmi.rest.controller
23
24 import org.onap.cps.ncmp.dmi.TestUtils
25 import org.onap.cps.ncmp.dmi.config.WebSecurityConfig
26 import org.onap.cps.ncmp.dmi.exception.DmiException
27 import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
28 import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException
29 import org.onap.cps.ncmp.dmi.model.ModuleSet
30 import org.onap.cps.ncmp.dmi.model.ModuleSetSchemasInner
31 import org.onap.cps.ncmp.dmi.model.YangResource
32 import org.onap.cps.ncmp.dmi.model.YangResources
33 import org.onap.cps.ncmp.dmi.notifications.async.AsyncTaskExecutor
34 import org.onap.cps.ncmp.dmi.notifications.async.DmiAsyncRequestResponseEventProducer
35 import org.onap.cps.ncmp.dmi.service.DmiService
36 import org.onap.cps.ncmp.dmi.service.model.ModuleReference
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.context.annotation.Import
42 import org.springframework.http.HttpStatus
43 import org.springframework.http.MediaType
44 import org.springframework.kafka.core.KafkaTemplate
45 import org.springframework.security.test.context.support.WithMockUser
46 import org.springframework.test.web.servlet.MockMvc
47 import spock.lang.Specification
48
49 import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE
50 import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.DELETE
51 import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.PATCH
52 import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ
53 import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE
54 import static org.springframework.http.HttpStatus.BAD_REQUEST
55 import static org.springframework.http.HttpStatus.CREATED
56 import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
57 import static org.springframework.http.HttpStatus.NO_CONTENT
58 import static org.springframework.http.HttpStatus.OK
59 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
60
61 @Import(WebSecurityConfig)
62 @WebMvcTest(DmiRestController.class)
63 @WithMockUser
64 class DmiRestControllerSpec extends Specification {
65
66     @Autowired
67     private MockMvc mvc
68
69     @SpringBean
70     DmiService mockDmiService = Mock()
71
72     @SpringBean
73     DmiAsyncRequestResponseEventProducer cpsAsyncRequestResponseEventProducer = new DmiAsyncRequestResponseEventProducer(Mock(KafkaTemplate))
74
75     @SpringBean
76     AsyncTaskExecutor asyncTaskExecutor = new AsyncTaskExecutor(cpsAsyncRequestResponseEventProducer)
77
78     @Value('${rest.api.dmi-base-path}/v1')
79     def basePathV1
80
81     def 'Get all modules.'() {
82         given: 'URL for getting all modules and some request data'
83             def getModuleUrl = "$basePathV1/ch/node1/modules"
84             def someValidJson = '{}'
85         and: 'DMI service returns some module'
86             def moduleSetSchema = new ModuleSetSchemasInner(namespace:'some-namespace',
87                     moduleName:'some-moduleName',
88                     revision:'some-revision')
89             def moduleSetSchemasList = [moduleSetSchema] as List<ModuleSetSchemasInner>
90             def moduleSet = new ModuleSet()
91             moduleSet.schemas(moduleSetSchemasList)
92             mockDmiService.getModulesForCmHandle('node1') >> moduleSet
93         when: 'the request is posted'
94             def response = mvc.perform(post(getModuleUrl)
95                     .contentType(MediaType.APPLICATION_JSON).content(someValidJson))
96                     .andReturn().response
97         then: 'status is OK'
98             response.status == OK.value()
99         and: 'the response content matches the result from the DMI service'
100             response.getContentAsString() == '{"schemas":[{"moduleName":"some-moduleName","revision":"some-revision","namespace":"some-namespace"}]}'
101     }
102
103     def 'Get all modules with exception handling of #scenario.'() {
104         given: 'URL for getting all modules and some request data'
105             def getModuleUrl = "$basePathV1/ch/node1/modules"
106             def someValidJson = '{}'
107         and: 'a #exception is thrown during the process'
108             mockDmiService.getModulesForCmHandle('node1') >> { throw exception }
109         when: 'the request is posted'
110             def response = mvc.perform( post(getModuleUrl)
111                     .contentType(MediaType.APPLICATION_JSON).content(someValidJson))
112                     .andReturn().response
113         then: 'response status is #expectedResponse'
114             response.status == expectedResponse.value()
115         where: 'the scenario is #scenario'
116             scenario                       | exception                                        || expectedResponse
117             'dmi service exception'        | new DmiException('','')                          || HttpStatus.INTERNAL_SERVER_ERROR
118             'no modules found'             | new ModulesNotFoundException('','')              || HttpStatus.NOT_FOUND
119             'any other runtime exception'  | new RuntimeException()                           || HttpStatus.INTERNAL_SERVER_ERROR
120             'runtime exception with cause' | new RuntimeException('', new RuntimeException()) || HttpStatus.INTERNAL_SERVER_ERROR
121     }
122
123     def 'Register given list.'() {
124         given: 'register cm handle url and cmHandles'
125             def registerCmhandlesPost = "${basePathV1}/inventory/cmHandles"
126             def cmHandleJson = '{"cmHandles":["node1", "node2"]}'
127         when: 'the request is posted'
128             def response = mvc.perform(
129                     post(registerCmhandlesPost)
130                             .contentType(MediaType.APPLICATION_JSON)
131                             .content(cmHandleJson)
132             ).andReturn().response
133         then: 'register cm handles in dmi service is invoked with correct parameters'
134             1 * mockDmiService.registerCmHandles(_ as List<String>)
135         and: 'response status is created'
136             response.status == CREATED.value()
137     }
138
139     def 'register cm handles called with empty content.'() {
140         given: 'register cm handle url and empty json'
141             def registerCmhandlesPost = "${basePathV1}/inventory/cmHandles"
142             def emptyJson = '{"cmHandles":[]}'
143         when: 'the request is posted'
144             def response = mvc.perform(
145                     post(registerCmhandlesPost).contentType(MediaType.APPLICATION_JSON)
146                             .content(emptyJson)
147             ).andReturn().response
148         then: 'response status is "bad request"'
149             response.status == BAD_REQUEST.value()
150         and: 'dmi service is not called'
151             0 * mockDmiService.registerCmHandles(_)
152     }
153
154     def 'Retrieve module resources.'() {
155         given: 'URL to get module resources'
156             def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
157         and: 'request data to get some modules'
158             String jsonData = TestUtils.getResourceFileContent('moduleResources.json')
159         and: 'the DMI service returns the yang resources'
160             ModuleReference moduleReference1 = new ModuleReference(name: 'ietf-yang-library', revision: '2016-06-21')
161             ModuleReference moduleReference2 = new ModuleReference(name: 'nc-notifications', revision: '2008-07-14')
162             def moduleReferences = [moduleReference1, moduleReference2]
163             def yangResources = new YangResources()
164             def yangResource = new YangResource(yangSource: '"some-data"', moduleName: 'NAME', revision: 'REVISION')
165             yangResources.add(yangResource)
166             mockDmiService.getModuleResources('some-cm-handle', moduleReferences) >> yangResources
167         when: 'the request is posted'
168             def response = mvc.perform(post(getModulesEndpoint)
169                     .contentType(MediaType.APPLICATION_JSON)
170                     .content(jsonData)).andReturn().response
171         then: 'a OK status is returned'
172             response.status == OK.value()
173         and: 'the response content matches the result from the DMI service'
174             response.getContentAsString() == '[{"yangSource":"\\"some-data\\"","moduleName":"NAME","revision":"REVISION"}]'
175     }
176
177     def 'Retrieve module resources with exception handling.'() {
178         given: 'URL to get module resources'
179             def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
180         and: 'request data to get some modules'
181             String jsonData = TestUtils.getResourceFileContent('moduleResources.json')
182         and: 'the system throws a not-found exception (during the processing)'
183             mockDmiService.getModuleResources('some-cm-handle', _) >> { throw Mock(ModuleResourceNotFoundException.class) }
184         when: 'the request is posted'
185             def response = mvc.perform(post(getModulesEndpoint)
186                     .contentType(MediaType.APPLICATION_JSON)
187                     .content(jsonData)).andReturn().response
188         then: 'a not found status is returned'
189             response.status == HttpStatus.NOT_FOUND.value()
190     }
191
192     def 'Get resource data for pass-through operational.'() {
193         given: 'Get resource data url and some request data'
194             def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-operational" +
195                     "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
196             def someValidJson = '{}'
197         when: 'the request is posted'
198             def response = mvc.perform(
199                     post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(someValidJson)
200             ).andReturn().response
201         then: 'response status is ok'
202             response.status == OK.value()
203         and: 'dmi service method to get resource data is invoked once'
204             1 * mockDmiService.getResourceData('some-cmHandle',
205                     'parent/child',
206                     '(fields=myfields,depth=5)',
207                     'content=all')
208     }
209
210     def 'Get resource data for pass-through operational with write request (invalid).'() {
211         given: 'Get resource data url'
212             def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-operational" +
213                     "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
214         and: 'an invalid write request data for "create" operation'
215             def jsonData = '{"operation":"create"}'
216         when: 'the request is posted'
217             def response = mvc.perform(
218                     post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
219             ).andReturn().response
220         then: 'response status is bad request'
221             response.status == BAD_REQUEST.value()
222         and: 'dmi service is not invoked'
223             0 * mockDmiService.getResourceData(*_)
224     }
225
226     def 'Get resource data for invalid datastore'() {
227         given: 'Get resource data url'
228             def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/dummy-datastore" +
229                 "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
230         and: 'an invalid write request data for "create" operation'
231             def jsonData = '{"operation":"create"}'
232         when: 'the request is posted'
233             def response = mvc.perform(
234                 post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
235             ).andReturn().response
236         then: 'response status is internal server error'
237             response.status == INTERNAL_SERVER_ERROR.value()
238         and: 'response contains expected error message'
239             response.contentAsString.contains('dummy-datastore is an invalid datastore name')
240     }
241
242     def 'data with #scenario operation using passthrough running.'() {
243         given: 'write data for passthrough running url'
244             def writeDataForPassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
245                     "?resourceIdentifier=some-resourceIdentifier"
246         and: 'request data for #scenario'
247             def jsonData = TestUtils.getResourceFileContent(requestBodyFile)
248         and: 'dmi service is called'
249             mockDmiService.writeData(operationEnum, 'some-cmHandle',
250                     'some-resourceIdentifier', dataType,
251                     'normal request body' ) >> '{some-json}'
252         when: 'the request is posted'
253             def response = mvc.perform(
254                     post(writeDataForPassthroughRunning).contentType(MediaType.APPLICATION_JSON)
255                             .content(jsonData)
256             ).andReturn().response
257         then: 'response status is #expectedResponseStatus'
258             response.status == expectedResponseStatus
259         and: 'the response content matches the result from the DMI service'
260             response.getContentAsString() == expectedJsonResponse
261         where: 'given request body and data'
262             scenario   | requestBodyFile                 | operationEnum     | dataType                      || expectedResponseStatus | expectedJsonResponse
263             'Create'   | 'createDataWithNormalChar.json' | CREATE            | 'application/json'            || CREATED.value()        | '{some-json}'
264             'Update'   | 'updateData.json'               | UPDATE            | 'application/json'            || OK.value()             | '{some-json}'
265             'Delete'   | 'deleteData.json'               | DELETE            | 'application/json'            || NO_CONTENT.value()     | '{some-json}'
266             'Read'     | 'readData.json'                 | READ              | 'application/json'            || OK.value()             | ''
267             'Patch'    | 'patchData.json'                | PATCH             | 'application/yang.patch+json' || OK.value()             | '{some-json}'
268     }
269
270     def 'Create data using passthrough for special characters.'(){
271         given: 'create data for cmHandle url'
272             def writeDataForCmHandlePassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
273                     "?resourceIdentifier=some-resourceIdentifier"
274         and: 'request data with special characters'
275             def jsonData = TestUtils.getResourceFileContent('createDataWithSpecialChar.json')
276         and: 'dmi service returns data'
277             mockDmiService.writeData(CREATE, 'some-cmHandle', 'some-resourceIdentifier', 'application/json',
278                     'data with quote \" and new line \n') >> '{some-json}'
279         when: 'the request is posted'
280             def response = mvc.perform(
281                     post(writeDataForCmHandlePassthroughRunning).contentType(MediaType.APPLICATION_JSON).content(jsonData)
282             ).andReturn().response
283         then: 'response status is CREATED'
284             response.status == CREATED.value()
285         and: 'the response content matches the result from the DMI service'
286             response.getContentAsString() == '{some-json}'
287     }
288
289     def 'PassThrough Returns OK when topic is used for async'(){
290         given: 'Passthrough read URL and request data  with a topic (parameter)'
291             def readPassThroughUrl ="${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:" +
292                     resourceIdentifier +
293                     '?resourceIdentifier=some-resourceIdentifier&topic=test-topic'
294             def jsonData = TestUtils.getResourceFileContent('readData.json')
295         when: 'the request is posted'
296             def response = mvc.perform(
297                     post(readPassThroughUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
298             ).andReturn().response
299         then: 'response status is OK'
300             assert response.status == HttpStatus.NO_CONTENT.value()
301         where: 'the following values are used'
302             resourceIdentifier << ['passthrough-operational', 'passthrough-running']
303     }
304
305     def 'Get resource data for pass-through running with #scenario value in resource identifier param.'() {
306         given: 'Get resource data url'
307             def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
308                     "?resourceIdentifier="+resourceIdentifier+"&options=(fields=myfields,depth=5)"
309         and: 'some valid json data'
310             def json = '{"cmHandleProperties" : { "prop1" : "value1", "prop2" : "value2"}}'
311         when: 'the request is posted'
312             def response = mvc.perform(
313                     post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(json)
314             ).andReturn().response
315         then: 'response status is ok'
316             response.status == OK.value()
317         and: 'dmi service method to get resource data is invoked once with correct parameters'
318             1 * mockDmiService.getResourceData('some-cmHandle',
319                     resourceIdentifier,
320                     '(fields=myfields,depth=5)',
321                     'content=config')
322         where: 'tokens are used in the resource identifier parameter'
323             scenario                       | resourceIdentifier
324             '/'                            | 'id/with/slashes'
325             '?'                            | 'idWith?'
326             ','                            | 'idWith,'
327             '='                            | 'idWith='
328             '[]'                           | 'idWith[]'
329             '? needs to be encoded as %3F' | 'idWith%3F'
330
331     }
332
333     def 'Execute a data operation for a list of operations.'() {
334         given: 'an endpoint for a data operation request with list of cmhandles in request body'
335             def resourceDataUrl = "$basePathV1/data?topic=client-topic-name&requestId=some-requestId"
336         and: 'list of operation details are received into request body'
337             def dataOperationRequestBody = '[{"operation": "read", "operationId": "14", "datastore": "ncmp-datastore:passthrough-operational", "options": "some options", "resourceIdentifier": "some resourceIdentifier",' +
338                 '    "cmhandles": [ {"id": "cmHanlde123", "cmHandleProperties": { "myProp`": "some value", "otherProp": "other value"}}]}]'
339         when: 'the dmi resource data for dataOperation api is called.'
340             def response = mvc.perform(
341                 post(resourceDataUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestBody)
342             ).andReturn().response
343         then: 'the resource data operation endpoint returns the not implemented response'
344             assert response.status == 501
345     }
346 }