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