P1 - Get module name and revision persistence layer
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021 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.ncmp.api.impl
24
25 import com.fasterxml.jackson.core.JsonProcessingException
26 import com.fasterxml.jackson.databind.ObjectMapper
27 import org.onap.cps.api.CpsAdminService
28 import org.onap.cps.api.CpsDataService
29 import org.onap.cps.api.CpsModuleService
30 import org.onap.cps.api.CpsQueryService
31 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
32 import org.onap.cps.ncmp.api.impl.exception.NcmpException
33 import org.onap.cps.ncmp.api.impl.operation.DmiOperations
34 import org.onap.cps.ncmp.api.models.CmHandle
35 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
36 import org.onap.cps.ncmp.api.models.PersistenceCmHandle
37 import org.onap.cps.ncmp.utils.TestUtils
38 import org.onap.cps.spi.FetchDescendantsOption
39 import org.onap.cps.spi.model.DataNode
40 import org.onap.cps.spi.model.ModuleReference
41 import org.springframework.http.HttpStatus
42 import org.springframework.http.ResponseEntity
43 import spock.lang.Shared
44 import spock.lang.Specification
45
46 class NetworkCmProxyDataServiceImplSpec extends Specification {
47
48     @Shared
49     def persistenceCmHandle = new CmHandle()
50     @Shared
51     def cmHandlesArray = ['cmHandle001']
52
53     def mockCpsDataService = Mock(CpsDataService)
54     def mockCpsQueryService = Mock(CpsQueryService)
55     def mockDmiOperations = Mock(DmiOperations)
56     def mockCpsModuleService = Mock(CpsModuleService)
57     def mockCpsAdminService = Mock(CpsAdminService)
58     def mockDmiProperties = Mock(NcmpConfiguration.DmiProperties)
59
60     def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
61             mockCpsDataService, mockCpsQueryService, mockCpsAdminService, new ObjectMapper())
62
63     def cmHandle = 'some handle'
64     def noTimestamp = null
65     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
66     def cmHandleForModelSync = new PersistenceCmHandle(id:'some cm handle', dmiServiceName: 'some service name')
67     def expectedDataspaceNameForModleSync = 'NCMP-Admin'
68     def NO_NAMESPACE = null
69
70     def expectedDataspaceName = 'NFP-Operational'
71     def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
72         given: 'a cm Handle and a cps path'
73             def cpsPath = '/cps-path'
74         when: 'queryDataNodes is invoked'
75             objectUnderTest.queryDataNodes(cmHandle, cpsPath, fetchDescendantsOption)
76         then: 'the persistence service is called once with the correct parameters'
77             1 * mockCpsQueryService.queryDataNodes(expectedDataspaceName, cmHandle, cpsPath, fetchDescendantsOption)
78         where: 'all fetch descendants options are supported'
79             fetchDescendantsOption << FetchDescendantsOption.values()
80     }
81
82     def 'Create full data node: #scenario.'() {
83         given: 'a cm handle and root xpath'
84             def jsonData = 'some json'
85         when: 'createDataNode is invoked'
86             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
87         then: 'the CPS service method is invoked once with the expected parameters'
88             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, jsonData, noTimestamp)
89         where: 'following parameters were used'
90             scenario           | xpath
91             'no xpath'         | ''
92             'root level xpath' | '/'
93     }
94
95     def 'Create child data node.'() {
96         given: 'a cm handle and parent node xpath'
97             def jsonData = 'some json'
98             def xpath = '/test-node'
99         when: 'createDataNode is invoked'
100             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
101         then: 'the CPS service method is invoked once with the expected parameters'
102             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
103     }
104
105     def 'Add list-node elements.'() {
106         given: 'a cm handle and parent node xpath'
107             def jsonData = 'some json'
108             def xpath = '/test-node'
109         when: 'addListNodeElements is invoked'
110             objectUnderTest.addListNodeElements(cmHandle, xpath, jsonData)
111         then: 'the CPS service method is invoked once with the expected parameters'
112             1 * mockCpsDataService.saveListNodeData(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
113     }
114
115     def 'Update data node leaves.'() {
116         given: 'a cm Handle and a cps path'
117             def xpath = '/xpath'
118             def jsonData = 'some json'
119         when: 'updateNodeLeaves is invoked'
120             objectUnderTest.updateNodeLeaves(cmHandle, xpath, jsonData)
121         then: 'the persistence service is called once with the correct parameters'
122             1 * mockCpsDataService.updateNodeLeaves(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
123     }
124
125     def 'Replace data node tree.'() {
126         given: 'a cm Handle and a cps path'
127             def xpath = '/xpath'
128             def jsonData = 'some json'
129         when: 'replaceNodeTree is invoked'
130             objectUnderTest.replaceNodeTree(cmHandle, xpath, jsonData)
131         then: 'the persistence service is called once with the correct parameters'
132             1 * mockCpsDataService.replaceNodeTree(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
133     }
134
135     def 'Register or re-register a DMI Plugin with #scenario cm handles.'() {
136         given: 'a registration '
137             NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
138             def dmiPluginRegistration = new DmiPluginRegistration()
139             dmiPluginRegistration.dmiPlugin = 'my-server'
140             persistenceCmHandle.cmHandleID = '123'
141             persistenceCmHandle.cmHandleProperties = [name1: 'value1', name2: 'value2']
142             dmiPluginRegistration.createdCmHandles = createdCmHandles
143             dmiPluginRegistration.updatedCmHandles = updatedCmHandles
144             dmiPluginRegistration.removedCmHandles = removedCmHandles
145             def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[{"name":"name1","value":"value1"},{"name":"name2","value":"value2"}]}]}'
146         when: 'registration is updated'
147             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
148         then: 'the CPS save list node data is invoked with the expected parameters'
149             expectedCallsToSaveNode * mockCpsDataService.saveListNodeData('NCMP-Admin', 'ncmp-dmi-registry',
150                 '/dmi-registry', expectedJsonData, noTimestamp)
151         and: 'update Node and Child Data Nodes is invoked with correct parameters'
152             expectedCallsToUpdateNode * mockCpsDataService.updateNodeLeavesAndExistingDescendantLeaves('NCMP-Admin',
153                 'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, noTimestamp)
154         and : 'delete list data node is invoked with the correct parameters'
155             expectedCallsToDeleteListDataNode * mockCpsDataService.deleteListNodeData('NCMP-Admin',
156                 'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
157
158         where:
159             scenario                        | createdCmHandles       | updatedCmHandles       | removedCmHandles || expectedCallsToSaveNode   | expectedCallsToUpdateNode | expectedCallsToDeleteListDataNode
160             'create'                        | [persistenceCmHandle ] | []                     | []               || 1                         | 0                         | 0
161             'update'                        | []                     | [persistenceCmHandle ] | []               || 0                         | 1                         | 0
162             'delete'                        | []                     | []                     | cmHandlesArray   || 0                         | 0                         | 1
163             'create, update and delete'     | [persistenceCmHandle ] | [persistenceCmHandle ] | cmHandlesArray   || 1                         | 1                         | 1
164
165     }
166
167     def 'Register a DMI Plugin for the given cmHandle without additional properties.'() {
168         given: 'a registration without cmHandle properties '
169             NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
170             def dmiPluginRegistration = new DmiPluginRegistration()
171             dmiPluginRegistration.dmiPlugin = 'my-server'
172             persistenceCmHandle.cmHandleID = '123'
173             persistenceCmHandle.cmHandleProperties = null
174             dmiPluginRegistration.createdCmHandles = [persistenceCmHandle ]
175             def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[]}]}'
176         when: 'registration is updated'
177             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
178         then: 'the CPS save list node data is invoked with the expected parameters'
179             1 * mockCpsDataService.saveListNodeData('NCMP-Admin', 'ncmp-dmi-registry',
180                 '/dmi-registry', expectedJsonData, noTimestamp)
181     }
182
183     def 'Get resource data for pass-through operational from dmi.'() {
184         given: 'data node representing cmHandle and its properties'
185             def cmHandleDataNode = getCmHandleDataNodeForTest()
186         when: 'get resource data is called'
187             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
188             'testResourceId',
189             'testAcceptParam',
190             'testFieldQuery',
191             5)
192         then: 'cps data service is being called once to get data node'
193             1 * mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
194                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
195         and: 'dmi operation is being called to get resource data'
196             1 * mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
197                     'testCmHandle',
198                     'testResourceId',
199                     'testFieldQuery',
200                     5,
201                     'testAcceptParam',
202             '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >>
203                 new ResponseEntity<>('result-json', HttpStatus.OK)
204         and: 'dmi returns ok response'
205             response == 'result-json'
206     }
207
208     def 'Get resource data for pass-through operational from dmi threw parsing exception.'() {
209         given: 'data node representing cmHandle and its properties'
210             def cmHandleDataNode = getCmHandleDataNodeForTest()
211         and: 'cps data service returns valid cmHandle data node'
212             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
213                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
214         and: 'objectMapper not able to parse object'
215             def mockObjectMapper = Mock(ObjectMapper)
216             objectUnderTest.objectMapper = mockObjectMapper
217             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException("testException") }
218         when: 'get resource data is called'
219             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
220                     'testResourceId',
221                     'testAcceptParam',
222                     'testFieldQuery',
223                     5)
224         then: 'exception is thrown'
225             thrown(NcmpException.class)
226     }
227
228     def 'Get resource data for pass-through operational from dmi return NOK response.'() {
229         given: 'data node representing cmHandle and its properties'
230             def cmHandleDataNode = getCmHandleDataNodeForTest()
231         and: 'cps data service returns valid cmHandle data node'
232             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
233                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
234         and: 'dmi returns NOK response'
235             mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
236                     'testCmHandle',
237                     'testResourceId',
238                     'testFieldQuery',
239                     5,
240                     'testAcceptParam',
241                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
242                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
243         when: 'get resource data is called'
244             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
245                     'testResourceId',
246                     'testAcceptParam',
247                     'testFieldQuery',
248                     5)
249         then: 'exception is thrown'
250             thrown(NcmpException.class)
251     }
252
253     def 'Get resource data for pass-through running from dmi.'() {
254         given: 'data node representing cmHandle and its properties'
255             def cmHandleDataNode = getCmHandleDataNodeForTest()
256         and: 'cpsDataService returns valid dataNode'
257             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
258                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
259         and: 'dmi returns valid response and data'
260             mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
261                     'testCmHandle',
262                     'testResourceId',
263                     'testFieldQuery',
264                     5,
265                     'testAcceptParam',
266                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
267         when: 'get resource data is called'
268             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
269                     'testResourceId',
270                     'testAcceptParam',
271                     'testFieldQuery',
272                     5)
273         then: 'get resource data returns expected response'
274             response == '{result-json}'
275     }
276
277     def 'Get resource data for pass-through running from dmi threw parsing exception.'() {
278         given: 'data node representing cmHandle and its properties'
279             def cmHandleDataNode = getCmHandleDataNodeForTest()
280         and: 'cpsDataService returns valid dataNode'
281             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
282                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
283         and: 'objectMapper not able to parse object'
284             def mockObjectMapper = Mock(ObjectMapper)
285             objectUnderTest.objectMapper = mockObjectMapper
286             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException("testException") }
287         when: 'get resource data is called'
288             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
289                     'testResourceId',
290                     'testAcceptParam',
291                     'testFieldQuery',
292                     5)
293         then: 'exception is thrown'
294             thrown(NcmpException.class)
295     }
296
297     def 'Get resource data for pass-through running from dmi return NOK response.'() {
298         given: 'data node representing cmHandle and its properties'
299             def cmHandleDataNode = getCmHandleDataNodeForTest()
300         and: 'cpsDataService returns valid dataNode'
301             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
302                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
303         and: 'dmi returns NOK response'
304             mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
305                     'testCmHandle',
306                     'testResourceId',
307                     'testFieldQuery',
308                     5,
309                     'testAcceptParam',
310                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
311                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
312         when: 'get resource data is called'
313             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
314                     'testResourceId',
315                     'testAcceptParam',
316                     'testFieldQuery',
317                     5)
318         then: 'exception is thrown'
319             thrown(NcmpException.class)
320     }
321
322     def 'Write resource data for pass-through running from dmi using POST.'() {
323         given: 'data node representing cmHandle and its properties'
324             def cmHandleDataNode = getCmHandleDataNodeForTest()
325         and: 'cpsDataService returns valid dataNode'
326             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
327                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
328         when: 'get resource data is called'
329             objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
330                     'testResourceId',
331                     '{some-json}', 'application/json')
332         then: 'dmi called with correct data'
333             1 * mockDmiOperations.createResourceDataPassThroughRunningFromDmi('testDmiService',
334                 'testCmHandle',
335                 'testResourceId',
336                 '{"operation":"create","dataType":"application/json","data":"{some-json}","cmHandleProperties":{"testName":"testValue"}}')
337                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
338     }
339
340     def 'Write resource data for pass-through running from dmi using POST "not found" response (from DMI).'() {
341         given: 'data node representing cmHandle and its properties'
342             def cmHandleDataNode = getCmHandleDataNodeForTest()
343         and: 'cpsDataService returns valid dataNode'
344             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
345                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
346         and: 'dmi throws exception'
347             1 * mockDmiOperations.createResourceDataPassThroughRunningFromDmi(_ as String, _ as String, _ as String, _ as String)
348                     >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
349         when: 'get resource data is called'
350             objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
351                     'testResourceId',
352                     '{some-json}', 'application/json')
353         then: 'exception is thrown'
354             thrown(NcmpException.class)
355     }
356
357     def 'Sync model for a (new) cm handle with #scenario'() {
358         given: 'DMI PLug-in returns a list of module references'
359             getModulesForCmHandle()
360             def knownModule1 = new ModuleReference('module1', '1')
361             def knownOtherModule = new ModuleReference('some other module', 'some revision')
362         and: 'CPS-Core returns list of known modules'
363             mockCpsModuleService.getAllYangResourceModuleReferences(_) >> [knownModule1, knownOtherModule]
364         and: 'DMI-Plugin returns resource(s) for "new" module(s)'
365             def moduleResources = new ResponseEntity<String>(sdncReponseBody, HttpStatus.OK)
366             mockDmiOperations.getResourceFromDmi(_, cmHandleForModelSync.getId(), 'moduleResources') >> moduleResources
367         when: 'module Sync is triggered'
368             objectUnderTest.createAnchorAndSyncModel(cmHandleForModelSync)
369         then: 'the CPS module service is called once with the correct parameters'
370             1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceNameForModleSync, cmHandleForModelSync.getId(), expectedYangResourceToContentMap, [knownModule1])
371         and: 'admin service create anchor method has been called with correct parameters'
372             1 * mockCpsAdminService.createAnchor(expectedDataspaceNameForModleSync, cmHandleForModelSync.getId(), cmHandleForModelSync.getId())
373         where: 'the following responses are recieved from SDNC'
374             scenario             | sdncReponseBody                                                                  || expectedYangResourceToContentMap
375             'one unknown module' | '[{"moduleName" : "someModule", "revision" : "1","yangSource": "someResource"}]' || [someModule: 'someResource']
376             'no unknown module'  | '[]'                                                                             || [:]
377     }
378
379     def getModulesForCmHandle() {
380         def jsonData = TestUtils.getResourceFileContent('cmHandleModules.json')
381         mockDmiProperties.getAuthUsername() >> 'someUser'
382         mockDmiProperties.getAuthPassword() >> 'somePassword'
383         mockDmiProperties.getDmiPluginBasePath() >> 'someUrl'
384         def moduleReferencesFromCmHandleAsJson = new ResponseEntity<String>(jsonData, HttpStatus.OK)
385         mockDmiOperations.getResourceFromDmi(_, cmHandleForModelSync.getId(), 'modules') >> moduleReferencesFromCmHandleAsJson
386     }
387
388     def getObjectUnderTestWithModelSyncDisabled() {
389         def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
390                 mockCpsDataService, mockCpsQueryService, mockCpsAdminService, new ObjectMapper()))
391         objectUnderTest.createAnchorAndSyncModel(_) >> null
392         return objectUnderTest
393     }
394
395     def getCmHandleDataNodeForTest() {
396         def cmHandleDataNode = new DataNode()
397         cmHandleDataNode.leaves = ['dmi-service-name': 'testDmiService']
398         def cmHandlePropertyDataNode = new DataNode()
399         cmHandlePropertyDataNode.leaves = ['name': 'testName', 'value': 'testValue']
400         cmHandleDataNode.childDataNodes = [cmHandlePropertyDataNode]
401         return cmHandleDataNode
402     }
403
404 }