Raising condition coverage for sonar 72/142572/1
authoradheli.tavares <adheli.tavares@est.tech>
Thu, 27 Nov 2025 11:13:34 +0000 (11:13 +0000)
committeradheli.tavares <adheli.tavares@est.tech>
Thu, 27 Nov 2025 16:20:10 +0000 (16:20 +0000)
Issue-ID: POLICY-5403
Change-Id: I49914552749b626a92bbf0b3216bc97c2c327d3f
Signed-off-by: adheli.tavares <adheli.tavares@est.tech>
21 files changed:
.gitignore
participant/participant-impl/participant-impl-acelement/src/main/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorController.java
participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementControllerTest.java [moved from participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/AcElementControllerTest.java with 97% similarity]
participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorControllerTest.java [new file with mode: 0644]
participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/ActuatorControllerTest.java [moved from participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/ActuatorControllerTest.java with 94% similarity]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClient.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/service/ChartService.java
participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClientTest.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV1Test.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV4Test.java [new file with mode: 0644]
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/comm/ParticipantCommTest.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/cache/CacheProviderTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/MonitoringScanner.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilsTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorControllerTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandlerExtraTest.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerExtraTest.java [new file with mode: 0644]

index 517a3f3..61bc06b 100644 (file)
@@ -22,4 +22,8 @@ ui-react/build
 *~
 auto-save-list
 tramp
-venv
\ No newline at end of file
+venv
+
+#Visual Studio
+.DS_Store
+**/.DS_Store
index 770f5d8..48df885 100644 (file)
@@ -1,7 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation.
- * ================================================================================
+ * Copyright (C) 2021-2023, 2025 OpenInfra Foundation Europe. All rights reserved
  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -65,7 +64,7 @@ public class AcElementErrorController implements ErrorController {
     }
 
     /**
-     * Handle Errors not handled to GlobalControllerExceptionHandler.
+     * Handle errors that were not handled to GlobalControllerExceptionHandler.
      *
      * @param request HttpServletRequest
      * @return ResponseEntity
@@ -1,13 +1,12 @@
-/*-
+/*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
+ *  Copyright (C) 2022-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
- *
+ *       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.
@@ -18,7 +17,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.clamp.acm.element.rest;
+package org.onap.policy.clamp.acm.element.main.rest;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -42,7 +41,6 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.onap.policy.clamp.acm.element.main.concepts.ElementConfig;
 import org.onap.policy.clamp.acm.element.main.parameters.AcElement;
-import org.onap.policy.clamp.acm.element.main.rest.AcElementController;
 import org.onap.policy.clamp.acm.element.service.ConfigService;
 import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException;
 import org.onap.policy.common.utils.coder.Coder;
diff --git a/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorControllerTest.java b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorControllerTest.java
new file mode 100644 (file)
index 0000000..3f00385
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * ============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.policy.clamp.acm.element.main.rest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.context.request.ServletWebRequest;
+
+class AcElementErrorControllerTest {
+
+    @Mock
+    private ErrorAttributes errorAttributes;
+
+    @Mock
+    private HttpServletRequest request;
+
+    private AcElementErrorController controller;
+    private AutoCloseable closeable;
+
+    @BeforeEach
+    void setUp() {
+        this.closeable = MockitoAnnotations.openMocks(this);
+        controller = new AcElementErrorController(errorAttributes);
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        closeable.close();
+    }
+
+    @Test
+    void testGetStatus_validStatus() {
+        when(request.getAttribute("jakarta.servlet.error.status_code")).thenReturn(404);
+
+        HttpStatus status = controller.getStatus(request);
+
+        assertThat(status).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+
+    @Test
+    void testGetStatus_nullStatus() {
+        when(request.getAttribute("jakarta.servlet.error.status_code")).thenReturn(null);
+
+        HttpStatus status = controller.getStatus(request);
+
+        assertThat(status).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    void testGetStatus_invalidStatus() {
+        when(request.getAttribute("jakarta.servlet.error.status_code")).thenReturn(999);
+
+        HttpStatus status = controller.getStatus(request);
+
+        assertThat(status).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    void testHandleError_buildsErrorDetails() {
+        when(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(400);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("exception", "IllegalArgumentException");
+        map.put("error", "Bad Request");
+        map.put("message", "Invalid payload");
+
+        when(errorAttributes.getErrorAttributes(any(ServletWebRequest.class), any(ErrorAttributeOptions.class)))
+            .thenReturn(map);
+
+        ResponseEntity<?> response = controller.handleError(request);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+
+        var body = (org.onap.policy.clamp.models.acm.messages.rest.TypedSimpleResponse<?>) response.getBody();
+        assertThat(body).isNotNull();
+        assertThat(body.getErrorDetails()).isEqualTo("IllegalArgumentException Bad Request Invalid payload");
+    }
+
+    @Test
+    void testHandleError_missingFields() {
+        when(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)).thenReturn(500);
+
+        Map<String, Object> map = new HashMap<>();
+        // no exception, no error, only the message
+        map.put("message", "Something went wrong");
+
+        when(errorAttributes.getErrorAttributes(any(ServletWebRequest.class), any(ErrorAttributeOptions.class)))
+            .thenReturn(map);
+
+        ResponseEntity<?> response = controller.handleError(request);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+
+        var body = (org.onap.policy.clamp.models.acm.messages.rest.TypedSimpleResponse<?>) response.getBody();
+        assertThat(body).isNotNull();
+        assertThat(body.getErrorDetails()).isEqualTo("Something went wrong");
+    }
+}
+
@@ -1,13 +1,12 @@
-/*-
+/*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2025 Nordix Foundation.
+ * Copyright (C) 2022-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
- *
+ *       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.
@@ -18,7 +17,7 @@
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.policy.clamp.acm.element.rest;
+package org.onap.policy.clamp.acm.element.main.rest;
 
 import static org.springframework.http.MediaType.APPLICATION_JSON;
 import static org.springframework.http.MediaType.TEXT_PLAIN;
index 5b13a44..da8d455 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ========================LICENSE_START=================================
- * Copyright (C) 2021-2024 Nordix Foundation. All rights reserved.
+ * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
  * ======================================================================
  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ======================================================================
@@ -26,6 +26,7 @@ import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import lombok.NoArgsConstructor;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.clamp.acm.participant.kubernetes.exception.ServiceException;
@@ -41,9 +42,9 @@ import org.springframework.stereotype.Component;
  * Client to talk with Helm cli. Supports helm3 + version
  */
 @Component
+@NoArgsConstructor
 public class HelmClient {
 
-    @Autowired
     private ChartStore chartStore;
 
     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -52,14 +53,19 @@ public class HelmClient {
     private static final String COMMAND_HELM = "/usr/local/bin/helm";
     public static final String COMMAND_KUBECTL = "/usr/local/bin/kubectl";
 
+    @Autowired
+    public HelmClient(ChartStore chartStore) {
+        this.chartStore = chartStore;
+    }
+
     /**
      * Install a chart.
      *
      * @param chart name and version.
-     * @throws ServiceException incase of error
+     * @throws ServiceException in case of error
      */
     public void installChart(ChartInfo chart) throws ServiceException {
-        if (! checkNamespaceExists(chart.getNamespace())) {
+        if (!checkNamespaceExists(chart.getNamespace())) {
             var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
             executeCommand(processBuilder);
         }
@@ -71,10 +77,11 @@ public class HelmClient {
     }
 
     /**
-     * Add repository if doesn't exist.
+     * Add a repository if it doesn't exist.
+     *
      * @param repo HelmRepository
-     * @return boolean true of false based on add repo success or failed
-     * @throws ServiceException incase of error
+     * @return boolean true of false based on added repo success or failed
+     * @throws ServiceException in case of error
      */
     public boolean addRepository(HelmRepository repo) throws ServiceException {
         if (!verifyHelmRepoAlreadyExist(repo)) {
@@ -95,7 +102,7 @@ public class HelmClient {
      * @param chart ChartInfo.
      * @return the chart repository as a string
      * @throws ServiceException in case of error
-     * @throws IOException in case of IO errors
+     * @throws IOException      in case of IO errors
      */
     public String findChartRepository(ChartInfo chart) throws ServiceException, IOException {
         if (updateHelmRepo()) {
@@ -114,15 +121,15 @@ public class HelmClient {
     }
 
     /**
-     * Verify helm chart in configured repositories.
+     * Verify the helm chart in configured repositories.
+     *
      * @param chart chartInfo
      * @return repo name
-     * @throws IOException incase of error
-     * @throws ServiceException incase of error
+     * @throws ServiceException in case of error
      */
-    public String verifyConfiguredRepo(ChartInfo chart) throws IOException, ServiceException {
+    public String verifyConfiguredRepo(ChartInfo chart) throws ServiceException {
         logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartId().getName());
-        String repository = null;
+        String repository;
         var builder = helmRepoVerifyCommand(chart.getChartId().getName());
         String output = executeCommand(builder);
         repository = verifyOutput(output, chart.getChartId().getName());
@@ -133,7 +140,7 @@ public class HelmClient {
      * Uninstall a chart.
      *
      * @param chart name and version.
-     * @throws ServiceException incase of error
+     * @throws ServiceException in case of error
      */
     public void uninstallChart(ChartInfo chart) throws ServiceException {
         executeCommand(prepareUnInstallCommand(chart));
@@ -141,10 +148,11 @@ public class HelmClient {
 
 
     /**
-     * Execute helm cli bash commands .
-     * @param processBuilder processbuilder
+     * Execute helm cli bash commands.
+     *
+     * @param processBuilder process builder object
      * @return string output
-     * @throws ServiceException incase of error.
+     * @throws ServiceException in case of error.
      */
     public String executeCommand(ProcessBuilder processBuilder) throws ServiceException {
         var commandStr = toString(processBuilder);
@@ -156,7 +164,7 @@ public class HelmClient {
 
             if (exitValue != 0) {
                 var error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
-                if (! error.isEmpty()) {
+                if (!error.isEmpty()) {
                     throw new ServiceException("Command execution failed: " + commandStr + " " + error);
                 }
             }
@@ -181,7 +189,7 @@ public class HelmClient {
     }
 
     private String verifyOutput(String output, String value) {
-        for (var line: output.split("\\R")) {
+        for (var line : output.split("\\R")) {
             if (line.contains(value)) {
                 return line.split("/")[0];
             }
index 888600f..2274dd7 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ========================LICENSE_START=================================
- * Copyright (C) 2021-2022 Nordix Foundation. All rights reserved.
+ * Copyright (C) 2021-2022, 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.
@@ -21,6 +21,7 @@ package org.onap.policy.clamp.acm.participant.kubernetes.service;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.Collection;
+import lombok.NoArgsConstructor;
 import org.onap.policy.clamp.acm.participant.kubernetes.configurations.HelmRepositoryConfig;
 import org.onap.policy.clamp.acm.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.acm.participant.kubernetes.helm.HelmClient;
@@ -33,18 +34,30 @@ import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
 @Service
+@NoArgsConstructor
 public class ChartService {
     private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-    @Autowired
     private ChartStore chartStore;
 
-    @Autowired
     private HelmClient helmClient;
 
-    @Autowired
     private HelmRepositoryConfig helmRepositoryConfig;
 
+    /**
+     * Instantiates the ChartService with the specified dependencies.
+     *
+     * @param chartStore the store for managing Helm chart information
+     * @param helmClient the client for interacting with Helm
+     * @param helmRepositoryConfig the configuration for Helm repositories
+     */
+    @Autowired
+    public ChartService(ChartStore chartStore, HelmClient helmClient, HelmRepositoryConfig helmRepositoryConfig) {
+        this.chartStore = chartStore;
+        this.helmClient = helmClient;
+        this.helmRepositoryConfig = helmRepositoryConfig;
+    }
+
     /**
      * Get all the installed charts.
      * @return list of charts.
@@ -105,8 +118,8 @@ public class ChartService {
                 chart.setRepository(repo);
             }
         } else {
-            // Add remote repository if passed via TOSCA
-            // check whether the repo is permitted
+            // Add remote repository if passed via TOSCA
+            // and check whether the repo is permitted
             for (HelmRepository repo : helmRepositoryConfig.getRepos()) {
                 if (repo.getAddress().equals(chart.getRepository().getAddress())
                         && helmRepositoryConfig.getProtocols()
@@ -129,7 +142,7 @@ public class ChartService {
     /**
      * Configure remote repository.
      * @param repo HelmRepository
-     * @throws ServiceException incase of error
+     * @throws ServiceException in case of error
      */
     public boolean configureRepository(HelmRepository repo) throws ServiceException {
         return helmClient.addRepository(repo);
index d37f470..77b3ec5 100644 (file)
@@ -144,7 +144,7 @@ class HelmClientTest {
     }
 
     @Test
-    void test_verifyConfiguredRepoForInvalidChart() throws IOException, ServiceException {
+    void test_verifyConfiguredRepoForInvalidChart() throws ServiceException {
         doReturn("").when(helmClient).executeCommand(any());
         String configuredRepo = helmClient.verifyConfiguredRepo(charts.get(1));
         assertNull(configuredRepo);
index 3ea221e..5408b83 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2024 Nordix Foundation.
+ *  Copyright (C) 2024-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.
@@ -25,6 +25,8 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import org.junit.jupiter.api.Test;
@@ -139,15 +141,16 @@ class AcElementListenerV1Test {
     @Test
     void handleRestartComposition() {
         var acElementListenerV1 = createAcElementListenerV1(mock(ParticipantIntermediaryApi.class));
-        assertThatThrownBy(() -> acElementListenerV1.handleRestartComposition(null, null))
+        assertThatThrownBy(() -> acElementListenerV1.handleRestartComposition(UUID.randomUUID(),
+            List.of(), AcTypeState.COMMISSIONED))
                 .isInstanceOf(PfModelException.class);
     }
 
     @Test
     void handleRestartInstance() {
         var acElementListenerV1 = createAcElementListenerV1(mock(ParticipantIntermediaryApi.class));
-        assertThatThrownBy(() -> acElementListenerV1.handleRestartInstance(null, null,
-                null, null)).isInstanceOf(PfModelException.class);
+        assertThatThrownBy(() -> acElementListenerV1.handleRestartInstance(UUID.randomUUID(), new AcElementDeploy(),
+            new HashMap<>(), DeployState.UNDEPLOYED, LockState.UNLOCKED)).isInstanceOf(PfModelException.class);
     }
 
     @Test
@@ -215,4 +218,5 @@ class AcElementListenerV1Test {
             }
         };
     }
+
 }
diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV4Test.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV4Test.java
new file mode 100644 (file)
index 0000000..3a5cc67
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * ============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.policy.clamp.acm.participant.intermediary.api.impl;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.participant.intermediary.api.CompositionElementDto;
+import org.onap.policy.clamp.acm.participant.intermediary.api.InstanceElementDto;
+import org.onap.policy.clamp.models.acm.concepts.DeployState;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.models.base.PfModelException;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+
+class AcElementListenerV4Test {
+
+    private AcElementListenerV4 acElementListenerV4;
+    private ParticipantIntermediaryApiImpl intermediaryApi;
+
+    @BeforeEach
+    void setup() {
+        intermediaryApi = mock(ParticipantIntermediaryApiImpl.class);
+        acElementListenerV4 = new AcElementListenerV4(intermediaryApi) {
+            @Override
+            public void deploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
+                throws PfModelException {
+                // do nothing
+            }
+
+            @Override
+            public void undeploy(CompositionElementDto compositionElement, InstanceElementDto instanceElement)
+                throws PfModelException {
+                // do nothing
+            }
+        };
+    }
+
+    @Test
+    void testPrepare() throws PfModelException {
+        var compositionElement = new CompositionElementDto(UUID.randomUUID(), new ToscaConceptIdentifier(),
+            Map.of(), Map.of());
+        var instanceElement = new InstanceElementDto(UUID.randomUUID(), UUID.randomUUID(), Map.of(), Map.of());
+        acElementListenerV4.prepare(compositionElement, instanceElement, 1);
+        verify(intermediaryApi).updateAutomationCompositionElementState(instanceElement.instanceId(),
+            instanceElement.elementId(), DeployState.UNDEPLOYED, null,
+            StateChangeResult.NO_ERROR, "Prepare completed");
+    }
+}
index cb42e4c..5099cc1 100644 (file)
 package org.onap.policy.clamp.acm.participant.intermediary.api.impl;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import java.util.List;
 import java.util.Map;
@@ -33,6 +36,7 @@ import org.junit.jupiter.api.Test;
 import org.onap.policy.clamp.acm.participant.intermediary.api.CompositionElementDto;
 import org.onap.policy.clamp.acm.participant.intermediary.api.ElementState;
 import org.onap.policy.clamp.acm.participant.intermediary.api.InstanceElementDto;
+import org.onap.policy.clamp.acm.participant.intermediary.comm.ParticipantMessagePublisher;
 import org.onap.policy.clamp.acm.participant.intermediary.handler.AutomationCompositionOutHandler;
 import org.onap.policy.clamp.acm.participant.intermediary.handler.cache.AcDefinition;
 import org.onap.policy.clamp.acm.participant.intermediary.handler.cache.CacheProvider;
@@ -278,6 +282,24 @@ class ParticipantIntermediaryApiImplTest {
         assertEquals(0, result);
     }
 
+    @Test
+    void testUpdateAutomationCompositionElementStage() {
+        var instanceId = UUID.randomUUID();
+        var mockCacheProvider = mock(CacheProvider.class);
+        when(mockCacheProvider.getAutomationComposition(instanceId)).thenReturn(null);
+        var mockAutomationCompositionHandler = mock(AutomationCompositionOutHandler.class,
+            withSettings().useConstructor(mock(ParticipantMessagePublisher.class), mockCacheProvider));
+        var elementId = UUID.randomUUID();
+        doCallRealMethod().when(mockAutomationCompositionHandler)
+            .updateAutomationCompositionElementStage(instanceId, elementId, StateChangeResult.NO_ERROR, 1, "message");
+        var api = new ParticipantIntermediaryApiImpl(mockAutomationCompositionHandler, mockCacheProvider);
+
+        assertDoesNotThrow(() -> api.updateAutomationCompositionElementStage(instanceId, elementId,
+            StateChangeResult.NO_ERROR, 1, "message"));
+        verify(mockAutomationCompositionHandler).updateAutomationCompositionElementStage(instanceId, elementId,
+            StateChangeResult.NO_ERROR, 1, "message");
+    }
+
     @Test
     void testGetPrepareNextStage() {
         var cacheProvider = mock(CacheProvider.class);
index 07c20ee..e2d40aa 100644 (file)
@@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.util.Collections;
 import java.util.List;
@@ -38,6 +39,7 @@ import org.onap.policy.clamp.acm.participant.intermediary.handler.ParticipantHan
 import org.onap.policy.clamp.acm.participant.intermediary.main.parameters.CommonTestData;
 import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException;
 import org.onap.policy.clamp.models.acm.messages.kafka.participant.AutomationCompositionDeployAck;
+import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantAckMessage;
 import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantDeregister;
 import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessage;
 import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessageType;
@@ -73,7 +75,7 @@ class ParticipantCommTest {
 
         var participantDeregisterAckListener = new ParticipantDeregisterAckListener(participantHandler);
         assertEquals(ParticipantMessageType.PARTICIPANT_DEREGISTER_ACK.name(),
-                participantDeregisterAckListener.getType());
+            participantDeregisterAckListener.getType());
 
         var participantPrimeListener = new ParticipantPrimeListener(participantHandler);
         assertEquals(ParticipantMessageType.PARTICIPANT_PRIME.name(), participantPrimeListener.getType());
@@ -84,15 +86,15 @@ class ParticipantCommTest {
 
         var automationCompositionUpdateListener = new AutomationCompositionDeployListener(participantHandler);
         assertEquals(ParticipantMessageType.AUTOMATION_COMPOSITION_DEPLOY.name(),
-                automationCompositionUpdateListener.getType());
+            automationCompositionUpdateListener.getType());
 
         var automationCompositionStateChangeListener = new AutomationCompositionStateChangeListener(participantHandler);
         assertEquals(ParticipantMessageType.AUTOMATION_COMPOSITION_STATE_CHANGE.name(),
-                automationCompositionStateChangeListener.getType());
+            automationCompositionStateChangeListener.getType());
 
         var participantSyncListener = new ParticipantSyncListener(participantHandler);
         assertEquals(ParticipantMessageType.PARTICIPANT_SYNC_MSG.name(),
-                participantSyncListener.getType());
+            participantSyncListener.getType());
         assertFalse(participantSyncListener.isDefaultTopic());
 
         var acMigrationListener = new AutomationCompositionMigrationListener(participantHandler);
@@ -126,7 +128,7 @@ class ParticipantCommTest {
         verify(mockTopicSink).send(coder.encode(participantPrimeAck));
 
         var automationCompositionAck =
-                new AutomationCompositionDeployAck(ParticipantMessageType.AUTOMATION_COMPOSITION_DEPLOY);
+            new AutomationCompositionDeployAck(ParticipantMessageType.AUTOMATION_COMPOSITION_DEPLOY);
         assertDoesNotThrow(() -> publisher.sendAutomationCompositionAck(automationCompositionAck));
         verify(mockTopicSink).send(coder.encode(automationCompositionAck));
 
@@ -141,19 +143,19 @@ class ParticipantCommTest {
 
         var participantStatus = new ParticipantStatus();
         assertThrows(AutomationCompositionRuntimeException.class,
-                () -> publisher.sendParticipantStatus(participantStatus));
+            () -> publisher.sendParticipantStatus(participantStatus));
 
         var participantRegister = new ParticipantRegister();
         assertThrows(AutomationCompositionRuntimeException.class,
-                () -> publisher.sendParticipantRegister(participantRegister));
+            () -> publisher.sendParticipantRegister(participantRegister));
 
         var participantDeregister = new ParticipantDeregister();
         assertThrows(AutomationCompositionRuntimeException.class,
-                () -> publisher.sendParticipantDeregister(participantDeregister));
+            () -> publisher.sendParticipantDeregister(participantDeregister));
 
         var automationCompositionAck = mock(AutomationCompositionDeployAck.class);
         assertThrows(AutomationCompositionRuntimeException.class,
-                () -> publisher.sendAutomationCompositionAck(automationCompositionAck));
+            () -> publisher.sendAutomationCompositionAck(automationCompositionAck));
 
         List<TopicSink> emptyList = Collections.emptyList();
         assertThrows(IllegalArgumentException.class, () -> publisher.active(emptyList));
@@ -178,10 +180,10 @@ class ParticipantCommTest {
         Consumer<ParticipantMessage> consumer = Mockito.mock(Consumer.class);
         ParticipantMessage message = Mockito.mock(ParticipantMessage.class);
 
-        Mockito.when(handler.appliesTo(message)).thenReturn(true);
+        when(handler.appliesTo(message)).thenReturn(true);
 
         ParticipantListener<ParticipantMessage> listener =
-                new ParticipantListener<>(ParticipantMessage.class, handler, consumer) {
+            new ParticipantListener<>(ParticipantMessage.class, handler, consumer) {
                 @Override
                 public String getType() {
                     return "";
@@ -189,7 +191,31 @@ class ParticipantCommTest {
             };
         assertNotNull(listener);
         listener.onTopicEvent(Mockito.mock(Topic.CommInfrastructure.class),
-                "topic", Mockito.mock(StandardCoderObject.class), message);
-        Mockito.verify(handler).appliesTo(message);
+            "topic", Mockito.mock(StandardCoderObject.class), message);
+        verify(handler).appliesTo(message);
+        verify(consumer).accept(message);
     }
+
+    @Test
+    void testOnTopicEvent_AckListener() {
+        ParticipantHandler handler = mock(ParticipantHandler.class);
+        Consumer<ParticipantAckMessage> consumer = mock(Consumer.class);
+        ParticipantAckMessage message = mock(ParticipantAckMessage.class);
+
+        when(handler.appliesTo(message)).thenReturn(true);
+
+        ParticipantAckListener<ParticipantAckMessage> listener =
+            new ParticipantAckListener<>(ParticipantAckMessage.class, handler, consumer) {
+                @Override
+                public String getType() {
+                    return "";
+                }
+            };
+        assertNotNull(listener);
+        listener.onTopicEvent(mock(Topic.CommInfrastructure.class),
+            "topic", mock(StandardCoderObject.class), message);
+        verify(handler).appliesTo(message);
+        verify(consumer).accept(message);
+    }
+
 }
index 39d1b55..2013a22 100644 (file)
@@ -24,13 +24,20 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.ArrayList;
 import java.util.Map;
+import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
 import org.junit.jupiter.api.Test;
 import org.onap.policy.clamp.acm.participant.intermediary.api.ElementState;
 import org.onap.policy.clamp.acm.participant.intermediary.main.parameters.CommonTestData;
+import org.onap.policy.clamp.models.acm.concepts.AcElementRestart;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementDefinition;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.ParticipantDeploy;
 import org.onap.policy.clamp.models.acm.concepts.SubState;
@@ -297,4 +304,90 @@ class CacheProviderTest {
         revisionId = UUID.randomUUID();
         assertFalse(cacheProvider.isInstanceUpdated(instanceId, revisionId));
     }
+
+    @Test
+    void test_addElementDefinition() {
+        var parameter = CommonTestData.getParticipantParameters();
+        var cacheProvider = new CacheProvider(parameter);
+        var compositionId = UUID.randomUUID();
+        var revisionId = UUID.randomUUID();
+
+        var acElementDefinition = new AutomationCompositionElementDefinition();
+        acElementDefinition.setAcElementDefinitionId(new ToscaConceptIdentifier("name", "1.0.0"));
+        var list = new ArrayList<AutomationCompositionElementDefinition>();
+        list.add(acElementDefinition);
+
+        cacheProvider.addElementDefinition(compositionId, list, revisionId);
+        assertEquals(1, cacheProvider.getAcElementsDefinitions().size());
+        var acDefinition = cacheProvider.getAcElementsDefinitions().get(compositionId);
+        assertNotNull(acDefinition);
+        assertEquals(compositionId, acDefinition.getCompositionId());
+        assertEquals(revisionId, acDefinition.getRevisionId());
+
+        var element = acDefinition.getElements().get(acElementDefinition.getAcElementDefinitionId());
+        assertEquals(acElementDefinition, element);
+        assertNotNull(element.getAutomationCompositionElementToscaNodeTemplate());
+        assertNotNull(element.getAutomationCompositionElementToscaNodeTemplate().getProperties());
+    }
+
+    @Test
+    void test_initializeAutomationComposition_NullValue() {
+        var parameter = CommonTestData.getParticipantParameters();
+        var cacheProvider = new CacheProvider(parameter);
+        assertThatThrownBy(() -> cacheProvider.initializeAutomationComposition(null, null))
+                .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void test_initializeAutomationComposition_ParticipantsDontMatch() {
+        var element = new AcElementRestart();
+        element.setId(UUID.randomUUID());
+        element.setParticipantId(UUID.randomUUID());
+        var participantRestartAc = CommonTestData.createParticipantRestartAc();
+        participantRestartAc.setAcElementList(new ArrayList<>());
+        participantRestartAc.getAcElementList().add(element);
+
+        var parameter = CommonTestData.getParticipantParameters();
+        var cacheProvider = new CacheProvider(parameter);
+        assertTrue(cacheProvider.getAutomationCompositions().isEmpty());
+        var compositionId = UUID.randomUUID();
+        cacheProvider.initializeAutomationComposition(compositionId, participantRestartAc);
+        assertFalse(cacheProvider.getAutomationCompositions().isEmpty());
+    }
+
+    @Test
+    void test_createAcInstance_NullValues() {
+        var parameter = CommonTestData.getParticipantParameters();
+        var cacheProvider = new CacheProvider(parameter);
+
+        var randomID = UUID.randomUUID();
+        var participantDeploy = new ParticipantDeploy();
+        assertThrows(NullPointerException.class, () ->
+            cacheProvider.createAcInstance(null, randomID, randomID, participantDeploy,
+                DeployState.UNDEPLOYED, SubState.NONE, randomID));
+        assertThrows(NullPointerException.class, () ->
+            cacheProvider.createAcInstance(randomID, randomID, null, participantDeploy,
+                DeployState.UNDEPLOYED, SubState.NONE, randomID));
+    }
+
+    @Test
+    void test_createAcInstance_NotNullCompositionId() {
+        var parameter = CommonTestData.getParticipantParameters();
+        var cacheProvider = new CacheProvider(parameter);
+        var automationComposition = CommonTestData.getTestAutomationCompositions()
+            .getAutomationCompositionList().get(0);
+        var participantDeploy = CommonTestData.createparticipantDeploy(cacheProvider
+            .getParticipantId(), automationComposition);
+
+        var compositionId = UUID.randomUUID();
+        var compositionTargetId = UUID.randomUUID();
+        AtomicReference<UUID> instanceId = new AtomicReference<>(UUID.randomUUID());
+        Optional.ofNullable(automationComposition.getInstanceId()).ifPresent(instanceId::set);
+
+        var acInstance = cacheProvider.createAcInstance(compositionId, compositionTargetId, instanceId.get(),
+            participantDeploy, DeployState.UNDEPLOYED, SubState.NONE, UUID.randomUUID());
+        assertNotNull(acInstance);
+        assertEquals(instanceId.get(), acInstance.getInstanceId());
+        assertEquals(compositionId, acInstance.getCompositionId());
+    }
 }
index fdb9cd2..a3b90dd 100644 (file)
@@ -185,9 +185,9 @@ public class AutomationCompositionInstantiationProvider {
      * @param acToBeUpdated         the composition to be updated
      * @return the result of the update
      */
-    private InstantiationResponse updateDeployedAutomationComposition(
-            AutomationComposition automationComposition, AutomationComposition acToBeUpdated,
-            AutomationCompositionDefinition acDefinition) {
+    InstantiationResponse updateDeployedAutomationComposition(
+        AutomationComposition automationComposition, AutomationComposition acToBeUpdated,
+        AutomationCompositionDefinition acDefinition) {
         // save copy in case of a rollback
         automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
         LOGGER.info("Updating deployed instance with id {}", automationComposition.getInstanceId());
index 7a77aa1..03f4a24 100644 (file)
@@ -208,10 +208,10 @@ public class EncryptionUtils {
     }
 
 
-    private List<ToscaProperty> filterSensitiveProperties(AutomationCompositionElement acInstanceElement,
-                                                          Collection<ToscaNodeType> nodeTypes,
-                                                          Collection<ToscaDataType> dataTypes,
-                                                          Collection<ToscaNodeTemplate> nodeTemplates) {
+    List<ToscaProperty> filterSensitiveProperties(AutomationCompositionElement acInstanceElement,
+                                                  Collection<ToscaNodeType> nodeTypes,
+                                                  Collection<ToscaDataType> dataTypes,
+                                                  Collection<ToscaNodeTemplate> nodeTemplates) {
 
         List<ToscaProperty> sensitiveProperties = new ArrayList<>();
 
index 5761d69..8ca1c16 100644 (file)
@@ -1,7 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021,2023 Nordix Foundation.
- * ================================================================================
+ * Copyright (C) 2021,2023,2025 OpenInfra Foundation Europe. All rights reserved
  * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -72,7 +71,7 @@ public class RuntimeErrorController implements ErrorController {
     }
 
     /**
-     * Handle Errors not handled to GlobalControllerExceptionHandler.
+     * Handle errors that aren't handled to GlobalControllerExceptionHandler.
      *
      * @param request HttpServletRequest
      * @return ResponseEntity
index b48a02b..e5fcdf1 100644 (file)
@@ -68,6 +68,7 @@ public class MonitoringScanner {
                     acDefinition -> updateSync.or(acDefinitionScanner.scanMessage(acDefinition, message)));
             messageProvider.removeMessage(message.getMessageId());
         }
+
         acDefinitionOpt.ifPresent(acDefinition ->
                 acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, updateSync));
     }
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilsTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilsTest.java
new file mode 100644 (file)
index 0000000..1d827b6
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * ============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.policy.clamp.acm.runtime.main.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.crypto.Cipher;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
+import org.onap.policy.clamp.acm.runtime.main.parameters.AcmParameters;
+import org.onap.policy.clamp.common.acm.exception.AutomationCompositionRuntimeException;
+import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementDefinition;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaTopologyTemplate;
+
+class EncryptionUtilsTest {
+
+    private EncryptionUtils encryptionUtilsEnabled;
+    private EncryptionUtils encryptionUtilsDisabled;
+
+    @BeforeEach
+    void setup() {
+        AcRuntimeParameterGroup acRuntimeParameterGroup = mock(AcRuntimeParameterGroup.class);
+        AcmParameters acmParameters = mock(AcmParameters.class);
+        when(acRuntimeParameterGroup.getAcmParameters()).thenReturn(acmParameters);
+
+        when(acmParameters.isEnableEncryption()).thenReturn(true);
+        encryptionUtilsEnabled = new EncryptionUtils(acRuntimeParameterGroup);
+
+        when(acmParameters.isEnableEncryption()).thenReturn(false);
+        encryptionUtilsDisabled = new EncryptionUtils(acRuntimeParameterGroup);
+    }
+
+    @Test
+    void testEncryptionEnabledFlag() {
+        assertThat(encryptionUtilsEnabled.encryptionEnabled()).isTrue();
+        assertThat(encryptionUtilsDisabled.encryptionEnabled()).isFalse();
+    }
+
+    @Test
+    void testEncryptDecrypt_topLevelProperty() {
+        // Build a definition with a nodeTemplate and a nodeType whose property is marked sensitive
+        ToscaProperty sensitiveProp = new ToscaProperty();
+        sensitiveProp.setName("password");
+        Map<String, String> metadata = new HashMap<>();
+        metadata.put("sensitive", "true");
+        sensitiveProp.setMetadata(metadata);
+        Map<String, ToscaProperty> props = new HashMap<>();
+        props.put("password", sensitiveProp);
+
+        ToscaNodeType nodeType = new ToscaNodeType();
+        nodeType.setName("MyType");
+        nodeType.setProperties(props);
+
+        ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
+        nodeTemplate.setName("MyNode");
+        nodeTemplate.setType("MyType");
+
+        // build service template containers
+        Map<String, ToscaNodeType> nodeTypes = new HashMap<>();
+        nodeTypes.put("MyType", nodeType);
+        Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
+        nodeTemplates.put("MyNode", nodeTemplate);
+
+        // service template wrapper classes used by your implementation
+        ToscaServiceTemplate serviceTemplate = new ToscaServiceTemplate();
+        serviceTemplate.setNodeTypes(nodeTypes);
+        serviceTemplate.setToscaTopologyTemplate(new ToscaTopologyTemplate());
+        serviceTemplate.getToscaTopologyTemplate().setNodeTemplates(nodeTemplates);
+
+        AutomationCompositionDefinition acDefinition = new AutomationCompositionDefinition();
+        acDefinition.setServiceTemplate(serviceTemplate);
+
+        // automation composition instance with one element that refers to the node template name MyNode
+        AutomationCompositionElementDefinition def = new AutomationCompositionElementDefinition();
+        var toscaDefinition = new ToscaConceptIdentifier("MyNode", "1.0.0");
+        def.setAcElementDefinitionId(toscaDefinition);
+
+        AutomationCompositionElement element = new AutomationCompositionElement();
+        var id = UUID.randomUUID();
+        element.setId(id);
+        element.setDefinition(toscaDefinition);
+        Map<String, Object> elementProps = new HashMap<>();
+        elementProps.put("password", "topSecret");
+        element.setProperties(elementProps);
+
+        def.setAutomationCompositionElementToscaNodeTemplate(nodeTemplate);
+
+        AutomationComposition ac = new AutomationComposition();
+        Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+        elements.put(element.getId(), element);
+        ac.setElements(elements);
+
+        // run encryption
+        encryptionUtilsEnabled.findAndEncryptSensitiveData(acDefinition, ac);
+
+        // property should be encrypted
+        var after = (String) ac.getElements().get(id).getProperties().get("password");
+        assertThat(after).startsWith("ENCRYPTED:");
+
+        // now decrypt via public API
+        encryptionUtilsEnabled.decryptInstanceProperties(ac);
+        var decrypted = (String) ac.getElements().get(id).getProperties().get("password");
+        assertThat(decrypted).isEqualTo("topSecret");
+    }
+
+    @Test
+    void testEncryptDecrypt_nestedMapList() {
+        // Prepare similar types but place the sensitive property nested in a list of maps
+        ToscaProperty sensitiveProp = new ToscaProperty();
+        sensitiveProp.setName("token");
+        Map<String, String> metadata = new HashMap<>();
+        metadata.put("sensitive", "true");
+        sensitiveProp.setMetadata(metadata);
+        Map<String, ToscaProperty> props = new HashMap<>();
+        props.put("token", sensitiveProp);
+
+        ToscaNodeType nodeType = new ToscaNodeType();
+        nodeType.setName("Type2");
+        nodeType.setProperties(props);
+
+        ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
+        nodeTemplate.setName("Node2");
+        nodeTemplate.setType(nodeType.getName());
+
+        Map<String, ToscaNodeType> nodeTypes = new HashMap<>();
+        nodeTypes.put(nodeType.getName(), nodeType);
+        Map<String, ToscaNodeTemplate> nodeTemplates = new HashMap<>();
+        nodeTemplates.put(nodeTemplate.getName(), nodeTemplate);
+
+        ToscaServiceTemplate serviceTemplate = new ToscaServiceTemplate();
+        serviceTemplate.setName("ServiceTemplate2");
+        serviceTemplate.setNodeTypes(nodeTypes);
+        serviceTemplate.setToscaTopologyTemplate(new ToscaTopologyTemplate());
+        serviceTemplate.getToscaTopologyTemplate().setNodeTemplates(nodeTemplates);
+
+        AutomationComposition ac = new AutomationComposition();
+        ac.setCompositionId(UUID.randomUUID());
+
+        AutomationCompositionDefinition acDefinition = new AutomationCompositionDefinition();
+        acDefinition.setServiceTemplate(serviceTemplate);
+        acDefinition.setCompositionId(ac.getCompositionId());
+
+        AutomationCompositionElement element = new AutomationCompositionElement();
+        element.setId(UUID.randomUUID());
+        element.setDefinition(new ToscaConceptIdentifier("Node2", "1.0.0"));
+
+        // nested structure: properties -> list -> map -> token
+        Map<String, Object> innerMap = new HashMap<>();
+        innerMap.put("token", "listSecret");
+        List<Map<String, Object>> list = new ArrayList<>();
+        list.add(innerMap);
+        Map<String, Object> propsInstance = new HashMap<>();
+        propsInstance.put("someList", list);
+        Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+        element.setProperties(propsInstance);
+        elements.put(element.getId(), element);
+        ac.setElements(elements);
+
+        encryptionUtilsEnabled.findAndEncryptSensitiveData(acDefinition, ac);
+
+        // confirm if the nested value was encrypted
+        var storedList = (List<?>) ac.getElements().get(element.getId()).getProperties().get("someList");
+        var storedMap = (Map<?, ?>) storedList.get(0);
+        var storedVal = (String) storedMap.get("token");
+        assertThat(storedVal).startsWith("ENCRYPTED:");
+
+        // decrypt
+        encryptionUtilsEnabled.decryptInstanceProperties(ac);
+        var after = (Map<?, ?>) ((List<?>) ac.getElements().get(element.getId())
+            .getProperties().get("someList")).get(0);
+        assertEquals("listSecret", after.get("token"));
+    }
+
+    @Test
+    void testDecrypt_invalidCipher_throwsRuntimeException() {
+        // create a composition with a property that has a malformed ENCRYPTED: value
+        var elementId = UUID.randomUUID();
+        AutomationCompositionElement element = new AutomationCompositionElement();
+        element.setId(elementId);
+        Map<String, Object> props = new HashMap<>();
+        props.put("bad", "ENCRYPTED:invalidbase64!!");
+        element.setProperties(props);
+
+        AutomationComposition ac = new AutomationComposition();
+        Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+        elements.put(element.getId(), element);
+        ac.setElements(elements);
+
+        // expect exception when decrypt called (Base64 decode -> fail -> AutomationCompositionRuntimeException)
+        assertThatThrownBy(() -> encryptionUtilsEnabled.decryptInstanceProperties(ac))
+            .isInstanceOf(AutomationCompositionRuntimeException.class)
+            .hasMessageContaining("Failed to decrypt instance field");
+    }
+
+    @Test
+    void testDecryptInstanceProperties_listVersion_and_disabled() {
+        // Disabled should not attempt to decrypt (no exception thrown)
+        var elementId = UUID.randomUUID();
+        AutomationCompositionElement element = new AutomationCompositionElement();
+        element.setId(elementId);
+        Map<String, Object> props = new HashMap<>();
+        props.put("field", "ENCRYPTED:whatever");
+        element.setProperties(props);
+
+        AutomationComposition ac = new AutomationComposition();
+        Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+        elements.put(element.getId(), element);
+        ac.setElements(elements);
+
+        List<AutomationComposition> list = new ArrayList<>();
+        list.add(ac);
+
+        // should not throw when encryption disabled
+        assertDoesNotThrow(() -> encryptionUtilsDisabled.decryptInstanceProperties(list));
+    }
+
+    @Test
+    void testFilterSensitiveProperties_datatypeRef() {
+        // create a property that references a data type by name and data type contains sensitive property
+        ToscaProperty propRef = new ToscaProperty();
+        propRef.setName("p1");
+        propRef.setType("MyDataTypeName");
+
+        Map<String, ToscaProperty> nodeProps = new HashMap<>();
+        nodeProps.put("p1", propRef);
+
+        ToscaNodeType nodeType = new ToscaNodeType();
+        nodeType.setName("NodeTypeA");
+        nodeType.setProperties(nodeProps);
+
+        ToscaDataType dataType = new ToscaDataType();
+        dataType.setName("MyDataTypeName");
+        ToscaProperty nested = new ToscaProperty();
+        nested.setName("secretNested");
+        Map<String, String> meta = new HashMap<>();
+        meta.put("sensitive", "true");
+        nested.setMetadata(meta);
+        Map<String, ToscaProperty> dtProps = new HashMap<>();
+        dtProps.put("secretNested", nested);
+        dataType.setProperties(dtProps);
+
+        ToscaNodeTemplate nodeTemplate = new ToscaNodeTemplate();
+        nodeTemplate.setName("NodeInstance");
+        nodeTemplate.setType("NodeTypeA");
+
+        ToscaServiceTemplate ser = new ToscaServiceTemplate();
+        ser.setDataTypes(Collections.singletonMap(dataType.getName(), dataType));
+        ser.setNodeTypes(Collections.singletonMap(nodeType.getName(), nodeType));
+        ser.setToscaTopologyTemplate(new ToscaTopologyTemplate());
+        ser.getToscaTopologyTemplate().setNodeTemplates(Collections.singletonMap(nodeTemplate.getName(), nodeTemplate));
+
+        AutomationComposition composition = new AutomationComposition();
+        composition.setCompositionId(UUID.randomUUID());
+        AutomationCompositionDefinition acDefinition = new AutomationCompositionDefinition();
+        acDefinition.setServiceTemplate(ser);
+        acDefinition.setCompositionId(composition.getCompositionId());
+        AutomationCompositionElement acElement = new AutomationCompositionElement();
+        acElement.setId(UUID.randomUUID());
+        acElement.setDefinition(new ToscaConceptIdentifier("NodeInstance", "1.0.0"));
+        composition.setElements(Collections.singletonMap(acElement.getId(), acElement));
+
+        List<ToscaNodeType> nodeTypes = Collections.singletonList(nodeType);
+        List<ToscaDataType> dataTypes = Collections.singletonList(dataType);
+        List<ToscaNodeTemplate> nodeTemplates = Collections.singletonList(nodeTemplate);
+
+        var found = encryptionUtilsEnabled.filterSensitiveProperties(acElement, nodeTypes, dataTypes, nodeTemplates);
+        assertThat(found).isNotEmpty();
+        assertThat(found.get(0).getName()).isEqualTo("secretNested");
+    }
+
+    @Test
+    void testGetCipher_Roundtrip() throws Exception {
+        // ensure getCipher can be used to encrypt/decrypt bytes with same iv and key
+        byte[] iv = new byte[12];
+        // create encrypt cipher
+        Cipher encryptCipher = encryptionUtilsEnabled.getCipher(iv, Cipher.ENCRYPT_MODE);
+        byte[] plain = "plainText".getBytes();
+        byte[] cipherBytes = encryptCipher.doFinal(plain);
+
+        // combine iv + cipherBytes as the class does
+        ByteBuffer buf = ByteBuffer.allocate(iv.length + cipherBytes.length);
+        buf.put(iv);
+        buf.put(cipherBytes);
+        byte[] combined = buf.array();
+        var encoded = "ENCRYPTED:" + Base64.getEncoder().encodeToString(combined);
+
+        // decrypt using the class' decrypt method by invoking decryptInstanceProperties flow:
+        AutomationCompositionElement element = new AutomationCompositionElement();
+        var elementId = UUID.randomUUID();
+        element.setId(elementId);
+        Map<String, Object> props = new HashMap<>();
+        props.put("x", encoded);
+        element.setProperties(props);
+
+        AutomationComposition ac = new AutomationComposition();
+        Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
+        elements.put(element.getId(), element);
+        ac.setElements(elements);
+
+        // Should decrypt successfully to original plaintext
+        encryptionUtilsEnabled.decryptInstanceProperties(ac);
+        assertEquals("plainText", ac.getElements().get(elementId).getProperties().get("x"));
+    }
+}
+
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorControllerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorControllerTest.java
new file mode 100644 (file)
index 0000000..8984d12
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * ============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.policy.clamp.acm.runtime.main.web;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.RequestDispatcher;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.web.error.ErrorAttributeOptions;
+import org.springframework.boot.web.servlet.error.ErrorAttributes;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+
+class RuntimeErrorControllerTest {
+
+    private ErrorAttributes errorAttributes;
+    private RuntimeErrorController controller;
+
+    @BeforeEach
+    void setup() {
+        errorAttributes = Mockito.mock(ErrorAttributes.class);
+        controller = new RuntimeErrorController(errorAttributes);
+    }
+
+    @Test
+    void testGetStatus_validStatus() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 404);
+
+        HttpStatus result = controller.getStatus(request);
+        assertThat(result).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+
+    @Test
+    void testGetStatus_nullStatus() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+
+        HttpStatus result = controller.getStatus(request);
+        assertThat(result).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    void testGetStatus_invalidStatus() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 9999); // invalid
+
+        HttpStatus result = controller.getStatus(request);
+        assertThat(result).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+
+    @Test
+    void testHandleError_fullAttributes() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 400);
+
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put("exception", "IllegalArgumentException");
+        attributes.put("error", "Bad Request");
+        attributes.put("message", "Invalid input");
+
+        when(errorAttributes.getErrorAttributes(any(ServletWebRequest.class), any(ErrorAttributeOptions.class)))
+            .thenReturn(attributes);
+
+        var response = controller.handleError(request);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+
+        assertNotNull(response.getBody());
+        String details = response.getBody().getErrorDetails();
+        assertThat(details).contains("IllegalArgumentException")
+            .contains("Bad Request")
+            .contains("Invalid input");
+    }
+
+    @Test
+    void testHandleError_missingException() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500);
+
+        Map<String, Object> attributes = new HashMap<>();
+        attributes.put("error", "Internal Server Error");
+        attributes.put("message", "Something broke");
+
+        when(errorAttributes.getErrorAttributes(any(ServletWebRequest.class), any(ErrorAttributeOptions.class)))
+            .thenReturn(attributes);
+
+        var response = controller.handleError(request);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+
+        assertNotNull(response.getBody());
+        String details = response.getBody().getErrorDetails();
+        assertThat(details).isEqualTo("Internal Server Error Something broke");
+    }
+
+    @Test
+    void testHandleError_noFieldsProvided() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500);
+
+        Map<String, Object> attributes = new HashMap<>();
+
+        when(errorAttributes.getErrorAttributes(any(ServletWebRequest.class), any(ErrorAttributeOptions.class)))
+            .thenReturn(attributes);
+
+        var response = controller.handleError(request);
+
+        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
+        assertNotNull(response.getBody());
+        assertThat(response.getBody().getErrorDetails()).isEmpty();
+    }
+}
+
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandlerExtraTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandlerExtraTest.java
new file mode 100644 (file)
index 0000000..59f7db4
--- /dev/null
@@ -0,0 +1,149 @@
+
+/*
+ * ============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.policy.clamp.acm.runtime.supervision;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantPrimeAck;
+import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
+import org.onap.policy.clamp.models.acm.persistence.provider.MessageProvider;
+
+/**
+ * Extra tests to exercise additional branches in SupervisionHandler.
+ */
+class SupervisionHandlerExtraTest {
+
+    private AcDefinitionProvider acDefinitionProvider;
+    private MessageProvider messageProvider;
+    private SupervisionHandler handler;
+
+    @BeforeEach
+    void setUp() {
+        acDefinitionProvider = mock(AcDefinitionProvider.class);
+        messageProvider = mock(MessageProvider.class);
+        handler = new SupervisionHandler(acDefinitionProvider, messageProvider);
+    }
+
+    @Test
+    void whenMessageHasNullFields_thenReturnsEarly() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        // all null -> should return without interactions
+        handler.handleParticipantMessage(msg);
+
+        verifyNoInteractions(acDefinitionProvider, messageProvider);
+    }
+
+    @Test
+    void whenCompositionStateInvalid_thenReturnsEarly() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        msg.setCompositionId(UUID.randomUUID());
+        msg.setParticipantId(UUID.randomUUID());
+        // set compositionState to PRIMING which should cause early return
+        msg.setCompositionState(AcTypeState.PRIMING);
+        msg.setStateChangeResult(StateChangeResult.NO_ERROR);
+
+        handler.handleParticipantMessage(msg);
+
+        verifyNoInteractions(acDefinitionProvider, messageProvider);
+    }
+
+    @Test
+    void whenStateChangeResultInvalid_thenReturnsEarly() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        msg.setCompositionId(UUID.randomUUID());
+        msg.setParticipantId(UUID.randomUUID());
+        msg.setCompositionState(AcTypeState.PRIMED);
+        // set an unexpected state change result (use null-equivalent by creating a different enum via valueOf)
+        msg.setStateChangeResult(StateChangeResult.TIMEOUT);
+
+        handler.handleParticipantMessage(msg);
+
+        verifyNoInteractions(acDefinitionProvider, messageProvider);
+    }
+
+    @Test
+    void whenAcDefinitionNotFound_thenReturnsEarly() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        UUID compId = UUID.randomUUID();
+        msg.setCompositionId(compId);
+        msg.setParticipantId(UUID.randomUUID());
+        msg.setCompositionState(AcTypeState.PRIMED);
+        msg.setStateChangeResult(StateChangeResult.NO_ERROR);
+
+        when(acDefinitionProvider.findAcDefinition(compId)).thenReturn(Optional.empty());
+
+        handler.handleParticipantMessage(msg);
+
+        verify(acDefinitionProvider).findAcDefinition(compId);
+        verifyNoMoreInteractions(messageProvider);
+    }
+
+    @Test
+    void whenAcDefinitionStateMismatches_thenReturnsEarly() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        UUID compId = UUID.randomUUID();
+        msg.setCompositionId(compId);
+        msg.setParticipantId(UUID.randomUUID());
+        msg.setCompositionState(AcTypeState.PRIMED);
+        msg.setStateChangeResult(StateChangeResult.NO_ERROR);
+
+        AutomationCompositionDefinition def = new AutomationCompositionDefinition();
+        def.setState(AcTypeState.COMMISSIONED);
+
+        when(acDefinitionProvider.findAcDefinition(compId)).thenReturn(Optional.of(def));
+
+        handler.handleParticipantMessage(msg);
+
+        verify(acDefinitionProvider).findAcDefinition(compId);
+        verifyNoInteractions(messageProvider);
+    }
+
+    @Test
+    void whenAllOk_thenMessageSaved() {
+        ParticipantPrimeAck msg = new ParticipantPrimeAck();
+        UUID compId = UUID.randomUUID();
+        msg.setCompositionId(compId);
+        msg.setParticipantId(UUID.randomUUID());
+        msg.setCompositionState(AcTypeState.PRIMED);
+        msg.setStateChangeResult(StateChangeResult.NO_ERROR);
+
+        AutomationCompositionDefinition def = new AutomationCompositionDefinition();
+        def.setState(AcTypeState.PRIMING);
+
+        when(acDefinitionProvider.findAcDefinition(compId)).thenReturn(Optional.of(def));
+
+        handler.handleParticipantMessage(msg);
+
+        verify(acDefinitionProvider).findAcDefinition(compId);
+        verify(messageProvider).save(msg);
+    }
+}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerExtraTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerExtraTest.java
new file mode 100644 (file)
index 0000000..3e528da
--- /dev/null
@@ -0,0 +1,116 @@
+
+/*
+ * ============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.policy.clamp.acm.runtime.supervision;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyMap;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.AcDefinitionScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.MonitoringScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.PhaseScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.SimpleScanner;
+import org.onap.policy.clamp.acm.runtime.supervision.scanner.StageScanner;
+import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
+import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
+import org.onap.policy.clamp.models.acm.persistence.provider.MessageProvider;
+
+/**
+ * Extra tests to exercise additional branches in SupervisionScanner.
+ */
+class SupervisionScannerExtraTest {
+
+    private AcDefinitionProvider acDefinitionProvider;
+    private MessageProvider messageProvider;
+    private MonitoringScanner monitoringScanner;
+    private SupervisionScanner scanner;
+
+    @BeforeEach
+    void setUp() {
+        var automationCompositionProvider = mock(AutomationCompositionProvider.class);
+        acDefinitionProvider = mock(AcDefinitionProvider.class);
+        messageProvider = mock(MessageProvider.class);
+        monitoringScanner = mock(MonitoringScanner.class,
+            withSettings().useConstructor(automationCompositionProvider, acDefinitionProvider,
+                mock(AcDefinitionScanner.class), mock(StageScanner.class), mock(SimpleScanner.class),
+                mock(PhaseScanner.class), messageProvider));
+
+        scanner = new SupervisionScanner(automationCompositionProvider, acDefinitionProvider,
+            messageProvider, monitoringScanner);
+    }
+
+    @Test
+    void whenNoDefinitionsInTransition_thenRunDoesNothing() {
+        when(acDefinitionProvider.getAllAcDefinitionsInTransition()).thenReturn(new HashSet<>());
+        scanner.run();
+        verify(messageProvider).removeOldJobs();
+        verifyNoMoreInteractions(monitoringScanner);
+    }
+
+    @Test
+    void whenJobCreationFails_scanAutomationCompositionReturnsEarly() {
+        UUID compId = UUID.randomUUID();
+        when(acDefinitionProvider.getAllAcDefinitionsInTransition()).thenReturn(new HashSet<>(Set.of(compId)));
+        when(messageProvider.createJob(compId)).thenReturn(Optional.empty());
+
+        scanner.run();
+
+        // removeOldJobs is always called at the start
+        verify(messageProvider).removeOldJobs();
+        verify(messageProvider).createJob(compId);
+        verify(messageProvider, never()).removeJob(any());
+        verify(monitoringScanner, never()).scanAutomationComposition(any(), any());
+    }
+
+    @Test
+    void whenJobCreated_thenMonitoringScannerCalled_and_jobRemoved() {
+        UUID compId = UUID.randomUUID();
+        when(messageProvider.getAllMessages(any(UUID.class))).thenReturn(new ArrayList<>());
+        when(messageProvider.findCompositionMessages()).thenReturn(new HashSet<>(Set.of(compId)));
+        doNothing().when(monitoringScanner).scanAcDefinition(any());
+        when(acDefinitionProvider.getAllAcDefinitionsInTransition()).thenReturn(new HashSet<>(Set.of(compId)));
+        when(messageProvider.findInstanceMessages()).thenReturn(new HashSet<>(Set.of(compId)));
+        when(messageProvider.createJob(compId)).thenReturn(Optional.of(UUID.randomUUID().toString()));
+
+
+        scanner.run();
+
+        verify(messageProvider).removeOldJobs();
+        verify(messageProvider, atLeastOnce()).createJob(compId);
+        verify(monitoringScanner).scanAutomationComposition(eq(compId), anyMap());
+        verify(messageProvider, atLeastOnce()).removeJob(any());
+    }
+}