Merge "Async: NCMP Rest impl. including Request ID generation"
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 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 org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
26 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
27 import spock.lang.Shared
28
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
34
35 import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
36 import org.onap.cps.utils.JsonObjectMapper
37 import com.fasterxml.jackson.core.JsonProcessingException
38 import com.fasterxml.jackson.databind.ObjectMapper
39 import org.onap.cps.api.CpsAdminService
40 import org.onap.cps.api.CpsDataService
41 import org.onap.cps.api.CpsModuleService
42 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
43 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
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
49
50 class NetworkCmProxyDataServiceImplSpec extends Specification {
51
52     def mockCpsDataService = Mock(CpsDataService)
53     def mockCpsModuleService = Mock(CpsModuleService)
54     def mockCpsAdminService = Mock(CpsAdminService)
55     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
56     def mockDmiModelOperations = Mock(DmiModelOperations)
57     def mockDmiDataOperations = Mock(DmiDataOperations)
58     def nullNetworkCmProxyDataServicePropertyHandler = null
59     def mockYangModelCmHandleRetriever = Mock(YangModelCmHandleRetriever)
60     def NO_TOPIC = null
61     def NO_REQUEST_ID = null
62     @Shared
63     def OPTIONS_PARAM = '(a=1,b=2)'
64
65     def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
66         mockCpsModuleService, mockCpsAdminService, nullNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever)
67
68     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
69
70     def dataNode = new DataNode(leaves: ['dmi-service-name': 'testDmiService'])
71
72     def 'Write resource data for pass-through running from DMI using POST #scenario cm handle properties.'() {
73         given: 'cpsDataService returns valid datanode'
74             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
75                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
76         when: 'get resource data is called'
77             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
78                 'testResourceId', CREATE,
79                 '{some-json}', 'application/json')
80         then: 'DMI called with correct data'
81             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
82                 CREATE, '{some-json}', 'application/json')
83                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
84     }
85
86     def 'Write resource data for pass-through running from DMI using POST "not found" response (from DMI).'() {
87         given: 'cpsDataService returns valid dataNode'
88             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
89                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
90         and: 'DMI returns a response with 404 status code'
91             mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle',
92                 'testResourceId', CREATE,
93                 '{some-json}', 'application/json')
94                 >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
95         when: 'write resource data is called'
96             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
97                 'testResourceId', CREATE,
98                 '{some-json}', 'application/json')
99         then: 'exception is thrown'
100             def exceptionThrown = thrown(ServerNcmpException.class)
101         and: 'details contains (not found) error code: 404'
102             exceptionThrown.details.contains('404')
103     }
104
105     def 'Get resource data for pass-through operational from DMI.'() {
106         given: 'get data node is called'
107             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
108                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
109         and: 'get resource data from DMI is called'
110             mockDmiDataOperations.getResourceDataFromDmi(
111                     'testCmHandle',
112                     'testResourceId',
113                     OPTIONS_PARAM,
114                     'testAcceptParam',
115                     PASSTHROUGH_OPERATIONAL,
116                     NO_REQUEST_ID,
117                     NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK)
118         when: 'get resource data operational for cm-handle is called'
119             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
120                     'testResourceId',
121                     'testAcceptParam',
122                     OPTIONS_PARAM,
123                     NO_TOPIC)
124         then: 'DMI returns a json response'
125             response == 'dmi-response'
126     }
127
128     def 'Get resource data for pass-through operational from DMI with Json Processing Exception.'() {
129         given: 'cps data service returns valid data node'
130             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
131                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
132         and: 'objectMapper not able to parse object'
133             spiedJsonObjectMapper.asJsonString(_) >> { throw new JsonProcessingException('testException') }
134         and: 'DMI returns NOK response'
135             mockDmiDataOperations.getResourceDataFromDmi(*_)
136                 >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
137         when: 'get resource data is called'
138             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
139                     'testResourceId',
140                     'testAcceptParam',
141                     OPTIONS_PARAM,
142                     NO_TOPIC)
143         then: 'exception is thrown with the expected details'
144             def exceptionThrown = thrown(ServerNcmpException.class)
145             exceptionThrown.details == 'DMI status code: 404, DMI response body: NOK-json'
146     }
147
148     def 'Get resource data for pass-through operational from DMI return NOK response.'() {
149         given: 'cps data service returns valid data node'
150             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
151                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
152         and: 'DMI returns NOK response'
153             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
154                     'testResourceId',
155                     OPTIONS_PARAM,
156                     'testAcceptParam',
157                     PASSTHROUGH_OPERATIONAL,
158                     NO_REQUEST_ID,
159                     NO_TOPIC)
160                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
161         when: 'get resource data is called'
162             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
163                     'testResourceId',
164                     'testAcceptParam',
165                     OPTIONS_PARAM,
166                     NO_TOPIC)
167         then: 'exception is thrown'
168             def exceptionThrown = thrown(ServerNcmpException.class)
169         and: 'details contains the original response'
170             exceptionThrown.details.contains('NOK-json')
171     }
172
173     def 'Get resource data for pass-through running from DMI.'() {
174         given: 'cpsDataService returns valid data node'
175             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
176                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
177         and: 'DMI returns valid response and data'
178             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
179                     'testResourceId',
180                     OPTIONS_PARAM,
181                     'testAcceptParam',
182                     PASSTHROUGH_RUNNING,
183                     NO_REQUEST_ID,
184                     NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
185         when: 'get resource data is called'
186             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
187                     'testResourceId',
188                     'testAcceptParam',
189                     OPTIONS_PARAM,
190                     NO_TOPIC)
191         then: 'get resource data returns expected response'
192             response == '{dmi-response}'
193     }
194
195     def 'Get resource data for pass-through running from DMI return NOK response.'() {
196         given: 'cpsDataService returns valid dataNode'
197             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
198                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
199         and: 'DMI returns NOK response'
200             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
201                     'testResourceId',
202                     OPTIONS_PARAM,
203                     'testAcceptParam',
204                     PASSTHROUGH_RUNNING,
205                     NO_REQUEST_ID,
206                     NO_TOPIC)
207                     >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
208         when: 'get resource data is called'
209             objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
210                     'testResourceId',
211                     'testAcceptParam',
212                     OPTIONS_PARAM,
213                     NO_TOPIC)
214         then: 'exception is thrown'
215             def exceptionThrown = thrown(ServerNcmpException.class)
216         and: 'details contains the original response'
217             exceptionThrown.details.contains('NOK-json')
218     }
219
220     def 'Get resource data for operational from DMI with empty topic sync request.'() {
221         given: 'cps data service returns valid data node'
222             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
223                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
224         and: 'dmi data operation returns valid response and data'
225             mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
226                     >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
227         when: 'get resource data is called data operational with blank topic'
228             def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '',
229                     '', '', emptyTopic)
230         then: '(synchronous) the dmi response is expected'
231             assert responseData == '{dmi-response}'
232         where: 'the following parameters are used'
233             scenario             | emptyTopic
234             'No topic in url'    | ''
235             'Null topic in url'  | null
236             'Empty topic in url' | '\"\"'
237             'Blank topic in url' | ' '
238     }
239
240     def 'Get resource data for data operational from DMI with valid topic i.e. async request.'() {
241         given: 'cps data service returns valid data node'
242             mockCpsDataService.getDataNode(*_) >> dataNode
243         and: 'dmi data operation returns valid response and data'
244             mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, _, 'my-topic-name')
245                     >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
246         when: 'get resource data is called for data operational with valid topic'
247             def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '', '', '', 'my-topic-name')
248         then: 'non empty request id is generated'
249             assert responseData.body.requestId.length() > 0
250     }
251
252     def 'Get resource data for pass through running from DMI with valid topic async request.'() {
253         given: 'cps data service returns valid data node'
254             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
255                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
256         and: 'dmi data operation returns valid response and data'
257             mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, _, 'my-topic-name')
258                     >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
259         when: 'get resource data is called for data operational with valid topic'
260             def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
261                     '', '', OPTIONS_PARAM, 'my-topic-name')
262         then: 'non empty request id is generated'
263             assert responseData.body.requestId.length() > 0
264     }
265
266     def 'Get resource data for pass through running from DMI sync request where #scenario.'() {
267         given: 'cps data service returns valid data node'
268             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
269                     cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
270         and: 'dmi data operation returns valid response and data'
271             mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
272                     >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
273         when: 'get resource data is called for data operational with valid topic'
274             def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
275                     '', '', '', emptyTopic)
276         then: '(synchronous) the dmi response is expected'
277             assert responseData == '{dmi-response}'
278         where: 'the following parameters are used'
279             scenario             | emptyTopic
280             'No topic in url'    | ''
281             'Null topic in url'  | null
282             'Empty topic in url' | '\"\"'
283             'Blank topic in url' | ' '
284     }
285
286     def 'Getting Yang Resources.'() {
287         when: 'yang resources is called'
288             objectUnderTest.getYangResourcesModuleReferences('some cm handle')
289         then: 'CPS module services is invoked for the correct dataspace and cm handle'
290             1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some cm handle')
291     }
292
293     def 'Get cm handle identifiers for the given module names.'() {
294         when: 'execute a cm handle search for the given module names'
295             objectUnderTest.executeCmHandleHasAllModulesSearch(['some-module-name'])
296         then: 'get anchor identifiers is invoked  with the expected parameters'
297             1 * mockCpsAdminService.queryAnchorNames('NFP-Operational', ['some-module-name'])
298     }
299
300     def 'Get a cm handle.'() {
301         given: 'the system returns a yang modelled cm handle'
302             def dmiServiceName = 'some service name'
303             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
304             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
305             def yangModelCmHandle = new YangModelCmHandle(id:'Some-Cm-Handle', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, publicProperties: publicProperties)
306             1 * mockYangModelCmHandleRetriever.getDmiServiceNamesAndProperties('Some-Cm-Handle') >> yangModelCmHandle
307         when: 'getting cm handle details for a given cm handle id from ncmp service'
308             def result = objectUnderTest.getNcmpServiceCmHandle('Some-Cm-Handle')
309         then: 'the result returns the correct data'
310             result.cmHandleID == 'Some-Cm-Handle'
311             result.dmiProperties ==[ Book:'Romance Novel' ]
312             result.publicProperties == [ "Public Book":'Public Romance Novel' ]
313
314     }
315
316     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
317         given: 'cpsDataService returns valid datanode'
318             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
319                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
320         when: 'get resource data is called'
321             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
322                 'testResourceId', UPDATE,
323                 '{some-json}', 'application/json')
324         then: 'DMI called with correct data'
325             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
326                 UPDATE, '{some-json}', 'application/json')
327                 >> { new ResponseEntity<>(HttpStatus.OK) }
328     }
329
330     def 'Verify error message from handleResponse is correct for #scenario operation.'() {
331         given: 'writeResourceDataPassThroughRunningFromDmi fails to return OK HttpStatus'
332             mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi(*_)
333                 >> new ResponseEntity<>(HttpStatus.NOT_FOUND)
334         when: 'get resource data is called'
335             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle(
336                 'testCmHandle',
337                 'testResourceId',
338                 givenOperation,
339                 '{some-json}',
340                 'application/json')
341         then: 'an exception is thrown with the expected error message details with correct operation'
342             def exceptionThrown = thrown(ServerNcmpException.class)
343             exceptionThrown.getMessage().contains(expectedResponseMessage)
344         where:
345             scenario | givenOperation || expectedResponseMessage
346             'CREATE' | CREATE         || 'Not able to create resource data.'
347             'READ'   | READ           || 'Not able to read resource data.'
348             'UPDATE' | UPDATE         || 'Not able to update resource data.'
349     }
350 }