Changing putOperationWithJson to postOperationWithJson
[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 static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
26 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
27 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
28 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ
29 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
30
31 import com.fasterxml.jackson.core.JsonProcessingException
32 import com.fasterxml.jackson.databind.ObjectMapper
33 import org.onap.cps.api.CpsAdminService
34 import org.onap.cps.api.CpsDataService
35 import org.onap.cps.api.CpsModuleService
36 import org.onap.cps.api.CpsQueryService
37 import org.onap.cps.ncmp.api.impl.exception.NcmpException
38 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
39 import org.onap.cps.spi.FetchDescendantsOption
40 import org.onap.cps.spi.model.DataNode
41 import org.springframework.http.HttpStatus
42 import org.springframework.http.ResponseEntity
43 import spock.lang.Specification
44
45 class NetworkCmProxyDataServiceImplSpec extends Specification {
46
47     def mockCpsDataService = Mock(CpsDataService)
48     def mockCpsQueryService = Mock(CpsQueryService)
49     def mockCpsModuleService = Mock(CpsModuleService)
50     def mockCpsAdminService = Mock(CpsAdminService)
51     def spyObjectMapper = Spy(ObjectMapper)
52     def mockDmiDataOperations = Mock(DmiDataOperations)
53
54     def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiDataOperations, null,
55         mockCpsModuleService, mockCpsDataService, mockCpsQueryService, mockCpsAdminService, spyObjectMapper)
56
57     def cmHandle = 'some handle'
58     def noTimestamp = null
59     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
60     def expectedDataspaceName = 'NFP-Operational'
61
62     def 'Create full data node: #scenario.'() {
63         given: 'json data'
64             def jsonData = 'some json'
65         when: 'createDataNode is invoked'
66             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
67         then: 'save data is invoked once with the expected parameters'
68             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, jsonData, noTimestamp)
69         where: 'following parameters were used'
70             scenario           | xpath
71             'no xpath'         | ''
72             'root level xpath' | '/'
73     }
74
75     def 'Create child data node.'() {
76         given: 'json data and xpath'
77             def jsonData = 'some json'
78             def xpath = '/test-node'
79         when: 'create data node is invoked'
80             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
81         then: 'save data is invoked once with the expected parameters'
82             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
83     }
84
85     def 'Add list-node elements.'() {
86         given: 'json data and xpath'
87             def jsonData = 'some json'
88             def xpath = '/test-node'
89         when: 'add list node element is invoked'
90             objectUnderTest.addListNodeElements(cmHandle, xpath, jsonData)
91         then: 'the save list elements is invoked once with the expected parameters'
92             1 * mockCpsDataService.saveListElements(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
93     }
94
95     def 'Write resource data for pass-through running from dmi using POST #scenario cm handle properties.'() {
96         given: 'a data node'
97             def dataNode = getDataNode(includeCmHandleProperties)
98         and: 'cpsDataService returns valid datanode'
99             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
100                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
101         when: 'get resource data is called'
102             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
103                 'testResourceId', CREATE,
104                 '{some-json}', 'application/json')
105         then: 'dmi called with correct data'
106             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
107                 CREATE, '{some-json}', 'application/json')
108                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
109         where:
110             scenario  | includeCmHandleProperties || expectedJsonForCmhandleProperties
111             'with'    | true                      || '{"testName":"testValue"}'
112             'without' | false                     || '{}'
113     }
114
115     def 'Write resource data for pass-through running from dmi using POST "not found" response (from DMI).'() {
116         given: 'a data node'
117             def dataNode = getDataNode(true)
118         and: 'cpsDataService returns valid dataNode'
119             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
120                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
121         and: 'dmi returns a response with 404 status code'
122             mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle',
123                 'testResourceId', CREATE,
124                 '{some-json}', 'application/json')
125                 >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
126         when: 'write resource data is called'
127             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
128                 'testResourceId', CREATE,
129                 '{some-json}', 'application/json')
130         then: 'exception is thrown'
131             def exceptionThrown = thrown(NcmpException.class)
132         and: 'details contains (not found) error code: 404'
133             exceptionThrown.details.contains('404')
134     }
135
136     def 'Get data node.'() {
137         when: 'get data node is invoked'
138             objectUnderTest.getDataNode(cmHandle, 'some xpath', fetchDescendantsOption)
139         then: 'the persistence data service is called once with the correct parameters'
140             1 * mockCpsDataService.getDataNode(expectedDataspaceName, cmHandle, 'some xpath', fetchDescendantsOption)
141         where: 'all fetch descendants options are supported'
142             fetchDescendantsOption << FetchDescendantsOption.values()
143     }
144
145     def 'Get resource data for pass-through operational from dmi.'() {
146         given: 'a data node'
147             def dataNode = getDataNode(true)
148         and: 'get data node is called'
149             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
150                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
151         and: 'get resource data from dmi is called'
152             mockDmiDataOperations.getResourceDataFromDmi(
153                 'testCmHandle',
154                 'testResourceId',
155                 '(a=1,b=2)',
156                 'testAcceptParam' ,
157                 PASSTHROUGH_OPERATIONAL) >> new ResponseEntity<>('result-json', HttpStatus.OK)
158         when: 'get resource data operational for cm-handle is called'
159             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
160                 'testResourceId',
161                 'testAcceptParam',
162                 '(a=1,b=2)')
163         then: 'dmi returns a json response'
164             response == 'result-json'
165     }
166
167     def 'Get resource data for pass-through operational from dmi with Json Processing Exception.'() {
168         given: 'a data node'
169             def dataNode = getDataNode(true)
170         and: 'cps data service returns valid data node'
171             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
172                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
173         and: 'objectMapper not able to parse object'
174             def mockObjectMapper = Mock(ObjectMapper)
175             objectUnderTest.objectMapper = mockObjectMapper
176             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException('testException') }
177         and: 'dmi returns NOK response'
178             mockDmiDataOperations.getResourceDataFromDmi(*_)
179                 >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
180         when: 'get resource data is called'
181             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
182                 'testResourceId',
183                 'testAcceptParam',
184                 '(a=1,b=2)')
185         then: 'exception is thrown with the expected details'
186             def exceptionThrown = thrown(NcmpException.class)
187             exceptionThrown.details == 'DMI status code: 404, DMI response body: NOK-json'
188     }
189
190     def 'Get resource data for pass-through operational from dmi return NOK response.'() {
191         given: 'a data node'
192             def dataNode = getDataNode(true)
193         and: 'cps data service returns valid data node'
194             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
195                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
196         and: 'dmi returns NOK response'
197             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
198                 'testResourceId',
199                 '(a=1,b=2)',
200                 'testAcceptParam',
201                 PASSTHROUGH_OPERATIONAL)
202                 >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
203         when: 'get resource data is called'
204             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
205                 'testResourceId',
206                 'testAcceptParam',
207                 '(a=1,b=2)')
208         then: 'exception is thrown'
209             def exceptionThrown = thrown(NcmpException.class)
210         and: 'details contains the original response'
211             exceptionThrown.details.contains('NOK-json')
212     }
213
214     def 'Get resource data for pass-through running from dmi.'() {
215         given: 'a data node'
216             def dataNode = getDataNode(true)
217         and: 'cpsDataService returns valid data node'
218             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
219                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
220         and: 'dmi returns valid response and data'
221             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
222                 'testResourceId',
223                 '(a=1,b=2)',
224                 'testAcceptParam',
225                 PASSTHROUGH_RUNNING) >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
226         when: 'get resource data is called'
227             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
228                 'testResourceId',
229                 'testAcceptParam',
230                 '(a=1,b=2)')
231         then: 'get resource data returns expected response'
232             response == '{result-json}'
233     }
234
235     def 'Get resource data for pass-through running from dmi return NOK response.'() {
236         given: 'a data node'
237             def dataNode = getDataNode(true)
238         and: 'cpsDataService returns valid dataNode'
239             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
240                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
241         and: 'dmi returns NOK response'
242             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
243                 'testResourceId',
244                 '(a=1,b=2)',
245                 'testAcceptParam',
246                 PASSTHROUGH_RUNNING)
247                 >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
248         when: 'get resource data is called'
249             objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
250                 'testResourceId',
251                 'testAcceptParam',
252                 '(a=1,b=2)')
253         then: 'exception is thrown'
254             def exceptionThrown = thrown(NcmpException.class)
255         and: 'details contains the original response'
256             exceptionThrown.details.contains('NOK-json')
257     }
258
259     def 'Getting Yang Resources.'() {
260         when: 'yang resources is called'
261             objectUnderTest.getYangResourcesModuleReferences('some cm handle')
262         then: 'CPS module services is invoked for the correct dataspace and cm handle'
263             1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some cm handle')
264     }
265
266     def 'Get cm handle identifiers for the given module names.'() {
267         when: 'execute a cm handle search for the given module names'
268             objectUnderTest.executeCmHandleHasAllModulesSearch(['some-module-name'])
269         then: 'get anchor identifiers is invoked  with the expected parameters'
270             1 * mockCpsAdminService.queryAnchorNames('NFP-Operational', ['some-module-name'])
271     }
272
273     def 'Update data node leaves.'() {
274         given: 'json data and xpath'
275             def jsonData = 'some json'
276             def xpath = '/xpath'
277         when: 'update node leaves is invoked'
278             objectUnderTest.updateNodeLeaves(cmHandle, xpath, jsonData)
279         then: 'the persistence service is called once with the correct parameters'
280             1 * mockCpsDataService.updateNodeLeaves(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
281     }
282
283     def 'Replace data node tree.'() {
284         given: 'json data and xpath'
285             def jsonData = 'some json'
286             def xpath = '/xpath'
287         when: 'replace node tree is invoked'
288             objectUnderTest.replaceNodeTree(cmHandle, xpath, jsonData)
289         then: 'the persistence service is called once with the correct parameters'
290             1 * mockCpsDataService.replaceNodeTree(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
291     }
292
293     def 'Update resource data for pass-through running from dmi using POST #scenario cm handle properties.'() {
294         given: 'a data node'
295             def dataNode = getDataNode(includeCmHandleProperties)
296         and: 'cpsDataService returns valid datanode'
297             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
298                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
299         when: 'get resource data is called'
300             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
301                 'testResourceId', UPDATE,
302                 '{some-json}', 'application/json')
303         then: 'dmi called with correct data'
304             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
305                 UPDATE, '{some-json}', 'application/json')
306                 >> { new ResponseEntity<>(HttpStatus.OK) }
307         where:
308             scenario  | includeCmHandleProperties || expectedJsonForCmhandleProperties
309             'with'    | true                      || '{"testName":"testValue"}'
310             'without' | false                     || '{}'
311     }
312
313     def 'Verify error message from handleResponse is correct for #scenario operation.'() {
314         given: 'writeResourceDataPassThroughRunningFromDmi fails to return OK HttpStatus'
315             mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi(*_)
316                 >> new ResponseEntity<>(HttpStatus.NOT_FOUND)
317         when: 'get resource data is called'
318             def response = objectUnderTest.writeResourceDataPassThroughRunningForCmHandle(
319                 'testCmHandle',
320                 'testResourceId',
321                 givenOperation,
322                 '{some-json}',
323                 'application/json')
324         then: 'an exception is thrown with the expected error message detailsd with correct operation'
325             def exceptionThrown = thrown(NcmpException.class)
326             exceptionThrown.getMessage().contains(expectedResponseMessage)
327         where:
328             scenario | givenOperation || expectedResponseMessage
329             'CREATE' | CREATE         || 'Not able to create resource data.'
330             'READ'   | READ           || 'Not able to read resource data.'
331             'UPDATE' | UPDATE         || 'Not able to update resource data.'
332     }
333
334     def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
335         given: 'a cps path'
336             def cpsPath = '/cps-path'
337         when: 'query data nodes is invoked'
338             objectUnderTest.queryDataNodes(cmHandle, cpsPath, fetchDescendantsOption)
339         then: 'the persistence query service is called once with the correct parameters'
340             1 * mockCpsQueryService.queryDataNodes(expectedDataspaceName, cmHandle, cpsPath, fetchDescendantsOption)
341         where: 'all fetch descendants options are supported'
342             fetchDescendantsOption << FetchDescendantsOption.values()
343     }
344
345     def getDataNode(boolean includeCmHandleProperties) {
346         def dataNode = new DataNode()
347         dataNode.leaves = ['dmi-service-name': 'testDmiService']
348         if (includeCmHandleProperties) {
349             def cmHandlePropertyDataNode = new DataNode()
350             cmHandlePropertyDataNode.leaves = ['name': 'testName', 'value': 'testValue']
351             dataNode.childDataNodes = [cmHandlePropertyDataNode]
352         }
353         return dataNode
354     }
355 }