Improve code coverage CPS-NCMP-Service 58/142458/2
authorToineSiebelink <toine.siebelink@est.tech>
Tue, 18 Nov 2025 10:05:33 +0000 (10:05 +0000)
committerToineSiebelink <toine.siebelink@est.tech>
Thu, 20 Nov 2025 11:58:37 +0000 (11:58 +0000)
- 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 <toine.siebelink@est.tech>
12 files changed:
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumer.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcOperationEnum.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/utils/events/CloudEventMapper.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AdminCacheConfigSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cache/CmSubscriptionConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventConsumerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/datajobs/subscription/cmavc/CmAvcEventServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/dmi/DmiRestClientIntegrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy

index 4517d4d..33ec70f 100644 (file)
@@ -34,7 +34,7 @@
     <artifactId>cps-ncmp-service</artifactId>
 
     <properties>
-        <minimum-coverage>0.97</minimum-coverage>
+        <minimum-coverage>0.99</minimum-coverage>
     </properties>
     <dependencies>
         <dependency>
index a0c478e..3397df7 100644 (file)
@@ -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);
     }
 }
index ea9cd34..885c668 100644 (file)
@@ -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);
         }
     }
 
index d33d543..a251c0a 100644 (file)
@@ -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 + "'");
-    }
-
 }
index 4462b16..ef231ee 100644 (file)
@@ -44,10 +44,10 @@ public class CloudEventMapper {
      * @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) {
@@ -55,7 +55,7 @@ public class CloudEventMapper {
                     runtimeException.getMessage());
         }
 
-        return mappedCloudEvent == null ? null : mappedCloudEvent.getValue();
+        return pojoCloudEventData == null ? null : pojoCloudEventData.getValue();
     }
 
 }
index a576865..d4027f9 100644 (file)
@@ -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 (file)
index 0000000..354eeac
--- /dev/null
@@ -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<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
+    }
+}
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 (file)
index 0000000..08e6084
--- /dev/null
@@ -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<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
+    }
+}
index 82d979e..eedc961 100644 (file)
@@ -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<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'
@@ -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<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()
-
     }
 }
index 62b20e4..3b0c50d 100644 (file)
@@ -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'
index f5b9835..dcc5c31 100644 (file)
@@ -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 {
index ddb4bcd..2c02c64 100644 (file)
@@ -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'])
+        }
 }
+