Merge "Move persistence methods from NCMPproperty handler"
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / inventory / InventoryPersistenceSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022 Nordix Foundation
4  *  Modifications Copyright (C) 2022 Bell Canada
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.ncmp.api.inventory
23
24 import com.fasterxml.jackson.databind.ObjectMapper
25 import org.onap.cps.api.CpsDataService
26 import org.onap.cps.api.CpsModuleService
27 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
28 import org.onap.cps.spi.CascadeDeleteAllowed
29 import org.onap.cps.spi.CpsDataPersistenceService
30 import org.onap.cps.spi.CpsAdminPersistenceService
31 import org.onap.cps.spi.FetchDescendantsOption
32 import org.onap.cps.spi.exceptions.DataValidationException
33 import org.onap.cps.spi.model.DataNode
34 import org.onap.cps.spi.model.ModuleDefinition
35 import org.onap.cps.spi.model.ModuleReference
36 import org.onap.cps.utils.JsonObjectMapper
37 import spock.lang.Shared
38 import spock.lang.Specification
39
40 import java.time.OffsetDateTime
41 import java.time.ZoneOffset
42 import java.time.format.DateTimeFormatter
43
44 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
45 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
46 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
47
48 class InventoryPersistenceSpec extends Specification {
49
50     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
51
52     def mockCpsDataService = Mock(CpsDataService)
53
54     def mockCpsModuleService = Mock(CpsModuleService)
55
56     def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
57
58     def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService)
59
60     def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
61             mockCpsDataPersistenceService, mockCpsAdminPersistenceService)
62
63     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
64             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
65
66     def cmHandleId = 'some-cm-handle'
67     def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
68     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
69
70     @Shared
71     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]),
72                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])]
73
74     @Shared
75     def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])]
76
77     @Shared
78     def childDataNodesForCmHandleWithPublicProperties = [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 childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])]
82
83     @Shared
84     def static sampleDataNodes = [new DataNode()]
85
86     def "Retrieve CmHandle using datanode with #scenario."() {
87         given: 'the cps data service returns a data node from the DMI registry'
88             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
89             mockCpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
90         when: 'retrieving the yang modelled cm handle'
91             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
92         then: 'the result has the correct id and service names'
93             result.id == cmHandleId
94             result.dmiServiceName == 'common service name'
95             result.dmiDataServiceName == 'data service name'
96             result.dmiModelServiceName == 'model service name'
97         and: 'the expected DMI properties'
98             result.dmiProperties == expectedDmiProperties
99             result.publicProperties == expectedPublicProperties
100         and: 'the state details are returned'
101             result.compositeState.cmHandleState == expectedCompositeState
102         where: 'the following parameters are used'
103             scenario                    | childDataNodes                                || expectedDmiProperties                               || expectedPublicProperties                              || expectedCompositeState
104             'no properties'             | []                                            || []                                                  || []                                                    || null
105             'DMI and public properties' | childDataNodesForCmHandleWithAllProperties    || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null
106             'just DMI properties'       | childDataNodesForCmHandleWithDMIProperties    || [new YangModelCmHandle.Property("name1", "value1")] || []                                                    || null
107             'just public properties'    | childDataNodesForCmHandleWithPublicProperties || []                                                  || [new YangModelCmHandle.Property("name2", "value2")]   || null
108             'with state details'        | childDataNodesForCmHandleWithState            || []                                                  || []                                                    || CmHandleState.ADVISED
109     }
110
111     def "Retrieve CmHandle using datanode with invalid CmHandle id."() {
112         when: 'retrieving the yang modelled cm handle with an invalid id'
113             def result = objectUnderTest.getYangModelCmHandle('cm handle id with spaces')
114         then: 'a data validation exception is thrown'
115             thrown(DataValidationException)
116         and: 'the result is not returned'
117             result == null
118     }
119
120     def "Handling missing service names as null CPS-1043."() {
121         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
122             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
123             mockCpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
124         when: 'retrieving the yang modelled cm handle'
125             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
126         then: 'the service names ae returned as null'
127             result.dmiServiceName == null
128             result.dmiDataServiceName == null
129             result.dmiModelServiceName == null
130     }
131
132     def 'Get a Cm Handle Composite State'() {
133         given: 'a valid cm handle id'
134             def cmHandleId = 'Some-Cm-Handle'
135             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
136         and: 'cps data service returns a valid data node'
137             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
138                     '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
139         when: 'get cm handle state is invoked'
140             def result = objectUnderTest.getCmHandleState(cmHandleId)
141         then: 'result has returned the correct cm handle state'
142             result.cmHandleState == CmHandleState.ADVISED
143     }
144
145     def 'Update Cm Handle with #scenario State'() {
146         given: 'a cm handle and a composite state'
147             def cmHandleId = 'Some-Cm-Handle'
148             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
149         when: 'update cm handle state is invoked with the #scenario state'
150             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
151         then: 'update node leaves is invoked with the correct params'
152             1 * mockCpsDataService.replaceNodeTree('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime)
153         where: 'the following states are used'
154             scenario    | cmHandleState          || expectedJsonData
155             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
156             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
157             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
158     }
159
160     def 'Get Cm Handles By State'() {
161         given: 'a cm handle state to query'
162             def cmHandleState = CmHandleState.ADVISED
163         and: 'cps data service returns a list of data nodes'
164             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
165                     '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
166         when: 'get cm handles by state is invoked'
167             def result = objectUnderTest.getCmHandlesByState(cmHandleState)
168         then: 'the returned result is a list of data nodes returned by cps data service'
169             assert result == sampleDataNodes
170     }
171
172     def 'Get Cm Handles By State and Cm-Handle Id'() {
173         given: 'a cm handle state to query'
174             def cmHandleState = CmHandleState.READY
175         and: 'cps data service returns a list of data nodes'
176             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
177                     '//cm-handles[@id=\'some-cm-handle\']/state[@cm-handle-state="'+ 'READY'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
178         when: 'get cm handles by state and id is invoked'
179             def result = objectUnderTest.getCmHandlesByIdAndState(cmHandleId, cmHandleState)
180         then: 'the returned result is a list of data nodes returned by cps data service'
181             assert result == sampleDataNodes
182     }
183
184     def 'Get Cm Handles By Operational Sync State : UNSYNCHRONIZED'() {
185         given: 'a cm handle state to query'
186             def cmHandleState = CmHandleState.READY
187         and: 'cps data service returns a list of data nodes'
188             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
189                     '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes
190         when: 'get cm handles by operational sync state as UNSYNCHRONIZED is invoked'
191             def result = objectUnderTest.getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED)
192         then: 'the returned result is a list of data nodes returned by cps data service'
193             assert result == sampleDataNodes
194     }
195
196     def 'Retrieve cm handle by cps path '() {
197         given: 'a cm handle state to query based on the cps path'
198             def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED'])
199             def cpsPath = '//cps-path'
200         and: 'cps data service returns a valid data node'
201             mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
202                     cpsPath, INCLUDE_ALL_DESCENDANTS)
203                     >> Arrays.asList(cmHandleDataNode)
204         when: 'get cm handles by cps path is invoked'
205             def result = objectUnderTest.getCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS)
206         then: 'the returned result is a list of data nodes returned by cps data service'
207             assert result.contains(cmHandleDataNode)
208     }
209
210     def 'Get module definitions'() {
211         given: 'cps module service returns a collection of module definitions'
212             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
213             mockCpsModuleService.getModuleDefinitionsByAnchorName('NFP-Operational','some-cmHandle-Id') >> moduleDefinitions
214         when: 'get module definitions by cmHandle is invoked'
215             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
216         then: 'the returned result are the same module definitions as returned from the module service'
217             assert result == moduleDefinitions
218     }
219
220     def 'Get module references'() {
221         given: 'cps module service returns a collection of module references'
222             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
223             mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some-cmHandle-Id') >> moduleReferences
224         when: 'get yang resources module references by cmHandle is invoked'
225             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
226         then: 'the returned result is a collection of module definitions'
227             assert result == moduleReferences
228     }
229
230     def 'Save list elements'() {
231         when: 'the method to save list elements is called'
232             objectUnderTest.saveListElements('sample Json data')
233         then: 'the data service method to save list elements is called once'
234             1 * mockCpsDataService.saveListElements('NCMP-Admin','ncmp-dmi-registry','/dmi-registry','sample Json data',null)
235     }
236
237     def 'Delete list or list elements'() {
238         when: 'the method to delete list or list elements is called'
239             objectUnderTest.deleteListOrListElement('sample xPath')
240         then: 'the data service method to save list elements is called once'
241             1 * mockCpsDataService.deleteListOrListElement('NCMP-Admin','ncmp-dmi-registry','sample xPath',null)
242     }
243
244     def 'Delete schema set with a valid schema set name'() {
245         when: 'the method to delete schema set is called with valid schema set name'
246             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
247         then: 'the module service to delete schemaSet is invoked once'
248             1 * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
249     }
250
251     def 'Delete schema set with an invalid schema set name'() {
252         when: 'the method to delete schema set is called with an invalid schema set name'
253             objectUnderTest.deleteSchemaSetWithCascade('invalid SchemaSet name')
254         then: 'a data validation exception is thrown'
255             thrown(DataValidationException)
256         and: 'the module service to delete schemaSet is not called'
257             0 * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'sampleSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
258     }
259
260     def 'Query data nodes via cpsPath'() {
261         when: 'the method to query data nodes is called'
262             objectUnderTest.queryDataNodes('sample cpsPath')
263         then: 'the data persistence service method to query data nodes is invoked once'
264             1 * mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin','ncmp-dmi-registry','sample cpsPath', INCLUDE_ALL_DESCENDANTS)
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 * mockCpsDataPersistenceService.getDataNode('NCMP-Admin','ncmp-dmi-registry','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 * mockCpsDataPersistenceService.getDataNode('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
281     }
282
283     def 'Query anchors'() {
284         when: 'the method to query anchors is called'
285             objectUnderTest.queryAnchors(['sample-module-name'])
286         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
287             1 * mockCpsAdminPersistenceService.queryAnchors('NFP-Operational',['sample-module-name'])
288     }
289
290     def 'Get anchors'() {
291         when: 'the method to get anchors with no parameters is called'
292             objectUnderTest.getAnchors()
293         then: 'the admin persistence service method to query anchors is invoked once with a specific dataspace name'
294             1 * mockCpsAdminPersistenceService.getAnchors('NFP-Operational')
295     }
296
297     def 'Replace list content'() {
298         when: 'replace list content method is called with xpath and data nodes collection'
299             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
300         then: 'the cps data service method to replace list content is invoked once with same parameters'
301             1 * mockCpsDataService.replaceListContent('NCMP-Admin', 'ncmp-dmi-registry',
302                     'sample xpath', [new DataNode()], NO_TIMESTAMP);
303     }
304
305     def 'Delete data node via xPath'() {
306         when: 'Delete data node method is called with xpath as parameter'
307             objectUnderTest.deleteDataNode('sample dataNode xpath')
308         then: 'the cps data service method to delete data node is invoked once with the same xPath'
309             1 * mockCpsDataService.deleteDataNode('NCMP-Admin', 'ncmp-dmi-registry',
310                     'sample dataNode xpath', NO_TIMESTAMP);
311     }
312
313 }