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