866ff716d33ace79e310e894bb7a1df3d6bf5a40
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
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 com.hazelcast.map.IMap
27 import org.onap.cps.api.CpsAnchorService
28 import org.onap.cps.api.CpsDataService
29 import org.onap.cps.api.CpsModuleService
30 import org.onap.cps.api.exceptions.DataNodeNotFoundException
31 import org.onap.cps.api.exceptions.DataValidationException
32 import org.onap.cps.api.model.DataNode
33 import org.onap.cps.api.model.ModuleDefinition
34 import org.onap.cps.api.model.ModuleReference
35 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
36 import org.onap.cps.ncmp.api.inventory.models.CompositeState
37 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
38 import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate
39 import org.onap.cps.utils.ContentType
40 import org.onap.cps.utils.CpsValidator
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.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
50 import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
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
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 mockCmHandleIdPerAlternateId = Mock(IMap)
70
71     def objectUnderTest = Spy(new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleIdPerAlternateId))
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 = 'ch-1'
77     def updates = [
78             new CmHandleStateUpdate("ch-1", "READY"),
79             new CmHandleStateUpdate("ch-2", "DELETING")
80     ]
81     def alternateId = 'some-alternate-id'
82     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"]
83     def xpath = "/dmi-registry/cm-handles[@id='ch-1']"
84
85     def cmHandleId2 = 'another-cm-handle'
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='myAdditionalProperty']", leaves: leaves)
89
90     @Shared
91     def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='myAdditionalProperty']", leaves: ["name":"myAdditionalProperty", "value":"myAdditionalValue"]),
92                                                       new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='myPublicProperty']", leaves: ["name":"myPublicProperty","value":"myPublicValue"])]
93
94     @Shared
95     def childDataNodesForCmHandleWithAdditionalProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/additional-properties[@name='myAdditionalProperty']", leaves: ["name":"myAdditionalProperty", "value":"myAdditionalValue"])]
96
97     @Shared
98     def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/public-properties[@name='myPublicProperty']", leaves: ["name":"myPublicProperty","value":"myPublicValue"])]
99
100     @Shared
101     def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/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 additional properties'
115             result.additionalProperties.name == expectedAdditionalProperties
116         and: 'the expected public properties'
117             result.publicProperties.name == expectedPublicProperties
118         and: 'the state details are returned'
119             result.compositeState.cmHandleState == expectedCompositeState
120         and: 'the CM Handle ID is validated'
121             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
122         where: 'the following parameters are used'
123             scenario                           | childDataNodes                                    || expectedAdditionalProperties || expectedPublicProperties || expectedCompositeState
124             'no properties'                    | []                                                || []                           || []                       || null
125             'additional and public properties' | childDataNodesForCmHandleWithAllProperties        || ["myAdditionalProperty"]     || ["myPublicProperty"]     || null
126             'just additional properties'       | childDataNodesForCmHandleWithAdditionalProperties || ["myAdditionalProperty"]     || []                       || null
127             'just public properties'           | childDataNodesForCmHandleWithPublicProperties     || []                           || ["myPublicProperty"]     || null
128             'with state details'               | childDataNodesForCmHandleWithState                || []                           || []                       || CmHandleState.ADVISED
129     }
130
131     def 'Handling missing service names as null.'() {
132         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
133             def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId])
134             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
135         when: 'retrieving the yang modelled cm handle'
136             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
137         then: 'the service names are returned as null'
138             result.dmiServiceName == null
139             result.dmiDataServiceName == null
140             result.dmiModelServiceName == null
141         and: 'the CM Handle ID is validated'
142             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
143     }
144
145     def 'Retrieve multiple YangModelCmHandles using cm handle ids.'() {
146         given: 'the cps data service returns 2 data nodes from the DMI registry'
147             def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])]
148             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
149         when: 'retrieving the yang modelled cm handles'
150             def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
151         then: 'verify both have returned and cm handle Ids are correct'
152             assert results.size() == 2
153             assert results.id.containsAll([cmHandleId, cmHandleId2])
154     }
155
156     def 'YangModelCmHandles are not returned for invalid cm handle ids.'() {
157         given: 'invalid cm handle id throws a data validation exception'
158             mockCpsValidator.validateNameCharacters('Invalid Cm Handle Id') >> {throw new DataValidationException('','')}
159         and: 'empty collection is returned as no valid cm handle ids are given'
160             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [] , INCLUDE_ALL_DESCENDANTS) >> []
161         when: 'retrieving the yang modelled cm handles'
162             def results = objectUnderTest.getYangModelCmHandles(['Invalid Cm Handle Id'])
163         then: 'no YangModelCmHandle is returned'
164             assert results.size() == 0
165     }
166
167     def 'Get a Cm Handle Composite State.'() {
168         given: 'a valid cm handle id'
169             def cmHandleId = 'ch-1'
170             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
171         and: 'cps data service returns a valid data node'
172             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
173                     '/dmi-registry/cm-handles[@id=\'ch-1\']/state', INCLUDE_ALL_DESCENDANTS) >> [dataNode]
174         when: 'get cm handle state is invoked'
175             def result = objectUnderTest.getCmHandleState(cmHandleId)
176         then: 'result has returned the correct cm handle state'
177             result.cmHandleState == CmHandleState.ADVISED
178         and: 'the CM Handle ID is validated'
179             1 * mockCpsValidator.validateNameCharacters(cmHandleId)
180     }
181
182     def 'Update Cm Handle with #scenario State.'() {
183         given: 'a cm handle and a composite state'
184             def cmHandleId = 'ch-1'
185             def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
186         when: 'update cm handle state is invoked with the #scenario state'
187             objectUnderTest.saveCmHandleState(cmHandleId, compositeState)
188         then: 'update node leaves is invoked with the correct params'
189             1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'ch-1\']', expectedJsonData, _ as OffsetDateTime, ContentType.JSON)
190         where: 'the following states are used'
191             scenario    | cmHandleState          || expectedJsonData
192             'READY'     | CmHandleState.READY    || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
193             'LOCKED'    | CmHandleState.LOCKED   || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
194             'DELETING'  | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
195     }
196
197     def 'Update Cm Handles with #scenario States.'() {
198         given: 'a map of cm handles composite states'
199             def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
200             def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
201         and: 'alternate id cache contains the given cm handle reference'
202             mockCmHandleIdPerAlternateId.containsKey(_) >> true
203         when: 'update cm handle state is invoked with the #scenario state'
204             def cmHandleStateMap = ['ch-11' : compositeState1, 'ch-12' : compositeState2]
205             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
206         then: 'update node leaves is invoked with the correct params'
207             1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime, ContentType.JSON)
208         where: 'the following states are used'
209             scenario    | cmHandleState          || cmHandlesJsonDataMap
210             'READY'     | CmHandleState.READY    || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
211             'LOCKED'    | CmHandleState.LOCKED   || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
212             'DELETING'  | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
213     }
214
215     def 'Update cm handle states when #scenario in alternate id cache.'() {
216         given: 'a map of cm handles composite states'
217             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, lastUpdateTime: formattedDateAndTime)
218             def cmHandleStateMap = ['ch-1' : compositeState]
219         and: 'alternate id cache returns #scenario'
220             mockCmHandleIdPerAlternateId.containsKey(_) >> keyExists
221             mockCmHandleIdPerAlternateId.containsValue(_) >> valueExists
222         when: 'we update the state of a cm handle when #scenario'
223             objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap)
224         then: 'update node leaves is invoked correct number of times'
225             expectedCalls * mockCpsDataService.updateDataNodesAndDescendants(*_)
226         where: 'the following cm handle ids are used'
227             scenario            | keyExists | valueExists || expectedCalls
228             'id exists as key'  | true      | false       || 1
229             'id exists as value'| false     | true        || 1
230             'id does not exist' | false     | false       || 0
231
232     }
233
234     def 'Getting module definitions by module.'() {
235         given: 'cps module service returns module definition for module name'
236             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
237             mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id', 'some-module', '2024-01-25') >> moduleDefinitions
238         when: 'get module definitions is invoked with module name'
239             def result = objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cmHandle-Id', 'some-module', '2024-01-25')
240         then: 'returned result are the same module definitions as returned from module service'
241             assert result == moduleDefinitions
242         and: 'cm handle id and module name validated'
243             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id', 'some-module')
244     }
245
246     def 'Getting module definitions with cm handle id.'() {
247         given: 'cps module service returns module definitions for cm handle id'
248             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
249             mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions
250         when: 'get module definitions is invoked with cm handle id'
251             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
252         then: 'the returned result are the same module definitions as returned from the module service'
253             assert result == moduleDefinitions
254     }
255
256     def 'Get module references.'() {
257         given: 'cps module service returns a collection of module references'
258             def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
259             mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences
260         when: 'get yang resources module references by cmHandle is invoked'
261             def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id')
262         then: 'the returned result is a collection of module definitions'
263             assert result == moduleReferences
264         and: 'the CM Handle ID is validated'
265             1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
266     }
267
268     def 'Save Cmhandle.'() {
269         given: 'cmHandle represented as Yang Model'
270             def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', additionalProperties: [], publicProperties: [])
271         when: 'the method to save cmhandle is called'
272             objectUnderTest.saveCmHandle(yangModelCmHandle)
273         then: 'the data service method to save list elements is called once'
274             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
275                     _,null, ContentType.JSON) >> {
276                 args -> {
277                     assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}')
278                 }
279             }
280     }
281
282     def 'Save Multiple Cmhandles.'() {
283         given: 'cm handles represented as Yang Model'
284             def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
285             def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
286         when: 'the cm handles are saved'
287             objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2])
288         then: 'CPS Data Service persists both cm handles as a batch'
289             1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
290                     NCMP_DMI_REGISTRY_PARENT, _,null, ContentType.JSON) >> {
291                 args -> {
292                     def jsonData = (args[3] as String)
293                     jsonData.contains('cmhandle1')
294                     jsonData.contains('cmhandle2')
295                 }
296             }
297     }
298
299     def 'Delete list or list elements.'() {
300         when: 'the method to delete list or list elements is called'
301             objectUnderTest.deleteListOrListElement('sample xPath')
302         then: 'the data service method to save list elements is called once'
303             1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null)
304     }
305
306     def 'Get data node via xPath.'() {
307         when: 'the method to get data nodes is called'
308             objectUnderTest.getDataNode('sample xPath')
309         then: 'the data persistence service method to get data node is invoked once'
310             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS)
311     }
312
313     def 'Get cmHandle data node.'() {
314         given: 'expected xPath to get cmHandle data node'
315             def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']'
316         when: 'the method to get data nodes is called'
317             objectUnderTest.getCmHandleDataNodeByCmHandleId('sample cmHandleId', INCLUDE_ALL_DESCENDANTS)
318         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
319             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
320     }
321
322     def 'Get CM handle ids for CM Handles that has given module names.'() {
323         when: 'the method to get cm handles is called'
324             objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false)
325         then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
326             1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name'])
327     }
328
329     def 'Get Alternate Ids for CM Handles that has given module names.'() {
330         given: 'cps anchor service returns a CM-handle ID for the given module name'
331             mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) >> ['ch-1']
332         and: 'cps data service returns some data nodes for the given CM-handle ID'
333             def dataNodes = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])]
334             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ["/dmi-registry/cm-handles[@id='ch-1']"], OMIT_DESCENDANTS) >> dataNodes
335         when: 'the method to get cm-handle references by modules is called (outputting alternate IDs)'
336             def result = objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], true)
337         then: 'the result contains the correct alternate Id'
338             assert result == ['alt-1'] as Set
339     }
340
341     def 'Replace list content.'() {
342         when: 'replace list content method is called with xpath and data nodes collection'
343             objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
344         then: 'the cps data service method to replace list content is invoked once with same parameters'
345             1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP);
346     }
347
348     def 'Delete data node via xPath.'() {
349         when: 'Delete data node method is called with xpath as parameter'
350             objectUnderTest.deleteDataNode('sample dataNode xpath')
351         then: 'the cps data service method to delete data node is invoked once with the same xPath'
352             1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP);
353     }
354
355     def 'Delete multiple data nodes via xPath.'() {
356         when: 'Delete data nodes method is called with multiple xpaths as parameters'
357             objectUnderTest.deleteDataNodes(['xpath1', 'xpath2'])
358         then: 'the cps data service method to delete data nodes is invoked once with the same xPaths'
359             1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP);
360     }
361
362     def 'CM handle exists.'() {
363         given: 'data service returns a datanode with correct cm handle id'
364             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, OMIT_DESCENDANTS) >> [dataNode]
365         expect: 'cm handle exists for given cm handle id'
366             assert true == objectUnderTest.isExistingCmHandleId(cmHandleId)
367     }
368
369     def 'CM handle does not exist (data service returns empty collection).'() {
370         given: 'data service returns an empty datanode'
371             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, OMIT_DESCENDANTS) >> []
372         expect: 'false is returned for non-existent cm handle'
373             assert false == objectUnderTest.isExistingCmHandleId(cmHandleId)
374     }
375
376     def 'CM handle does not exist (data service throws).'() {
377         given: 'data service throws an exception'
378             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry/cm-handles[@id='non-existent-cm-handle']", OMIT_DESCENDANTS) >> {throw new DataNodeNotFoundException('','')}
379         expect: 'false is returned for non-existent cm handle'
380             assert false == objectUnderTest.isExistingCmHandleId('non-existent-cm-handle')
381     }
382
383     def 'Delete anchors.'() {
384         when: 'Deleting some anchors'
385             objectUnderTest.deleteAnchors(['anchor1' ,'anchor2'])
386         then: 'The call is delegated to the anchor service with teh correct parameters'
387             mockCpsAnchorService.deleteAnchors(NCMP_DATASPACE_NAME ,['anchor1' ,'anchor2'])
388     }
389
390     def 'Get Yang Model CM Handles without properties.'() {
391         given: 'the cps data service returns 2 data nodes from the DMI registry (omitting descendants)'
392             def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])]
393             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , OMIT_DESCENDANTS) >> dataNodes
394         when: 'retrieving cm handles without properties'
395             def result = objectUnderTest.getYangModelCmHandlesWithoutProperties([cmHandleId, cmHandleId2])
396         then: 'The cm handles from the data service are returned'
397             assert result.size() == 2
398             assert result.id.containsAll([cmHandleId, cmHandleId2])
399     }
400
401     def 'Update Cm Handle Field.'(){
402         when: 'update is called.'
403             objectUnderTest.updateCmHandleField('ch-1', 'my field', 'my new value')
404         then: 'call is delegated to updateCmHandleFields'
405         1 * objectUnderTest.updateCmHandleFields('my field', ['ch-1':'my new value'])
406     }
407
408     def 'Bulk update cm handle state.'(){
409         when: 'bulk update is called'
410             objectUnderTest.bulkUpdateCmHandleStates(updates)
411         then: 'call is made to update the fileds of the cm handle'
412             1 * objectUnderTest.updateCmHandleFields('cm-handle-state', ['ch-1':'READY','ch-2':'DELETING'])
413     }
414
415 }
416