From: ToineSiebelink Date: Tue, 18 Nov 2025 10:05:33 +0000 (+0000) Subject: Improve code coverage CPS-NCMP-Service X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=07814ac24d7c903bbab745ecabb33f1d4cbd277d;p=cps.git Improve code coverage CPS-NCMP-Service - Increase coverage threshold cps-ncmp-service to 99% - Use real configuration classes for DMI Client Integration Test (fills big coverage gap in WebClientConfiguration) - Filled testing gaps in CM AVC Events package (partly by removing redundant code!) - Filled testing gaps in hazelcast cache configs - Added missing test for Inventory Persistence service Issue-ID: CPS-475 Change-Id: I0987427c4d50ed48848809d10f3486af6e14312b Signed-off-by: ToineSiebelink --- diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 4517d4db8c..33ec70f414 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -34,7 +34,7 @@ cps-ncmp-service - 0.97 + 0.99 diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumer.java index a0c478e84c..3397df73f3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumer.java @@ -34,7 +34,6 @@ import org.onap.cps.ncmp.events.avc1_0_0.AvcEvent; 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; @@ -48,7 +47,6 @@ 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}") @@ -77,11 +75,6 @@ public class CmAvcEventConsumer { 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); } @@ -100,6 +93,6 @@ public class CmAvcEventConsumer { 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); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventService.java index ea9cd3432a..885c6681ce 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventService.java @@ -63,7 +63,10 @@ public class CmAvcEventService { 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, @@ -72,24 +75,17 @@ public class CmAvcEventService { 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); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcOperationEnum.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcOperationEnum.java index d33d543577..a251c0a8e6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcOperationEnum.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcOperationEnum.java @@ -48,19 +48,4 @@ public enum CmAvcOperationEnum { 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 + "'"); - } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java index 4462b169e2..ef231eead4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java @@ -44,10 +44,10 @@ public class CloudEventMapper { * @return mapped target event */ public static T toTargetEvent(final CloudEvent cloudEvent, final Class targetEventClass) { - PojoCloudEventData mappedCloudEvent = null; + PojoCloudEventData pojoCloudEventData = null; try { - mappedCloudEvent = + pojoCloudEventData = CloudEventUtils.mapData(cloudEvent, PojoCloudEventDataMapper.from(objectMapper, targetEventClass)); } catch (final RuntimeException runtimeException) { @@ -55,7 +55,7 @@ public class CloudEventMapper { runtimeException.getMessage()); } - return mappedCloudEvent == null ? null : mappedCloudEvent.getValue(); + return pojoCloudEventData == null ? null : pojoCloudEventData.getValue(); } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AdminCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AdminCacheConfigSpec.groovy index a576865262..d4027f9724 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AdminCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AdminCacheConfigSpec.groovy @@ -27,8 +27,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration; import spock.lang.Specification; -@SpringBootTest -@ContextConfiguration(classes = [AdminCacheConfig]) +@SpringBootTest(classes = [AdminCacheConfig]) class AdminCacheConfigSpec extends Specification { @Autowired diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfigSpec.groovy new file mode 100644 index 0000000000..354eeac27d --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfigSpec.groovy @@ -0,0 +1,50 @@ +/* + * ============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 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 + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cache/CmSubscriptionConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cache/CmSubscriptionConfigSpec.groovy new file mode 100644 index 0000000000..08e6084fa3 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cache/CmSubscriptionConfigSpec.groovy @@ -0,0 +1,49 @@ +/* + * ============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> 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 + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumerSpec.groovy index 82d979ed61..eedc961cd8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumerSpec.groovy @@ -57,31 +57,31 @@ class CmAvcEventConsumerSpec extends MessagingBaseSpec { 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(cmAvcEventConsumer.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent) + def consumerRecordReceived = new ConsumerRecord(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' @@ -93,44 +93,59 @@ class CmAvcEventConsumerSpec extends MessagingBaseSpec { 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(cmAvcEventConsumer.cmEventsTopicName, 0, 0, testEventKey, testCloudEventSent) - consumerRecord.headers().add('ce_source', sourceSystem.getBytes(Charset.defaultCharset())) + def consumerRecord = new ConsumerRecord(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(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() - } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventServiceSpec.groovy index 62b20e4330..3b0c50d357 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventServiceSpec.groovy @@ -42,20 +42,22 @@ class CmAvcEventServiceSpec extends Specification { 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' @@ -64,46 +66,34 @@ class CmAvcEventServiceSpec extends Specification { 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' @@ -114,7 +104,7 @@ class CmAvcEventServiceSpec extends Specification { 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' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy index f5b9835e7d..dcc5c312d6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper 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 @@ -40,12 +41,16 @@ class DmiRestClientIntegrationSpec extends Specification { 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 { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index ddb4bcd331..2c02c64379 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -24,10 +24,6 @@ package org.onap.cps.ncmp.impl.inventory 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 @@ -36,15 +32,19 @@ import org.onap.cps.api.exceptions.DataValidationException 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 @@ -138,7 +138,7 @@ class InventoryPersistenceImplSpec extends Specification { 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 @@ -149,7 +149,7 @@ class InventoryPersistenceImplSpec extends Specification { 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' @@ -160,7 +160,7 @@ class InventoryPersistenceImplSpec extends Specification { 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']) @@ -175,7 +175,7 @@ class InventoryPersistenceImplSpec extends Specification { 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) @@ -190,7 +190,7 @@ class InventoryPersistenceImplSpec extends Specification { '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) @@ -208,7 +208,7 @@ class InventoryPersistenceImplSpec extends Specification { '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] @@ -227,7 +227,7 @@ class InventoryPersistenceImplSpec extends Specification { } - 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 @@ -239,7 +239,7 @@ class InventoryPersistenceImplSpec extends Specification { 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 @@ -249,7 +249,7 @@ class InventoryPersistenceImplSpec extends Specification { 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 @@ -261,7 +261,7 @@ class InventoryPersistenceImplSpec extends Specification { 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' @@ -275,7 +275,7 @@ class InventoryPersistenceImplSpec extends Specification { } } - 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') @@ -292,21 +292,21 @@ class InventoryPersistenceImplSpec extends Specification { } } - 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' @@ -315,14 +315,14 @@ class InventoryPersistenceImplSpec extends Specification { 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' @@ -334,45 +334,53 @@ class InventoryPersistenceImplSpec extends Specification { 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']) + } } +