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