diff --git a/api-project-component-v0/openapi/api-project-component-v0-internal.yaml b/api-project-component-v0/openapi/api-project-component-v0-internal.yaml
new file mode 100644
index 0000000..a903d6e
--- /dev/null
+++ b/api-project-component-v0/openapi/api-project-component-v0-internal.yaml
@@ -0,0 +1,66 @@
+openapi: 3.0.3
+info:
+ title: ODS API Server
+ description: API documentation for ODS (Open DevStack) API Service
+ contact:
+ name: ODS Team
+ version: v0.0.1
+servers:
+ - url: http://{baseurl}/api/pub/v1
+ variables:
+ baseurl:
+ default: localhost:8080
+ description: Development environment
+tags:
+ - name: Project Components Internal
+ description: API for managing project components with extended methods
+
+paths:
+ /projects/{projectId}/components/{componentId}:
+ delete:
+ tags:
+ - Project Components Internal
+ summary: Delete component information
+ operationId: deleteProjectComponent
+ description: Deletes a specific component
+ parameters:
+ - name: projectId
+ in: path
+ required: true
+ description: Project key
+ schema:
+ type: string
+ pattern: "^[A-Z]{2}[A-Z0-9]{1,8}$"
+ minLength: 3
+ maxLength: 10
+ - name: componentId
+ in: path
+ required: true
+ description: Component id
+ schema:
+ type: string
+ minLength: 2
+ maxLength: 52
+ pattern: "^[a-z]+(-?[a-z0-9]+)*$"
+ responses:
+ "204":
+ description: Project component properly deleted or no component found for the provided componentId.
+ "400":
+ description: Bad request.
+ "401":
+ description: Invalid credentials on the request.
+ "403":
+ description: Insufficient permissions for the client to access the resource.
+ "500":
+ description: Server error.
+
+components:
+ securitySchemes:
+ basicAuth:
+ type: http
+ scheme: basic
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
+
diff --git a/api-project-component-v0/pom.xml b/api-project-component-v0/pom.xml
index 35f3ddf..495e438 100644
--- a/api-project-component-v0/pom.xml
+++ b/api-project-component-v0/pom.xml
@@ -161,6 +161,35 @@
+
+ generate-api-project-component-extended
+
+ generate
+
+
+ spring
+
+ spring-boot
+ ${project.basedir}/openapi/api-project-component-v0-internal.yaml
+ org.opendevstack.apiservice.project.api
+ org.opendevstack.apiservice.project.model
+ org.opendevstack.apiservice.project
+ false
+ false
+ false
+ false
+ false
+ false
+
+ true
+ true
+ springdoc
+ true
+ true
+ true
+
+
+
diff --git a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalController.java b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalController.java
new file mode 100644
index 0000000..63c1f3d
--- /dev/null
+++ b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalController.java
@@ -0,0 +1,29 @@
+package org.opendevstack.apiservice.project.controller;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.opendevstack.apiservice.project.api.ProjectComponentsInternalApi;
+import org.opendevstack.apiservice.project.facade.ComponentsFacade;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AllArgsConstructor
+@Slf4j
+@RequestMapping(ProjectComponentsInternalController.API_BASE_PATH)
+public class ProjectComponentsInternalController implements ProjectComponentsInternalApi {
+
+ public static final String API_BASE_PATH = "/api/pub/v1";
+
+ private final ComponentsFacade componentsFacade;
+
+ @Override
+ public ResponseEntity deleteProjectComponent(String projectId, String componentId) {
+ componentsFacade.deleteProjectComponent(projectId, componentId);
+ log.info("Deleted component with id '{}' for project '{}'", componentId, projectId);
+ return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
+ }
+
+}
diff --git a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/advice/ProjectComponentsExceptionHandler.java b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/advice/ProjectComponentsExceptionHandler.java
index add5c7f..5af8579 100644
--- a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/advice/ProjectComponentsExceptionHandler.java
+++ b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/controller/advice/ProjectComponentsExceptionHandler.java
@@ -7,10 +7,10 @@
import org.opendevstack.apiservice.project.exception.ComponentAlreadyExistsException;
import org.opendevstack.apiservice.project.exception.ComponentBadRequestException;
import org.opendevstack.apiservice.project.exception.ComponentCreationException;
+import org.opendevstack.apiservice.project.exception.ComponentDeletionException;
import org.opendevstack.apiservice.project.exception.ComponentErrorKey;
import org.opendevstack.apiservice.project.exception.ComponentNotFoundException;
import org.opendevstack.apiservice.project.exception.ComponentRetrievalException;
-import org.opendevstack.apiservice.project.model.Component;
import org.opendevstack.apiservice.project.model.CreateComponentResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -142,6 +142,16 @@ public ResponseEntity handleComponentCreationException(
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
+ @ExceptionHandler(ComponentDeletionException.class)
+ public ResponseEntity handleComponentDeletionException(
+ ComponentDeletionException ex,
+ HttpServletRequest request) {
+
+ log.error("Component deletion failed: {}", ex.getMessage(), ex);
+
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
+ }
+
@ExceptionHandler(ComponentRetrievalException.class)
public ResponseEntity handleComponentRetrievalException(
ComponentRetrievalException ex,
diff --git a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/exception/ComponentDeletionException.java b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/exception/ComponentDeletionException.java
new file mode 100644
index 0000000..f1eeffa
--- /dev/null
+++ b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/exception/ComponentDeletionException.java
@@ -0,0 +1,12 @@
+package org.opendevstack.apiservice.project.exception;
+
+public class ComponentDeletionException extends RuntimeException {
+
+ public ComponentDeletionException(String message) {
+ super(message);
+ }
+
+ public ComponentDeletionException(String message, Exception e) {
+ super(message, e);
+ }
+}
diff --git a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/facade/ComponentsFacade.java b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/facade/ComponentsFacade.java
index 3888310..56cc6c3 100644
--- a/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/facade/ComponentsFacade.java
+++ b/api-project-component-v0/src/main/java/org/opendevstack/apiservice/project/facade/ComponentsFacade.java
@@ -12,6 +12,7 @@
import org.opendevstack.apiservice.project.exception.ComponentAlreadyExistsException;
import org.opendevstack.apiservice.project.exception.ComponentBadRequestException;
import org.opendevstack.apiservice.project.exception.ComponentCreationException;
+import org.opendevstack.apiservice.project.exception.ComponentDeletionException;
import org.opendevstack.apiservice.project.exception.ComponentNotFoundException;
import org.opendevstack.apiservice.project.exception.ComponentRetrievalException;
import org.opendevstack.apiservice.project.mapper.MarketplaceMapper;
@@ -144,13 +145,34 @@ private String extractHttpErrorMessage(Throwable throwable) {
return throwable.getMessage();
}
- public Boolean deleteProjectComponent(String projectId, String componentId) {
+ public void deleteProjectComponent(String projectId, String componentId) {
try {
- return marketplaceExternalService.deleteProjectComponent(projectId, componentId);
+ marketplaceExternalService.deleteProjectComponent(projectId, componentId);
+ log.info("Successfully deleted component '{}' for project '{}'", componentId, projectId);
} catch (MarketplaceException e) {
- log.error("Failed to delete component with id {} for project with id {}", componentId, projectId, e);
- return false;
+ log.error("Failed to delete component '{}' for project '{}': {}", componentId, projectId, e.getMessage(), e);
+ // Check if it's an access denied error
+ if (isAccessDeniedCause(e)) {
+ throw new ComponentDeletionException(
+ String.format("Access denied when deleting component '%s' from project '%s'", componentId, projectId), e);
+ }
+ // Generic deletion failure
+ throw new ComponentDeletionException(
+ String.format("Failed to delete component '%s' for project '%s': %s", componentId, projectId, e.getMessage()), e
+ );
+ }
+ }
+
+ private boolean isAccessDeniedCause(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof HttpClientErrorException.Unauthorized
+ || current instanceof HttpClientErrorException.Forbidden) {
+ return true;
+ }
+ current = current.getCause();
}
+ return false;
}
public boolean registerProjectComponent(String projectId, String componentId) {
diff --git a/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsControllerTest.java b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsControllerTest.java
index 8142c12..3f69e77 100644
--- a/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsControllerTest.java
+++ b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsControllerTest.java
@@ -107,4 +107,5 @@ void get_project_component_throws_not_found_when_component_does_not_exist() thro
.hasMessage("Component '" + componentId + "' not found for project 'projectId'");
verify(componentsFacade).getProjectComponent(projectId, componentId);
}
+
}
\ No newline at end of file
diff --git a/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalControllerTest.java b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalControllerTest.java
new file mode 100644
index 0000000..4554359
--- /dev/null
+++ b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/controller/ProjectComponentsInternalControllerTest.java
@@ -0,0 +1,64 @@
+package org.opendevstack.apiservice.project.controller;
+
+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.opendevstack.apiservice.project.exception.ComponentDeletionException;
+import org.opendevstack.apiservice.project.facade.ComponentsFacade;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+class ProjectComponentsInternalControllerTest {
+
+ @Mock
+ private ComponentsFacade componentsFacade;
+
+ private ProjectComponentsInternalController projectComponentsInternalController;
+
+ private AutoCloseable openMocks;
+
+ @BeforeEach
+ void setup() {
+ openMocks = MockitoAnnotations.openMocks(this);
+ projectComponentsInternalController = new ProjectComponentsInternalController(componentsFacade);
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ openMocks.close();
+ }
+
+ @Test
+ void delete_project_component_returns_no_content_when_component_delete_works() {
+ String projectId = "projectId";
+ String componentId = "test-component-delete";
+
+ ResponseEntity response = projectComponentsInternalController.deleteProjectComponent(projectId, componentId);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
+ assertThat(response.getBody()).isNull();
+ verify(componentsFacade).deleteProjectComponent(projectId, componentId);
+ }
+
+ @Test
+ void delete_project_component_throws_exception_when_component_delete_api_throws_exception() {
+ String projectId = "projectId";
+ String componentId = "test-component-delete";
+
+ doThrow(new ComponentDeletionException("test exception"))
+ .when(componentsFacade).deleteProjectComponent(anyString(), anyString());
+
+ assertThrows(ComponentDeletionException.class, () ->
+ projectComponentsInternalController.deleteProjectComponent(projectId, componentId)
+ );
+ verify(componentsFacade).deleteProjectComponent(projectId, componentId);
+ }
+}
\ No newline at end of file
diff --git a/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/facade/ComponentsFacadeTest.java b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/facade/ComponentsFacadeTest.java
index b8c7748..0507499 100644
--- a/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/facade/ComponentsFacadeTest.java
+++ b/api-project-component-v0/src/test/java/org/opendevstack/apiservice/project/facade/ComponentsFacadeTest.java
@@ -12,6 +12,7 @@
import org.opendevstack.apiservice.externalservice.marketplace.service.MarketplaceService;
import org.opendevstack.apiservice.project.exception.ComponentAlreadyExistsException;
import org.opendevstack.apiservice.project.exception.ComponentCreationException;
+import org.opendevstack.apiservice.project.exception.ComponentDeletionException;
import org.opendevstack.apiservice.project.exception.ComponentNotFoundException;
import org.opendevstack.apiservice.project.mapper.MarketplaceMapper;
import org.opendevstack.apiservice.project.model.Component;
@@ -21,11 +22,12 @@
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpClientErrorException;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opendevstack.apiservice.project.util.TestObjectsBuilder.buildTestCatalogItem;
@@ -120,4 +122,23 @@ void create_project_component_throws_already_exists_when_marketplace_returns_con
.hasMessage("This component name already exists, please choose another name.");
verify(marketplaceExternalService).provisionProjectComponent(eq("testProject"), anyList());
}
+
+
+ @Test
+ void delete_project_component_ends_successfully_for_existing_component() throws MarketplaceException {
+ componentsFacade.deleteProjectComponent("testProject", "testComponent");
+
+ verify(marketplaceExternalService).deleteProjectComponent("testProject", "testComponent");
+ }
+
+ @Test
+ void delete_project_component_throws_component_deletion_exception_when_marketplace_exception_is_thrown() throws MarketplaceException {
+ doThrow(new MarketplaceException("Test exception"))
+ .when(marketplaceExternalService).deleteProjectComponent("testProject", "testComponent");
+
+ assertThatThrownBy(() -> componentsFacade.deleteProjectComponent("testProject", "testComponent"))
+ .isInstanceOf(ComponentDeletionException.class)
+ .hasMessageContaining("Test exception");
+ verify(marketplaceExternalService).deleteProjectComponent("testProject", "testComponent");
+ }
}
\ No newline at end of file
diff --git a/external-service-marketplace/openapi/openapi-component_provisioner-v1.0.0.yaml b/external-service-marketplace/openapi/openapi-component_provisioner-v1.0.0.yaml
index 4de634a..69e19ab 100644
--- a/external-service-marketplace/openapi/openapi-component_provisioner-v1.0.0.yaml
+++ b/external-service-marketplace/openapi/openapi-component_provisioner-v1.0.0.yaml
@@ -244,7 +244,7 @@ paths:
schema:
$ref: '#/components/schemas/ProvisioningDeleteRequest'
responses:
- "200":
+ "204":
description: Project component properly deleted.
"400":
description: Bad request.
diff --git a/external-service-marketplace/pom.xml b/external-service-marketplace/pom.xml
index bdc0f96..a8799c6 100644
--- a/external-service-marketplace/pom.xml
+++ b/external-service-marketplace/pom.xml
@@ -186,7 +186,7 @@
generate
- FILTER=operationId:triggerProvisionAction|notifyProvisioningStatusUpdate|createIncident|getProvisionerHealth
+ FILTER=operationId:triggerProvisionAction|notifyProvisioningStatusUpdate|deleteProvisioningStatus|getProvisionerHealth
java
resttemplate
diff --git a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClient.java b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClient.java
index 7796f68..6205674 100644
--- a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClient.java
+++ b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClient.java
@@ -37,6 +37,12 @@ public MarketplaceApiClient(String instanceName, MarketplaceInstanceConfig confi
// Initialize the generated ApiClient
this.apiClient = new ApiClient(restTemplate);
+ if (config.getUsername() != null && config.getPassword() != null) {
+ this.apiClient.setUsername(config.getUsername());
+ this.apiClient.setPassword(config.getPassword());
+ log.info("MarketplaceApiClient for instance '{}' uses basic authentication", instanceName);
+ }
+
log.info("MarketplaceApiClient initialized for instance '{}'", instanceName);
}
diff --git a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClientFactory.java b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClientFactory.java
index 093825d..3b6fed5 100644
--- a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClientFactory.java
+++ b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/client/MarketplaceApiClientFactory.java
@@ -34,7 +34,7 @@ public class MarketplaceApiClientFactory {
* @param restTemplateBuilder RestTemplate builder for creating HTTP clients
*/
public MarketplaceApiClientFactory(MarketplaceServiceConfig configuration,
- RestTemplateBuilder restTemplateBuilder) {
+ RestTemplateBuilder restTemplateBuilder) {
this.configuration = configuration;
this.restTemplateBuilder = restTemplateBuilder;
@@ -70,7 +70,7 @@ public String getDefaultInstanceName() throws MarketplaceException {
/**
* Get a {@link MarketplaceApiClient} for a specific instance.
- * If {@code instanceName} is {@code null} or blank, this method will throw a {@link MarketplaceException}
+ * If {@code instanceName} is {@code null} or blank, this method will throw a {@link MarketplaceException}
* to avoid ambiguity. The caller should explicitly call {@link #getClient()} to get the default instance client
* in that case.
*
@@ -212,4 +212,4 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod
}
};
}
-}
+}
\ No newline at end of file
diff --git a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/config/MarketplaceInstanceConfig.java b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/config/MarketplaceInstanceConfig.java
index 7b170d2..9fb66a1 100644
--- a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/config/MarketplaceInstanceConfig.java
+++ b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/config/MarketplaceInstanceConfig.java
@@ -14,6 +14,16 @@ public class MarketplaceInstanceConfig {
*/
private String provisionerActionsBaseUrl;
+ /**
+ * The username used for basic auth
+ */
+ private String username;
+
+ /**
+ * The password used for basic auth
+ */
+ private String password;
+
/**
* Connection timeout in milliseconds (default: 30000)
*/
diff --git a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceService.java b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceService.java
index fc47b9b..29ffdf4 100644
--- a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceService.java
+++ b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceService.java
@@ -33,9 +33,9 @@ public interface MarketplaceService extends ExternalService {
boolean provisionProjectComponent(String instanceName, String projectId, List params) throws MarketplaceException;
- boolean deleteProjectComponent(String projectId, String componentId) throws MarketplaceException;
+ void deleteProjectComponent(String projectId, String componentId) throws MarketplaceException;
- boolean deleteProjectComponent(String instanceName, String projectId, String componentId) throws MarketplaceException;
+ void deleteProjectComponent(String instanceName, String projectId, String componentId) throws MarketplaceException;
void registerProjectComponent(String projectId, String componentId) throws MarketplaceException;
diff --git a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/impl/MarketplaceServiceImpl.java b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/impl/MarketplaceServiceImpl.java
index 1e58a50..de7e36e 100644
--- a/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/impl/MarketplaceServiceImpl.java
+++ b/external-service-marketplace/src/main/java/org/opendevstack/apiservice/externalservice/marketplace/service/impl/MarketplaceServiceImpl.java
@@ -15,7 +15,6 @@
import org.opendevstack.apiservice.externalservice.marketplace.openapi.api.ProvisionerActionsApi;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.api.ProvisionerHealthApi;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.CatalogItem;
-import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.CreateIncidentAction;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.GetCatalogHealth200Response;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.GetProvisionerHealth200Response;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.NotifyProvisioningStatusUpdateRequest;
@@ -23,6 +22,7 @@
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.ProvisionAction;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.ProvisionActionParameter;
import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.ProvisionActionResponse;
+import org.opendevstack.apiservice.externalservice.marketplace.openapi.model.ProvisioningDeleteRequest;
import org.opendevstack.apiservice.externalservice.marketplace.service.MarketplaceService;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
@@ -140,9 +140,9 @@ public boolean provisionProjectComponent(String projectId, List params)
+ List params)
throws MarketplaceException {
log.debug("Marketplace service PROVISION component for project {}: ", projectId);
try {
@@ -177,24 +177,24 @@ public boolean provisionProjectComponent(String instanceName,
}
@Override
- public boolean deleteProjectComponent(String projectId, String componentId) throws MarketplaceException {
- return deleteProjectComponent(getDefaultInstance(), projectId, componentId);
+ public void deleteProjectComponent(String projectId, String componentId) throws MarketplaceException {
+ deleteProjectComponent(getDefaultInstance(), projectId, componentId);
}
@Override
- public boolean deleteProjectComponent(String instanceName, String projectId, String componentId) throws MarketplaceException {
+ public void deleteProjectComponent(String instanceName, String projectId, String componentId) throws MarketplaceException {
log.debug("Marketplace service DELETE component {} for project {}: ", componentId, projectId);
try {
- MarketplaceApiClient marketplaceClient = getOboAuthenticatedClient(instanceName);
+ MarketplaceApiClient marketplaceClient = clientFactory.getClient(instanceName);
ApiClient apiClient = marketplaceClient.getApiClient();
ProvisionResultsApi provisionResultsApi = new ProvisionResultsApi(apiClient);
apiClient.setBasePath(marketplaceClient.getConfig().getProvisionerActionsBaseUrl());
log.debug("Api client base path: {}", apiClient.getBasePath());
- CreateIncidentAction deleteAction = new CreateIncidentAction();
- ProvisionActionResponse response = provisionResultsApi.createIncident(projectId, componentId, deleteAction);
- return !Boolean.TRUE.equals(response.getFailed());
+ ProvisioningDeleteRequest provisioningDeleteRequest = new ProvisioningDeleteRequest();
+ provisioningDeleteRequest.setComponentId(componentId);
+ provisionResultsApi.deleteProvisioningStatus(projectId, provisioningDeleteRequest);
} catch (HttpClientErrorException.Unauthorized | HttpClientErrorException.Forbidden e) {
throw new MarketplaceException(
String.format("Access denied when deleting project component '%s' in project '%s' and instance '%s'",
diff --git a/external-service-marketplace/src/test/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceServiceImplTest.java b/external-service-marketplace/src/test/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceServiceImplTest.java
index 4f6749a..03fb26e 100644
--- a/external-service-marketplace/src/test/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceServiceImplTest.java
+++ b/external-service-marketplace/src/test/java/org/opendevstack/apiservice/externalservice/marketplace/service/MarketplaceServiceImplTest.java
@@ -209,88 +209,88 @@ void testGetProjectComponent_NotFound_ReturnsNull() throws MarketplaceException
// -------------------------------------------------------------------------
@Test
- void testIsHealthy_NoInstancesConfigured_ReturnsFalse() {
- when(clientFactory.getAvailableInstances()).thenReturn(Set.of());
+ void testIsHealthy_NoInstancesConfigured_ReturnsFalse() {
+ when(clientFactory.getAvailableInstances()).thenReturn(Set.of());
- MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
- @Override
- protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
- return true;
- }
+ MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
+ @Override
+ protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return true;
+ }
- @Override
- protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
- return true;
- }
- };
+ @Override
+ protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return true;
+ }
+ };
- boolean result = service.isHealthy();
+ boolean result = service.isHealthy();
- assertFalse(result);
+ assertFalse(result);
}
@Test
- void testIsHealthy_BothEndpointsUp_ReturnsTrue() throws MarketplaceException {
- when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
- when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
- when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
+ void testIsHealthy_BothEndpointsUp_ReturnsTrue() throws MarketplaceException {
+ when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
+ when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
+ when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
- MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
- @Override
- protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
- return true;
- }
+ MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
+ @Override
+ protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return true;
+ }
- @Override
- protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
- return true;
- }
- };
+ @Override
+ protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return true;
+ }
+ };
- boolean result = service.isHealthy();
+ boolean result = service.isHealthy();
- assertTrue(result);
- }
+ assertTrue(result);
+ }
- @Test
- void testIsHealthy_ProvisionerDown_ReturnsFalse() throws MarketplaceException {
- when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
- when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
- when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
+ @Test
+ void testIsHealthy_ProvisionerDown_ReturnsFalse() throws MarketplaceException {
+ when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
+ when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
+ when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
- MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
- @Override
- protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
- return false;
- }
- };
+ MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
+ @Override
+ protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return false;
+ }
+ };
- boolean result = service.isHealthy();
+ boolean result = service.isHealthy();
- assertFalse(result);
- }
+ assertFalse(result);
+ }
- @Test
- void testIsHealthy_CatalogDown_ReturnsFalse() throws MarketplaceException {
- when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
- when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
- when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
+ @Test
+ void testIsHealthy_CatalogDown_ReturnsFalse() throws MarketplaceException {
+ when(clientFactory.getAvailableInstances()).thenReturn(Set.of("dev"));
+ when(clientFactory.getDefaultInstanceName()).thenReturn("dev");
+ when(clientFactory.getClient("dev")).thenReturn(marketplaceApiClient);
- MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
- @Override
- protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
- return true;
- }
+ MarketplaceServiceImpl service = new MarketplaceServiceImpl(clientFactory, oboTokenService) {
+ @Override
+ protected boolean isProvisionerEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return true;
+ }
- @Override
- protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
- return false;
- }
- };
+ @Override
+ protected boolean isCatalogEndpointUp(MarketplaceApiClient marketplaceClient) {
+ return false;
+ }
+ };
- boolean result = service.isHealthy();
+ boolean result = service.isHealthy();
- assertFalse(result);
+ assertFalse(result);
}
// -------------------------------------------------------------------------
@@ -447,11 +447,11 @@ void testProvisionProjectComponent_Conflict_ThrowsMarketplaceExceptionWithDuplic
instanceConfig.setOboScope("api://test/scope");
HttpClientErrorException conflictEx = HttpClientErrorException.create(
- HttpStatus.CONFLICT,
- "Conflict",
- HttpHeaders.EMPTY,
- "{\"message\":\"This component name already exists, please choose another name.\"}".getBytes(StandardCharsets.UTF_8),
- StandardCharsets.UTF_8
+ HttpStatus.CONFLICT,
+ "Conflict",
+ HttpHeaders.EMPTY,
+ "{\"message\":\"This component name already exists, please choose another name.\"}".getBytes(StandardCharsets.UTF_8),
+ StandardCharsets.UTF_8
);
when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
@@ -460,7 +460,7 @@ void testProvisionProjectComponent_Conflict_ThrowsMarketplaceExceptionWithDuplic
whenInvokeAPI(PATH_PROVISION_ACTIONS, HttpMethod.POST).thenThrow(conflictEx);
MarketplaceException exception = assertThrows(MarketplaceException.class, () ->
- marketplaceService.provisionProjectComponent(instanceName, projectKey, List.of()));
+ marketplaceService.provisionProjectComponent(instanceName, projectKey, List.of()));
assertEquals("This component name already exists, please choose another name.", exception.getMessage());
}
@@ -471,7 +471,7 @@ void testGetCatalogItem_RestClientException() throws MarketplaceException {
String instanceName = "dev";
String catalogItemId = "test-catalog-item-base64-string";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setOboScope("api://test/scope");
+ instanceConfig.setOboScope("api://test/scope");
when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
@@ -493,7 +493,7 @@ void testGetCatalogItem_NotFound_ReturnsNull() throws MarketplaceException {
String instanceName = "dev";
String catalogItemId = "test-catalog-item-base64-string";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setOboScope("api://test/scope");
+ instanceConfig.setOboScope("api://test/scope");
HttpClientErrorException notFoundEx = HttpClientErrorException.create(
HttpStatus.NOT_FOUND, "Not Found", HttpHeaders.EMPTY, new byte[0], null);
@@ -516,7 +516,7 @@ void testGetCatalogItem_AuthError_ThrowsMarketplaceException() throws Marketplac
String instanceName = "dev";
String catalogItemId = "test-catalog-item-base64-string";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setOboScope("api://test/scope");
+ instanceConfig.setOboScope("api://test/scope");
HttpClientErrorException forbiddenEx = HttpClientErrorException.create(
HttpStatus.FORBIDDEN, "Forbidden", HttpHeaders.EMPTY, new byte[0], null);
@@ -537,7 +537,7 @@ void testGetCatalogItem_DefaultInstance() throws MarketplaceException {
// Arrange
String catalogItemId = "test-catalog-item-base64-string";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setOboScope("api://test/scope");
+ instanceConfig.setOboScope("api://test/scope");
HttpClientErrorException notFoundEx = HttpClientErrorException.create(
HttpStatus.NOT_FOUND, "Not Found", HttpHeaders.EMPTY, new byte[0], null);
@@ -730,132 +730,71 @@ void testProvisionProjectComponent_OboScopeNotConfigured_ThrowsMarketplaceExcept
// -------------------------------------------------------------------------
// deleteProjectComponent
// -------------------------------------------------------------------------
-
@Test
- void testDeleteProjectComponent_Success_ReturnsTrue() throws MarketplaceException {
+ void testDeleteProjectComponent_RestClientException() throws MarketplaceException {
// Arrange
String instanceName = "dev";
- String projectKey = "PROJ";
- String componentId = "test-component";
+ String componentId = "test-component-id";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setProvisionerActionsBaseUrl("https://example/provision-actions");
- instanceConfig.setOboScope("api://test/scope");
-
- ProvisionActionResponse mockResponse = new ProvisionActionResponse();
- mockResponse.setFailed(false);
+ when(clientFactory.getDefaultInstanceName()).thenReturn(instanceName);
when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
when(marketplaceApiClient.getConfig()).thenReturn(instanceConfig);
- whenInvokeAPI(PATH_DELETE_COMPONENT, HttpMethod.POST)
- .thenReturn(new ResponseEntity<>(mockResponse, HttpStatus.OK));
+ when(apiClient.invokeAPI(anyString(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenThrow(new RestClientException("Connection failed"));
- // Act
- boolean result = marketplaceService.deleteProjectComponent(instanceName, projectKey, componentId);
+ // Act & Assert
+ assertThrows(MarketplaceException.class, () ->
+ marketplaceService.deleteProjectComponent(instanceName, componentId));
- // Assert
- assertTrue(result);
verify(clientFactory).getClient(instanceName);
+ verify(marketplaceApiClient).getApiClient();
}
@Test
- void testDeleteProjectComponent_Failed_ReturnsFalse() throws MarketplaceException {
+ void testDeleteProjectComponent_Unauthorized_ThrowsException() throws MarketplaceException {
// Arrange
String instanceName = "dev";
- String projectKey = "PROJ";
- String componentId = "test-component";
+ String componentId = "test-component-id";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setProvisionerActionsBaseUrl("https://example/provision-actions");
- instanceConfig.setOboScope("api://test/scope");
-
- ProvisionActionResponse mockResponse = new ProvisionActionResponse();
- mockResponse.setFailed(true);
+ HttpClientErrorException unauthorizedException = HttpClientErrorException.create(
+ HttpStatus.UNAUTHORIZED, "Unauthorized", HttpHeaders.EMPTY, new byte[0], null);
+ when(clientFactory.getDefaultInstanceName()).thenReturn(instanceName);
when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
+ when(apiClient.invokeAPI(anyString(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenThrow(unauthorizedException);
when(marketplaceApiClient.getConfig()).thenReturn(instanceConfig);
- whenInvokeAPI(PATH_DELETE_COMPONENT, HttpMethod.POST)
- .thenReturn(new ResponseEntity<>(mockResponse, HttpStatus.OK));
// Act
- boolean result = marketplaceService.deleteProjectComponent(instanceName, projectKey, componentId);
+ assertThrows(MarketplaceException.class, () ->
+ marketplaceService.deleteProjectComponent(instanceName, componentId));
// Assert
- assertFalse(result);
- }
-
- @Test
- void testDeleteProjectComponent_RestClientException_ThrowsMarketplaceException() throws MarketplaceException {
- // Arrange
- String instanceName = "dev";
- String projectKey = "PROJ";
- String componentId = "test-component";
- MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setProvisionerActionsBaseUrl("https://example/provision-actions");
- instanceConfig.setOboScope("api://test/scope");
-
- when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
- when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
- when(marketplaceApiClient.getConfig()).thenReturn(instanceConfig);
- whenInvokeAPI(PATH_DELETE_COMPONENT, HttpMethod.POST)
- .thenThrow(new RestClientException("Connection refused"));
-
- // Act & Assert
- MarketplaceException exception = assertThrows(MarketplaceException.class, () ->
- marketplaceService.deleteProjectComponent(instanceName, projectKey, componentId));
-
- assertTrue(exception.getMessage().contains("Failed to delete"));
+ verify(clientFactory).getClient(instanceName);
}
@Test
- void testDeleteProjectComponent_AuthError_ThrowsMarketplaceException() throws MarketplaceException {
+ void testDeleteProjectComponent_ComponentExists_NoExceptionThrown() throws MarketplaceException {
// Arrange
String instanceName = "dev";
- String projectKey = "PROJ";
- String componentId = "test-component";
+ String componentId = "test-component-id";
MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setProvisionerActionsBaseUrl("https://example/provision-actions");
- instanceConfig.setOboScope("api://test/scope");
- HttpClientErrorException forbiddenEx = HttpClientErrorException.create(
- HttpStatus.FORBIDDEN, "Forbidden", HttpHeaders.EMPTY, new byte[0], null);
+ when(clientFactory.getDefaultInstanceName()).thenReturn(instanceName);
when(clientFactory.getClient(instanceName)).thenReturn(marketplaceApiClient);
when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
+ when(apiClient.invokeAPI(anyString(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
+ .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT).build());
when(marketplaceApiClient.getConfig()).thenReturn(instanceConfig);
- whenInvokeAPI(PATH_DELETE_COMPONENT, HttpMethod.POST).thenThrow(forbiddenEx);
-
- // Act & Assert
- MarketplaceException exception = assertThrows(MarketplaceException.class, () ->
- marketplaceService.deleteProjectComponent(instanceName, projectKey, componentId));
-
- assertTrue(exception.getMessage().contains("Access denied"));
- }
-
- @Test
- void testDeleteProjectComponent_DefaultInstance() throws MarketplaceException {
- // Arrange
- String projectKey = "PROJ";
- String componentId = "test-component";
- MarketplaceInstanceConfig instanceConfig = new MarketplaceInstanceConfig();
- instanceConfig.setProvisionerActionsBaseUrl("https://example/provision-actions");
- instanceConfig.setOboScope("api://test/scope");
-
- ProvisionActionResponse mockResponse = new ProvisionActionResponse();
- mockResponse.setFailed(false);
-
- when(clientFactory.getDefaultInstanceName()).thenReturn("default");
- when(clientFactory.getClient("default")).thenReturn(marketplaceApiClient);
- when(marketplaceApiClient.getApiClient()).thenReturn(apiClient);
- when(marketplaceApiClient.getConfig()).thenReturn(instanceConfig);
- whenInvokeAPI(PATH_DELETE_COMPONENT, HttpMethod.POST)
- .thenReturn(new ResponseEntity<>(mockResponse, HttpStatus.OK));
// Act
- boolean result = marketplaceService.deleteProjectComponent(projectKey, componentId);
+ marketplaceService.deleteProjectComponent(instanceName, componentId);
// Assert
- assertTrue(result);
- verify(clientFactory).getClient("default");
+ verify(clientFactory).getClient(instanceName);
}
// -------------------------------------------------------------------------