From: adheli.tavares Date: Thu, 27 Nov 2025 11:13:34 +0000 (+0000) Subject: Raising condition coverage for sonar X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=8b5ccbff6bac1922da512aba454644975634868d;p=policy%2Fclamp.git Raising condition coverage for sonar Issue-ID: POLICY-5403 Change-Id: I49914552749b626a92bbf0b3216bc97c2c327d3f Signed-off-by: adheli.tavares --- diff --git a/.gitignore b/.gitignore index 517a3f3af..61bc06b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/participant/participant-impl/participant-impl-acelement/src/main/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorController.java b/participant/participant-impl/participant-impl-acelement/src/main/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorController.java index 770f5d8bf..48df8853a 100644 --- a/participant/participant-impl/participant-impl-acelement/src/main/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorController.java +++ b/participant/participant-impl/participant-impl-acelement/src/main/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorController.java @@ -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 diff --git a/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/AcElementControllerTest.java b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementControllerTest.java similarity index 97% rename from participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/AcElementControllerTest.java rename to participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementControllerTest.java index b91c45437..952a405b2 100644 --- a/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/AcElementControllerTest.java +++ b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementControllerTest.java @@ -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 index 000000000..3f00385fd --- /dev/null +++ b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/AcElementErrorControllerTest.java @@ -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 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 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"); + } +} + diff --git a/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/ActuatorControllerTest.java b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/ActuatorControllerTest.java similarity index 94% rename from participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/ActuatorControllerTest.java rename to participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/ActuatorControllerTest.java index e3c665ace..ecf3456a5 100644 --- a/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/rest/ActuatorControllerTest.java +++ b/participant/participant-impl/participant-impl-acelement/src/test/java/org/onap/policy/clamp/acm/element/main/rest/ActuatorControllerTest.java @@ -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; diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClient.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClient.java index 5b13a44a1..da8d455b8 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClient.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClient.java @@ -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]; } diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/service/ChartService.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/service/ChartService.java index 888600fde..2274dd73c 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/service/ChartService.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/acm/participant/kubernetes/service/ChartService.java @@ -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 a 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); diff --git a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClientTest.java b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClientTest.java index d37f4705f..77b3ec588 100644 --- a/participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClientTest.java +++ b/participant/participant-impl/participant-impl-kubernetes/src/test/java/org/onap/policy/clamp/acm/participant/kubernetes/helm/HelmClientTest.java @@ -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); diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV1Test.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV1Test.java index 3ea221e92..5408b836d 100644 --- a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV1Test.java +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV1Test.java @@ -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 index 000000000..3a5cc6701 --- /dev/null +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/AcElementListenerV4Test.java @@ -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"); + } +} diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java index cb42e4c12..5099cc155 100644 --- a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/api/impl/ParticipantIntermediaryApiImplTest.java @@ -21,10 +21,13 @@ 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); diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/comm/ParticipantCommTest.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/comm/ParticipantCommTest.java index 07c20ee9e..e2d40aa62 100644 --- a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/comm/ParticipantCommTest.java +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/comm/ParticipantCommTest.java @@ -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 emptyList = Collections.emptyList(); assertThrows(IllegalArgumentException.class, () -> publisher.active(emptyList)); @@ -178,10 +180,10 @@ class ParticipantCommTest { Consumer 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 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 consumer = mock(Consumer.class); + ParticipantAckMessage message = mock(ParticipantAckMessage.class); + + when(handler.appliesTo(message)).thenReturn(true); + + ParticipantAckListener 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); + } + } diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/cache/CacheProviderTest.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/cache/CacheProviderTest.java index 39d1b557d..2013a221d 100644 --- a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/cache/CacheProviderTest.java +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/cache/CacheProviderTest.java @@ -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(); + 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 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()); + } } diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java index fdb9cd28e..a3b90ddbb 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java @@ -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()); diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java index 7a77aa10f..03f4a2490 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtils.java @@ -208,10 +208,10 @@ public class EncryptionUtils { } - private List filterSensitiveProperties(AutomationCompositionElement acInstanceElement, - Collection nodeTypes, - Collection dataTypes, - Collection nodeTemplates) { + List filterSensitiveProperties(AutomationCompositionElement acInstanceElement, + Collection nodeTypes, + Collection dataTypes, + Collection nodeTemplates) { List sensitiveProperties = new ArrayList<>(); diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java index 5761d6948..8ca1c1633 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorController.java @@ -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 diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/MonitoringScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/MonitoringScanner.java index b48a02b6c..e5fcdf121 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/MonitoringScanner.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/scanner/MonitoringScanner.java @@ -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 index 000000000..1d827b626 --- /dev/null +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/utils/EncryptionUtilsTest.java @@ -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 metadata = new HashMap<>(); + metadata.put("sensitive", "true"); + sensitiveProp.setMetadata(metadata); + Map 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 nodeTypes = new HashMap<>(); + nodeTypes.put("MyType", nodeType); + Map 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 elementProps = new HashMap<>(); + elementProps.put("password", "topSecret"); + element.setProperties(elementProps); + + def.setAutomationCompositionElementToscaNodeTemplate(nodeTemplate); + + AutomationComposition ac = new AutomationComposition(); + Map 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 metadata = new HashMap<>(); + metadata.put("sensitive", "true"); + sensitiveProp.setMetadata(metadata); + Map 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 nodeTypes = new HashMap<>(); + nodeTypes.put(nodeType.getName(), nodeType); + Map 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 innerMap = new HashMap<>(); + innerMap.put("token", "listSecret"); + List> list = new ArrayList<>(); + list.add(innerMap); + Map propsInstance = new HashMap<>(); + propsInstance.put("someList", list); + Map 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 props = new HashMap<>(); + props.put("bad", "ENCRYPTED:invalidbase64!!"); + element.setProperties(props); + + AutomationComposition ac = new AutomationComposition(); + Map 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 props = new HashMap<>(); + props.put("field", "ENCRYPTED:whatever"); + element.setProperties(props); + + AutomationComposition ac = new AutomationComposition(); + Map elements = new HashMap<>(); + elements.put(element.getId(), element); + ac.setElements(elements); + + List 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 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 meta = new HashMap<>(); + meta.put("sensitive", "true"); + nested.setMetadata(meta); + Map 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 nodeTypes = Collections.singletonList(nodeType); + List dataTypes = Collections.singletonList(dataType); + List 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 props = new HashMap<>(); + props.put("x", encoded); + element.setProperties(props); + + AutomationComposition ac = new AutomationComposition(); + Map 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 index 000000000..8984d1255 --- /dev/null +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/main/web/RuntimeErrorControllerTest.java @@ -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 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 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 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 index 000000000..59f7db4be --- /dev/null +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionHandlerExtraTest.java @@ -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 index 000000000..3e528da22 --- /dev/null +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerExtraTest.java @@ -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()); + } +}