Remove the dependency-cycle between beans
[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-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.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 'Get module definitions'() {
188         given: 'cps module service returns a collection of module definitions'
189             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
190             mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions
191         when: 'get module definitions by cmHandle is invoked'
192             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
193         then: 'the returned result are the same module definitions as returned from the module service'
194             assert result == moduleDefinitions
195     }
196
197     def 'Get module references'() {
198         given: 'cps module service returns a collection of module references'
199             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
200             mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences
201         when: 'get yang resources module references by cmHandle is invoked'
202             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
203         then: 'the returned result is a collection of module definitions'
204             assert result == moduleReferences
205         and: 'the CM Handle ID is validated'
206             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
207     }
208
209     def 'Save Cmhandle'() {
210         given: 'cmHandle represented as Yang Model'
211             def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: [])
212         when: 'the method to save cmhandle is called'
213             objectUnderTest.saveCmHandle(yangModelCmHandle)
214         then: 'the data service method to save list elements is called once'
215             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
216                     _,null) >> {
217                 args -> {
218                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
219                 }
220             }
221     }
222
223     def 'Save Multiple Cmhandles'() {
224         given: 'cm handles represented as Yang Model'
225             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
226             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
227         when: 'the cm handles are saved'
228             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
229         then: 'CPS Data Service persists both cm handles as a batch'
230             1 * mockCpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
231                     NCMP_DMI_REGISTRY_PARENT, _,null) >> {
232                 args -> {
233                     def jsonDataList = (args[3] as List)
234                     (jsonDataList[0] as String).contains('cmhandle1')
235                     (jsonDataList[0] as String).contains('cmhandle2')
236                 }
237             }
238     }
239
240     def 'Delete list or list elements'() {
241         when: 'the method to delete list or list elements is called'
242             objectUnderTest.deleteListOrListElement('sample xPath')
243         then: 'the data service method to save list elements is called once'
244             1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null)
245     }
246
247     def 'Delete schema set with a valid schema set name'() {
248         when: 'the method to delete schema set is called with valid schema set name'
249             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
250         then: 'the module service to delete schemaSet is invoked once'
251             1 * mockCpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
252         and: 'the schema set name is validated'
253             1 * mockCpsValidator.validateNameCharacters('validSchemaSetName')
254     }
255
256     def 'Delete multiple schema sets with valid schema set names'() {
257         when: 'the method to delete schema sets is called with valid schema set names'
258             objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2'])
259         then: 'the module service to delete schema sets is invoked once'
260             1 * mockCpsModuleService.deleteSchemaSetsWithCascade(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['validSchemaSetName1', 'validSchemaSetName2'])
261         and: 'the schema set names are validated'
262             1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2'])
263     }
264
265     def 'Get data node via xPath'() {
266         when: 'the method to get data nodes is called'
267             objectUnderTest.getDataNode('sample xPath')
268         then: 'the data persistence service method to get data node is invoked once'
269             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS)
270     }
271
272     def 'Get cmHandle data node'() {
273         given: 'expected xPath to get cmHandle data node'
274             def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']';
275         when: 'the method to get data nodes is called'
276             objectUnderTest.getCmHandleDataNode('sample cmHandleId')
277         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
278             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
279     }
280
281     def 'Get CM handles that has given module names'() {
282         when: 'the method to get cm handles is called'
283             objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name'])
284         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
285             1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name'])
286     }
287
288     def 'Replace list content'() {
289         when: 'replace list content method is called with xpath and data nodes collection'
290             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
291         then: 'the cps data service method to replace list content is invoked once with same parameters'
292             1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP);
293     }
294
295     def 'Delete data node via xPath'() {
296         when: 'Delete data node method is called with xpath as parameter'
297             objectUnderTest.deleteDataNode('sample dataNode xpath')
298         then: 'the cps data service method to delete data node is invoked once with the same xPath'
299             1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP);
300     }
301
302     def 'Delete multiple data nodes via xPath'() {
303         when: 'Delete data nodes method is called with multiple xpaths as parameters'
304             objectUnderTest.deleteDataNodes(['xpath1', 'xpath2'])
305         then: 'the cps data service method to delete data nodes is invoked once with the same xPaths'
306             1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP);
307     }
308
309 }