CPS-635 - Module Resource call does not include body
[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.api.models.PersistenceCmHandlesList
38 import org.onap.cps.ncmp.utils.TestUtils
39 import org.onap.cps.spi.FetchDescendantsOption
40 import org.onap.cps.spi.model.DataNode
41 import org.onap.cps.spi.model.ModuleReference
42 import org.springframework.http.HttpStatus
43 import org.springframework.http.ResponseEntity
44 import spock.lang.Shared
45 import spock.lang.Specification
46
47 class NetworkCmProxyDataServiceImplSpec extends Specification {
48
49     @Shared
50     def persistenceCmHandle = new CmHandle()
51     @Shared
52     def cmHandlesArray = ['cmHandle001']
53
54     def mockCpsDataService = Mock(CpsDataService)
55     def mockCpsQueryService = Mock(CpsQueryService)
56     def mockDmiOperations = Mock(DmiOperations)
57     def mockCpsModuleService = Mock(CpsModuleService)
58     def mockCpsAdminService = Mock(CpsAdminService)
59     def mockDmiProperties = Mock(NcmpConfiguration.DmiProperties)
60
61     def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
62             mockCpsDataService, mockCpsQueryService, mockCpsAdminService, new ObjectMapper())
63
64     def cmHandle = 'some handle'
65     def noTimestamp = null
66     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
67     def cmHandleForModelSync = new PersistenceCmHandle(id:'some cm handle', dmiServiceName: 'some service name')
68     def expectedDataspaceNameForModleSync = 'NCMP-Admin'
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         and: 'data node is got from data service'
187             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
188                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
189         and: 'resource data is got from DMI'
190             mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
191                 'testCmHandle',
192                 'testResourceId',
193                 'testFieldQuery',
194                 5,
195                 'testAcceptParam',
196                 '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('result-json', HttpStatus.OK)
197         when: 'get resource data is called'
198             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
199             'testResourceId',
200             'testAcceptParam',
201             'testFieldQuery',
202             5)
203         then: 'dmi returns ok response'
204             response == 'result-json'
205     }
206
207     def 'Get resource data for pass-through operational from dmi threw parsing exception.'() {
208         given: 'data node representing cmHandle and its properties'
209             def cmHandleDataNode = getCmHandleDataNodeForTest()
210         and: 'cps data service returns valid cmHandle data node'
211             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
212                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
213         and: 'objectMapper not able to parse object'
214             def mockObjectMapper = Mock(ObjectMapper)
215             objectUnderTest.objectMapper = mockObjectMapper
216             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException("testException") }
217         when: 'get resource data is called'
218             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
219                     'testResourceId',
220                     'testAcceptParam',
221                     'testFieldQuery',
222                     5)
223         then: 'exception is thrown'
224             thrown(NcmpException.class)
225     }
226
227     def 'Get resource data for pass-through operational from dmi return NOK response.'() {
228         given: 'data node representing cmHandle and its properties'
229             def cmHandleDataNode = getCmHandleDataNodeForTest()
230         and: 'cps data service returns valid cmHandle data node'
231             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
232                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
233         and: 'dmi returns NOK response'
234             mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
235                     'testCmHandle',
236                     'testResourceId',
237                     'testFieldQuery',
238                     5,
239                     'testAcceptParam',
240                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
241                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
242         when: 'get resource data is called'
243             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
244                     'testResourceId',
245                     'testAcceptParam',
246                     'testFieldQuery',
247                     5)
248         then: 'exception is thrown'
249             thrown(NcmpException.class)
250     }
251
252     def 'Get resource data for pass-through running from dmi.'() {
253         given: 'data node representing cmHandle and its properties'
254             def cmHandleDataNode = getCmHandleDataNodeForTest()
255         and: 'cpsDataService returns valid dataNode'
256             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
257                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
258         and: 'dmi returns valid response and data'
259             mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
260                     'testCmHandle',
261                     'testResourceId',
262                     'testFieldQuery',
263                     5,
264                     'testAcceptParam',
265                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
266         when: 'get resource data is called'
267             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
268                     'testResourceId',
269                     'testAcceptParam',
270                     'testFieldQuery',
271                     5)
272         then: 'get resource data returns expected response'
273             response == '{result-json}'
274     }
275
276     def 'Get resource data for pass-through running from dmi threw parsing exception.'() {
277         given: 'data node representing cmHandle and its properties'
278             def cmHandleDataNode = getCmHandleDataNodeForTest()
279         and: 'cpsDataService returns valid dataNode'
280             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
281                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
282         and: 'objectMapper not able to parse object'
283             def mockObjectMapper = Mock(ObjectMapper)
284             objectUnderTest.objectMapper = mockObjectMapper
285             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException("testException") }
286         when: 'get resource data is called'
287             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
288                     'testResourceId',
289                     'testAcceptParam',
290                     'testFieldQuery',
291                     5)
292         then: 'exception is thrown'
293             thrown(NcmpException.class)
294     }
295
296     def 'Get resource data for pass-through running from dmi return NOK response.'() {
297         given: 'data node representing cmHandle and its properties'
298             def cmHandleDataNode = getCmHandleDataNodeForTest()
299         and: 'cpsDataService returns valid dataNode'
300             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
301                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
302         and: 'dmi returns NOK response'
303             mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
304                     'testCmHandle',
305                     'testResourceId',
306                     'testFieldQuery',
307                     5,
308                     'testAcceptParam',
309                     '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
310                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
311         when: 'get resource data is called'
312             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
313                     'testResourceId',
314                     'testAcceptParam',
315                     'testFieldQuery',
316                     5)
317         then: 'exception is thrown'
318             thrown(NcmpException.class)
319     }
320
321     def 'Write resource data for pass-through running from dmi using POST.'() {
322         given: 'data node representing cmHandle and its properties'
323             def cmHandleDataNode = getCmHandleDataNodeForTest()
324         and: 'cpsDataService returns valid cm-handle datanode'
325             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
326                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
327         when: 'get resource data is called'
328             objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
329                     'testResourceId',
330                     '{some-json}', 'application/json')
331         then: 'dmi called with correct data'
332             1 * mockDmiOperations.createResourceDataPassThroughRunningFromDmi('testDmiService',
333                 'testCmHandle',
334                 'testResourceId',
335                 '{"operation":"create","dataType":"application/json","data":"{some-json}","cmHandleProperties":{"testName":"testValue"}}')
336                 >> { new ResponseEntity<>(HttpStatus.OK) }
337     }
338
339     def 'Write resource data for pass-through running from dmi using POST "not found" response (from DMI).'() {
340         given: 'data node representing cmHandle and its properties'
341             def cmHandleDataNode = getCmHandleDataNodeForTest()
342         and: 'cpsDataService returns valid dataNode'
343             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
344                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
345         and: 'dmi throws exception'
346             mockDmiOperations.createResourceDataPassThroughRunningFromDmi(_ as String, _ as String, _ as String, _ as String)
347                     >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
348         when: 'get resource data is called'
349             objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
350                     'testResourceId',
351                     '{some-json}', 'application/json')
352         then: 'exception is thrown'
353             thrown(NcmpException.class)
354     }
355
356     def 'Sync model for a (new) cm handle with #scenario'() {
357         given: 'DMI PLug-in returns a list of module references'
358             getModulesForCmHandle()
359             def knownModule1 = new ModuleReference('module1', '1')
360             def knownOtherModule = new ModuleReference('some other module', 'some revision')
361         and: 'CPS-Core returns list of known modules'
362             mockCpsModuleService.getYangResourceModuleReferences(_) >> [knownModule1, knownOtherModule]
363         and: 'DMI-Plugin returns resource(s) for "new" module(s)'
364             def moduleResources = new ResponseEntity<String>(sdncReponseBody, HttpStatus.OK)
365             mockDmiOperations.getResourceFromDmiWithJsonData(_, _, _, 'moduleResources') >> moduleResources
366         when: 'module Sync is triggered'
367             objectUnderTest.createAnchorAndSyncModel(cmHandleForModelSync)
368         then: 'the CPS module service is called once with the correct parameters'
369             1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceNameForModleSync, cmHandleForModelSync.getId(), expectedYangResourceToContentMap, [knownModule1])
370         and: 'admin service create anchor method has been called with correct parameters'
371             1 * mockCpsAdminService.createAnchor(expectedDataspaceNameForModleSync, cmHandleForModelSync.getId(), cmHandleForModelSync.getId())
372         where: 'the following responses are recieved from SDNC'
373             scenario             | sdncReponseBody                                                                        || expectedYangResourceToContentMap
374             'one unknown module' | '[{"moduleName" : "someModule", "revision" : "1","yangSource": "[some yang source]"}]' || [someModule: 'some yang source']
375             'no unknown module'  | '[]'                                                                                   || [:]
376     }
377
378     def getModulesForCmHandle() {
379         def jsonData = TestUtils.getResourceFileContent('cmHandleModules.json')
380         mockDmiProperties.getAuthUsername() >> 'someUser'
381         mockDmiProperties.getAuthPassword() >> 'somePassword'
382         mockDmiProperties.getDmiPluginBasePath() >> 'someUrl'
383         def moduleReferencesFromCmHandleAsJson = new ResponseEntity<String>(jsonData, HttpStatus.OK)
384         mockDmiOperations.getResourceFromDmi(_, cmHandleForModelSync.getId(), 'modules') >> moduleReferencesFromCmHandleAsJson
385     }
386
387     def getObjectUnderTestWithModelSyncDisabled() {
388         def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
389                 mockCpsDataService, mockCpsQueryService, mockCpsAdminService, new ObjectMapper()))
390         objectUnderTest.createAnchorAndSyncModel(_) >> null
391         return objectUnderTest
392     }
393
394     def getCmHandleDataNodeForTest() {
395         def cmHandleDataNode = new DataNode()
396         cmHandleDataNode.leaves = ['dmi-service-name': 'testDmiService']
397         def cmHandlePropertyDataNode = new DataNode()
398         cmHandlePropertyDataNode.leaves = ['name': 'testName', 'value': 'testValue']
399         cmHandleDataNode.childDataNodes = [cmHandlePropertyDataNode]
400         return cmHandleDataNode
401     }
402
403 }