Save new cm notification subscription
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2024 Nordix Foundation
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2021-2022 Bell Canada
6  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
7  *  ================================================================================
8  *  Licensed under the Apache License, Version 2.0 (the "License");
9  *  you may not use this file except in compliance with the License.
10  *  You may obtain a copy of the License at
11  *
12  *        http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *  Unless required by applicable law or agreed to in writing, software
15  *  distributed under the License is distributed on an "AS IS" BASIS,
16  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  *  See the License for the specific language governing permissions and
18  *  limitations under the License.
19  *
20  *  SPDX-License-Identifier: Apache-2.0
21  *  ============LICENSE_END=========================================================
22  */
23
24 package org.onap.cps.ncmp.api.impl
25
26 import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse
27
28 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
29 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
30 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
31 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
32 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
33 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
34 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
35 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
36
37 import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
38 import com.hazelcast.map.IMap
39 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
40 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
41 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
42 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
43 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
44 import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
45 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
46 import org.onap.cps.ncmp.api.impl.inventory.CompositeState
47 import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
48 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
49 import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
50 import org.onap.cps.ncmp.api.models.DataOperationDefinition
51 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
52 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
53 import org.onap.cps.ncmp.api.models.ConditionApiProperties
54 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
55 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
56 import org.onap.cps.ncmp.api.models.DataOperationRequest
57 import org.onap.cps.spi.exceptions.CpsException
58 import org.onap.cps.spi.model.ConditionProperties
59
60 import java.util.stream.Collectors
61 import org.onap.cps.utils.JsonObjectMapper
62 import com.fasterxml.jackson.databind.ObjectMapper
63 import org.onap.cps.api.CpsDataService
64 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
65 import org.onap.cps.spi.FetchDescendantsOption
66 import org.onap.cps.spi.model.DataNode
67 import org.springframework.http.HttpStatus
68 import org.springframework.http.ResponseEntity
69 import spock.lang.Specification
70
71 class NetworkCmProxyDataServiceImplSpec extends Specification {
72
73     def mockCpsDataService = Mock(CpsDataService)
74     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
75     def mockDmiDataOperations = Mock(DmiDataOperations)
76     def nullNetworkCmProxyDataServicePropertyHandler = null
77     def mockInventoryPersistence = Mock(InventoryPersistence)
78     def mockCmHandleQueries = Mock(CmHandleQueries)
79     def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
80     def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
81     def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
82     def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
83     def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>)
84     def mockTrustLevelManager = Mock(TrustLevelManager)
85     def mockAlternateIdChecker = Mock(AlternateIdChecker)
86
87     def NO_TOPIC = null
88     def NO_REQUEST_ID = null
89     def NO_AUTH_HEADER = null
90     def OPTIONS_PARAM = '(a=1,b=2)'
91     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
92
93     def objectUnderTest = new NetworkCmProxyDataServiceImpl(
94             spiedJsonObjectMapper,
95             mockDmiDataOperations,
96             nullNetworkCmProxyDataServicePropertyHandler,
97             mockInventoryPersistence,
98             mockCmHandleQueries,
99             mockCpsCmHandlerQueryService,
100             mockLcmEventsCmHandleStateHandler,
101             mockCpsDataService,
102             stubModuleSyncStartedOnCmHandles,
103             stubTrustLevelPerDmiPlugin,
104             mockTrustLevelManager,
105             mockAlternateIdChecker)
106
107     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
108
109     def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
110
111     def 'Write resource data for pass-through running from DMI using POST.'() {
112         given: 'cpsDataService returns valid datanode'
113             mockDataNode()
114         when: 'write resource data is called'
115             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
116                 'testResourceId', CREATE,
117                 '{some-json}', 'application/json', null)
118         then: 'DMI called with correct data'
119             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
120                     CREATE, '{some-json}', 'application/json', null)
121                 >> { new ResponseEntity<>(HttpStatus.CREATED) }
122     }
123
124     def 'Get resource data for pass-through operational from DMI.'() {
125         given: 'cpsDataService returns valid data node'
126             mockDataNode()
127         and: 'get resource data from DMI is called'
128             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
129                     new ResponseEntity<>('dmi-response', HttpStatus.OK)
130         when: 'get resource data operational for cm-handle is called'
131             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
132         then: 'DMI returns a json response'
133             assert response == 'dmi-response'
134     }
135
136     def 'Get resource data for pass-through running from DMI.'() {
137         given: 'cpsDataService returns valid data node'
138             mockDataNode()
139         and: 'DMI returns valid response and data'
140             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER) >>
141                     new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
142         when: 'get resource data is called'
143             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID, NO_AUTH_HEADER)
144         then: 'get resource data returns expected response'
145             assert response == '{dmi-response}'
146     }
147
148     def 'Get resource data for operational (cached) data.'() {
149         given: 'CPS Data service returns some object(s)'
150             mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
151         when: 'get resource data is called'
152             def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
153         then: 'get resource data returns teh first object from the data service'
154             assert response == 'First Object'
155     }
156
157     def 'Execute (async) data operation for #datastoreName from DMI.'() {
158         given: 'cpsDataService returns valid data node'
159             def dataOperationRequest = getDataOperationRequest(datastoreName)
160         when: 'request resource data for data operation is called'
161             objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
162         then: 'request resource data for data operation returns expected response'
163             1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
164         where: 'the following data stores are used'
165             datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
166     }
167
168     def 'Getting Yang Resources.'() {
169         when: 'yang resources is called'
170             objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
171         then: 'CPS module services is invoked for the correct dataspace and cm handle'
172             1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
173     }
174
175     def 'Get a cm handle.'() {
176         given: 'the system returns a yang modelled cm handle'
177             def dmiServiceName = 'some service name'
178             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
179                     lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
180                     lastUpdateTime: 'some-timestamp',
181                     dataSyncEnabled: false,
182                     dataStores: dataStores())
183             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
184             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
185             def moduleSetTag = 'some-module-set-tag'
186             def alternateId = 'some-alternate-id'
187             def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
188                     dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState,
189                     moduleSetTag: moduleSetTag, alternateId: alternateId)
190             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
191         when: 'getting cm handle details for a given cm handle id from ncmp service'
192             def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
193         then: 'the result is a ncmpServiceCmHandle'
194             result.class == NcmpServiceCmHandle.class
195         and: 'the cm handle contains the cm handle id'
196             result.cmHandleId == 'some-cm-handle'
197         and: 'the cm handle contains the alternate id'
198             result.alternateId == 'some-alternate-id'
199         and: 'the cm handle contains the module-set-tag'
200             result.moduleSetTag == 'some-module-set-tag'
201         and: 'the cm handle contains the DMI Properties'
202             result.dmiProperties ==[ Book:'Romance Novel' ]
203         and: 'the cm handle contains the public Properties'
204             result.publicProperties == [ "Public Book":'Public Romance Novel' ]
205         and: 'the cm handle contains the cm handle composite state'
206             result.compositeState == compositeState
207     }
208
209     def 'Get cm handle public properties'() {
210         given: 'a yang modelled cm handle'
211             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
212             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
213             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
214         and: 'the system returns this yang modelled cm handle'
215             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
216         when: 'getting cm handle public properties for a given cm handle id from ncmp service'
217             def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
218         then: 'the result returns the correct data'
219             result == [ 'public prop' : 'some public prop' ]
220     }
221
222     def 'Execute cm handle id search for inventory'() {
223         given: 'a ConditionApiProperties object'
224             def conditionProperties = new ConditionProperties()
225             conditionProperties.conditionName = 'hasAllProperties'
226             conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
227             def conditionServiceProps = new CmHandleQueryServiceParameters()
228             conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
229         and: 'the system returns an set of cmHandle ids'
230             mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
231         when: 'getting cm handle id set for a given dmi property'
232             def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
233         then: 'the result returns the correct 2 elements'
234             assert result.size() == 2
235             assert result.contains('cmHandle1')
236             assert result.contains('cmHandle2')
237     }
238
239     def 'Get cm handle composite state'() {
240         given: 'a yang modelled cm handle'
241             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
242                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
243                 lastUpdateTime: 'some-timestamp',
244                 dataSyncEnabled: false,
245                 dataStores: dataStores())
246             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
247             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
248             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
249         and: 'the system returns this yang modelled cm handle'
250             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
251         when: 'getting cm handle composite state for a given cm handle id from ncmp service'
252             def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
253         then: 'the result returns the correct data'
254             result == compositeState
255     }
256
257     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
258         given: 'cpsDataService returns valid datanode'
259             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
260         when: 'get resource data is called'
261             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
262                 'testResourceId', UPDATE,
263                 '{some-json}', 'application/json', NO_AUTH_HEADER)
264         then: 'DMI called with correct data'
265             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
266                     UPDATE, '{some-json}', 'application/json', NO_AUTH_HEADER)
267                 >> { new ResponseEntity<>(HttpStatus.OK) }
268     }
269
270     def 'Verify modules and create anchor params.'() {
271         given: 'dmi plugin registration return created cm handles'
272             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
273                     dmiDataPlugin: 'service2')
274             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
275             mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
276         and: 'no rejected cm handles because of alternate ids'
277             mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> []
278         when: 'parse and create cm handle in dmi registration then sync module'
279             mockDmiPluginRegistration.createdCmHandles = ['test-cm-handle-id']
280             objectUnderTest.processCreatedCmHandles(mockDmiPluginRegistration, new DmiPluginRegistrationResponse())
281         then: 'system persists the cm handle state'
282             1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
283                 args -> {
284                           def cmHandleStatePerCmHandle = (args[0] as Collection)
285                           cmHandleStatePerCmHandle.each {
286                             assert it.id == 'test-cm-handle-id'
287                           }
288                     }
289             }
290     }
291
292     def 'Execute cm handle id search'() {
293         given: 'valid CmHandleQueryApiParameters input'
294             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
295             def conditionApiProperties = new ConditionApiProperties()
296             conditionApiProperties.conditionName = 'hasAllModules'
297             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
298             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
299         and: 'query cm handle method return with a data node list'
300             mockCpsCmHandlerQueryService.queryCmHandleIds(
301                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
302                     >> ['cm-handle-id-1']
303         when: 'execute cm handle search is called'
304             def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
305         then: 'result is the same collection as returned by the CPS Data Service'
306             assert result == ['cm-handle-id-1']
307     }
308
309     def 'Getting module definitions by module'() {
310         when: 'get module definitions is performed with module name'
311             objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
312         then: 'ncmp inventory persistence service is invoked once with correct parameters'
313             1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
314     }
315
316     def 'Getting module definitions by cm handle id'() {
317         when: 'get module definitions is performed with cm handle id'
318             objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
319         then: 'ncmp inventory persistence service is invoked once with correct parameter'
320             1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
321     }
322
323     def 'Execute cm handle search'() {
324         given: 'valid CmHandleQueryApiParameters input'
325             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
326             def conditionApiProperties = new ConditionApiProperties()
327             conditionApiProperties.conditionName = 'hasAllModules'
328             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
329             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
330         and: 'query cm handle method return with a data node list'
331             mockCpsCmHandlerQueryService.queryCmHandles(
332                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
333                     >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
334         when: 'execute cm handle search is called'
335             def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
336         then: 'result is the same collection as returned by the CPS Data Service'
337             assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
338     }
339
340     def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
341         given: 'an existing cm handle composite state'
342             def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
343                 dataStores: CompositeState.DataStores.builder()
344                     .operationalDataStore(CompositeState.Operational.builder()
345                         .dataStoreSyncState(initialDataSyncState)
346                         .build()).build())
347         and: 'get cm handle state returns the composite state for the given cm handle id'
348             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
349         when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
350             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
351         then: 'the data sync enabled flag is set to #dataSyncEnabled'
352             compositeState.dataSyncEnabled == dataSyncEnabledFlag
353         and: 'the data store sync state is set to #expectedDataStoreSyncState'
354             compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
355         and: 'the cps data service to delete data nodes is invoked the expected number of times'
356             deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
357         and: 'the inventory persistence service to update node leaves is called with the correct values'
358             saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
359         where: 'the following data sync enabled flag is used'
360             scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
361             'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 1
362             'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
363             'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
364             'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
365     }
366
367     def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
368         given: 'a cm handle composite state'
369             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
370         and: 'get cm handle state returns the composite state for the given cm handle id'
371             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
372         when: 'set data sync enabled is called with the data sync enabled flag set to true'
373             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
374         then: 'the expected exception is thrown'
375             thrown(CpsException)
376         and: 'the inventory persistence service to update node leaves is not invoked'
377             0 * mockInventoryPersistence.saveCmHandleState(_, _)
378     }
379
380     def 'Get all cm handle IDs by DMI plugin identifier.' () {
381         given: 'cm handle queries service returns cm handles'
382             1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
383         when: 'cm handle Ids are requested with dmi plugin identifier'
384             def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
385         then: 'the result size is correct'
386             assert result.size() == 2
387         and: 'the result returns the correct details'
388             assert result.containsAll('cm-handle-1','cm-handle-2')
389     }
390
391     def dataStores() {
392         CompositeState.DataStores.builder()
393             .operationalDataStore(CompositeState.Operational.builder()
394                 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
395                 .lastSyncTime('some-timestamp').build()).build()
396     }
397
398     def mockDataNode() {
399         mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
400     }
401
402     def getDataOperationRequest(datastore) {
403         def dataOperationRequest = new DataOperationRequest()
404         def dataOperationDefinitions = new ArrayList()
405         dataOperationDefinitions.add(getDataOperationDefinition(datastore))
406         dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
407         return dataOperationRequest
408     }
409
410     def getDataOperationDefinition(datastore) {
411         def dataOperationDefinition = new DataOperationDefinition()
412         dataOperationDefinition.setOperation("read")
413         dataOperationDefinition.setOperationId("operational-12")
414         dataOperationDefinition.setDatastore(datastore)
415         def targetIds = new ArrayList()
416         targetIds.add("some-cm-handle")
417         dataOperationDefinition.setCmHandleIds(targetIds)
418         return dataOperationDefinition
419     }
420 }