From 1cdcc948ded24d214271db6f8a6fc0ef55853aa8 Mon Sep 17 00:00:00 2001 From: Angel Martinez Date: Thu, 5 Mar 2026 12:04:01 +0100 Subject: [PATCH 1/5] Add project service module with key generation and existence checks --- external-service-jira/pom.xml | 2 +- external-service-projects/pom.xml | 69 +++++++++ .../ProjectKeyGenerationException.java | 13 ++ .../model/CreateProjectRequest.java | 17 +++ .../model/CreateProjectResponse.java | 21 +++ .../service/GenerateProjectKeyService.java | 11 ++ .../service/ProjectService.java | 12 ++ .../impl/GenerateProjectKeyServiceImpl.java | 144 ++++++++++++++++++ .../service/impl/ProjectServiceImpl.java | 48 ++++++ .../GenerateProjectKeyServiceImplTest.java | 92 +++++++++++ .../service/impl/ProjectServiceImplTest.java | 34 +++++ pom.xml | 1 + 12 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 external-service-projects/pom.xml create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java create mode 100644 external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java create mode 100644 external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java create mode 100644 external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java diff --git a/external-service-jira/pom.xml b/external-service-jira/pom.xml index c1ef1bd..edb7e05 100644 --- a/external-service-jira/pom.xml +++ b/external-service-jira/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2-SNAPSHOT + 0.0.2 external-service-jira diff --git a/external-service-projects/pom.xml b/external-service-projects/pom.xml new file mode 100644 index 0000000..c6d622e --- /dev/null +++ b/external-service-projects/pom.xml @@ -0,0 +1,69 @@ + + 4.0.0 + + + org.opendevstack.apiservice + devstack-api-service + 0.0.2 + + + external-service-projects + External Service Projects + Service module for project operations: key generation and project existence checks + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable.version} + + + + org.opendevstack.apiservice + external-service-bitbucket + ${project.version} + + + + org.opendevstack.apiservice + external-service-jira + ${project.version} + + + + org.opendevstack.apiservice + external-service-ocp + ${project.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java new file mode 100644 index 0000000..dad4ffa --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java @@ -0,0 +1,13 @@ +package org.opendevstack.apiservice.serviceproject.exception; + +public class ProjectKeyGenerationException extends Exception { + + public ProjectKeyGenerationException(String message) { + super(message); + } + + public ProjectKeyGenerationException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java new file mode 100644 index 0000000..6127031 --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java @@ -0,0 +1,17 @@ +package org.opendevstack.apiservice.serviceproject.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CreateProjectRequest { + + private String projectKey; + + private String projectKeyPattern; + + private String projectName; + + private String projectDescription; +} diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java new file mode 100644 index 0000000..0775c9b --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java @@ -0,0 +1,21 @@ +package org.opendevstack.apiservice.serviceproject.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CreateProjectResponse { + + private String projectKey; + + private String status; + + private String message; + + private String error; + + private String errorKey; + + private String errorDescription; +} diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java new file mode 100644 index 0000000..33ffda0 --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java @@ -0,0 +1,11 @@ +package org.opendevstack.apiservice.serviceproject.service; + +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; + +public interface GenerateProjectKeyService { + + String DEFAULT_PROJECT_KEY_PATTERN = "SS%06d"; + + String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException; +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java new file mode 100644 index 0000000..0c59b5e --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java @@ -0,0 +1,12 @@ +package org.opendevstack.apiservice.serviceproject.service; + +import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; + +public interface ProjectService { + + CreateProjectResponse createProject(CreateProjectRequest request); + + CreateProjectResponse getProject(String projectKey); +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java new file mode 100644 index 0000000..c70b85d --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java @@ -0,0 +1,144 @@ +package org.opendevstack.apiservice.serviceproject.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.bitbucket.exception.BitbucketException; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.exception.JiraException; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.exception.OpenshiftException; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.Random; +import java.util.Set; + +@Service +@Slf4j +public class GenerateProjectKeyServiceImpl implements GenerateProjectKeyService { + + private static final int MAX_RETRIES = 10; + + private final OpenshiftService openshiftService; + + private final BitbucketService bitbucketService; + + private final JiraService jiraService; + + private final Random random; + + @Autowired + public GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService) { + this(bitbucketService, jiraService, openshiftService, new Random()); + } + + GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService, Random random) { + this.bitbucketService = bitbucketService; + this.jiraService = jiraService; + this.openshiftService = openshiftService; + this.random = random; + } + + @Override + public String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException { + String pattern = resolveProjectKeyPattern(projectKeyPattern); + + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + int randomNumber = random.nextInt(1_000_000); + String projectKey = String.format(pattern, randomNumber); + + if (!isProjectFound(projectKey)) { + log.debug("Generated unique project key '{}' on attempt {}", projectKey, attempt); + return projectKey; + } + + log.debug("Project key '{}' already exists (attempt {}/{})", projectKey, attempt, MAX_RETRIES); + } + + throw new ProjectKeyGenerationException( + String.format("Failed to generate unique project key after %d retries", MAX_RETRIES)); + } + + private String resolveProjectKeyPattern(String projectKeyPattern) { + if (projectKeyPattern == null || projectKeyPattern.isBlank()) { + return DEFAULT_PROJECT_KEY_PATTERN; + } + return projectKeyPattern; + } + + private boolean isProjectFound(String projectKey) throws ProjectKeyGenerationException { + try { + if (existsInAnyBitbucketInstance(projectKey)) { + return true; + } + + if (existsInAnyJiraInstance(projectKey)) { + return true; + } + + if (existsInAnyOpenshift(projectKey)) { + return true; + } + + return false; + } catch (BitbucketException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Bitbucket", projectKey), e); + } catch (JiraException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Jira", projectKey), e); + } catch (OpenshiftException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Openshift", projectKey), e); + } + } + + private boolean existsInAnyBitbucketInstance(String projectKey) throws BitbucketException { + Set instances = bitbucketService.getAvailableInstances(); + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (bitbucketService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } + + private boolean existsInAnyJiraInstance(String projectKey) throws JiraException { + Set instances = jiraService.getAvailableInstances(); + + if (instances == null || instances.isEmpty()) { + return jiraService.projectExists(projectKey); + } + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (jiraService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } + + private boolean existsInAnyOpenshift(String projectKey) throws OpenshiftException { + Set instances = openshiftService.getAvailableInstances(); + + if (instances == null || instances.isEmpty()) { + return false; + } + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (openshiftService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } +} diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..09b8f10 --- /dev/null +++ b/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java @@ -0,0 +1,48 @@ +package org.opendevstack.apiservice.serviceproject.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; +import org.opendevstack.apiservice.serviceproject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class ProjectServiceImpl implements ProjectService { + + private final OpenshiftService openshiftService; + + private final BitbucketService bitbucketService; + + private final JiraService jiraService; + + private final GenerateProjectKeyService generateProjectKeyService; + + @Autowired + public ProjectServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService, + GenerateProjectKeyService generateProjectKeyService) { + this.bitbucketService = bitbucketService; + this.jiraService = jiraService; + this.openshiftService = openshiftService; + this.generateProjectKeyService = generateProjectKeyService; + } + + @Override + public CreateProjectResponse createProject(CreateProjectRequest request) { + // TODO Implement project creation against external systems. + return null; + } + + @Override + public CreateProjectResponse getProject(String projectKey) { + // TODO Implement project retrieval by key from external systems. + return null; + } +} + diff --git a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java b/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java new file mode 100644 index 0000000..b9cf5a0 --- /dev/null +++ b/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java @@ -0,0 +1,92 @@ +package org.opendevstack.apiservice.serviceproject.service.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; + +import java.util.Random; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class GenerateProjectKeyServiceImplTest { + + @Mock + private BitbucketService bitbucketService; + + @Mock + private JiraService jiraService; + + @Mock + private OpenshiftService openshiftService; + + @Mock + private Random random; + + private GenerateProjectKeyServiceImpl tested; + + @BeforeEach + void setup() { + tested = new GenerateProjectKeyServiceImpl(bitbucketService, jiraService, openshiftService, random); + when(bitbucketService.getAvailableInstances()).thenReturn(Set.of("dev")); + when(jiraService.getAvailableInstances()).thenReturn(Set.of("default")); + when(openshiftService.getAvailableInstances()).thenReturn(Set.of()); + } + + @Test + void generateProjectKey_whenFirstCandidateIsFree_thenReturnKey() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(7); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); + + String result = tested.generateProjectKey(null); + + assertThat(result).isEqualTo("SS000007"); + } + + @Test + void generateProjectKey_whenFirstCandidateExists_thenRetryUntilUnique() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(1, 2); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true, false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(true, false); + + String result = tested.generateProjectKey("SS%06d"); + + assertThat(result).isEqualTo("SS000002"); + } + + @Test + void generateProjectKey_whenNoUniqueKeyAfterMaxRetries_thenThrowException() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(1); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(true); + + assertThatThrownBy(() -> tested.generateProjectKey("SS%06d")) + .isInstanceOf(ProjectKeyGenerationException.class) + .hasMessageContaining("Failed to generate unique project key after 10 retries"); + } + + @Test + void generateProjectKey_whenCustomPatternProvided_thenUseIt() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(42); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); + + String result = tested.generateProjectKey("AB%04d"); + + assertThat(result).isEqualTo("AB0042"); + } +} diff --git a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java b/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java new file mode 100644 index 0000000..fcb48f1 --- /dev/null +++ b/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java @@ -0,0 +1,34 @@ +package org.opendevstack.apiservice.serviceproject.service.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; + +@ExtendWith(MockitoExtension.class) +class ProjectServiceImplTest { + + @Mock + private BitbucketService bitbucketService; + + @Mock + private JiraService jiraService; + + @Mock + private OpenshiftService openshiftService; + + @Mock + private GenerateProjectKeyService generateProjectKeyService; + + private ProjectServiceImpl sut; + + @BeforeEach + void setup() { + sut = new ProjectServiceImpl(bitbucketService, jiraService, openshiftService, generateProjectKeyService); + } +} + diff --git a/pom.xml b/pom.xml index 605d1ef..623b478 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,7 @@ external-service-bitbucket external-service-jira external-service-webhookproxy + external-service-projects api-project-users api-project-platform From b12605b3b8169d86a521a84054b060a45c63764b Mon Sep 17 00:00:00 2001 From: Angel Martinez Date: Thu, 5 Mar 2026 13:11:16 +0100 Subject: [PATCH 2/5] Bump version to 0.0.3 in pom.xml for all modules --- CHANGELOG.md | 2 ++ api-project-platform/pom.xml | 2 +- api-project-users/pom.xml | 2 +- core/pom.xml | 2 +- external-service-aap/pom.xml | 2 +- external-service-api/pom.xml | 2 +- external-service-bitbucket/pom.xml | 2 +- external-service-jira/pom.xml | 2 +- external-service-ocp/pom.xml | 2 +- external-service-projects-info-service/pom.xml | 2 +- external-service-projects/pom.xml | 2 +- external-service-uipath/pom.xml | 2 +- external-service-webhookproxy/pom.xml | 2 +- pom.xml | 2 +- 14 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a01dc46..714dc88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Created **New module** external service projects to manage EDP Projects. + ### External Service Jira (`external-service-jira`) - **New module** for checking project existance in Jira (Server) - Caching for the client diff --git a/api-project-platform/pom.xml b/api-project-platform/pom.xml index 52d3186..abf2834 100644 --- a/api-project-platform/pom.xml +++ b/api-project-platform/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 api-project-platform diff --git a/api-project-users/pom.xml b/api-project-users/pom.xml index fbd989c..c75bf70 100644 --- a/api-project-users/pom.xml +++ b/api-project-users/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 api-project-users diff --git a/core/pom.xml b/core/pom.xml index 76bb484..82994d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 core diff --git a/external-service-aap/pom.xml b/external-service-aap/pom.xml index 8250a95..0c80ffe 100644 --- a/external-service-aap/pom.xml +++ b/external-service-aap/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-aap diff --git a/external-service-api/pom.xml b/external-service-api/pom.xml index af29c09..b695737 100644 --- a/external-service-api/pom.xml +++ b/external-service-api/pom.xml @@ -7,7 +7,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2-SNAPSHOT + 0.0.3 external-service-api diff --git a/external-service-bitbucket/pom.xml b/external-service-bitbucket/pom.xml index efa5d4b..39467b7 100644 --- a/external-service-bitbucket/pom.xml +++ b/external-service-bitbucket/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-bitbucket diff --git a/external-service-jira/pom.xml b/external-service-jira/pom.xml index edb7e05..f89d456 100644 --- a/external-service-jira/pom.xml +++ b/external-service-jira/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-jira diff --git a/external-service-ocp/pom.xml b/external-service-ocp/pom.xml index a2f7df9..befcf6f 100644 --- a/external-service-ocp/pom.xml +++ b/external-service-ocp/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-ocp diff --git a/external-service-projects-info-service/pom.xml b/external-service-projects-info-service/pom.xml index d444710..76a7779 100644 --- a/external-service-projects-info-service/pom.xml +++ b/external-service-projects-info-service/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-projects-info-service diff --git a/external-service-projects/pom.xml b/external-service-projects/pom.xml index c6d622e..ca9fd77 100644 --- a/external-service-projects/pom.xml +++ b/external-service-projects/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-projects diff --git a/external-service-uipath/pom.xml b/external-service-uipath/pom.xml index aa7962c..b2a9925 100644 --- a/external-service-uipath/pom.xml +++ b/external-service-uipath/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-uipath diff --git a/external-service-webhookproxy/pom.xml b/external-service-webhookproxy/pom.xml index d4ed9a8..94c8ec1 100644 --- a/external-service-webhookproxy/pom.xml +++ b/external-service-webhookproxy/pom.xml @@ -6,7 +6,7 @@ org.opendevstack.apiservice devstack-api-service - 0.0.2 + 0.0.3 external-service-webhookproxy diff --git a/pom.xml b/pom.xml index 623b478..ac47e42 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.opendevstack.apiservice devstack-api-service Devstack API Service - 0.0.2 + 0.0.3 21 From 37362564882e671cff1af9615ea6367af4a3ded5 Mon Sep 17 00:00:00 2001 From: Angel Martinez Date: Thu, 5 Mar 2026 15:59:49 +0100 Subject: [PATCH 3/5] Rename external service project files and update artifactId in pom.xml --- pom.xml | 2 +- .../pom.xml | 138 ++++----- .../ProjectKeyGenerationException.java | 26 +- .../model/CreateProjectRequest.java | 0 .../model/CreateProjectResponse.java | 0 .../service/GenerateProjectKeyService.java | 22 +- .../service/ProjectService.java | 24 +- .../impl/GenerateProjectKeyServiceImpl.java | 288 +++++++++--------- .../service/impl/ProjectServiceImpl.java | 96 +++--- .../GenerateProjectKeyServiceImplTest.java | 184 +++++------ .../service/impl/ProjectServiceImplTest.java | 68 ++--- 11 files changed, 424 insertions(+), 424 deletions(-) rename {external-service-projects => service-projects}/pom.xml (95%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java (96%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java (100%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java (100%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java (96%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java (96%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java (97%) rename {external-service-projects => service-projects}/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java (97%) rename {external-service-projects => service-projects}/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java (97%) rename {external-service-projects => service-projects}/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java (96%) diff --git a/pom.xml b/pom.xml index ac47e42..fcea173 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ external-service-bitbucket external-service-jira external-service-webhookproxy - external-service-projects + service-projects api-project-users api-project-platform diff --git a/external-service-projects/pom.xml b/service-projects/pom.xml similarity index 95% rename from external-service-projects/pom.xml rename to service-projects/pom.xml index ca9fd77..bd0ab43 100644 --- a/external-service-projects/pom.xml +++ b/service-projects/pom.xml @@ -1,69 +1,69 @@ - - 4.0.0 - - - org.opendevstack.apiservice - devstack-api-service - 0.0.3 - - - external-service-projects - External Service Projects - Service module for project operations: key generation and project existence checks - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-validation - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - - org.openapitools - jackson-databind-nullable - ${jackson-databind-nullable.version} - - - - org.opendevstack.apiservice - external-service-bitbucket - ${project.version} - - - - org.opendevstack.apiservice - external-service-jira - ${project.version} - - - - org.opendevstack.apiservice - external-service-ocp - ${project.version} - - - - org.projectlombok - lombok - provided - - - - org.springframework.boot - spring-boot-starter-test - test - - - - + + 4.0.0 + + + org.opendevstack.apiservice + devstack-api-service + 0.0.3 + + + service-projects + External Service Projects + Service module for project operations: key generation and project existence checks + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable.version} + + + + org.opendevstack.apiservice + external-service-bitbucket + ${project.version} + + + + org.opendevstack.apiservice + external-service-jira + ${project.version} + + + + org.opendevstack.apiservice + external-service-ocp + ${project.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java similarity index 96% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java index dad4ffa..0d1fa5f 100644 --- a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/exception/ProjectKeyGenerationException.java @@ -1,13 +1,13 @@ -package org.opendevstack.apiservice.serviceproject.exception; - -public class ProjectKeyGenerationException extends Exception { - - public ProjectKeyGenerationException(String message) { - super(message); - } - - public ProjectKeyGenerationException(String message, Throwable cause) { - super(message, cause); - } -} - +package org.opendevstack.apiservice.serviceproject.exception; + +public class ProjectKeyGenerationException extends Exception { + + public ProjectKeyGenerationException(String message) { + super(message); + } + + public ProjectKeyGenerationException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java similarity index 100% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java similarity index 100% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java similarity index 96% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java index 33ffda0..289b977 100644 --- a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/GenerateProjectKeyService.java @@ -1,11 +1,11 @@ -package org.opendevstack.apiservice.serviceproject.service; - -import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; - -public interface GenerateProjectKeyService { - - String DEFAULT_PROJECT_KEY_PATTERN = "SS%06d"; - - String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException; -} - +package org.opendevstack.apiservice.serviceproject.service; + +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; + +public interface GenerateProjectKeyService { + + String DEFAULT_PROJECT_KEY_PATTERN = "SS%06d"; + + String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException; +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java similarity index 96% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java index 0c59b5e..63f2d66 100644 --- a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/ProjectService.java @@ -1,12 +1,12 @@ -package org.opendevstack.apiservice.serviceproject.service; - -import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; -import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; - -public interface ProjectService { - - CreateProjectResponse createProject(CreateProjectRequest request); - - CreateProjectResponse getProject(String projectKey); -} - +package org.opendevstack.apiservice.serviceproject.service; + +import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; + +public interface ProjectService { + + CreateProjectResponse createProject(CreateProjectRequest request); + + CreateProjectResponse getProject(String projectKey); +} + diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java similarity index 97% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java index c70b85d..b852443 100644 --- a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImpl.java @@ -1,144 +1,144 @@ -package org.opendevstack.apiservice.serviceproject.service.impl; - -import lombok.extern.slf4j.Slf4j; -import org.opendevstack.apiservice.externalservice.bitbucket.exception.BitbucketException; -import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; -import org.opendevstack.apiservice.externalservice.jira.exception.JiraException; -import org.opendevstack.apiservice.externalservice.jira.service.JiraService; -import org.opendevstack.apiservice.externalservice.ocp.exception.OpenshiftException; -import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; -import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; -import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Comparator; -import java.util.Random; -import java.util.Set; - -@Service -@Slf4j -public class GenerateProjectKeyServiceImpl implements GenerateProjectKeyService { - - private static final int MAX_RETRIES = 10; - - private final OpenshiftService openshiftService; - - private final BitbucketService bitbucketService; - - private final JiraService jiraService; - - private final Random random; - - @Autowired - public GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, - OpenshiftService openshiftService) { - this(bitbucketService, jiraService, openshiftService, new Random()); - } - - GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, - OpenshiftService openshiftService, Random random) { - this.bitbucketService = bitbucketService; - this.jiraService = jiraService; - this.openshiftService = openshiftService; - this.random = random; - } - - @Override - public String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException { - String pattern = resolveProjectKeyPattern(projectKeyPattern); - - for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { - int randomNumber = random.nextInt(1_000_000); - String projectKey = String.format(pattern, randomNumber); - - if (!isProjectFound(projectKey)) { - log.debug("Generated unique project key '{}' on attempt {}", projectKey, attempt); - return projectKey; - } - - log.debug("Project key '{}' already exists (attempt {}/{})", projectKey, attempt, MAX_RETRIES); - } - - throw new ProjectKeyGenerationException( - String.format("Failed to generate unique project key after %d retries", MAX_RETRIES)); - } - - private String resolveProjectKeyPattern(String projectKeyPattern) { - if (projectKeyPattern == null || projectKeyPattern.isBlank()) { - return DEFAULT_PROJECT_KEY_PATTERN; - } - return projectKeyPattern; - } - - private boolean isProjectFound(String projectKey) throws ProjectKeyGenerationException { - try { - if (existsInAnyBitbucketInstance(projectKey)) { - return true; - } - - if (existsInAnyJiraInstance(projectKey)) { - return true; - } - - if (existsInAnyOpenshift(projectKey)) { - return true; - } - - return false; - } catch (BitbucketException e) { - throw new ProjectKeyGenerationException( - String.format("Failed to check project '%s' in Bitbucket", projectKey), e); - } catch (JiraException e) { - throw new ProjectKeyGenerationException( - String.format("Failed to check project '%s' in Jira", projectKey), e); - } catch (OpenshiftException e) { - throw new ProjectKeyGenerationException( - String.format("Failed to check project '%s' in Openshift", projectKey), e); - } - } - - private boolean existsInAnyBitbucketInstance(String projectKey) throws BitbucketException { - Set instances = bitbucketService.getAvailableInstances(); - - for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { - if (bitbucketService.projectExists(instanceName, projectKey)) { - return true; - } - } - - return false; - } - - private boolean existsInAnyJiraInstance(String projectKey) throws JiraException { - Set instances = jiraService.getAvailableInstances(); - - if (instances == null || instances.isEmpty()) { - return jiraService.projectExists(projectKey); - } - - for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { - if (jiraService.projectExists(instanceName, projectKey)) { - return true; - } - } - - return false; - } - - private boolean existsInAnyOpenshift(String projectKey) throws OpenshiftException { - Set instances = openshiftService.getAvailableInstances(); - - if (instances == null || instances.isEmpty()) { - return false; - } - - for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { - if (openshiftService.projectExists(instanceName, projectKey)) { - return true; - } - } - - return false; - } -} +package org.opendevstack.apiservice.serviceproject.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.bitbucket.exception.BitbucketException; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.exception.JiraException; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.exception.OpenshiftException; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.Random; +import java.util.Set; + +@Service +@Slf4j +public class GenerateProjectKeyServiceImpl implements GenerateProjectKeyService { + + private static final int MAX_RETRIES = 10; + + private final OpenshiftService openshiftService; + + private final BitbucketService bitbucketService; + + private final JiraService jiraService; + + private final Random random; + + @Autowired + public GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService) { + this(bitbucketService, jiraService, openshiftService, new Random()); + } + + GenerateProjectKeyServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService, Random random) { + this.bitbucketService = bitbucketService; + this.jiraService = jiraService; + this.openshiftService = openshiftService; + this.random = random; + } + + @Override + public String generateProjectKey(String projectKeyPattern) throws ProjectKeyGenerationException { + String pattern = resolveProjectKeyPattern(projectKeyPattern); + + for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { + int randomNumber = random.nextInt(1_000_000); + String projectKey = String.format(pattern, randomNumber); + + if (!isProjectFound(projectKey)) { + log.debug("Generated unique project key '{}' on attempt {}", projectKey, attempt); + return projectKey; + } + + log.debug("Project key '{}' already exists (attempt {}/{})", projectKey, attempt, MAX_RETRIES); + } + + throw new ProjectKeyGenerationException( + String.format("Failed to generate unique project key after %d retries", MAX_RETRIES)); + } + + private String resolveProjectKeyPattern(String projectKeyPattern) { + if (projectKeyPattern == null || projectKeyPattern.isBlank()) { + return DEFAULT_PROJECT_KEY_PATTERN; + } + return projectKeyPattern; + } + + private boolean isProjectFound(String projectKey) throws ProjectKeyGenerationException { + try { + if (existsInAnyBitbucketInstance(projectKey)) { + return true; + } + + if (existsInAnyJiraInstance(projectKey)) { + return true; + } + + if (existsInAnyOpenshift(projectKey)) { + return true; + } + + return false; + } catch (BitbucketException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Bitbucket", projectKey), e); + } catch (JiraException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Jira", projectKey), e); + } catch (OpenshiftException e) { + throw new ProjectKeyGenerationException( + String.format("Failed to check project '%s' in Openshift", projectKey), e); + } + } + + private boolean existsInAnyBitbucketInstance(String projectKey) throws BitbucketException { + Set instances = bitbucketService.getAvailableInstances(); + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (bitbucketService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } + + private boolean existsInAnyJiraInstance(String projectKey) throws JiraException { + Set instances = jiraService.getAvailableInstances(); + + if (instances == null || instances.isEmpty()) { + return jiraService.projectExists(projectKey); + } + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (jiraService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } + + private boolean existsInAnyOpenshift(String projectKey) throws OpenshiftException { + Set instances = openshiftService.getAvailableInstances(); + + if (instances == null || instances.isEmpty()) { + return false; + } + + for (String instanceName : instances.stream().sorted(Comparator.naturalOrder()).toList()) { + if (openshiftService.projectExists(instanceName, projectKey)) { + return true; + } + } + + return false; + } +} diff --git a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java similarity index 97% rename from external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java rename to service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java index 09b8f10..c019f5a 100644 --- a/external-service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImpl.java @@ -1,48 +1,48 @@ -package org.opendevstack.apiservice.serviceproject.service.impl; - -import lombok.extern.slf4j.Slf4j; -import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; -import org.opendevstack.apiservice.externalservice.jira.service.JiraService; -import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; -import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; -import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; -import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; -import org.opendevstack.apiservice.serviceproject.service.ProjectService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class ProjectServiceImpl implements ProjectService { - - private final OpenshiftService openshiftService; - - private final BitbucketService bitbucketService; - - private final JiraService jiraService; - - private final GenerateProjectKeyService generateProjectKeyService; - - @Autowired - public ProjectServiceImpl(BitbucketService bitbucketService, JiraService jiraService, - OpenshiftService openshiftService, - GenerateProjectKeyService generateProjectKeyService) { - this.bitbucketService = bitbucketService; - this.jiraService = jiraService; - this.openshiftService = openshiftService; - this.generateProjectKeyService = generateProjectKeyService; - } - - @Override - public CreateProjectResponse createProject(CreateProjectRequest request) { - // TODO Implement project creation against external systems. - return null; - } - - @Override - public CreateProjectResponse getProject(String projectKey) { - // TODO Implement project retrieval by key from external systems. - return null; - } -} - +package org.opendevstack.apiservice.serviceproject.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest; +import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; +import org.opendevstack.apiservice.serviceproject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class ProjectServiceImpl implements ProjectService { + + private final OpenshiftService openshiftService; + + private final BitbucketService bitbucketService; + + private final JiraService jiraService; + + private final GenerateProjectKeyService generateProjectKeyService; + + @Autowired + public ProjectServiceImpl(BitbucketService bitbucketService, JiraService jiraService, + OpenshiftService openshiftService, + GenerateProjectKeyService generateProjectKeyService) { + this.bitbucketService = bitbucketService; + this.jiraService = jiraService; + this.openshiftService = openshiftService; + this.generateProjectKeyService = generateProjectKeyService; + } + + @Override + public CreateProjectResponse createProject(CreateProjectRequest request) { + // TODO Implement project creation against external systems. + return null; + } + + @Override + public CreateProjectResponse getProject(String projectKey) { + // TODO Implement project retrieval by key from external systems. + return null; + } +} + diff --git a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java similarity index 97% rename from external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java rename to service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java index b9cf5a0..52c3073 100644 --- a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java +++ b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java @@ -1,92 +1,92 @@ -package org.opendevstack.apiservice.serviceproject.service.impl; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; -import org.opendevstack.apiservice.externalservice.jira.service.JiraService; -import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; -import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; - -import java.util.Random; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class GenerateProjectKeyServiceImplTest { - - @Mock - private BitbucketService bitbucketService; - - @Mock - private JiraService jiraService; - - @Mock - private OpenshiftService openshiftService; - - @Mock - private Random random; - - private GenerateProjectKeyServiceImpl tested; - - @BeforeEach - void setup() { - tested = new GenerateProjectKeyServiceImpl(bitbucketService, jiraService, openshiftService, random); - when(bitbucketService.getAvailableInstances()).thenReturn(Set.of("dev")); - when(jiraService.getAvailableInstances()).thenReturn(Set.of("default")); - when(openshiftService.getAvailableInstances()).thenReturn(Set.of()); - } - - @Test - void generateProjectKey_whenFirstCandidateIsFree_thenReturnKey() throws Exception { - when(random.nextInt(1_000_000)).thenReturn(7); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); - when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); - - String result = tested.generateProjectKey(null); - - assertThat(result).isEqualTo("SS000007"); - } - - @Test - void generateProjectKey_whenFirstCandidateExists_thenRetryUntilUnique() throws Exception { - when(random.nextInt(1_000_000)).thenReturn(1, 2); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true, false); - when(jiraService.projectExists(anyString(), anyString())).thenReturn(true, false); - - String result = tested.generateProjectKey("SS%06d"); - - assertThat(result).isEqualTo("SS000002"); - } - - @Test - void generateProjectKey_whenNoUniqueKeyAfterMaxRetries_thenThrowException() throws Exception { - when(random.nextInt(1_000_000)).thenReturn(1); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true); - when(jiraService.projectExists(anyString(), anyString())).thenReturn(true); - - assertThatThrownBy(() -> tested.generateProjectKey("SS%06d")) - .isInstanceOf(ProjectKeyGenerationException.class) - .hasMessageContaining("Failed to generate unique project key after 10 retries"); - } - - @Test - void generateProjectKey_whenCustomPatternProvided_thenUseIt() throws Exception { - when(random.nextInt(1_000_000)).thenReturn(42); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); - when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); - - String result = tested.generateProjectKey("AB%04d"); - - assertThat(result).isEqualTo("AB0042"); - } -} +package org.opendevstack.apiservice.serviceproject.service.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.exception.ProjectKeyGenerationException; + +import java.util.Random; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class GenerateProjectKeyServiceImplTest { + + @Mock + private BitbucketService bitbucketService; + + @Mock + private JiraService jiraService; + + @Mock + private OpenshiftService openshiftService; + + @Mock + private Random random; + + private GenerateProjectKeyServiceImpl tested; + + @BeforeEach + void setup() { + tested = new GenerateProjectKeyServiceImpl(bitbucketService, jiraService, openshiftService, random); + when(bitbucketService.getAvailableInstances()).thenReturn(Set.of("dev")); + when(jiraService.getAvailableInstances()).thenReturn(Set.of("default")); + when(openshiftService.getAvailableInstances()).thenReturn(Set.of()); + } + + @Test + void generateProjectKey_whenFirstCandidateIsFree_thenReturnKey() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(7); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); + + String result = tested.generateProjectKey(null); + + assertThat(result).isEqualTo("SS000007"); + } + + @Test + void generateProjectKey_whenFirstCandidateExists_thenRetryUntilUnique() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(1, 2); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true, false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(true, false); + + String result = tested.generateProjectKey("SS%06d"); + + assertThat(result).isEqualTo("SS000002"); + } + + @Test + void generateProjectKey_whenNoUniqueKeyAfterMaxRetries_thenThrowException() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(1); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(true); + + assertThatThrownBy(() -> tested.generateProjectKey("SS%06d")) + .isInstanceOf(ProjectKeyGenerationException.class) + .hasMessageContaining("Failed to generate unique project key after 10 retries"); + } + + @Test + void generateProjectKey_whenCustomPatternProvided_thenUseIt() throws Exception { + when(random.nextInt(1_000_000)).thenReturn(42); + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(false); + + String result = tested.generateProjectKey("AB%04d"); + + assertThat(result).isEqualTo("AB0042"); + } +} diff --git a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java similarity index 96% rename from external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java rename to service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java index fcb48f1..0779419 100644 --- a/external-service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java +++ b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/ProjectServiceImplTest.java @@ -1,34 +1,34 @@ -package org.opendevstack.apiservice.serviceproject.service.impl; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; -import org.opendevstack.apiservice.externalservice.jira.service.JiraService; -import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; -import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; - -@ExtendWith(MockitoExtension.class) -class ProjectServiceImplTest { - - @Mock - private BitbucketService bitbucketService; - - @Mock - private JiraService jiraService; - - @Mock - private OpenshiftService openshiftService; - - @Mock - private GenerateProjectKeyService generateProjectKeyService; - - private ProjectServiceImpl sut; - - @BeforeEach - void setup() { - sut = new ProjectServiceImpl(bitbucketService, jiraService, openshiftService, generateProjectKeyService); - } -} - +package org.opendevstack.apiservice.serviceproject.service.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.apiservice.externalservice.bitbucket.service.BitbucketService; +import org.opendevstack.apiservice.externalservice.jira.service.JiraService; +import org.opendevstack.apiservice.externalservice.ocp.service.OpenshiftService; +import org.opendevstack.apiservice.serviceproject.service.GenerateProjectKeyService; + +@ExtendWith(MockitoExtension.class) +class ProjectServiceImplTest { + + @Mock + private BitbucketService bitbucketService; + + @Mock + private JiraService jiraService; + + @Mock + private OpenshiftService openshiftService; + + @Mock + private GenerateProjectKeyService generateProjectKeyService; + + private ProjectServiceImpl sut; + + @BeforeEach + void setup() { + sut = new ProjectServiceImpl(bitbucketService, jiraService, openshiftService, generateProjectKeyService); + } +} + From 70fc0f30e326806ec4eb88b37d667c57ffd7ebcd Mon Sep 17 00:00:00 2001 From: Angel Martinez Date: Fri, 6 Mar 2026 17:38:19 +0100 Subject: [PATCH 4/5] Add API module for project management with create and retrieve functionality --- .gitignore | 5 +- .../openapi/api-project-users.yaml | 4 +- api-project/openapi/api-project.yaml | 152 +++++++++++++++ api-project/pom.xml | 163 ++++++++++++++++ .../project/controller/ProjectController.java | 73 ++++++++ .../exception/ProjectCreationException.java | 12 ++ .../project/exception/ProjectException.java | 12 ++ .../ProjectKeyGenerationException.java | 12 ++ .../project/facade/ProjectsFacade.java | 14 ++ .../facade/impl/ProjectsFacadeImpl.java | 35 ++++ .../project/mapper/ProjectMapper.java | 15 ++ .../ProjectControllerIntegrationTest.java | 81 ++++++++ .../controller/ProjectControllerTest.java | 132 +++++++++++++ .../facade/impl/ProjectsFacadeImplTest.java | 92 +++++++++ application.yaml | 175 ------------------ core/pom.xml | 7 + pom.xml | 4 +- service-projects/pom.xml | 8 +- .../model/CreateProjectRequest.java | 2 + .../model/CreateProjectResponse.java | 2 + .../GenerateProjectKeyServiceImplTest.java | 14 +- 21 files changed, 828 insertions(+), 186 deletions(-) create mode 100644 api-project/openapi/api-project.yaml create mode 100644 api-project/pom.xml create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectController.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectCreationException.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectException.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectKeyGenerationException.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/facade/ProjectsFacade.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImpl.java create mode 100644 api-project/src/main/java/org/opendevstack/apiservice/project/mapper/ProjectMapper.java create mode 100644 api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerIntegrationTest.java create mode 100644 api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerTest.java create mode 100644 api-project/src/test/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImplTest.java delete mode 100644 application.yaml diff --git a/.gitignore b/.gitignore index b117982..3c453dd 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,7 @@ api-project-users/src/main/java/org/opendevstack/apiservice/projectusers/api api-project-users/src/main/java/org/opendevstack/apiservice/projectusers/model api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/api api-project-platform/src/main/java/org/opendevstack/apiservice/projectplatform/model -api-project-users/.openapi-generator -/api-project-platform/.openapi-generator/ +api-project/src/main/java/org/opendevstack/apiservice/project/api +api-project/src/main/java/org/opendevstack/apiservice/project/model +**/.openapi-generator diff --git a/api-project-users/openapi/api-project-users.yaml b/api-project-users/openapi/api-project-users.yaml index 2d0ba51..a78d1ef 100644 --- a/api-project-users/openapi/api-project-users.yaml +++ b/api-project-users/openapi/api-project-users.yaml @@ -16,7 +16,7 @@ tags: - name: Project Users description: API for managing project users and their roles paths: - /project/{projectKey}/users: + /projects/{projectKey}/users: post: tags: - Project Users @@ -89,7 +89,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ApiResponseMembershipRequestResponse" - /project/{projectKey}/users/{userid}/status: + /projects/{projectKey}/users/{userid}/status: get: tags: - Project Users diff --git a/api-project/openapi/api-project.yaml b/api-project/openapi/api-project.yaml new file mode 100644 index 0000000..f734d81 --- /dev/null +++ b/api-project/openapi/api-project.yaml @@ -0,0 +1,152 @@ +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/v0 + variables: + baseurl: + default: localhost:8080 + description: Development environment +tags: +- name: Project + description: API for manage EDP projects. +paths: + /projects: + post: + tags: + - Projects + summary: Create a new project. + description: Creates a new project with the provided configuration. Generates a unique project key if not provided. + operationId: createProject + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectRequest' + responses: + '200': + description: Project creation initiated successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + '400': + description: Invalid request body or validation error. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + '409': + description: A project with the specified key already exists. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + '500': + description: Internal server error during project creation. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + /projects/{projectKey}: + get: + tags: + - Projects + summary: Get project status by project key. + description: Returns the current status and details of the project identified by the given project key. + operationId: getProject + parameters: + - name: projectKey + in: path + required: true + schema: + type: string + description: Project key to retrieve information. + responses: + '200': + description: Project information retrieved successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + '404': + description: Project not found. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' + "401": + description: Invalid client token on the request. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + "403": + description: Insufficient permissions for the client to access the resource. + content: + application/json: + schema: + $ref: '#/components/schemas/RestErrorMessage' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProjectResponse' +components: + schemas: + RestErrorMessage: + properties: + message: + type: string + required: + - message + CreateProjectRequest: + type: object + properties: + projectKey: + type: string + description: Optional project key. If not provided, a unique key will be generated. + projectKeyPattern: + type: string + description: Optional pattern for generating the project key (e.g. 'SS%06d'). + projectName: + type: string + description: Name of the project. + projectDescription: + type: string + description: Description of the project. + required: + - projectName + CreateProjectResponse: + type: object + properties: + projectKey: + type: string + status: + type: string + message: + type: string + error: + type: string + errorKey: + type: string + errorDescription: + type: string diff --git a/api-project/pom.xml b/api-project/pom.xml new file mode 100644 index 0000000..8daceb0 --- /dev/null +++ b/api-project/pom.xml @@ -0,0 +1,163 @@ + + 4.0.0 + + + org.opendevstack.apiservice + devstack-api-service + 0.0.3 + + + api-project + API Projects + API module for managing EDP projects + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + org.opendevstack.apiservice + service-projects + ${project.version} + + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + + org.projectlombok + lombok + provided + + + + org.mapstruct + mapstruct + 1.6.3 + + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable.version} + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-core + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + 1.6.3 + + + + + + org.openapitools + openapi-generator-maven-plugin + + + generate-api-project + + generate + + + spring + ${project.basedir} + spring-boot + ${project.basedir}/openapi/api-project.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 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectController.java b/api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectController.java new file mode 100644 index 0000000..61210e7 --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/controller/ProjectController.java @@ -0,0 +1,73 @@ +package org.opendevstack.apiservice.project.controller; + +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.opendevstack.apiservice.project.api.ProjectsApi; +import org.opendevstack.apiservice.project.exception.ProjectCreationException; +import org.opendevstack.apiservice.project.facade.ProjectsFacade; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; +import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v0/projects") +@AllArgsConstructor +@Slf4j +public class ProjectController implements ProjectsApi { + + private final ProjectsFacade projectsFacade; + + @PostMapping + @Override + public ResponseEntity createProject(@Valid @RequestBody CreateProjectRequest createProjectRequest) { + try { + return ResponseEntity.ok(projectsFacade.createProject(createProjectRequest)); + } catch (ProjectCreationException e) { + log.error("Project creation conflict: {}", e.getMessage()); + CreateProjectResponse errorResponse = new CreateProjectResponse(); + errorResponse.setError("CONFLICT"); + errorResponse.setErrorKey("PROJECT_ALREADY_EXISTS"); + errorResponse.setMessage(e.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse); + } catch (ProjectKeyGenerationException e) { + log.error("Failed to generate project key: {}", e.getMessage(), e); + CreateProjectResponse errorResponse = new CreateProjectResponse(); + errorResponse.setError("INTERNAL_ERROR"); + errorResponse.setErrorKey("PROJECT_KEY_GENERATION_FAILED"); + errorResponse.setMessage("Failed to generate a unique project key."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + } + + @GetMapping("/{projectKey}") + @Override + public ResponseEntity getProject(@PathVariable String projectKey) { + try { + CreateProjectResponse response = projectsFacade.getProject(projectKey); + if (response == null) { + CreateProjectResponse notFoundResponse = new CreateProjectResponse(); + notFoundResponse.setError("NOT_FOUND"); + notFoundResponse.setErrorKey("PROJECT_NOT_FOUND"); + notFoundResponse.setMessage(String.format("Project with key '%s' not found", projectKey)); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(notFoundResponse); + } + return ResponseEntity.ok(response); + } catch (ProjectCreationException e) { + log.error("Error retrieving project '{}': {}", projectKey, e.getMessage(), e); + CreateProjectResponse errorResponse = new CreateProjectResponse(); + errorResponse.setError("INTERNAL_ERROR"); + errorResponse.setErrorKey("INTERNAL_ERROR"); + errorResponse.setMessage("An error occurred while processing the request."); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + } +} diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectCreationException.java b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectCreationException.java new file mode 100644 index 0000000..87e12af --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectCreationException.java @@ -0,0 +1,12 @@ +package org.opendevstack.apiservice.project.exception; + +public class ProjectCreationException extends Exception { + + public ProjectCreationException(String message) { + super(message); + } + + public ProjectCreationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectException.java b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectException.java new file mode 100644 index 0000000..e9d8e8b --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectException.java @@ -0,0 +1,12 @@ +package org.opendevstack.apiservice.project.exception; + +public class ProjectException extends Exception { + + public ProjectException(String message) { + super(message); + } + + public ProjectException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectKeyGenerationException.java b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectKeyGenerationException.java new file mode 100644 index 0000000..be9b66a --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/exception/ProjectKeyGenerationException.java @@ -0,0 +1,12 @@ +package org.opendevstack.apiservice.project.exception; + +public class ProjectKeyGenerationException extends Exception { + + public ProjectKeyGenerationException(String message) { + super(message); + } + + public ProjectKeyGenerationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/facade/ProjectsFacade.java b/api-project/src/main/java/org/opendevstack/apiservice/project/facade/ProjectsFacade.java new file mode 100644 index 0000000..9d29f13 --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/facade/ProjectsFacade.java @@ -0,0 +1,14 @@ +package org.opendevstack.apiservice.project.facade; + +import org.opendevstack.apiservice.project.exception.ProjectCreationException; +import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; + +public interface ProjectsFacade { + + CreateProjectResponse createProject(CreateProjectRequest request) + throws ProjectCreationException, ProjectKeyGenerationException; + + CreateProjectResponse getProject(String projectKey) throws ProjectCreationException; +} \ No newline at end of file diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImpl.java b/api-project/src/main/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImpl.java new file mode 100644 index 0000000..274ad1d --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImpl.java @@ -0,0 +1,35 @@ +package org.opendevstack.apiservice.project.facade.impl; + +import org.opendevstack.apiservice.project.exception.ProjectCreationException; +import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException; +import org.opendevstack.apiservice.project.facade.ProjectsFacade; +import org.opendevstack.apiservice.project.mapper.ProjectMapper; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; +import org.opendevstack.apiservice.serviceproject.service.ProjectService; +import org.springframework.stereotype.Component; + +@Component("apiProjectFacadeImpl") +public class ProjectsFacadeImpl implements ProjectsFacade { + + private final ProjectService projectService; + private final ProjectMapper projectMapper; + + public ProjectsFacadeImpl( + ProjectService projectService, + ProjectMapper projectMapper) { + this.projectService = projectService; + this.projectMapper = projectMapper; + } + + @Override + public CreateProjectResponse createProject(CreateProjectRequest request) + throws ProjectCreationException, ProjectKeyGenerationException { + return projectMapper.toApiResponse(projectService.createProject(projectMapper.toServiceRequest(request))); + } + + @Override + public CreateProjectResponse getProject(String projectKey) throws ProjectCreationException { + return projectMapper.toApiResponse(projectService.getProject(projectKey)); + } +} \ No newline at end of file diff --git a/api-project/src/main/java/org/opendevstack/apiservice/project/mapper/ProjectMapper.java b/api-project/src/main/java/org/opendevstack/apiservice/project/mapper/ProjectMapper.java new file mode 100644 index 0000000..f9b24c8 --- /dev/null +++ b/api-project/src/main/java/org/opendevstack/apiservice/project/mapper/ProjectMapper.java @@ -0,0 +1,15 @@ +package org.opendevstack.apiservice.project.mapper; + +import org.mapstruct.Mapper; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; + +@Mapper(componentModel = "spring") +public interface ProjectMapper { + + org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest toServiceRequest( + CreateProjectRequest apiRequest); + + CreateProjectResponse toApiResponse( + org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse serviceResponse); +} diff --git a/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerIntegrationTest.java b/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerIntegrationTest.java new file mode 100644 index 0000000..93b82c1 --- /dev/null +++ b/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerIntegrationTest.java @@ -0,0 +1,81 @@ +package org.opendevstack.apiservice.project.controller; + +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.opendevstack.apiservice.project.facade.impl.ProjectsFacadeImpl; +import org.opendevstack.apiservice.project.mapper.ProjectMapper; +import org.opendevstack.apiservice.serviceproject.service.ProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(classes = ProjectControllerIntegrationTest.TestConfig.class) +@AutoConfigureMockMvc(addFilters = false) +class ProjectControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + @MockitoBean + private ProjectService projectService; + + @Test + void createProject_withProvidedProjectKey_returnsInitiatedResponse() throws Exception { + org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse serviceResponse = + new org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse(); + serviceResponse.setProjectKey("PROJ01"); + serviceResponse.setStatus("Initiated"); + when(projectService.createProject(org.mockito.ArgumentMatchers.any( + org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest.class))) + .thenReturn(serviceResponse); + + String payload = """ + { + \"projectKey\": \"PROJ01\", + \"projectName\": \"My Project\", + \"projectDescription\": \"desc\" + } + """; + + mockMvc.perform(post("/api/v0/projects") + .contentType("application/json") + .content(payload == null ? "" : payload)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.projectKey").value("PROJ01")) + .andExpect(jsonPath("$.status").value("Initiated")); + } + + @Test + void getProject_whenNotFound_returns404() throws Exception { + when(projectService.getProject("UNKNOWN")).thenReturn(null); + + mockMvc.perform(get("/api/v0/projects/UNKNOWN")) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.error").value("NOT_FOUND")) + .andExpect(jsonPath("$.errorKey").value("PROJECT_NOT_FOUND")); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @Import({ProjectController.class, ProjectsFacadeImpl.class}) + static class TestConfig { + + @Bean + ProjectMapper projectMapper() { + return Mappers.getMapper(ProjectMapper.class); + } + } +} \ No newline at end of file diff --git a/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerTest.java b/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerTest.java new file mode 100644 index 0000000..39f5fd9 --- /dev/null +++ b/api-project/src/test/java/org/opendevstack/apiservice/project/controller/ProjectControllerTest.java @@ -0,0 +1,132 @@ +package org.opendevstack.apiservice.project.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.apiservice.project.exception.ProjectCreationException; +import org.opendevstack.apiservice.project.exception.ProjectKeyGenerationException; +import org.opendevstack.apiservice.project.facade.ProjectsFacade; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProjectControllerTest { + + @Mock + private ProjectsFacade projectsFacade; + + private ProjectController sut; + + @BeforeEach + void setup() { + sut = new ProjectController(projectsFacade); + } + + @Test + void createProject_whenSuccess_thenReturnOk() throws Exception { + CreateProjectRequest request = new CreateProjectRequest("My Project"); + request.setProjectKey("PROJ01"); + + CreateProjectResponse serviceResponse = new CreateProjectResponse(); + serviceResponse.setProjectKey("PROJ01"); + serviceResponse.setStatus("Initiated"); + serviceResponse.setMessage("The project creation process has been successfully initiated."); + + when(projectsFacade.createProject(any(CreateProjectRequest.class))) + .thenReturn(serviceResponse); + + ResponseEntity result = sut.createProject(request); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getProjectKey()).isEqualTo("PROJ01"); + assertThat(result.getBody().getStatus()).isEqualTo("Initiated"); + } + + @Test + void createProject_whenProjectCreationException_thenReturnConflict() throws Exception { + CreateProjectRequest request = new CreateProjectRequest("My Project"); + request.setProjectKey("EXISTING"); + + when(projectsFacade.createProject(any(CreateProjectRequest.class))) + .thenThrow(new ProjectCreationException("Project with key 'EXISTING' already exists")); + + ResponseEntity result = sut.createProject(request); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getError()).isEqualTo("CONFLICT"); + assertThat(result.getBody().getErrorKey()).isEqualTo("PROJECT_ALREADY_EXISTS"); + assertThat(result.getBody().getMessage()).contains("already exists"); + } + + @Test + void createProject_whenProjectKeyGenerationException_thenReturnInternalServerError() throws Exception { + CreateProjectRequest request = new CreateProjectRequest("My Project"); + + when(projectsFacade.createProject(any(CreateProjectRequest.class))) + .thenThrow(new ProjectKeyGenerationException("Failed to generate unique project key after 10 retries")); + + ResponseEntity result = sut.createProject(request); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getError()).isEqualTo("INTERNAL_ERROR"); + assertThat(result.getBody().getErrorKey()).isEqualTo("PROJECT_KEY_GENERATION_FAILED"); + assertThat(result.getBody().getMessage()).isEqualTo("Failed to generate a unique project key."); + } + + @Test + void getProject_whenFound_thenReturnOk() throws Exception { + CreateProjectResponse serviceResponse = new CreateProjectResponse(); + serviceResponse.setProjectKey("PROJ01"); + serviceResponse.setStatus("Initiated"); + + when(projectsFacade.getProject("PROJ01")).thenReturn(serviceResponse); + + ResponseEntity result = sut.getProject("PROJ01"); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getProjectKey()).isEqualTo("PROJ01"); + verify(projectsFacade).getProject("PROJ01"); + } + + @Test + void getProject_whenNotFound_thenReturnNotFound() throws Exception { + when(projectsFacade.getProject("UNKNOWN")).thenReturn(null); + + ResponseEntity result = sut.getProject("UNKNOWN"); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getError()).isEqualTo("NOT_FOUND"); + assertThat(result.getBody().getErrorKey()).isEqualTo("PROJECT_NOT_FOUND"); + assertThat(result.getBody().getMessage()).contains("UNKNOWN"); + } + + @Test + void getProject_whenServiceThrows_thenReturnInternalServerError() throws Exception { + when(projectsFacade.getProject(anyString())) + .thenThrow(new ProjectCreationException("Database error")); + + ResponseEntity result = sut.getProject("PROJ01"); + + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(result.getBody()).isNotNull(); + assertThat(result.getBody().getError()).isEqualTo("INTERNAL_ERROR"); + assertThat(result.getBody().getErrorKey()).isEqualTo("INTERNAL_ERROR"); + assertThat(result.getBody().getMessage()).isEqualTo("An error occurred while processing the request."); + } + +} diff --git a/api-project/src/test/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImplTest.java b/api-project/src/test/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImplTest.java new file mode 100644 index 0000000..dc0cf00 --- /dev/null +++ b/api-project/src/test/java/org/opendevstack/apiservice/project/facade/impl/ProjectsFacadeImplTest.java @@ -0,0 +1,92 @@ +package org.opendevstack.apiservice.project.facade.impl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opendevstack.apiservice.project.mapper.ProjectMapper; +import org.opendevstack.apiservice.project.model.CreateProjectRequest; +import org.opendevstack.apiservice.project.model.CreateProjectResponse; +import org.opendevstack.apiservice.serviceproject.service.ProjectService; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProjectsFacadeImplTest { + + @Mock + private ProjectService projectService; + + private final ProjectMapper projectMapper = Mappers.getMapper(ProjectMapper.class); + + private ProjectsFacadeImpl sut; + + @BeforeEach + void setup() { + sut = new ProjectsFacadeImpl(projectService, projectMapper); + } + + @Test + void createProject_whenServiceReturnsValue_thenMapToApiModel() throws Exception { + CreateProjectRequest request = new CreateProjectRequest("My Project"); + request.setProjectKey("PROJ01"); + + org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse serviceResponse = + new org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse(); + serviceResponse.setProjectKey("PROJ01"); + serviceResponse.setStatus("Initiated"); + + when(projectService.createProject(org.mockito.ArgumentMatchers.any( + org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest.class))) + .thenReturn(serviceResponse); + + CreateProjectResponse response = sut.createProject(request); + + assertThat(response).isNotNull(); + assertThat(response.getProjectKey()).isEqualTo("PROJ01"); + assertThat(response.getStatus()).isEqualTo("Initiated"); + verify(projectService).createProject(org.mockito.ArgumentMatchers.any( + org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest.class)); + } + + @Test + void createProject_whenServiceReturnsNull_thenReturnNull() throws Exception { + CreateProjectRequest request = new CreateProjectRequest("My Project"); + when(projectService.createProject(org.mockito.ArgumentMatchers.any( + org.opendevstack.apiservice.serviceproject.model.CreateProjectRequest.class))) + .thenReturn(null); + + CreateProjectResponse response = sut.createProject(request); + + assertThat(response).isNull(); + } + + @Test + void getProject_whenServiceReturnsValue_thenMapToApiModel() throws Exception { + org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse serviceResponse = + new org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse(); + serviceResponse.setProjectKey("PROJ01"); + serviceResponse.setStatus("Found"); + + when(projectService.getProject("PROJ01")).thenReturn(serviceResponse); + + CreateProjectResponse response = sut.getProject("PROJ01"); + + assertThat(response).isNotNull(); + assertThat(response.getProjectKey()).isEqualTo("PROJ01"); + assertThat(response.getStatus()).isEqualTo("Found"); + } + + @Test + void getProject_whenServiceReturnsNull_thenReturnNull() throws Exception { + when(projectService.getProject("UNKNOWN")).thenReturn(null); + + CreateProjectResponse response = sut.getProject("UNKNOWN"); + + assertThat(response).isNull(); + } +} diff --git a/application.yaml b/application.yaml deleted file mode 100644 index e1d762a..0000000 --- a/application.yaml +++ /dev/null @@ -1,175 +0,0 @@ -logging: - level: - org.springframework: INFO - org.springframework.security: TRACE - org.opendevstack.apiservice.externalservice: DEBUG - -management: - endpoints: - web: - exposure: - include: openapi, swagger-ui, beans, caches, configprops, env, health, httpexchanges, info, loggers, mappings - endpoint: - configprops: - show-values: always - env: - show-values: always - loggers: - access: unrestricted - health: - show-details: always - show-components: always - info: - git: - # Show all build-time generated file git.properties info on /actuator/info endpoint - mode: full - httpexchanges: - recording: - # Show all available info in /actuator/httpexchanges and also in Swagger - include: request-headers, response-headers, authorization_header, cookie_headers, principal, remote_address, session_id, time_taken -springdoc: - show-actuator: true - swagger-ui: - doc-expansion: none - try-it-out-enabled: true - filter: true - tags-sorter: alpha - operations-sorter: alpha - -openapi: - servers: - - url: "https://localhost:8080" - description: "Development environment" - -otel: - service: - name: devstack-api-service-dev - version: 0.0.2 - exporter: - otlp: - endpoint: http://opentelemetry.example.com - traces: - exporter: logging,otlp - sampler: parentbased_traceidratio - sampler_arg: 1.0 - metrics: - exporter: none - resource: - attributes: service.name=devstack-api-service,service.version=0.0.2,deployment.environment=development - instrumentation: - jdbc: - enabled: false - logback-appender: - enabled: true - -# External Service Configuration -automation: - platform: - ansible: - enabled: true - base-url: ${ANSIBLE_BASE_URL:http://localhost:8080/api/v2} - username: ${ANSIBLE_USERNAME:admin} - password: ${ANSIBLE_PASSWORD:password} - timeout: ${ANSIBLE_TIMEOUT:30000} - ssl: - verify-certificates: ${ANSIBLE_SSL_VERIFY:true} - trust-store-path: ${ANSIBLE_SSL_TRUSTSTORE_PATH:} - trust-store-password: ${ANSIBLE_SSL_TRUSTSTORE_PASSWORD:} - trust-store-type: ${ANSIBLE_SSL_TRUSTSTORE_TYPE:JKS} - - uipath: - # Base URL of the UIPath Orchestrator instance - host: ${UIPATH_HOST:https://orchestrator.example.com} - - # Authentication credentials - clientId: ${UIPATH_CLIENT_ID:your-client-id} - clientSecret: ${UIPATH_CLIENT_SECRET:your-client-secret} - - # Tenancy name (default: "default") - tenancy-name: ${UIPATH_TENANCY_NAME:default} - - # Organization Unit ID for multi-tenant setups - organization-unit-id: ${UIPATH_ORGANIZATION_UNIT_ID:123456} - - # API endpoints (defaults shown, can be overridden) - login-endpoint: /api/Account/Authenticate - queue-items-endpoint: /odata/QueueItems - - # Request timeout in milliseconds - timeout: 30000 - - # SSL Configuration - ssl: - # Set to false to disable certificate verification (DEV ONLY!) - verify-certificates: ${UIPATH_SSL_VERIFY:true} - # Optional: path to custom trust store - trust-store-path: ${UIPATH_SSL_TRUST_STORE_PATH:/path/to/truststore.jks} - trust-store-password: ${TRUSTSTORE_PASSWORD:changeit} - trust-store-type: ${UIPATH_SSL_TRUST_STORE_TYPE:JKS} - - -apis: - project-users: - ansible-workflow-name: ${API_PROJECT_USERS_WORKFLOW_NAME:ansible++workflow} - token: - secret: ${API_PROJECT_USERS_TOKEN_SECRET:devstack-api-service-jwt-secret-key-256bit-change-in-production} - expiration-hours: ${API_PROJECT_USERS_TOKEN_EXPIRATION_HOURS:24} - - -externalservices: - openshift: - instances: - # Development OpenShift instance - dev: - api-url: ${OPENSHIFT_US_TEST_API_URL:https://api.dev.ocp.example.com:6443} - token: ${OPENSHIFT_US_TEST_TOKEN:your-dev-token-here} - namespace: ${OPENSHIFT_US_TEST_NAMESPACE:devstack-dev} - connection-timeout: 30000 - read-timeout: 30000 - trust-all-certificates: ${OPENSHIFT_US_TEST_TRUST_ALL:true} - - # Test OpenShift instance - test: - api-url: ${OPENSHIFT_EU_DEV_API_URL:https://api.test.ocp.example.com:6443} - token: ${OPENSHIFT_EU_DEV_TOKEN:your-test-token-here} - namespace: ${OPENSHIFT_EU_DEV_NAMESPACE:devstack-test} - connection-timeout: 30000 - read-timeout: 30000 - trust-all-certificates: ${OPENSHIFT_EU_DEV_TRUST_ALL:true} - - bitbucket: - instances: - # Development Bitbucket instance - dev: - base-url: ${BITBUCKET_DEV_BASE_REST_URL:https://bitbucket.dev.example.com} - bearer-token: ${BITBUCKET_DEV_BEARER_TOKEN:} - # OR use basic auth if bearer token is not available: - # username: ${BITBUCKET_DEV_USERNAME:admin} - # password: ${BITBUCKET_DEV_PASSWORD:your-dev-password-here} - connection-timeout: 30000 - read-timeout: 30000 - trust-all-certificates: ${BITBUCKET_DEV_TRUST_ALL:true} - - # Production Bitbucket instance - prod: - base-url: ${BITBUCKET_PROD_BASE_REST_URL:https://bitbucket.prod.example.com} - bearer-token: ${BITBUCKET_PROD_BEARER_TOKEN:} - # OR use basic auth: - # username: ${BITBUCKET_PROD_USERNAME:admin} - # password: ${BITBUCKET_PROD_PASSWORD:your-prod-password-here} - connection-timeout: 30000 - read-timeout: 30000 - trust-all-certificates: ${BITBUCKET_PROD_TRUST_ALL:false} - - webhook-proxy: - clusters: - # Test Cluster - test: - cluster-base: ${WEBHOOK_PROXY_TEST_CLUSTER_BASE:apps.cluster.ocp.com} - connection-timeout: ${WEBHOOK_PROXY_TEST_CONNECTION_TIMEOUT:30000} - read-timeout: ${WEBHOOK_PROXY_TEST_READ_TIMEOUT:30000} - trust-all-certificates: ${WEBHOOK_PROXY_TEST_TRUST_ALL:false} - default-jenkinsfile-path: ${WEBHOOK_PROXY_TEST_JENKINSFILE_PATH:Jenkinsfile} - - projects-info-service: - base-url: ${PROJECTS_INFO_SERVICE_BASE_URL:http://localhost:8081} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 82994d6..977143e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -107,6 +107,12 @@ ${project.version} + + org.opendevstack.apiservice + api-project + ${project.version} + + org.opendevstack.apiservice api-project-platform @@ -145,6 +151,7 @@ org.opendevstack.apiservice.core.DevstackApiServiceApplication ../docker + ${project.parent.basedir} diff --git a/pom.xml b/pom.xml index fcea173..572d096 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,7 @@ service-projects api-project-users api-project-platform + api-project @@ -174,8 +175,7 @@ **/*Test.java - - ../application.yaml + ${SPRING_CONFIG_DIR} diff --git a/service-projects/pom.xml b/service-projects/pom.xml index bd0ab43..cd1a931 100644 --- a/service-projects/pom.xml +++ b/service-projects/pom.xml @@ -10,7 +10,7 @@ service-projects - External Service Projects + Service Projects Service module for project operations: key generation and project existence checks @@ -35,6 +35,12 @@ ${jackson-databind-nullable.version} + + org.opendevstack.apiservice + external-service-api + ${project.version} + + org.opendevstack.apiservice external-service-bitbucket diff --git a/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java index 6127031..22122b6 100644 --- a/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectRequest.java @@ -2,8 +2,10 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor @AllArgsConstructor public class CreateProjectRequest { diff --git a/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java index 0775c9b..191a182 100644 --- a/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java +++ b/service-projects/src/main/java/org/opendevstack/apiservice/serviceproject/model/CreateProjectResponse.java @@ -2,8 +2,10 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor @AllArgsConstructor public class CreateProjectResponse { diff --git a/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java index 52c3073..ebba523 100644 --- a/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java +++ b/service-projects/src/test/java/org/opendevstack/apiservice/serviceproject/service/impl/GenerateProjectKeyServiceImplTest.java @@ -60,8 +60,12 @@ void generateProjectKey_whenFirstCandidateIsFree_thenReturnKey() throws Exceptio @Test void generateProjectKey_whenFirstCandidateExists_thenRetryUntilUnique() throws Exception { when(random.nextInt(1_000_000)).thenReturn(1, 2); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true, false); - when(jiraService.projectExists(anyString(), anyString())).thenReturn(true, false); + + when(bitbucketService.projectExists("dev", "SS000001")).thenReturn(true); + when(jiraService.projectExists("default", "SS000001")).thenReturn(false); + + when(bitbucketService.projectExists("dev", "SS000002")).thenReturn(false); + when(jiraService.projectExists("default", "SS000002")).thenReturn(false); String result = tested.generateProjectKey("SS%06d"); @@ -70,8 +74,10 @@ void generateProjectKey_whenFirstCandidateExists_thenRetryUntilUnique() throws E @Test void generateProjectKey_whenNoUniqueKeyAfterMaxRetries_thenThrowException() throws Exception { - when(random.nextInt(1_000_000)).thenReturn(1); - when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(true); + when(random.nextInt(1_000_000)).thenReturn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + + when(bitbucketService.projectExists(anyString(), anyString())).thenReturn(false); + when(jiraService.projectExists(anyString(), anyString())).thenReturn(true); assertThatThrownBy(() -> tested.generateProjectKey("SS%06d")) From 78c4242659df46784d79cd16a9036c96b2afee4e Mon Sep 17 00:00:00 2001 From: Angel Martinez Date: Fri, 6 Mar 2026 17:40:03 +0100 Subject: [PATCH 5/5] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714dc88..ac89dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Created **New API module** for managing EDP Projects with create and retrieve endpoints. - Created **New module** external service projects to manage EDP Projects. ### External Service Jira (`external-service-jira`)