diff options
Diffstat (limited to 'cps-ncmp-service/src/test')
11 files changed, 113 insertions, 183 deletions
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index 572adb8f0f..313d1b4afd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -21,14 +21,14 @@ package org.onap.cps.ncmp.api.impl -import java.util.stream.Collectors - import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import java.util.stream.Collectors +import org.onap.cps.ncmp.api.impl.inventory.CompositeState import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker import org.onap.cps.ncmp.api.models.UpgradedCmHandles @@ -72,7 +72,6 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) def objectUnderTest = getObjectUnderTest() - def mockModuleSetTagCache = [:] def setup() { // always accept all cm handles @@ -89,6 +88,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) and: 'cm handles are persisted' mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()] + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) and: 'cm handle is in READY state' mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true when: 'registration is processed' @@ -96,7 +96,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { then: 'cm-handles are removed first' 1 * objectUnderTest.parseAndProcessDeletedCmHandlesInRegistration(*_) and: 'de-registered cm handle entry is removed from in progress map' - 2 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') then: 'cm-handles are created' 1 * objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(*_) then: 'cm-handles are updated' @@ -110,15 +110,15 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) and: 'exception while checking cm handle state' - mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> isReady + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) when: 'registration is processed' def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) then: 'upgrade operation contains expected error code' - assert result.upgradedCmHandles.status[0] == expectedResponseStatus + assert result.upgradedCmHandles[0].status == expectedResponseStatus where: 'the following parameters are used' - scenario | isReady || expectedResponseStatus - 'READY' | true || Status.SUCCESS - 'Not READY' | false || Status.FAILURE + scenario | cmHandleState || expectedResponseStatus + 'READY' | CmHandleState.READY || Status.SUCCESS + 'Not READY' | CmHandleState.LOCKED || Status.FAILURE } def 'DMI Registration upgrade with exception #scenario'() { @@ -126,7 +126,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) and: 'exception while checking cm handle state' - mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> { throw exception } + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } when: 'registration is processed' def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) then: 'upgrade operation contains expected error code' @@ -453,7 +453,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { return Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries, stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker, mockModuleSetTagCache)) + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker)) } def addPersistedYangModelCmHandles(ids) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index 64bedb8ad0..fc548ebe9b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -81,7 +81,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>) def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) - def mockModuleSetTagCache = [:] def NO_TOPIC = null def NO_REQUEST_ID = null @@ -100,8 +99,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { stubModuleSyncStartedOnCmHandles, stubTrustLevelPerDmiPlugin, mockTrustLevelManager, - mockAlternateIdChecker, - mockModuleSetTagCache) + mockAlternateIdChecker) def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" @@ -175,14 +173,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { given: 'the system returns a yang modelled cm handle' def dmiServiceName = 'some service name' def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] + def moduleSetTag = 'some-module-set-tag' + def alternateId = 'some-alternate-id' def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, - dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, + moduleSetTag: moduleSetTag, alternateId: alternateId) 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle when: 'getting cm handle details for a given cm handle id from ncmp service' def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') @@ -190,13 +191,16 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { result.class == NcmpServiceCmHandle.class and: 'the cm handle contains the cm handle id' result.cmHandleId == 'some-cm-handle' + and: 'the cm handle contains the alternate id' + result.alternateId == 'some-alternate-id' + and: 'the cm handle contains the module-set-tag' + result.moduleSetTag == 'some-module-set-tag' and: 'the cm handle contains the DMI Properties' result.dmiProperties ==[ Book:'Romance Novel' ] and: 'the cm handle contains the public Properties' result.publicProperties == [ "Public Book":'Public Romance Novel' ] and: 'the cm handle contains the cm handle composite state' result.compositeState == compositeState - } def 'Get cm handle public properties'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy index d822f2e639..e00a42674b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy @@ -56,7 +56,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(), new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(), new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()] - def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)] + def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])] def 'Update CM Handle Public Properties: #scenario'() { given: 'the CPS service return a CM handle' @@ -105,7 +105,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def 'Update CM Handle Properties, remove all properties: #scenario'() { given: 'the CPS service return a CM handle' - def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: originalPropertyDataNodes) + def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes) mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode] and: 'an update cm handle request that removes all public properties(existing and non-existing)' def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] @@ -182,9 +182,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def 'Update CM Handle Alternate ID with #scenario'() { given: 'an existing cm handle' - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath) + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId]) and: 'an update request with an alternate id' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1' ) + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1') when: 'update alternate id method is called with the update request' objectUnderTest.updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle) then: 'the update node leaves method is invoked as many times as expected' @@ -202,7 +202,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def 'Alternate ID removed from cache when persisting fails.'() { given: 'an existing data node and an update request with an alternate id' def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1') - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['alternate-id': null]) + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': null]) and: 'an applicable alternate id for the cm handle' mockAlternateIdChecker.canApplyAlternateId(cmHandleId, '','alt-1') >> true and: 'but an exception occurs while saving' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy deleted file mode 100644 index 8a6a32bd8d..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy +++ /dev/null @@ -1,76 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.config.embeddedcache - -import com.hazelcast.config.Config -import com.hazelcast.core.Hazelcast -import org.onap.cps.spi.model.ModuleReference -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification - -@SpringBootTest(classes = [ModuleSetTagCacheConfig]) -class ModuleSetTagCacheConfigSpec extends Specification { - - @Autowired - private Map<String, Collection<ModuleReference>> moduleSetTagCache - - def 'Embedded (hazelcast) caches for module set tag.'() { - expect: 'system is able to create an instance of a map to hold module set tags' - assert null != moduleSetTagCache - and: 'there is at least 1 instance' - assert Hazelcast.allHazelcastInstances.size() > 0 - and: 'hazelcast instance name of module set tag is correct' - assert Hazelcast.allHazelcastInstances.name.contains('moduleSetTags') - } - - def 'Verify configs of module set tag distributed object.'(){ - when: 'retrieving the map config of module set tag' - def cacheConfig = Hazelcast.getHazelcastInstanceByName('moduleSetTags').config - def moduleSetTagMapConfig = cacheConfig.mapConfigs.get('moduleSetTagCacheMapConfig') - then: 'the module set tag map config has correct backup counts' - assert moduleSetTagMapConfig.backupCount == 3 - assert moduleSetTagMapConfig.asyncBackupCount == 3 - } - - def 'Verify deployment network configs of distributed cache of module set tag object.'() { - given: 'network config of module set tag cache' - def moduleSetTagNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSetTags').config.networkConfig - expect: 'module set tag cache has the correct settings' - assert moduleSetTagNetworkConfig.join.autoDetectionConfig.enabled - assert !moduleSetTagNetworkConfig.join.kubernetesConfig.enabled - } - - def 'Verify network of module set tag cache'() { - given: 'Synchronization config object and test configuration' - def objectUnderTest = new ModuleSetTagCacheConfig() - def testConfig = new Config() - when: 'kubernetes properties are enabled' - objectUnderTest.cacheKubernetesEnabled = true - objectUnderTest.cacheKubernetesServiceName = 'test-service-name' - and: 'method called to update the discovery mode' - objectUnderTest.updateDiscoveryMode(testConfig) - then: 'applied properties are reflected' - assert testConfig.networkConfig.join.kubernetesConfig.enabled - assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' - - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy index e951c31094..eb0e1100ed 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy @@ -38,7 +38,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']"; and: 'datanodes optionally returned' 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', - cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode + cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode when: 'we check for an ongoing cm subscription' def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path') then: 'we get expected response' @@ -48,4 +48,20 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification 'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscriptionIds': ['sub-1', 'sub-2']])] || true 'no datanodes present' | [] || false } -} + + def 'Checking uniqueness of incoming subscription ID'() { + given: 'a cps path with a subscription ID for querying' + def cpsPathQuery = objectUnderTest.SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted('some-sub') + and: 'relevant datanodes are returned' + 1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> + dataNodes + when: 'a subscription ID is tested for uniqueness' + def result = objectUnderTest.isUniqueSubscriptionId('some-sub') + then: 'result is as expected' + assert result == isValidSubscriptionId + where: 'following scenarios are used' + scenario | dataNodes || isValidSubscriptionId + 'datanodes present' | [new DataNode()] || false + 'no datanodes present' | [] || true + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy index 83acb2238a..cbc985f783 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -71,7 +71,7 @@ class InventoryPersistenceImplSpec extends Specification { .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) def cmHandleId = 'some-cm-handle' - def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] + def leaves = ["id":cmHandleId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" def cmHandleId2 = 'another-cm-handle' @@ -119,7 +119,7 @@ class InventoryPersistenceImplSpec extends Specification { def "Handling missing service names as null."() { given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes' - def dataNode = new DataNode(childDataNodes:[], leaves: [:]) + def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId]) mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] when: 'retrieving the yang modelled cm handle' def result = objectUnderTest.getYangModelCmHandle(cmHandleId) @@ -133,7 +133,7 @@ class InventoryPersistenceImplSpec extends Specification { def "Retrieve multiple YangModelCmHandles"() { given: 'the cps data service returns 2 data nodes from the DMI registry' - def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)] + def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])] mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes when: 'retrieving the yang modelled cm handle' def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 827a548ae4..44bc182000 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -125,7 +125,7 @@ class ModuleOperationsUtilsSpec extends Specification{ where: scenario | moduleSetTag || expectedDetails 'a module set tag' | 'someModuleSetTag' || 'someModuleSetTag' - 'empty module set tag' | '' || 'not-specified' + 'empty module set tag' | '' || '' } def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy index de783ed2ca..0c60e88772 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPE import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService @@ -50,16 +49,18 @@ class ModuleSyncServiceSpec extends Specification { def mockCmHandleQueries = Mock(CmHandleQueries) def mockCpsDataService = Mock(CpsDataService) def mockJsonObjectMapper = Mock(JsonObjectMapper) - def mockModuleSetTagCache = [:] def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, - mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper, mockModuleSetTagCache) + mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper) def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME - def static cmHandleWithModuleSetTag = new DataNodeBuilder().withXpath("//cm-handles[@module-set-tag='tag-1'][@id='otherId']").withAnchor('otherId').build() + def static cmHandleWithModuleSetTag = new DataNodeBuilder() + .withXpath("/dmi-registry/cm-handles[@id='otherId']") + .withLeaves(['id': 'otherId', 'module-set-tag': 'tag-1']) + .withAnchor('otherId').build() - def 'Sync model for a NEW cm handle without cache with #scenario'() { - given: 'a cm handle having some state' + def 'Sync model for a NEW cm handle using module set tags: #scenario.'() { + given: 'a cm handle state to be synced' def ncmpServiceCmHandle = new NcmpServiceCmHandle() ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).build()) ncmpServiceCmHandle.cmHandleId = 'ch-1' @@ -68,22 +69,22 @@ class ModuleSyncServiceSpec extends Specification { def moduleReferences = [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ] mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences and: 'DMI-Plugin returns resource(s) for "new" module(s)' - mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('module1', '1')]) >> newModuleNameContentToMap + mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap and: 'the module service identifies #identifiedNewModuleReferences.size() new modules' - mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReferences(identifiedNewModuleReferences) - and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag' - mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='new-tag-1']", - FetchDescendantsOption.OMIT_DESCENDANTS) >> [] + mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences + and: 'system contains other cm handle with "same tag" (that is READY)' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag when: 'module sync is triggered' - objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle) + objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle) then: 'create schema set from module is invoked with correct parameters' 1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', newModuleNameContentToMap, moduleReferences) and: 'anchor is created with the correct parameters' 1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1') where: 'the following parameters are used' - scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | moduleSetTag - 'one new module' | [['module2': '2'], ['module3': '3']] | [['module1': '1']] | [module1: 'some yang source'] | '' - 'no new module' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'new-tag-1' + scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | moduleSetTag | existingCmHandlesWithSameTag + 'one new module, new tag' | [['module2': '2'], ['module3': '3']] | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | '' | [] + 'no new module, new tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'new-tag-1' | [] + 'same tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'same-tag' | [cmHandleWithModuleSetTag] } def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() { @@ -95,33 +96,29 @@ class ModuleSyncServiceSpec extends Specification { def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1', '') and: 'some module references' def moduleReferences = [ new ModuleReference('module1','1') ] - and: 'cache or DMI operations returns some module references for upgraded cm handle' - if (populateCache) { - mockModuleSetTagCache.put('tag-1', moduleReferences) - } else { - mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences - } + and: 'DMI operations returns some module references for upgraded cm handle' + mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences + mockDmiModelOperations.getNewYangResourcesFromDmi(_, []) >> [:] and: 'none of these module references are new (unknown to the system)' - mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> [] + mockCpsModuleService.identifyNewModuleReferences(_) >> [] and: 'CPS-Core returns list of existing module resources for TBD' mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ] and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag' - mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='tag-1']", FetchDescendantsOption.OMIT_DESCENDANTS) >> existingCmHandlesWithSameTag + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag and: 'the other cm handle is a state ready' mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true when: 'module sync is triggered' - objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle) + objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) then: 'update schema set from module is invoked for the upgraded cm handle' - expectedCallsToUpgradeSchemaSet * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) - and: 'create schema set from module is invoked for the upgraded cm handle' - expectedCallsToCeateSchemaSet * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) + 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) + and: 'create schema set from module is not invoked for the upgraded cm handle' + 0 * mockCpsModuleService.createSchemaSetFromModules(*_) and: 'No anchor is created for the upgraded cm handle' 0 * mockCpsAnchorService.createAnchor(*_) where: 'the following parameters are used' - scenario | populateCache | existingCmHandlesWithSameTag || expectedCallsToUpgradeSchemaSet | expectedCallsToCeateSchemaSet - 'new' | false | [] || 0 | 1 - 'in cache' | true | [] || 1 | 0 - 'in database' | false | [cmHandleWithModuleSetTag] || 1 | 0 + scenario | existingCmHandlesWithSameTag + 'new' | [] + 'in database' | [cmHandleWithModuleSetTag] } def 'upgrade model for a existing cm handle'() { @@ -136,10 +133,10 @@ class ModuleSyncServiceSpec extends Specification { def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences and: 'a cm handle with the same moduleSetTag can be found in the registry' - mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='targetModuleSetTag']", - FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1', 'cm-handle-state': 'READY'])] + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1'], + childDataNodes: [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']/state', leaves: ['cm-handle-state': 'READY'])])] when: 'module upgrade is triggered' - objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle) + objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) then: 'the upgrade is delegated to the module service (with the correct parameters)' 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', Collections.emptyMap(), moduleReferences) } @@ -171,13 +168,4 @@ class ModuleSyncServiceSpec extends Specification { result == unsupportedOperationException } - def toModuleReferences(moduleReferenceAsMap) { - def moduleReferences = [].withDefault { [:] } - moduleReferenceAsMap.forEach(property -> - property.forEach((moduleName, revision) -> { - moduleReferences.add(new ModuleReference('moduleName' : moduleName, 'revision' : revision)) - })) - return moduleReferences - } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy index ece2d9f86c..6c0d6dfb87 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -85,8 +85,8 @@ class ModuleSyncTasksSpec extends Specification { 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-1') 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-2') and: 'module sync service is invoked for each cm handle' - 1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } - 1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-2') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } and: 'the state handler is called for the both cm handles' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY) @@ -102,7 +102,7 @@ class ModuleSyncTasksSpec extends Specification { def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED) 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState and: 'module sync service attempts to sync the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } when: 'module sync is executed' objectUnderTest.performModuleSync([cmHandle], batchCount) then: 'update lock reason, details and attempts is invoked' @@ -123,7 +123,8 @@ class ModuleSyncTasksSpec extends Specification { .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build()) 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') } when: 'module sync is executed' objectUnderTest.performModuleSync([cmHandle], batchCount) then: 'update lock reason, details and attempts is invoked' @@ -170,7 +171,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'module sync poll is executed' objectUnderTest.performModuleSync([cmHandle1], batchCount) then: 'module sync service is invoked for cm handle' - 1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } and: 'the entry for other cm handle is still in the progress map' assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null } @@ -202,14 +203,6 @@ class ModuleSyncTasksSpec extends Specification { return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': cmHandleState]) } - def assertYamgModelCmHandleArgument(args, expectedCmHandleId) { - { - def yangModelCmHandle = args[0] - assert yangModelCmHandle.id == expectedCmHandleId - } - return true - } - def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) { { Map<YangModelCmHandle, CmHandleState> actualCmHandleStatePerCmHandle = args[0] diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index d1025f9d65..a105f84ebe 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,12 +51,6 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @SpringBean JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - @SpringBean - TaskExecutor stubbedTaskExecutor = Stub() - - @SpringBean - DmiServiceNameOrganizer stubbedDmiServiceNameOrganizer = Stub() - def 'Retrieving module references.'() { given: 'a cm handle' mockYangModelCmHandleRetrieval([]) @@ -148,23 +142,33 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'a positive response from DMI service when it is called with the expected parameters' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi + '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi when: 'get new yang resources from DMI service' - def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, unknownModuleReferences) + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result is the response from DMI service' assert result == [mod1:'some yang source'] where: 'the following DMI properties are used' - scenario | dmiProperties | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest - 'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences || '{"prop1":"val1"}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' - 'without module references' | [yangModelCmHandleProperty] | [] || '{"prop1":"val1"}' | '' - 'without properties' | [] | newModuleReferences || '{}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' + scenario | dmiProperties || expectedAdditionalPropertiesInRequest + 'with module references and properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}' + 'without properties' | [] || '{}' + } + + def 'Retrieving yang resources from DMI with no module references.'() { + given: 'a cm handle' + mockYangModelCmHandleRetrieval([]) + when: 'a get new yang resources from DMI is called with no module references' + def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, []) + then: 'no resources are returned' + assert result == [:] + and: 'no request is sent to DMI' + 0 * mockDmiRestClient.postOperationWithJsonData(*_) } def 'Retrieving yang resources from DMI with null DMI properties.'() { given: 'a cm handle' mockYangModelCmHandleRetrieval(null) when: 'a get new yang resources from DMI is called' - objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, []) + objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('mod1', 'A')]) then: 'a null pointer is thrown (we might need to address this later)' thrown(NullPointerException) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy index 6d733dc475..0ae348c074 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================== - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,9 +31,10 @@ class YangDataConverterSpec extends Specification{ leaves: ['name': 'dmiProp1', 'value': 'dmiValue1']) def dataNodePublicProperties = new DataNode(xpath:'/public-properties[@name="pubProp1"]', leaves: ['name': 'pubProp1', 'value': 'pubValue1']) - def dataNodeCmHandle = new DataNode(childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties]) + def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties]) when: 'the dataNode is converted' - def yangModelCmHandle = YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle,'sample-id') + def yangModelCmHandle = + YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) then: 'the converted object has the correct id' assert yangModelCmHandle.id == 'sample-id' and: 'the additional (dmi, private) properties are included' @@ -46,8 +47,8 @@ class YangDataConverterSpec extends Specification{ def 'Convert multiple cm handle data nodes'(){ given: 'two data nodes in a collection' - def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']'), - new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']')] + def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']', leaves: ['id':'some-cm-handle']), + new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']', leaves: ['id':'another-cm-handle'])] when: 'the data nodes are converted' def yangModelCmHandles = YangDataConverter.convertDataNodesToYangModelCmHandles(dataNodes) then: 'verify both have returned and CmHandleIds are correct' |