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