summaryrefslogtreecommitdiffstats
path: root/cps-ncmp-service/src/test/groovy
diff options
context:
space:
mode:
authorleventecsanyi <levente.csanyi@est.tech>2024-01-11 12:53:26 +0100
committerToine Siebelink <toine.siebelink@est.tech>2024-02-01 09:37:09 +0000
commit0f6f416f45d5ad0e9bba67d459858995196f7a73 (patch)
treeb2fedbb61706817393dfe2bd118616da005820dc /cps-ncmp-service/src/test/groovy
parent0f6a966b363e5347de2d44b1527d19b4cf2825a6 (diff)
Introduce Hazelcast for alternateId-cmHandle relation
- added new Hazelcast config - added cache initialization in CmHandleIdMapper - added unit tests - updated deployment.rst - refactored cache updating Issue-ID: CPS-1988 Change-Id: Iea6f884e584bf8cea8612ddbced4329e783c60a5 Signed-off-by: leventecsanyi <levente.csanyi@est.tech>
Diffstat (limited to 'cps-ncmp-service/src/test/groovy')
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy16
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy10
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy83
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy59
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy117
5 files changed, 234 insertions, 51 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 f565ede394..59bf9420cf 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
@@ -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");
@@ -22,6 +22,7 @@
package org.onap.cps.ncmp.api.impl
import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
+import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
import org.onap.cps.ncmp.api.models.UpgradedCmHandles
import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
@@ -71,6 +72,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
def trustLevelPerDmiPlugin = [:]
def mockTrustLevelManager = Mock(TrustLevelManager)
+ def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
def objectUnderTest = getObjectUnderTest()
def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() {
@@ -432,11 +434,21 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
}
+ def 'Adding data to alternate id caches.'() {
+ given: 'a registration with three CM Handles to be created'
+ def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+ createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1', alternateId: 'my-alternate-id-1')])
+ when: 'the DMI plugin registration happens'
+ objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+ then: 'the new alternate id is added to the cache'
+ 1 * mockCmHandleIdMapper.addMapping('cmhandle1', 'my-alternate-id-1')
+ }
+
def getObjectUnderTest() {
return Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations,
mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries,
stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService,
- mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager))
+ mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin as Map<String, TrustLevel>, mockTrustLevelManager, mockCmHandleIdMapper))
}
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 4f7b726985..c1af902a08 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
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada
* Modifications Copyright (C) 2023 TechMahindra Ltd.
@@ -22,6 +22,9 @@
*/
package org.onap.cps.ncmp.api.impl
+
+import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
+
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
@@ -78,6 +81,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>)
def mockTrustLevelManager = Mock(TrustLevelManager)
+ def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
def NO_TOPIC = null
def NO_REQUEST_ID = null
@@ -97,7 +101,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
mockCpsDataService,
stubModuleSyncStartedOnCmHandles,
stubTrustLevelPerDmiPlugin,
- mockTrustLevelManager)
+ mockTrustLevelManager,
+ mockCmHandleIdMapper
+ )
def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
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 2a15e6c1af..f94c34c589 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
@@ -22,13 +22,9 @@
package org.onap.cps.ncmp.api.impl
-import ch.qos.logback.classic.Level
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.spi.ILoggingEvent
+
import com.fasterxml.jackson.databind.ObjectMapper
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeEach
-import org.slf4j.LoggerFactory
+import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
@@ -37,7 +33,6 @@ import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
-import ch.qos.logback.core.read.ListAppender
import org.onap.cps.api.CpsDataService
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
@@ -53,20 +48,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
def mockInventoryPersistence = Mock(InventoryPersistence)
def mockCpsDataService = Mock(CpsDataService)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- def logger = Spy(ListAppender<ILoggingEvent>)
-
- @BeforeEach
- void setup() {
- ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).addAppender(logger);
- logger.start();
- }
+ def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
- @AfterEach
- void teardown() {
- ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders();
- }
-
- def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper)
+ def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockCmHandleIdMapper)
def static cmHandleId = 'myHandle1'
def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']"
@@ -198,36 +182,41 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
2 * mockInventoryPersistence.replaceListContent(cmHandleXpath,_)
}
- def 'Update CM Handle Alternate ID when #scenario'() {
- given: 'an existing cm handle with alternate id #existingAlternateId'
- DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['alternate-id': oldAlternateId])
- and: 'an update request with an alternate id #newAlternateId'
- def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: newAlternateId)
- when: 'update data node leaves is called with the update request'
+ def 'Update CM Handle Alternate ID with #scenario'() {
+ given: 'an existing cm handle'
+ DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath)
+ and: 'an update request with an alternate id'
+ 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'
- numberOfInvocations * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> { args ->
- assert args[3].contains(newAlternateId)
- }
- and: 'correct information is logged'
- def lastLoggingEvent = logger.list[0]
- if (expectLogWarning) {
- assert lastLoggingEvent.level == Level.WARN
- assert lastLoggingEvent.formattedMessage.contains('Unable')
- } else if (numberOfInvocations == 1) {
- assert lastLoggingEvent.level == Level.INFO
- assert lastLoggingEvent.formattedMessage.contains('Updating alternateId')
- } else {
- assert lastLoggingEvent == null
- }
+ callsToDataService * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >>
+ { args ->
+ assert args[3].contains('alt-1')
+ }
+ mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> isNewMapping
where: 'following updates are attempted'
- scenario | oldAlternateId | newAlternateId || numberOfInvocations | expectLogWarning
- 'old alternate id null' | null | 'new' || 1 | false
- 'old alternate id empty' | '' | 'new' || 1 | false
- 'old alternate id not empty' | 'old' | 'new' || 0 | true
- 'same alternate id' | 'old' | 'old' || 0 | false
- 'empty new alternate id' | 'old' | '' || 0 | false
- 'null new alternate id' | 'old' | null || 0 | false
+ scenario | isNewMapping || callsToDataService
+ 'new alternate id ' | true || 1
+ 'existing alternate id' | false || 0
+ }
+
+ 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])
+ and: 'a new mapping is added'
+ mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> true
+ and: 'but an exception occurs while saving'
+ def originalException = new NullPointerException('some exception')
+ mockCpsDataService.updateNodeLeaves(*_) >> { throw originalException }
+ when: 'updating of alternate id called'
+ objectUnderTest.updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle)
+ then: 'the original exception is thrown up'
+ def thrownException = thrown(NullPointerException)
+ assert thrownException == originalException
+ and: 'the mapping is removed from the cache'
+ 1 * mockCmHandleIdMapper.removeMapping(cmHandleId)
}
def convertToProperties(expectedPropertiesAfterUpdateAsMap) {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy
new file mode 100644
index 0000000000..71c5293e27
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START========================================================
+ * Copyright (C) 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.
+ * 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.core.Hazelcast
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import spock.lang.Specification
+
+@SpringBootTest(classes = [AlternateIdCacheConfig])
+class AlternateIdCacheConfigSpec extends Specification {
+
+ @Autowired
+ private Map<String, String> cmHandleIdPerAlternateId
+ @Autowired
+ private Map<String, String> alternateIdPerCmHandleId
+
+ def 'Embedded (hazelcast) cache for alternate id - cm handle id caches.'() {
+ expect: 'system is able to create an instance of the Alternate ID Cache'
+ assert null != cmHandleIdPerAlternateId
+ assert null != alternateIdPerCmHandleId
+ and: 'there are at least 2 instances'
+ assert Hazelcast.allHazelcastInstances.size() > 1
+ and: 'Alternate ID Caches are present'
+ assert Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceAlternateIdPerCmHandleIdMap')
+ && Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceCmHandleIdPerAlternateIdMap')
+ }
+
+ def 'Verify configs of the alternate id distributed objects.'(){
+ when: 'retrieving the map config of module set tag'
+ def alternateIdConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceAlternateIdPerCmHandleIdMap').config
+ def alternateIdMapConfig = alternateIdConfig.mapConfigs.get('alternateIdPerCmHandleIdMapConfig')
+ def cmHandleIdConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceCmHandleIdPerAlternateIdMap').config
+ def cmHandleIdIdMapConfig = cmHandleIdConfig.mapConfigs.get('cmHandleIdPerAlternateIdMapConfig')
+ then: 'the map configs have the correct number of backups'
+ assert alternateIdMapConfig.backupCount == 3
+ assert alternateIdMapConfig.asyncBackupCount == 3
+ assert cmHandleIdIdMapConfig.backupCount == 3
+ assert cmHandleIdIdMapConfig.asyncBackupCount == 3
+ }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy
new file mode 100644
index 0000000000..0a2962e98f
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy
@@ -0,0 +1,117 @@
+/*
+ * ============LICENSE_START========================================================
+ * Copyright (c) 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.
+ * 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.utils
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.slf4j.LoggerFactory
+import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
+import spock.lang.Specification
+
+class CmHandleIdMapperSpec extends Specification {
+
+ def alternateIdPerCmHandle = new HashMap<String, String>()
+ def cmHandlePerAlternateId = new HashMap<String, String>()
+ def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
+
+ def objectUnderTest = new CmHandleIdMapper(alternateIdPerCmHandle, cmHandlePerAlternateId, mockCpsCmHandlerQueryService)
+
+ def logger = Spy(ListAppender<ILoggingEvent>)
+
+ def setup() {
+ ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).addAppender(logger)
+ logger.start()
+ mockCpsCmHandlerQueryService.getAllCmHandles() >> []
+ assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id')
+ }
+
+ void cleanup() {
+ ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).detachAndStopAllAppenders()
+ }
+
+ def 'Checking entries in the cache.'() {
+ expect: 'the alternate id can be converted to cmhandle id'
+ assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == 'my cmhandle id'
+ and: 'the cmhandle id can be converted to alternate id'
+ assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
+ }
+
+ def 'Attempt adding #scenario alternate id.'() {
+ expect: 'cmhandle id - alternate id mapping fails'
+ assert objectUnderTest.addMapping('ch-1', alternateId) == false
+ and: 'alternate id looked up by cmhandle id unsuccessfully'
+ assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == null
+ where: 'alternate id has an invalid value'
+ scenario | alternateId
+ 'empty' | ''
+ 'blank' | ' '
+ 'null' | null
+ }
+
+ def 'Remove an entry from the cache.'() {
+ when: 'removing an entry'
+ objectUnderTest.removeMapping('my cmhandle id')
+ then: 'converting alternate id returns null'
+ assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == null
+ and: 'converting cmhandle id returns null'
+ assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == null
+ }
+
+ def 'Cannot update existing alternate id.'() {
+ given: 'attempt to update an existing alternate id'
+ objectUnderTest.addMapping('my cmhandle id', 'other id')
+ expect: 'still returns the original alternate id'
+ assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
+ and: 'converting other alternate id returns null'
+ assert objectUnderTest.alternateIdToCmHandleId('other id') == null
+ and: 'a warning is logged with the original alternate id'
+ def lastLoggingEvent = logger.list[1]
+ assert lastLoggingEvent.level == Level.WARN
+ assert lastLoggingEvent.formattedMessage.contains('my alternate id')
+ }
+
+ def 'Update existing alternate id with the same value.'() {
+ expect: 'update an existing alternate id with the same value returns false (no update)'
+ assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id') == false
+ and: 'conversion still returns the original alternate id'
+ assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
+ }
+
+ def 'Initializing cache #scenario.'() {
+ when: 'the cache is (re-)initialized'
+ objectUnderTest.cacheIsInitialized = false
+ objectUnderTest.initializeCache()
+ then: 'the alternate id can be converted to cmhandle id'
+ assert objectUnderTest.alternateIdToCmHandleId('alt-1') == convertedCmHandleId
+ and: 'the cm handle id can be converted to alternate id'
+ assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == convertedAlternatId
+ and: 'the query service is called to get the initial data'
+ 1 * mockCpsCmHandlerQueryService.getAllCmHandles() >> persistedCmHandles
+ where: 'the initial data has a cm handle #scenario'
+ scenario | persistedCmHandles || convertedAlternatId | convertedCmHandleId
+ 'with alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'alt-1')] || 'alt-1' | 'ch-1'
+ 'without alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1')] || null | null
+ 'with blank alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: ' ')] || null | null
+ }
+} \ No newline at end of file