Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api-project/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
<version>${mapstruct.version}</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -111,7 +111,7 @@
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.opendevstack.apiservice.project.controller;

import org.junit.jupiter.api.Test;
import org.opendevstack.apiservice.project.facade.ProjectsFacade;
import org.opendevstack.apiservice.project.model.CreateProjectRequest;
import org.opendevstack.apiservice.project.model.CreateProjectResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
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.ArgumentMatchers.any;
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;

@MockitoBean
private ProjectsFacade facade;

@Test
void createProject_withProvidedProjectKey_returnsInitiatedResponse() throws Exception {
CreateProjectResponse serviceResponse =
new CreateProjectResponse();
serviceResponse.setProjectKey("PROJ01");
serviceResponse.setStatus("Initiated");
when(facade.createProject(any(CreateProjectRequest.class)))
.thenReturn(serviceResponse);

String payload = """
{
\"projectKey\": \"PROJ01\",
\"projectName\": \"My Project\",
\"projectDescription\": \"desc\"
}
""";

mockMvc.perform(post("/api/pub/v0/projects")
.contentType("application/json")
.content(payload == null ? "" : payload))
.andExpect(status().isOk())
.andExpect(jsonPath("$.projectKey").value("PROJ01"))
.andExpect(jsonPath("$.status").value("Initiated"))
.andExpect(jsonPath("$.error").doesNotExist())
.andExpect(jsonPath("$.errorKey").doesNotExist())
.andExpect(jsonPath("$.errorDescription").doesNotExist());
}

@Test
void getProject_whenNotFound_returns404() throws Exception {
when(facade.getProject("UNKNOWN")).thenReturn(null);

mockMvc.perform(get("/api/pub/v0/projects/UNKNOWN"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").value("NOT_FOUND"))
.andExpect(jsonPath("$.errorKey").value("PROJECT_NOT_FOUND"))
.andExpect(jsonPath("$.message").value("Project with key 'UNKNOWN' not found"))
.andExpect(jsonPath("$.projectKey").doesNotExist())
.andExpect(jsonPath("$.status").doesNotExist())
.andExpect(jsonPath("$.errorDescription").doesNotExist());
}

@SpringBootConfiguration
@EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
@Import({ProjectController.class})
static class TestConfig {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ void createProject_whenSuccess_thenReturnOk() throws Exception {
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getProjectKey()).isEqualTo("PROJ01");
assertThat(result.getBody().getStatus()).isEqualTo("Initiated");
assertThat(result.getBody().getError()).isNull();
assertThat(result.getBody().getErrorKey()).isNull();
assertThat(result.getBody().getErrorDescription()).isNull();
}

@Test
Expand All @@ -68,6 +71,9 @@ void createProject_whenProjectCreationException_thenReturnConflict() throws Exce
assertThat(result.getBody().getError()).isEqualTo("CONFLICT");
assertThat(result.getBody().getErrorKey()).isEqualTo("PROJECT_ALREADY_EXISTS");
assertThat(result.getBody().getMessage()).contains("already exists");
assertThat(result.getBody().getProjectKey()).isNull();
assertThat(result.getBody().getStatus()).isNull();
assertThat(result.getBody().getErrorDescription()).isNull();
}

@Test
Expand All @@ -84,6 +90,9 @@ void createProject_whenProjectKeyGenerationException_thenReturnInternalServerErr
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.");
assertThat(result.getBody().getProjectKey()).isNull();
assertThat(result.getBody().getStatus()).isNull();
assertThat(result.getBody().getErrorDescription()).isNull();
}

@Test
Expand All @@ -99,6 +108,8 @@ void getProject_whenFound_thenReturnOk() throws Exception {
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).isNotNull();
assertThat(result.getBody().getProjectKey()).isEqualTo("PROJ01");
assertThat(result.getBody().getError()).isNull();
assertThat(result.getBody().getErrorKey()).isNull();
verify(projectsFacade).getProject("PROJ01");
}

Expand All @@ -113,6 +124,9 @@ void getProject_whenNotFound_thenReturnNotFound() throws Exception {
assertThat(result.getBody().getError()).isEqualTo("NOT_FOUND");
assertThat(result.getBody().getErrorKey()).isEqualTo("PROJECT_NOT_FOUND");
assertThat(result.getBody().getMessage()).contains("UNKNOWN");
assertThat(result.getBody().getProjectKey()).isNull();
assertThat(result.getBody().getStatus()).isNull();
assertThat(result.getBody().getErrorDescription()).isNull();
}

@Test
Expand All @@ -127,6 +141,9 @@ void getProject_whenServiceThrows_thenReturnInternalServerError() throws Excepti
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.");
assertThat(result.getBody().getProjectKey()).isNull();
assertThat(result.getBody().getStatus()).isNull();
assertThat(result.getBody().getErrorDescription()).isNull();
}

}
15 changes: 15 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Spring Boot DevTools for fast restart during development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- External Service API -->
<dependency>
<groupId>org.opendevstack.apiservice</groupId>
Expand Down Expand Up @@ -119,6 +127,13 @@
<version>${project.version}</version>
</dependency>

<!-- Service Projects module: project creation and key generation -->
<dependency>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>service-projects</artifactId>
<version>${project.version}</version>
</dependency>

<!-- Persistence module: JPA entities and Spring Data repositories -->
<dependency>
<groupId>org.opendevstack.apiservice</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication(scanBasePackages = { "org.opendevstack.apiservice" })
@EnableCaching
@EntityScan(basePackages = "org.opendevstack.apiservice.persistence.entity")
@EnableJpaRepositories(basePackages = "org.opendevstack.apiservice.persistence.repository")
public class DevstackApiServiceApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
*/
@SpringBootConfiguration
@EnableAutoConfiguration
@EntityScan(basePackages = "org.opendevstack.apiservice.persistence.entity")
@EnableJpaRepositories(basePackages = "org.opendevstack.apiservice.persistence.repository")
public class PersistenceTestApplication {

}
18 changes: 18 additions & 0 deletions persistence/src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ── HikariCP tuning for Testcontainers ─────────────────────────────────────────
#
# When the @Container PostgreSQL instance stops (after the test class finishes),
# HikariCP's background keepalive thread tries to validate open connections and
# logs "Failed to validate connection" warnings. These settings eliminate those
# warnings and make the pool give up quickly on unreachable connections during
# teardown — they have no effect on test correctness.

# Disable background keepalive probes entirely (default: 0 = disabled already,
# but some Spring Boot auto-config versions set it higher).
spring.datasource.hikari.keepalive-time=0

# Reduce how long HikariCP waits to acquire a connection (default: 30 000 ms).
# During teardown this prevents the pool from blocking on a dead container.
spring.datasource.hikari.connection-timeout=3000

# Reduce how long HikariCP waits to validate a connection (default: 5 000 ms).
spring.datasource.hikari.validation-timeout=1000
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<spring-javaformat-maven-plugin.version>0.0.47</spring-javaformat-maven-plugin.version>
<jackson-databind-nullable.version>0.2.7</jackson-databind-nullable.version>
<build-helper-maven-plugin.version>3.6.1</build-helper-maven-plugin.version>
<mapstruct.version>1.6.3</mapstruct.version>

<!-- Test execution -->
<jacoco.agent.argLine>
Expand Down
35 changes: 35 additions & 0 deletions service-projects/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
<version>${jackson-databind-nullable.version}</version>
</dependency>

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>

<dependency>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>persistence</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>external-service-api</artifactId>
Expand Down Expand Up @@ -71,5 +83,28 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.opendevstack.apiservice.serviceproject.mapper;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.opendevstack.apiservice.persistence.entity.ProjectEntity;
import org.opendevstack.apiservice.serviceproject.model.CreateProjectResponse;

@Mapper(componentModel = "spring")
public interface CreateProjectResponseMapper {

@Mapping(target = "message", ignore = true)
@Mapping(target = "error", ignore = true)
@Mapping(target = "errorKey", ignore = true)
@Mapping(target = "errorDescription", ignore = true)
CreateProjectResponse toCreateProjectResponse(ProjectEntity entity);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
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.persistence.entity.ProjectEntity;
import org.opendevstack.apiservice.persistence.repository.ProjectRepository;
import org.opendevstack.apiservice.serviceproject.mapper.CreateProjectResponseMapper;
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.stereotype.Service;

import java.util.Optional;

@Service
@Slf4j
@AllArgsConstructor
Expand All @@ -23,6 +28,10 @@ public class ProjectServiceImpl implements ProjectService {
private final JiraService jiraService;

private final GenerateProjectKeyService generateProjectKeyService;

private final ProjectRepository projectRepository;

private final CreateProjectResponseMapper createProjectResponseMapper;

@Override
public CreateProjectResponse createProject(CreateProjectRequest request) {
Expand All @@ -31,7 +40,13 @@ public CreateProjectResponse createProject(CreateProjectRequest request) {

@Override
public CreateProjectResponse getProject(String projectKey) {
return CreateProjectResponse.builder().build();
Optional<ProjectEntity> project = projectRepository.findByProjectKey(projectKey);

if (project.isPresent()) {
return createProjectResponseMapper.toCreateProjectResponse(project.get());
}

return null;
}
}

Loading
Loading