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