dccba0be48bf93eca8e02a45db17cf2b15647c9d
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplRegistrationSpec.groovy
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 Nordix Foundation
4  *  Modifications Copyright (C) 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.api.impl
23
24 import com.fasterxml.jackson.databind.ObjectMapper
25 import org.onap.cps.api.CpsModuleService
26 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
27 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
28 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
29 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
30 import org.onap.cps.ncmp.api.inventory.CmHandleState
31 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
32 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
33 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
34 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
35 import org.onap.cps.spi.exceptions.AlreadyDefinedException
36 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
37 import org.onap.cps.spi.exceptions.DataValidationException
38 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
39 import org.onap.cps.utils.JsonObjectMapper
40 import spock.lang.Shared
41 import spock.lang.Specification
42
43 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
44 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
45 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
46 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
47 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
48 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
49
50 class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
51
52     @Shared
53     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')
54
55     def mockCpsModuleService = Mock(CpsModuleService)
56     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
57     def mockDmiDataOperations = Mock(DmiDataOperations)
58     def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler)
59     def mockInventoryPersistence = Mock(InventoryPersistence)
60     def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandlerQueryService)
61     def objectUnderTest = getObjectUnderTest()
62
63     def 'DMI Registration: Create, Update & Delete operations are processed in the right order'() {
64         given: 'a registration with operations of all three types'
65             def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
66             dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
67             dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
68             dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
69         when: 'registration is processed'
70             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
71             // Spock validated invocation order between multiple then blocks
72         then: 'cm-handles are removed first'
73             1 * objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_)
74         then: 'cm-handles are created'
75             1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_)
76         then: 'cm-handles are updated'
77             1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_)
78     }
79
80     def 'DMI Registration: Response from all operations types are in response'() {
81         given: 'a registration with operations of all three types'
82             def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
83             dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
84             dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
85             dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
86         and: 'update cm-handles can be processed successfully'
87             def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')]
88             mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> updateResponses
89         and: 'create cm-handles can be processed successfully'
90             def createdResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-1')]
91             objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) >> createdResponses
92         and: 'delete cm-handles can be processed successfully'
93             def removeResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-3')]
94             objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) >> removeResponses
95         when: 'registration is processed'
96             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
97         then: 'response has values from all operations'
98             response.getRemovedCmHandles() == removeResponses
99             response.getCreatedCmHandles() == createdResponses
100             response.getUpdatedCmHandles() == updateResponses
101     }
102
103     def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
104         given: 'a registration '
105             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
106                 dmiDataPlugin: dmiDataPlugin)
107             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
108         when: 'update registration and sync module is called with correct DMI plugin information'
109             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
110         then: 'create cm handles registration and sync modules is called with the correct plugin information'
111             1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
112         where:
113             scenario                          | dmiPlugin  | dmiModelPlugin | dmiDataPlugin
114             'combined DMI plugin'             | 'service1' | ''             | ''
115             'data & model DMI plugins'        | ''         | 'service1'     | 'service2'
116             'data & model using same service' | ''         | 'service1'     | 'service1'
117     }
118
119     def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() {
120         given: 'a registration '
121             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
122                 dmiDataPlugin: dmiDataPlugin)
123             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
124         when: 'registration is called with incorrect DMI plugin information'
125             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
126         then: 'a DMI Request Exception is thrown with correct message details'
127             def exceptionThrown = thrown(DmiRequestException.class)
128             assert exceptionThrown.getMessage().contains(expectedMessageDetails)
129         and: 'registration is not called'
130             0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
131         where:
132             scenario                         | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
133             'empty DMI plugins'              | ''         | ''             | ''            || 'No DMI plugin service names'
134             'blank DMI plugins'              | ' '        | ' '            | ' '           || 'No DMI plugin service names'
135             'null DMI plugins'               | null       | null           | null          || 'No DMI plugin service names'
136             'all DMI plugins'                | 'service1' | 'service2'     | 'service3'    || 'Cannot register combined plugin service name and other service names'
137             '(combined)DMI and Data Plugin'  | 'service1' | ''             | 'service2'    || 'Cannot register combined plugin service name and other service names'
138             '(combined)DMI and model Plugin' | 'service1' | 'service2'     | ''            || 'Cannot register combined plugin service name and other service names'
139             'only model DMI plugin'          | ''         | 'service1'     | ''            || 'Cannot register just a Data or Model plugin service name'
140             'only data DMI plugin'           | ''         | ''             | 'service1'    || 'Cannot register just a Data or Model plugin service name'
141     }
142
143     def 'Create CM-Handle Successfully: #scenario.'() {
144         given: 'a registration without cm-handle properties'
145             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
146             dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)]
147         when: 'registration is updated'
148             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
149         then: 'a successful response is received'
150             response.getCreatedCmHandles().size() == 1
151             with(response.getCreatedCmHandles().get(0)) {
152                 assert it.status == Status.SUCCESS
153                 assert it.cmHandle == 'cmhandle'
154             }
155         and: 'save cmhandle is invoked once with the expected parameters'
156                 1 * mockInventoryPersistence.saveCmHandle(_) >> {
157                     args -> {
158                         def result = (args[0] as YangModelCmHandle)
159                         assert result.id == 'cmhandle'
160                         assert result.dmiServiceName == 'my-server'
161                         assert result.compositeState.cmHandleState == CmHandleState.ADVISED
162                     }
163                 }
164         where:
165             scenario                          | dmiProperties            | publicProperties               || expectedDmiProperties                      | expectedPublicProperties
166             'with dmi & public properties'    | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]'
167             'with only public properties'     | [:]                      | ['public-key': 'public-value'] || '[]'                                       | '[{"name":"public-key","value":"public-value"}]'
168             'with only dmi properties'        | ['dmi-key': 'dmi-value'] | [:]                            || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]'
169             'without dmi & public properties' | [:]                      | [:]                            || '[]'                                       | '[]'
170
171     }
172
173     def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() {
174         given: 'a registration with three cm-handles to be created'
175             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
176                 createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'),
177                                    new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'),
178                                    new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')])
179         and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
180             mockInventoryPersistence.saveCmHandle(_) >> {} >> { throw new RuntimeException("Failed") } >> {}
181         when: 'registration is updated to create cm-handles'
182             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
183         then: 'a response is received for all cm-handles'
184             response.getCreatedCmHandles().size() == 3
185         and: '1st and 3rd cm-handle are created successfully'
186             with(response.getCreatedCmHandles().get(0)) {
187                 assert it.status == Status.SUCCESS
188                 assert it.cmHandle == 'cmhandle1'
189             }
190             with(response.getCreatedCmHandles().get(2)) {
191                 assert it.status == Status.SUCCESS
192                 assert it.cmHandle == 'cmhandle3'
193             }
194         and: '2nd cm-handle creation fails'
195             with(response.getCreatedCmHandles().get(1)) {
196                 assert it.status == Status.FAILURE
197                 assert it.registrationError == UNKNOWN_ERROR
198                 assert it.errorText == 'Failed'
199                 assert it.cmHandle == 'cmhandle2'
200             }
201     }
202
203     def 'Create CM-Handle Error Handling: Registration fails: #scenario'() {
204         given: 'a registration without cm-handle properties'
205             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
206             dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId)]
207         and: 'cm-handler registration fails: #scenario'
208             mockInventoryPersistence.saveCmHandle(_) >> { throw exception }
209         when: 'registration is updated'
210             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
211         then: 'a failure response is received'
212             response.getCreatedCmHandles().size() == 1
213             with(response.getCreatedCmHandles().get(0)) {
214                 assert it.status == Status.FAILURE
215                 assert it.cmHandle ==  cmHandleId
216                 assert it.registrationError == expectedError
217                 assert it.errorText == expectedErrorText
218             }
219         where:
220             scenario                                        | cmHandleId             | exception                                               || expectedError           | expectedErrorText
221             'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
222             'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                     || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
223             'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
224     }
225
226     def 'Update CM-Handle: Update Operation Response is added to the response'() {
227         given: 'a registration to update CmHandles'
228             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
229                 updatedCmHandles: [{}])
230         and: 'cm-handle updates can be processed successfully'
231             def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'),
232                                            CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")),
233                                            CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST),
234                                            CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)]
235             mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse
236         when: 'registration is updated'
237             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
238         then: 'the response contains updateOperationResponse'
239             assert response.getUpdatedCmHandles().size() == 4
240             assert response.getUpdatedCmHandles().containsAll(updateOperationResponse)
241     }
242
243     def 'Remove CmHandle Successfully: #scenario'() {
244         given: 'a registration'
245             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
246                 removedCmHandles: ['cmhandle'])
247         and: '#scenario'
248             mockCpsModuleService.deleteSchemaSet(_, 'cmhandle', CASCADE_DELETE_ALLOWED) >>
249                 { if (!schemaSetExist) { throw new SchemaSetNotFoundException("", "") } }
250         when: 'registration is updated to delete cmhandle'
251             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
252         then: 'delete list or list element is called'
253             1 * mockInventoryPersistence.deleteListOrListElement(_)
254         and: 'successful response is received'
255             assert response.getRemovedCmHandles().size() == 1
256             with(response.getRemovedCmHandles().get(0)) {
257                 assert it.status == Status.SUCCESS
258                 assert it.cmHandle == 'cmhandle'
259             }
260         where:
261             scenario                                            | schemaSetExist
262             'schema-set exists and can be deleted successfully' | true
263             'schema-set does not exist'                         | false
264     }
265
266     def 'Remove CmHandle: All cm-handles delete requests are processed'() {
267         given: 'a registration with three cm-handles to be deleted'
268             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
269                 removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3'])
270         and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd'
271             mockInventoryPersistence.deleteListOrListElement(_) >> {} >> { throw new RuntimeException("Failed") } >> {}
272         when: 'registration is updated to delete cmhandles'
273             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
274         then: 'a response is received for all cm-handles'
275             response.getRemovedCmHandles().size() == 3
276         and: '1st and 3rd cm-handle deletes successfully'
277             with(response.getRemovedCmHandles().get(0)) {
278                 assert it.status == Status.SUCCESS
279                 assert it.cmHandle == 'cmhandle1'
280             }
281             with(response.getRemovedCmHandles().get(2)) {
282                 assert it.status == Status.SUCCESS
283                 assert it.cmHandle == 'cmhandle3'
284             }
285         and: '2nd cm-handle deletion fails'
286             with(response.getRemovedCmHandles().get(1)) {
287                 assert it.status == Status.FAILURE
288                 assert it.registrationError == UNKNOWN_ERROR
289                 assert it.errorText == 'Failed'
290                 assert it.cmHandle == 'cmhandle2'
291             }
292     }
293
294     def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() {
295         given: 'a registration'
296             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
297                 removedCmHandles: ['cmhandle'])
298         and: 'schema set deletion failed with unknown error'
299             mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') }
300         when: 'registration is updated to delete cmhandle'
301             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
302         then: 'no exception is thrown'
303             noExceptionThrown()
304         and: 'cm-handle is not deleted'
305             0 * mockInventoryPersistence.deleteListOrListElement(_)
306         and: 'a failure response is received'
307             assert response.getRemovedCmHandles().size() == 1
308             with(response.getRemovedCmHandles().get(0)) {
309                 assert it.status == Status.FAILURE
310                 assert it.cmHandle == 'cmhandle'
311                 assert it.errorText == 'Failed'
312                 assert it.registrationError == UNKNOWN_ERROR
313             }
314     }
315
316     def 'Remove CmHandle Error Handling: #scenario'() {
317         given: 'a registration'
318             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
319                 removedCmHandles: ['cmhandle'])
320         and: 'cm-handle deletion throws exception'
321             mockInventoryPersistence.deleteListOrListElement(_) >> { throw deleteListElementException }
322         when: 'registration is updated to delete cmhandle'
323             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
324         then: 'no exception is thrown'
325             noExceptionThrown()
326         and: 'a failure response is received'
327             assert response.getRemovedCmHandles().size() == 1
328             with(response.getRemovedCmHandles().get(0)) {
329                 assert it.status == Status.FAILURE
330                 assert it.cmHandle == 'cmhandle'
331                 assert it.registrationError == expectedError
332                 assert it.errorText == expectedErrorText
333             }
334         where:
335             scenario                     | cmHandleId             | deleteListElementException                ||  expectedError           | expectedErrorText
336             'cm-handle does not exist'   | 'cmhandle'             | new DataNodeNotFoundException("", "", "") || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
337             'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "")       || CM_HANDLE_INVALID_ID     | 'cm-handle has an invalid character(s) in id'
338             'an unexpected exception'    | 'cmhandle'             | new RuntimeException("Failed")            || UNKNOWN_ERROR            | 'Failed'
339     }
340
341     def getObjectUnderTest() {
342         return Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations,
343             mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, stubbedNetworkCmProxyCmHandlerQueryService))
344     }
345 }