<artifactId>cps-ncmp-service</artifactId>
<properties>
- <minimum-coverage>0.97</minimum-coverage>
+ <minimum-coverage>0.99</minimum-coverage>
</properties>
<dependencies>
<dependency>
import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.kafka.KafkaException;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
public class CmAvcEventConsumer {
-
private static final String CLOUD_EVENT_SOURCE_SYSTEM_HEADER_KEY = "ce_source";
@Value("${app.ncmp.avc.cm-events-topic}")
final CloudEvent outgoingAvcEvent = cmAvcEventAsConsumerRecord.value();
final String outgoingAvcEventKey = cmAvcEventAsConsumerRecord.key();
- // Only for testing/demo
- if (outgoingAvcEventKey.equals("retry")) {
- throw new KafkaException("test kafka exception for testing");
- }
-
log.debug("Consuming AVC event with key : {} and value : {}", outgoingAvcEventKey, outgoingAvcEvent);
eventsProducer.sendCloudEventUsingEos(cmEventsTopicName, outgoingAvcEventKey, outgoingAvcEvent);
}
private boolean isEventFromOnapDmiPlugin(final Headers headers) {
final String sourceSystem = KafkaHeaders.getParsedKafkaHeader(headers, CLOUD_EVENT_SOURCE_SYSTEM_HEADER_KEY);
- return sourceSystem != null && sourceSystem.equals("ONAP-DMI-PLUGIN");
+ return "ONAP-DMI-PLUGIN".equals(sourceSystem);
}
}
cmAvcEvent.getData().getPushChangeUpdate().getDatastoreChanges().getIetfYangPatchYangPatch().getEdit();
edits.forEach(
- edit -> handleCmAvcEventOperation(CmAvcOperationEnum.fromValue(edit.getOperation()), cmHandleId, edit));
+ edit -> {
+ final String operationNameUpperCase = edit.getOperation().toUpperCase();
+ handleCmAvcEventOperation(CmAvcOperationEnum.valueOf(operationNameUpperCase), cmHandleId, edit);
+ });
}
private void handleCmAvcEventOperation(final CmAvcOperationEnum cmAvcOperation, final String cmHandleId,
log.info("Operation : {} requested for cmHandleId : {}", cmAvcOperation.getValue(), cmHandleId);
switch (cmAvcOperation) {
- case CREATE:
- handleCreate(cmHandleId, cmAvcEventEdit);
- break;
-
case UPDATE:
handleUpdate(cmHandleId, cmAvcEventEdit);
break;
-
case PATCH:
handlePatch(cmHandleId, cmAvcEventEdit);
break;
-
case DELETE:
handleDelete(cmHandleId, cmAvcEventEdit);
break;
-
- default:
- log.error("Unhandled operation : {} for cmHandleId : {}", cmAvcOperation, cmHandleId);
+ default: // CREATE (checkstyle complains if there is NO default)
+ handleCreate(cmHandleId, cmAvcEventEdit);
}
}
return value;
}
- /**
- * Returns the Operation Enum.
- *
- * @param value string operation
- * @return CmAvcOperationEnum
- */
- public static CmAvcOperationEnum fromValue(final String value) {
- for (final CmAvcOperationEnum b : CmAvcOperationEnum.values()) {
- if (b.value.equals(value)) {
- return b;
- }
- }
- throw new IllegalArgumentException("Unexpected value '" + value + "'");
- }
-
}
* @return mapped target event
*/
public static <T> T toTargetEvent(final CloudEvent cloudEvent, final Class<T> targetEventClass) {
- PojoCloudEventData<T> mappedCloudEvent = null;
+ PojoCloudEventData<T> pojoCloudEventData = null;
try {
- mappedCloudEvent =
+ pojoCloudEventData =
CloudEventUtils.mapData(cloudEvent, PojoCloudEventDataMapper.from(objectMapper, targetEventClass));
} catch (final RuntimeException runtimeException) {
runtimeException.getMessage());
}
- return mappedCloudEvent == null ? null : mappedCloudEvent.getValue();
+ return pojoCloudEventData == null ? null : pojoCloudEventData.getValue();
}
}
import org.springframework.test.context.ContextConfiguration;
import spock.lang.Specification;
-@SpringBootTest
-@ContextConfiguration(classes = [AdminCacheConfig])
+@SpringBootTest(classes = [AdminCacheConfig])
class AdminCacheConfigSpec extends Specification {
@Autowired
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * 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.impl.cache
+
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.map.IMap
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Qualifier
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest(classes = [AlternateIdCacheConfig])
+class AlternateIdCacheConfigSpec extends Specification {
+
+ @Autowired
+ @Qualifier("cmHandleIdPerAlternateId")
+ IMap<String, String> cmHandleIdPerAlternateId
+
+ def cleanupSpec() {
+ Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown()
+ }
+
+ def 'Hazelcast cache for alternate ids.'() {
+ expect: 'system is able to create an instance alternate id cache'
+ assert null != cmHandleIdPerAlternateId
+ and: 'there is at least 1 instance'
+ assert Hazelcast.allHazelcastInstances.size() > 0
+ and: 'Hazelcast cache instance for alternate ids is present'
+ assert Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').getMap('cmHandleIdPerAlternateId') != null
+ }
+}
--- /dev/null
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.impl.datajobs.subscription.cache
+
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.map.IMap
+import org.onap.cps.ncmp.impl.datajobs.subscription.models.DmiCmSubscriptionDetails
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest(classes = [CmSubscriptionConfig])
+class CmSubscriptionConfigSpec extends Specification {
+
+ @Autowired
+ IMap<String, Map<String, DmiCmSubscriptionDetails>> cmNotificationSubscriptionCache
+
+ def cleanupSpec() {
+ Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown()
+ }
+
+ def 'Hazelcast cache for cm subscriptions.'() {
+ expect: 'system is able to create an instance of the cm subscription cache'
+ assert null != cmNotificationSubscriptionCache
+ and: 'there is at least 1 instance'
+ assert Hazelcast.allHazelcastInstances.size() > 0
+ and: 'Hazelcast cache instance for cm subscriptions present'
+ assert Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').getMap('cmNotificationSubscriptionCache') != null
+ }
+}
def mockInventoryPersistence = Mock(InventoryPersistence)
@SpringBean
- CmAvcEventConsumer cmAvcEventConsumer = new CmAvcEventConsumer(eventsProducer, mockCmAvcEventService, mockInventoryPersistence)
+ CmAvcEventConsumer objectUnderTest = new CmAvcEventConsumer(eventsProducer, mockCmAvcEventService, mockInventoryPersistence)
@Autowired
JsonObjectMapper jsonObjectMapper
def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('group for Test A', CloudEventDeserializer))
- def testAvcEvent
- def testEventKey
+ def testEventKey = 'sample-key'
+ def validAvcEventAsJson
+ def onapDmiSourceSystem = 'ONAP-DMI-PLUGIN'
def setup() {
- testEventKey = 'sample-key'
- testAvcEvent = jsonObjectMapper.convertJsonString(getResourceFileContent('sampleAvcInputEvent.json'), AvcEvent.class)
+ validAvcEventAsJson = jsonObjectMapper.convertJsonString(getResourceFileContent('sampleAvcInputEvent.json'), AvcEvent.class)
}
- def 'Test A : Consume and forward valid message'() {
+ def 'Consume and forward avc event [test A, using specific topic].'() {
given: 'a cloud event'
- def testCloudEventSent = buildCloudEvent('sample-source', 'test-cmhandle1')
+ def testCloudEventSent = buildCloudEvent('sample-source', 'test-cmhandle1', validAvcEventAsJson)
and: 'consumer has a subscription on the target topic for this test'
- cmAvcEventConsumer.cmEventsTopicName = 'target-topic-for-Test-A'
- cloudEventKafkaConsumer.subscribe([cmAvcEventConsumer.cmEventsTopicName])
+ objectUnderTest.cmEventsTopicName = 'target-topic-for-Test-A'
+ cloudEventKafkaConsumer.subscribe([objectUnderTest.cmEventsTopicName])
and: 'event is wrapped in a consumer record with message key(cmHandleId) and value as cloud event'
- def consumerRecordReceived = new ConsumerRecord<String, CloudEvent>(cmAvcEventConsumer.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent)
+ def consumerRecordReceived = new ConsumerRecord<String, CloudEvent>(objectUnderTest.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent)
when: 'the event is consumed and forwarded to target topic'
- cmAvcEventConsumer.consumeAndForward(consumerRecordReceived)
+ objectUnderTest.consumeAndForward(consumerRecordReceived)
then: 'the consumer record can be read from the target topic within 2 seconds'
def consumerRecordOnTargetTopic = cloudEventKafkaConsumer.poll(Duration.ofMillis(2000)).iterator().next()
and: 'the target event has the same key as the source event to maintain the ordering in a partition'
and: 'event id is same between consumed and forwarded'
assert KafkaHeaders.getParsedKafkaHeader(consumerRecordOnTargetTopic.headers(), 'ce_id') == 'sample-eventid'
and: 'the event payload still matches'
- assert avcEventFromTargetTopic == testAvcEvent
+ assert avcEventFromTargetTopic == validAvcEventAsJson
}
- def 'Test B : Consume and process CM Avc Event when #scenario'() {
- given: 'a cloud event is created(what we get from ONAP-DMI-PLUGIN)'
- def sourceSystem = 'ONAP-DMI-PLUGIN'
- def testCloudEventSent = buildCloudEvent(sourceSystem, 'some-cmhandle-id')
+ def 'Consume and process CM Avc Event with #scenario. [test B, using specific topic]'() {
+ given: 'a cloud event is created with source ONAP-DMI-PLUGIN'
+ def testCloudEventSent = buildCloudEvent(sourceSystem, 'some-cmhandle-id', validAvcEventAsJson)
and: 'a separate topic for this test'
- cmAvcEventConsumer.cmEventsTopicName = 'some-topic-for-Test-B'
+ objectUnderTest.cmEventsTopicName = 'some-topic-for-Test-B'
and: 'inventory persistence service has #scenario'
- def compositeState = new CompositeState(dataSyncEnabled: dataSyncFlag)
- 1 * mockInventoryPersistence.getCmHandleState(_) >> compositeState
+ def compositeState = new CompositeState(dataSyncEnabled: dataSyncEnabled)
+ mockInventoryPersistence.getCmHandleState(_) >> compositeState
and: 'event has source system as ONAP-DMI-PLUGIN and key(cmHandleId) and value as cloud event'
- def consumerRecord = new ConsumerRecord<String, CloudEvent>(cmAvcEventConsumer.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent)
- consumerRecord.headers().add('ce_source', sourceSystem.getBytes(Charset.defaultCharset()))
+ def consumerRecord = new ConsumerRecord<String, CloudEvent>(objectUnderTest.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent)
+ if (sourceSystem!=null) {
+ consumerRecord.headers().add('ce_source', sourceSystem.getBytes(Charset.defaultCharset()))
+ }
when: 'the event is consumed'
- cmAvcEventConsumer.consumeAndForward(consumerRecord)
+ objectUnderTest.consumeAndForward(consumerRecord)
then: 'cm avc event is processed for updating the cached data'
expectedCallToProcessCmAvcEvent * mockCmAvcEventService.processCmAvcEvent(testEventKey, _) >> {args ->
- {
- assert args[1] instanceof AvcEvent
- }
+ { assert args[1] instanceof AvcEvent }
}
where: 'following scenarios are used'
- scenario | dataSyncFlag || expectedCallToProcessCmAvcEvent
- 'data sync flag enabled' | true || 1
- 'data sync flag disabled' | false || 0
-
+ scenario | sourceSystem | dataSyncEnabled || expectedCallToProcessCmAvcEvent
+ 'source ONAP, data sync enabled' | 'ONAP-DMI-PLUGIN' | true || 1
+ 'source ONAP, data sync disabled' | 'ONAP-DMI-PLUGIN' | false || 0
+ 'other source, data sync enabled' | 'other' | true || 0
}
- def buildCloudEvent(sourceSystem, cmHandleId) {
+ def 'Forward non-avc invalid event with source ONAP-DMI-PLUGIN. [test C, using specific topic]'() {
+ given: 'an non-avc cloud event'
+ def someJsonForOtherStructure = '{"some attribute":"for other event"}'
+ def testCloudEventSent = buildCloudEvent(onapDmiSourceSystem, 'some-cmhandle-id', someJsonForOtherStructure)
+ and: 'a separate topic for this test'
+ objectUnderTest.cmEventsTopicName = 'some-topic-for-Test-C'
+ and: 'inventory persistence service has data sync enabled for any node'
+ def compositeState = new CompositeState(dataSyncEnabled: true)
+ mockInventoryPersistence.getCmHandleState(_) >> compositeState
+ and: 'event has source system as ONAP-DMI-PLUGIN and key(cmHandleId) and value as cloud event'
+ def consumerRecord = new ConsumerRecord<String, CloudEvent>(objectUnderTest.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent)
+ consumerRecord.headers().add('ce_source', onapDmiSourceSystem.getBytes(Charset.defaultCharset()))
+ when: 'the event is consumed'
+ objectUnderTest.consumeAndForward(consumerRecord)
+ then: 'no AVC event processing takes place'
+ 0 * mockCmAvcEventService.processCmAvcEvent(testEventKey, _)
+ }
+ def buildCloudEvent(sourceSystem, cmHandleId, sourceEvent) {
return CloudEventBuilder.v1()
- .withData(jsonObjectMapper.asJsonBytes(testAvcEvent))
+ .withData(jsonObjectMapper.asJsonBytes(sourceEvent))
.withId('sample-eventid')
.withType('sample-test-type')
.withSource(URI.create(sourceSystem as String))
.withExtension('correlationid', cmHandleId).build()
-
}
}
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockYangParser = Mock(YangParser)
- def static NO_TIMESTAMP = null
- def static NO_XPATH = ''
- def objectUnderTest = new CmAvcEventService(
- mockCpsDataService,
- mockCpsAnchorService,
- jsonObjectMapper,
- mockYangParser
- )
+ def objectUnderTest = new CmAvcEventService(mockCpsDataService, mockCpsAnchorService, jsonObjectMapper, mockYangParser)
+ def NO_TIMESTAMP = null
+ def NO_XPATH = ''
def cmHandleId = 'test-cmhandle-id'
def sampleJson = '{"some-data": "test-data"}'
+ def testTargetPath = '/test/path'
+ def testAnchor = Mock(Anchor)
- def 'process CREATE operation'() {
+ def setup() {
+ mockCpsAnchorService.getAnchor(_, cmHandleId) >> testAnchor
+ mockYangParser.getCpsPathFromRestConfStylePath(testAnchor, testTargetPath) >> '/parsed/cps/path'
+ }
+
+ def 'Process CREATE operation.'() {
given: 'An edit with CREATE operation'
def testAvcEventForCreate = testAvcEvent('create', NO_XPATH)
when: 'The AVC event is processed'
1 * mockCpsDataService.saveData(_, cmHandleId, sampleJson, NO_TIMESTAMP)
}
- def 'Process UPDATE operation'() {
+ def 'Process UPDATE operation.'() {
given: 'An edit with UPDATE operation and a valid target path'
- def targetPath = '/test/path'
- def anchor = Mock(Anchor)
- mockCpsAnchorService.getAnchor(_, cmHandleId) >> anchor
- mockYangParser.getCpsPathFromRestConfStylePath(anchor, targetPath) >> '/parsed/cps/path'
- def testAvcEventForUpdate = testAvcEvent('update', targetPath)
+ def testAvcEventForUpdate = testAvcEvent('update', testTargetPath)
when: 'The AVC event is processed'
objectUnderTest.processCmAvcEvent(cmHandleId, testAvcEventForUpdate)
then: 'Data node and descendants are updated via CPS service'
1 * mockCpsDataService.updateDataNodeAndDescendants(_, cmHandleId, _, sampleJson, NO_TIMESTAMP, _)
}
- def 'Process PATCH operation'() {
+ def 'Process PATCH operation.'() {
given: 'An edit with PATCH operation and a valid target path'
- def targetPath = '/test/path'
- def anchor = Mock(Anchor)
- mockCpsAnchorService.getAnchor(_, cmHandleId) >> anchor
- mockYangParser.getCpsPathFromRestConfStylePath(anchor, targetPath) >> '/parsed/cps/path'
- def testAvcEventForPatch = testAvcEvent('patch', targetPath)
+ def testAvcEventForPatch = testAvcEvent('patch', testTargetPath)
when: 'The AVC event is processed'
objectUnderTest.processCmAvcEvent(cmHandleId, testAvcEventForPatch)
then: 'Node leaves are updated via CPS service'
1 * mockCpsDataService.updateNodeLeaves(_, cmHandleId, _, sampleJson, NO_TIMESTAMP, _)
}
- def 'Process DELETE operation with target'() {
+ def 'Process DELETE operation with target.'() {
given: 'An edit with DELETE operation and a specific target path'
- def targetPath = '/test/path'
- def anchor = Mock(Anchor)
- mockCpsAnchorService.getAnchor(_, cmHandleId) >> anchor
- mockYangParser.getCpsPathFromRestConfStylePath(anchor, targetPath) >> '/parsed/cps/path'
- def testAvcEventForDelete = testAvcEvent('delete', targetPath)
+ def testAvcEventForDelete = testAvcEvent('delete', testTargetPath)
when: 'The AVC event is processed'
objectUnderTest.processCmAvcEvent(cmHandleId, testAvcEventForDelete)
then: 'Data node is deleted at the given path'
1 * mockCpsDataService.deleteDataNode(_, cmHandleId, '/parsed/cps/path', NO_TIMESTAMP)
}
- def 'Process DELETE operation with no target (delete all)'() {
+ def 'Process DELETE operation with no target (delete all).'() {
given: 'An edit with DELETE operation and no target'
def testAvcEventForDelete = testAvcEvent('delete', NO_XPATH)
when: 'The AVC event is processed'
target << [null, '']
}
- def 'Resolve parent xpath correctly: #scenario'() {
+ def 'Resolve parent xpath correctly: #scenario.'() {
expect: 'Parent xpath is resolved as expected'
assert objectUnderTest.resolveParentNodeXpath(inputXpath) == expectedXpath
where: 'following scenarios are used'
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
+import org.onap.cps.ncmp.config.DmiHttpClientConfig
import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters
import org.onap.cps.utils.JsonObjectMapper
import org.springframework.http.HttpStatus
def mockWebServer = new MockWebServer()
def baseUrl = mockWebServer.url('/')
- def webClientForMockServer = WebClient.builder().baseUrl(baseUrl.toString()).build()
def urlTemplateParameters = new UrlTemplateParameters('/myPath', [someParam: 'value'])
def mockDmiServiceAuthenticationProperties = Mock(DmiServiceAuthenticationProperties)
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+ def webClientBuilder = WebClient.builder().baseUrl(baseUrl.toString())
+ def dmiWebClientsConfiguration = new DmiWebClientsConfiguration(new DmiHttpClientConfig())
+ def webClientForMockServer = dmiWebClientsConfiguration.dataServicesWebClient(webClientBuilder)
+
def objectUnderTest = new DmiRestClient(mockDmiServiceAuthenticationProperties, jsonObjectMapper, webClientForMockServer, webClientForMockServer, webClientForMockServer)
def cleanup() throws IOException {
import com.fasterxml.jackson.databind.ObjectMapper
import com.hazelcast.map.IMap
-
-import java.time.OffsetDateTime
-import java.time.ZoneOffset
-import java.time.format.DateTimeFormatter
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.model.DataNode
import org.onap.cps.api.model.ModuleDefinition
import org.onap.cps.api.model.ModuleReference
-import org.onap.cps.utils.CpsValidator
-import org.onap.cps.ncmp.api.inventory.models.CompositeState
import org.onap.cps.ncmp.api.inventory.models.CmHandleState
+import org.onap.cps.ncmp.api.inventory.models.CompositeState
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
import org.onap.cps.utils.JsonObjectMapper
import spock.lang.Shared
import spock.lang.Specification
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+
import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
1 * mockCpsValidator.validateNameCharacters(cmHandleId)
}
- def 'Retrieve multiple YangModelCmHandles using cm handle ids'() {
+ def 'Retrieve multiple YangModelCmHandles using cm handle ids.'() {
given: 'the cps data service returns 2 data nodes from the DMI registry'
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
assert results.id.containsAll([cmHandleId, cmHandleId2])
}
- def 'YangModelCmHandles are not returned for invalid cm handle ids'() {
+ def 'YangModelCmHandles are not returned for invalid cm handle ids.'() {
given: 'invalid cm handle id throws a data validation exception'
mockCpsValidator.validateNameCharacters('Invalid Cm Handle Id') >> {throw new DataValidationException('','')}
and: 'empty collection is returned as no valid cm handle ids are given'
assert results.size() == 0
}
- def 'Get a Cm Handle Composite State'() {
+ def 'Get a Cm Handle Composite State.'() {
given: 'a valid cm handle id'
def cmHandleId = 'Some-Cm-Handle'
def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
1 * mockCpsValidator.validateNameCharacters(cmHandleId)
}
- def 'Update Cm Handle with #scenario State'() {
+ def 'Update Cm Handle with #scenario State.'() {
given: 'a cm handle and a composite state'
def cmHandleId = 'Some-Cm-Handle'
def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
'DELETING' | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'
}
- def 'Update Cm Handles with #scenario States'() {
+ def 'Update Cm Handles with #scenario States.'() {
given: 'a map of cm handles composite states'
def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime)
'DELETING' | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
}
- def 'Update cm handle states when #scenario in alternate id cache'() {
+ def 'Update cm handle states when #scenario in alternate id cache.'() {
given: 'a map of cm handles composite states'
def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, lastUpdateTime: formattedDateAndTime)
def cmHandleStateMap = ['some-cm-handle' : compositeState]
}
- def 'Getting module definitions by module'() {
+ def 'Getting module definitions by module.'() {
given: 'cps module service returns module definition for module name'
def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id', 'some-module', '2024-01-25') >> moduleDefinitions
1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id', 'some-module')
}
- def 'Getting module definitions with cm handle id'() {
+ def 'Getting module definitions with cm handle id.'() {
given: 'cps module service returns module definitions for cm handle id'
def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions
assert result == moduleDefinitions
}
- def 'Get module references'() {
+ def 'Get module references.'() {
given: 'cps module service returns a collection of module references'
def moduleReferences = [new ModuleReference('moduleName','revision','namespace')]
mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences
1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id')
}
- def 'Save Cmhandle'() {
+ def 'Save Cmhandle.'() {
given: 'cmHandle represented as Yang Model'
def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', additionalProperties: [], publicProperties: [])
when: 'the method to save cmhandle is called'
}
}
- def 'Save Multiple Cmhandles'() {
+ def 'Save Multiple Cmhandles.'() {
given: 'cm handles represented as Yang Model'
def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1')
def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2')
}
}
- def 'Delete list or list elements'() {
+ def 'Delete list or list elements.'() {
when: 'the method to delete list or list elements is called'
objectUnderTest.deleteListOrListElement('sample xPath')
then: 'the data service method to save list elements is called once'
1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null)
}
- def 'Get data node via xPath'() {
+ def 'Get data node via xPath.'() {
when: 'the method to get data nodes is called'
objectUnderTest.getDataNode('sample xPath')
then: 'the data persistence service method to get data node is invoked once'
1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS)
}
- def 'Get cmHandle data node'() {
+ def 'Get cmHandle data node.'() {
given: 'expected xPath to get cmHandle data node'
def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']'
when: 'the method to get data nodes is called'
1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
}
- def 'Get CM handle ids for CM Handles that has given module names'() {
+ def 'Get CM handle ids for CM Handles that has given module names.'() {
when: 'the method to get cm handles is called'
objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false)
then: 'the admin persistence service method to query anchors is invoked once with the same parameter'
1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name'])
}
- def 'Get Alternate Ids for CM Handles that has given module names'() {
+ def 'Get Alternate Ids for CM Handles that has given module names.'() {
given: 'cps anchor service returns a CM-handle ID for the given module name'
mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) >> ['ch-1']
and: 'cps data service returns some data nodes for the given CM-handle ID'
assert result == ['alt-1'] as Set
}
- def 'Replace list content'() {
+ def 'Replace list content.'() {
when: 'replace list content method is called with xpath and data nodes collection'
objectUnderTest.replaceListContent('sample xpath', [new DataNode()])
then: 'the cps data service method to replace list content is invoked once with same parameters'
1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP);
}
- def 'Delete data node via xPath'() {
+ def 'Delete data node via xPath.'() {
when: 'Delete data node method is called with xpath as parameter'
objectUnderTest.deleteDataNode('sample dataNode xpath')
then: 'the cps data service method to delete data node is invoked once with the same xPath'
1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP);
}
- def 'Delete multiple data nodes via xPath'() {
+ def 'Delete multiple data nodes via xPath.'() {
when: 'Delete data nodes method is called with multiple xpaths as parameters'
objectUnderTest.deleteDataNodes(['xpath1', 'xpath2'])
then: 'the cps data service method to delete data nodes is invoked once with the same xPaths'
1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP);
}
- def 'CM handle exists'() {
+ def 'CM handle exists.'() {
given: 'data service returns a datanode with correct cm handle id'
mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, OMIT_DESCENDANTS) >> [dataNode]
expect: 'cm handle exists for given cm handle id'
assert true == objectUnderTest.isExistingCmHandleId(cmHandleId)
}
- def 'CM handle does not exist, empty dataNode collection returned'() {
+ def 'CM handle does not exist (data service returns empty collection).'() {
given: 'data service returns an empty datanode'
mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, OMIT_DESCENDANTS) >> []
expect: 'false is returned for non-existent cm handle'
assert false == objectUnderTest.isExistingCmHandleId(cmHandleId)
}
- def 'CM handle does not exist, exception thrown'() {
+ def 'CM handle does not exist (data service throws).'() {
given: 'data service throws an exception'
mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry/cm-handles[@id='non-existent-cm-handle']", OMIT_DESCENDANTS) >> {throw new DataNodeNotFoundException('','')}
expect: 'false is returned for non-existent cm handle'
assert false == objectUnderTest.isExistingCmHandleId('non-existent-cm-handle')
}
+
+ def 'Delete anchors.'() {
+ when: 'Deleting some anchors'
+ objectUnderTest.deleteAnchors(['anchor1' ,'anchor2'])
+ then: 'The call is delegated to the anchor service with teh correct parameters'
+ mockCpsAnchorService.deleteAnchors(NCMP_DATASPACE_NAME ,['anchor1' ,'anchor2'])
+ }
}
+