Code Coverage gaps in CPS-NCMP-Service 100% 05/142505/1
authorToineSiebelink <toine.siebelink@est.tech>
Mon, 24 Nov 2025 12:10:44 +0000 (12:10 +0000)
committerToineSiebelink <toine.siebelink@est.tech>
Mon, 24 Nov 2025 14:20:16 +0000 (14:20 +0000)
- Covered last 3 remaining gaps
- Removed Coverage setting from CPS NCMP Service module pom so its set by parent to 100% ðŸ˜Š
- Removed Coverage setting from CPS NCMP Rest module pom so its set by parent to 100% ðŸ˜Š

Issue-ID: CPS-475
Change-Id: I63774f7bf4a7e44a0a4f143df5964c384783de85
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
cps-ncmp-rest/pom.xml
cps-ncmp-service/pom.xml
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy

index 2ec2174..762accd 100644 (file)
@@ -34,7 +34,6 @@
     <artifactId>cps-ncmp-rest</artifactId>
 
     <properties>
-        <minimum-coverage>0.99</minimum-coverage>
         <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
     </properties>
 
index 33ec70f..f2a5fe5 100644 (file)
@@ -33,9 +33,6 @@
 
     <artifactId>cps-ncmp-service</artifactId>
 
-    <properties>
-        <minimum-coverage>0.99</minimum-coverage>
-    </properties>
     <dependencies>
         <dependency>
             <groupId>io.opentelemetry</groupId>
index b7942dd..2eaabeb 100644 (file)
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.events.EventsProducer
 import org.onap.cps.ncmp.api.data.models.CmResourceAddress
 import org.onap.cps.ncmp.api.data.models.DataOperationRequest
+import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
 import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.config.CpsApplicationContext
@@ -43,7 +44,6 @@ import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
 import reactor.core.publisher.Mono
-import spock.lang.Shared
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
 import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_OPERATIONAL
@@ -64,8 +64,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
     def NO_REQUEST_ID = null
     def NO_AUTH_HEADER = null
 
-    @Shared
-    def OPTIONS_PARAM = '(a=1,b=2)'
+    static def OPTIONS_PARAM = '(a=1,b=2)'
 
     @SpringBean
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
@@ -82,7 +81,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
     @SpringBean
     AlternateIdMatcher alternateIdMatcher = Mock()
 
-    def 'call get resource data for #expectedDataStore from DMI without topic #scenario.'() {
+    def 'Get resource data for #expectedDataStore from DMI without topic #scenario.'() {
         given: 'a cm handle for #cmHandleId'
             alternateIdMatcher.getCmHandleId(cmHandleId) >> cmHandleId
             mockYangModelCmHandleRetrieval(additionalProperties)
@@ -107,7 +106,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             'datastore running with properties'    | [yangModelCmHandleProperty] || PASSTHROUGH_RUNNING     | OPTIONS_PARAM   | '{"prop1":"val1"}'
     }
 
-    def 'Execute (async) data operation from DMI service.'() {
+    def 'Request resource data from DMI (asynchronous).'() {
         given: 'collection of yang model cm Handles and data operation request'
             mockYangModelCmHandleRetrievalByCmHandleId([yangModelCmHandleProperty])
             def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
@@ -117,14 +116,26 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             def responseFromDmi = Mono.just(new ResponseEntity<Object>(HttpStatus.ACCEPTED))
             def expectedUrlTemplateWithVariables = new UrlTemplateParameters('myServiceName/dmi/v1/data?requestId={requestId}&topic={topic}', ['requestId': 'requestId', 'topic': 'my-topic-name'])
             def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}'
-            mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, _, READ, NO_AUTH_HEADER) >> responseFromDmi
         when: 'get resource data for group of cm handles is invoked'
             objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
-        then: 'the post operation was called with the expected URL and JSON request body'
-            1 * mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER)
+        then: 'A DMI request was made with the expected URL and JSON request body'
+            1 * mockDmiRestClient.asynchronousPostOperation(DATA, expectedUrlTemplateWithVariables, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER) >> responseFromDmi
     }
 
-    def 'Execute (async) data operation from DMI service with Exception.'() {
+    def 'Request resource data from DMI without any matching cm handle.'() {
+        given: ' a valid request'
+            def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
+            def dataOperationRequest = spiedJsonObjectMapper.convertJsonString(dataOperationBatchRequestJsonData, DataOperationRequest.class)
+        and: 'no valid cm handles are found for the request'
+            alternateIdMatcher.getCmHandleId(_) >> { throw new CmHandleNotFoundException('') }
+            mockInventoryPersistence.getYangModelCmHandles(_) >> []
+        when: 'get resource data for group of cm handles is invoked'
+            objectUnderTest.requestResourceDataFromDmi('my-topic-name', dataOperationRequest, 'requestId', NO_AUTH_HEADER)
+        then: 'No post request was made to the DMI'
+            0 * mockDmiRestClient.asynchronousPostOperation(*_)
+    }
+
+    def 'Request resource data from DMI with exception.'() {
         given: 'collection of yang model cm Handles and data operation request'
             mockYangModelCmHandleRetrievalByCmHandleId([yangModelCmHandleProperty])
             def dataOperationBatchRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
@@ -146,7 +157,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             assert eventDataValue.ids == dataOperationRequest.dataOperationDefinitions[0].cmHandleReferences
     }
 
-    def 'call get all resource data.'() {
+    def 'Get all resource data.'() {
         given: 'the system returns a cm handle with a sample property and sample module set tag'
             mockYangModelCmHandleRetrieval([yangModelCmHandleProperty], 'my-module-set-tag')
         and: 'a positive response from DMI service when it is called with the expected parameters'
@@ -183,7 +194,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             PATCH     || 'patch'
     }
 
-    def 'State Ready validation'() {
+    def 'State Ready validation.'() {
         given: 'a yang model cm handle'
             populateYangModelCmHandle([] ,'')
         when: 'Validating State of #cmHandleState'
@@ -194,13 +205,13 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
                 caughtException = e
             }
         then: 'only when not ready a exception is thrown'
-            if (expecteException) {
+            if (expectException) {
                 assert caughtException.details.contains('not in READY state')
             } else {
                 assert caughtException == null
             }
         where: 'the following states are used'
-            cmHandleState         || expecteException
+            cmHandleState         || expectException
             CmHandleState.READY   || false
             CmHandleState.ADVISED || true
     }
index 53a27c4..a1c10ae 100644 (file)
@@ -78,7 +78,6 @@ class InventoryPersistenceImplSpec extends Specification {
     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
 
     def cmHandleId2 = 'another-cm-handle'
-    def alternateId2 = 'another-alternate-id'
     def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']"
 
     def dataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='myAdditionalProperty']", leaves: leaves)
@@ -381,7 +380,7 @@ class InventoryPersistenceImplSpec extends Specification {
             objectUnderTest.deleteAnchors(['anchor1' ,'anchor2'])
         then: 'The call is delegated to the anchor service with teh correct parameters'
             mockCpsAnchorService.deleteAnchors(NCMP_DATASPACE_NAME ,['anchor1' ,'anchor2'])
-        }
+    }
 
     def 'Get Yang Model CM Handles without properties.'() {
         given: 'the cps data service returns 2 data nodes from the DMI registry (omitting descendants)'
index 33b2b84..a9b6b00 100644 (file)
@@ -95,31 +95,46 @@ class YangModelCmHandleSpec extends Specification {
             'only data service registered'  | null               | 'does not matter'  | null                | MODEL           || null
     }
 
-    def 'Yang Model Cm Handle Deep Copy'() {
+    def 'Yang Model Cm Handle Deep Copy.'() {
         given: 'a yang model cm handle'
-            def currentYangModelCmHandle = new YangModelCmHandle(id: 'cmhandle',
-                publicProperties: [new YangModelCmHandle.Property('publicProperty1', 'value1')],
-                additionalProperties: [new YangModelCmHandle.Property('additionalProperty1', 'value1')],
+            def original = new YangModelCmHandle(id: 'original id',
+                publicProperties: [new YangModelCmHandle.Property('publicProperty', 'value1')],
+                additionalProperties: [new YangModelCmHandle.Property('additionalProperty', 'value2')],
                 compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false))
         when: 'a deep copy is created'
-            def yangModelCmhandleDeepCopy = YangModelCmHandle.deepCopyOf(currentYangModelCmHandle)
-        and: 'we try to mutate current yang model cm handle'
-            currentYangModelCmHandle.id = 'cmhandle-changed'
-            currentYangModelCmHandle.additionalProperties = [new YangModelCmHandle.Property('updatedAdditionalProperty1', 'value1')]
-            currentYangModelCmHandle.publicProperties = [new YangModelCmHandle.Property('updatedPublicProperty1', 'value1')]
-            currentYangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY
-            currentYangModelCmHandle.compositeState.dataSyncEnabled = true
-        then: 'there is no change in the deep copied object'
-            assert yangModelCmhandleDeepCopy.id == 'cmhandle'
-            assert yangModelCmhandleDeepCopy.additionalProperties == [new YangModelCmHandle.Property('additionalProperty1', 'value1')]
-            assert yangModelCmhandleDeepCopy.publicProperties == [new YangModelCmHandle.Property('publicProperty1', 'value1')]
-            assert yangModelCmhandleDeepCopy.compositeState.cmHandleState == CmHandleState.ADVISED
-            assert yangModelCmhandleDeepCopy.compositeState.dataSyncEnabled == false
-        and: 'equality on reference and hashcode behave as expected'
-            assert currentYangModelCmHandle.hashCode() != yangModelCmhandleDeepCopy.hashCode()
-            assert currentYangModelCmHandle != yangModelCmhandleDeepCopy
-
+            def copy = YangModelCmHandle.deepCopyOf(original)
+        then: 'the objects are equal'
+            assert original == copy
+            assert original.equals(copy)
+        and: 'have the same hash code'
+            assert original.hashCode() == copy.hashCode()
+        when: 'mutate the original yang model cm handle'
+            original.id = 'changed id'
+            original.publicProperties = [new YangModelCmHandle.Property('updatedPublicProperty', 'some new value')]
+            original.additionalProperties = [new YangModelCmHandle.Property('updatedAdditionalProperty', 'some new value')]
+            original.compositeState.cmHandleState = CmHandleState.READY
+            original.compositeState.dataSyncEnabled = true
+        then: 'there is no change in the copied object'
+            assert copy.id == 'original id'
+            assert copy.publicProperties == [new YangModelCmHandle.Property('publicProperty', 'value1')]
+            assert copy.additionalProperties == [new YangModelCmHandle.Property('additionalProperty', 'value2')]
+            assert copy.compositeState.cmHandleState == CmHandleState.ADVISED
+            assert copy.compositeState.dataSyncEnabled == false
+        and: 'the objects are not equal'
+            assert original != copy
+            assert original.equals(copy) == false
+        and: 'have different hash codes'
+            assert original.hashCode() != copy.hashCode()
     }
 
+    def 'Yang Model Cm Handle Deep Copy for cm handled without optional properties.'() {
+        given: 'a yang model cm handle'
+            def original = new YangModelCmHandle(id: 'some id')
+        when: 'a deep copy is created'
+            def copy = YangModelCmHandle.deepCopyOf(original)
+        then: 'the objects are equal'
+            assert original == copy
+            assert original.equals(copy)
+    }
 
 }
index a26ba11..2f4fdc9 100644 (file)
@@ -85,8 +85,8 @@ class ModuleSyncWatchdogSpec extends Specification {
             mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> []
         and: 'the work queue can be locked'
             mockCpsCommonLocks.tryLock('workQueueLock') >> true
-        when: ' module sync is started'
-            objectUnderTest.moduleSyncAdvisedCmHandles()
+        when: ' module sync is started (using the scheduled method)'
+            objectUnderTest.scheduledModuleSyncAdvisedCmHandles()
         then: 'it performs #expectedNumberOfTaskExecutions tasks'
             expectedNumberOfTaskExecutions * mockModuleSyncTasks.performModuleSync(*_)
         and: 'the executing thread is unlocked'