Merge "RTD change to document migration to Spring Boot 3.0"
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / inventory / InventoryPersistenceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2023 Nordix Foundation
4  *  Modifications Copyright (C) 2022 Bell Canada
5  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
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.inventory
24
25 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
26 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
27 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
28 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
29 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP
30 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
31
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.ncmp.api.impl.inventory.CmHandleState
37 import org.onap.cps.ncmp.api.impl.inventory.CompositeState
38 import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistenceImpl
39 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
40 import org.onap.cps.spi.CascadeDeleteAllowed
41 import org.onap.cps.spi.FetchDescendantsOption
42 import org.onap.cps.spi.model.DataNode
43 import org.onap.cps.spi.model.ModuleDefinition
44 import org.onap.cps.spi.model.ModuleReference
45 import org.onap.cps.utils.JsonObjectMapper
46 import org.onap.cps.spi.utils.CpsValidator
47 import spock.lang.Shared
48 import spock.lang.Specification
49 import java.time.OffsetDateTime
50 import java.time.ZoneOffset
51 import java.time.format.DateTimeFormatter
52
53 class InventoryPersistenceImplSpec extends Specification {
54
55     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
56
57     def mockCpsDataService = Mock(CpsDataService)
58
59     def mockCpsModuleService = Mock(CpsModuleService)
60
61     def mockCpsAdminService = Mock(CpsAdminService)
62
63     def mockCpsValidator = Mock(CpsValidator)
64
65     def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
66             mockCpsValidator, mockCpsAdminService)
67
68     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
69             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
70
71     def cmHandleId = 'some-cm-handle'
72     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
73     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
74
75     def cmHandleId2 = 'another-cm-handle'
76     def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']"
77
78     @Shared
79     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
80                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
81
82     @Shared
83     def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])]
84
85     @Shared
86     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
87
88     @Shared
89     def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
90
91     def "Retrieve CmHandle using datanode with #scenario."() {
92         given: 'the cps data service returns a data node from the DMI registry'
93             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
94             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
95         when: 'retrieving the yang modelled cm handle'
96             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
97         then: 'the result has the correct id and service names'
98             result.id == cmHandleId
99             result.dmiServiceName == 'common service name'
100             result.dmiDataServiceName == 'data service name'
101             result.dmiModelServiceName == 'model service name'
102         and: 'the expected DMI properties'
103             result.dmiProperties == expectedDmiProperties
104             result.publicProperties == expectedPublicProperties
105         and: 'the state details are returned'
106             result.compositeState.cmHandleState == expectedCompositeState
107         and: 'the CM Handle ID is validated'
108             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
109         where: 'the following parameters are used'
110             scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
111             'no properties'             | []                                            || []                                                  || []                                                    || null
112             'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")]   || null
113             'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
114             'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
115             'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
116     }
117
118     def "Handling missing service names as null."() {
119         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
120             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
121             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
122         when: 'retrieving the yang modelled cm handle'
123             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
124         then: 'the service names are returned as null'
125             result.dmiServiceName == null
126             result.dmiDataServiceName == null
127             result.dmiModelServiceName == null
128         and: 'the CM Handle ID is validated'
129             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
130     }
131
132     def "Retrieve multiple YangModelCmHandles"() {
133         given: 'the cps data service returns 2 data nodes from the DMI registry'
134             def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
135             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
136         when: 'retrieving the yang modelled cm handle'
137             def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
138         then: 'verify both have returned and cmhandleIds are correct'
139             assert results.size() == 2
140             assert results.id.containsAll([cmHandleId, cmHandleId2])
141     }
142
143     def 'Get a Cm Handle Composite State'() {
144         given: 'a valid cm handle id'
145             def cmHandleId = 'Some-Cm-Handle'
146             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
147         and: 'cps data service returns a valid data node'
148             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
149                     '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
150         when: 'get cm handle state is invoked'
151             def result = objectUnderTest.getCmHandleState(cmHandleId)
152         then: 'result has returned the correct cm handle state'
153             result.cmHandleState == CmHandleState.ADVISED
154         and: 'the CM Handle ID is validated'
155             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
156     }
157
158     def 'Update Cm Handle with #scenario State'() {
159         given: 'a cm handle and a composite state'
160             def cmHandleId = 'Some-Cm-Handle'
161             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
162         when: 'update cm handle state is invoked with the #scenario state'
163             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
164         then: 'update node leaves is invoked with the correct params'
165             1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
166         where: 'the following states are used'
167             scenario    | cmHandleState          || expectedJsonData
168             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
169             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
170             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
171     }
172
173     def 'Update Cm Handles with #scenario States'() {
174         given: 'a map of cm handles composite states'
175             def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
176             def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
177         when: 'update cm handle state is invoked with the #scenario state'
178             def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2]
179             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
180         then: 'update node leaves is invoked with the correct params'
181             1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime)
182         where: 'the following states are used'
183             scenario    | cmHandleState          || cmHandlesJsonDataMap
184             'READY'     | CmHandleState.READY    || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
185             'LOCKED'    | CmHandleState.LOCKED   || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
186             'DELETING'  | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
187     }
188
189     def 'Get module definitions'() {
190         given: 'cps module service returns a collection of module definitions'
191             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
192             mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions
193         when: 'get module definitions by cmHandle is invoked'
194             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
195         then: 'the returned result are the same module definitions as returned from the module service'
196             assert result == moduleDefinitions
197     }
198
199     def 'Get module references'() {
200         given: 'cps module service returns a collection of module references'
201             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
202             mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences
203         when: 'get yang resources module references by cmHandle is invoked'
204             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
205         then: 'the returned result is a collection of module definitions'
206             assert result == moduleReferences
207         and: 'the CM Handle ID is validated'
208             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
209     }
210
211     def 'Save Cmhandle'() {
212         given: 'cmHandle represented as Yang Model'
213             def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: [])
214         when: 'the method to save cmhandle is called'
215             objectUnderTest.saveCmHandle(yangModelCmHandle)
216         then: 'the data service method to save list elements is called once'
217             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
218                     _,null) >> {
219                 args -> {
220                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
221                 }
222             }
223     }
224
225     def 'Save Multiple Cmhandles'() {
226         given: 'cm handles represented as Yang Model'
227             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
228             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
229         when: 'the cm handles are saved'
230             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
231         then: 'CPS Data Service persists both cm handles as a batch'
232             1 * mockCpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
233                     NCMP_DMI_REGISTRY_PARENT, _,null) >> {
234                 args -> {
235                     def jsonDataList = (args[3] as List)
236                     (jsonDataList[0] as String).contains('cmhandle1')
237                     (jsonDataList[0] as String).contains('cmhandle2')
238                 }
239             }
240     }
241
242     def 'Delete list or list elements'() {
243         when: 'the method to delete list or list elements is called'
244             objectUnderTest.deleteListOrListElement('sample xPath')
245         then: 'the data service method to save list elements is called once'
246             1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null)
247     }
248
249     def 'Delete schema set with a valid schema set name'() {
250         when: 'the method to delete schema set is called with valid schema set name'
251             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
252         then: 'the module service to delete schemaSet is invoked once'
253             1 * mockCpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
254         and: 'the schema set name is validated'
255             1 * mockCpsValidator.validateNameCharacters('validSchemaSetName')
256     }
257
258     def 'Delete multiple schema sets with valid schema set names'() {
259         when: 'the method to delete schema sets is called with valid schema set names'
260             objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2'])
261         then: 'the module service to delete schema sets is invoked once'
262             1 * mockCpsModuleService.deleteSchemaSetsWithCascade(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['validSchemaSetName1', 'validSchemaSetName2'])
263         and: 'the schema set names are validated'
264             1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2'])
265     }
266
267     def 'Get data node via xPath'() {
268         when: 'the method to get data nodes is called'
269             objectUnderTest.getDataNode('sample xPath')
270         then: 'the data persistence service method to get data node is invoked once'
271             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS)
272     }
273
274     def 'Get cmHandle data node'() {
275         given: 'expected xPath to get cmHandle data node'
276             def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']';
277         when: 'the method to get data nodes is called'
278             objectUnderTest.getCmHandleDataNode('sample cmHandleId')
279         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
280             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
281     }
282
283     def 'Get CM handles that has given module names'() {
284         when: 'the method to get cm handles is called'
285             objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name'])
286         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
287             1 * mockCpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name'])
288     }
289
290     def 'Replace list content'() {
291         when: 'replace list content method is called with xpath and data nodes collection'
292             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
293         then: 'the cps data service method to replace list content is invoked once with same parameters'
294             1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP);
295     }
296
297     def 'Delete data node via xPath'() {
298         when: 'Delete data node method is called with xpath as parameter'
299             objectUnderTest.deleteDataNode('sample dataNode xpath')
300         then: 'the cps data service method to delete data node is invoked once with the same xPath'
301             1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP);
302     }
303
304     def 'Delete multiple data nodes via xPath'() {
305         when: 'Delete data nodes method is called with multiple xpaths as parameters'
306             objectUnderTest.deleteDataNodes(['xpath1', 'xpath2'])
307         then: 'the cps data service method to delete data nodes is invoked once with the same xPaths'
308             1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP);
309     }
310
311 }