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