*~
auto-save-list
tramp
-venv
\ No newline at end of file
+venv
+
+#Visual Studio
+.DS_Store
+**/.DS_Store
/*-
* ============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");
}
/**
- * Handle Errors not handled to GlobalControllerExceptionHandler.
+ * Handle errors that were not handled to GlobalControllerExceptionHandler.
*
* @param request HttpServletRequest
* @return ResponseEntity
-/*-
+/*
* ============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.
* ============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;
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;
--- /dev/null
+/*
+ * ============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");
+ }
+}
+
-/*-
+/*
* ============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.
* ============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;
/*-
* ========================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.
* ======================================================================
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;
* 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());
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);
}
}
/**
- * 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)) {
* @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()) {
}
/**
- * 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());
* 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));
/**
- * 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);
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);
}
}
}
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];
}
/*-
* ========================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.
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;
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.
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()
/**
* 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);
}
@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);
/*-
* ============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.
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;
@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
}
};
}
+
}
--- /dev/null
+/*
+ * ============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");
+ }
+}
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;
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;
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);
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;
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;
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());
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);
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));
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));
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 "";
};
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);
+ }
+
}
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;
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());
+ }
}
* @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());
}
- 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<>();
/*-
* ============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");
}
/**
- * Handle Errors not handled to GlobalControllerExceptionHandler.
+ * Handle errors that aren't handled to GlobalControllerExceptionHandler.
*
* @param request HttpServletRequest
* @return ResponseEntity
acDefinition -> updateSync.or(acDefinitionScanner.scanMessage(acDefinition, message)));
messageProvider.removeMessage(message.getMessageId());
}
+
acDefinitionOpt.ifPresent(acDefinition ->
acDefinitionScanner.scanAutomationCompositionDefinition(acDefinition, updateSync));
}
--- /dev/null
+/*
+ * ============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"));
+ }
+}
+
--- /dev/null
+/*
+ * ============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();
+ }
+}
+
--- /dev/null
+
+/*
+ * ============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);
+ }
+}
--- /dev/null
+
+/*
+ * ============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());
+ }
+}