diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cb8cea0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.gradle +build +out +*.iml +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5eec42b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM eclipse-temurin:21-jdk AS build + +WORKDIR /app + +COPY build.gradle.kts settings.gradle.kts gradlew ./ +COPY gradle ./gradle + +RUN chmod +x gradlew + +RUN ./gradlew build -x test --dry-run + +COPY src ./src +RUN ./gradlew clean fatJar -x test + +FROM eclipse-temurin:21-jre + +WORKDIR /app + +COPY --from=build /app/build/libs/*-all.jar app.jar + +EXPOSE 8080 + +CMD ["java", "--enable-preview", "-jar", "app.jar"] \ No newline at end of file diff --git a/README.md b/README.md index 4a80115..19aec56 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/TvkQWWs6) # Features of modern Java # Цели и задачи л/р: diff --git a/build.gradle.kts b/build.gradle.kts index 79bf52a..0f26ec0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,16 @@ plugins { id("java") + id("application") +} + +application { + mainClass.set("org.lab.Main") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } } group = "org.lab" @@ -10,11 +21,55 @@ repositories { } dependencies { + implementation("io.javalin:javalin:6.1.3") + implementation("ch.qos.logback:logback-classic:1.4.11") + implementation("org.postgresql:postgresql:42.7.8") + implementation("com.zaxxer:HikariCP:5.0.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2") + implementation("com.fasterxml.jackson.core:jackson-core:2.17.2") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.2") + implementation("com.google.inject:guice:5.1.0") + implementation("javax.inject:javax.inject:1") + + compileOnly("org.projectlombok:lombok:1.18.34") + annotationProcessor("org.projectlombok:lombok:1.18.34") + + testImplementation("org.mockito:mockito-core:5.12.0") + testImplementation("org.mockito:mockito-inline:5.2.0") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testCompileOnly("org.projectlombok:lombok:1.18.34") + testAnnotationProcessor("org.projectlombok:lombok:1.18.34") +} + +tasks.register("fatJar") { + archiveClassifier.set("all") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes["Main-Class"] = "org.lab.Main" + } + from(sourceSets.main.get().output) + + dependsOn(configurations.runtimeClasspath) + from({ + configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } + }) +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} + +tasks.withType { + jvmArgs("--enable-preview") +} + +tasks.withType { + jvmArgs("--enable-preview") } tasks.test { useJUnitPlatform() -} \ No newline at end of file +} + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6358243 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +services: + postgres: + image: postgres:16 + container_name: app-postgres + restart: always + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mydb + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + + app: + build: . + container_name: java-app + depends_on: + - postgres + ports: + - "7070:7070" + restart: always + volumes: + - "./app:/var/www" + +volumes: + pgdata: \ No newline at end of file diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 22028ef..1a62b20 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,4 +1,63 @@ -void main() { - IO.println("Hello and welcome!"); -} +package org.lab; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; +import io.javalin.Javalin; + +import org.lab.api.adapters.employee.EmployeeCreateAdapter; +import org.lab.api.adapters.employee.EmployeeDeleteAdapter; +import org.lab.api.adapters.employee.EmployeeGetAdapter; +import org.lab.api.adapters.error_message.ErrorMessageCloseAdapter; +import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; +import org.lab.api.adapters.project.ProjectCreateAdapter; +import org.lab.api.adapters.project.ProjectDeleteAdapter; +import org.lab.api.adapters.project.ProjectGetAdapter; +import org.lab.api.adapters.project.ProjectListAdapter; +import org.lab.api.adapters.ticket.TicketCloseAdapter; +import org.lab.api.adapters.ticket.TicketCreateAdapter; +import org.lab.infra.di.AppModule; + +public class Main { + + public static void main(String[] args) { + Injector injector = Guice.createInjector(Stage.DEVELOPMENT, new AppModule()); + Javalin app = Javalin.create(config -> {}).start(7070); + + EmployeeCreateAdapter createEmployeeAdapter = injector.getInstance(EmployeeCreateAdapter.class); + EmployeeDeleteAdapter deleteEmployeeAdapter = injector.getInstance(EmployeeDeleteAdapter.class); + EmployeeGetAdapter getEmployeeAdapter = injector.getInstance(EmployeeGetAdapter.class); + + ProjectCreateAdapter createProjectAdapter = injector.getInstance(ProjectCreateAdapter.class); + ProjectGetAdapter projectGetAdapter = injector.getInstance(ProjectGetAdapter.class); + ProjectDeleteAdapter projectDeleteAdapter = injector.getInstance(ProjectDeleteAdapter.class); + ProjectListAdapter projectListAdapter = injector.getInstance(ProjectListAdapter.class); + TicketCreateAdapter ticketCreateAdapter = injector.getInstance(TicketCreateAdapter.class); + TicketCloseAdapter ticketCloseAdapter = injector.getInstance(TicketCloseAdapter.class); + + ErrorMessageCreateAdapter errorMessageCreateAdapter = injector.getInstance( + ErrorMessageCreateAdapter.class + ); + ErrorMessageCloseAdapter errorMessageCloseAdapter = injector.getInstance( + ErrorMessageCloseAdapter.class + ); + + app.get("/", ctx -> ctx.result("Hello World")); + + app.post("/employee/{actorId}", createEmployeeAdapter::createEmployee); + app.delete("/employee/{employeeId}/{actorId}", deleteEmployeeAdapter::deleteEmployee); + app.get("/employee/{employeeId}/{actorId}", getEmployeeAdapter::getEmployee); + + app.get("/project/list/{employeeId}", projectListAdapter::listProjects); + app.post("/project/{employeeId}", createProjectAdapter::createProject); + app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); + app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); + + app.post("/ticket/{employeeId}/{projectId}", ticketCreateAdapter::createTicket); + app.patch("/ticket/{employeeId}/{ticketId}", ticketCloseAdapter::closeTicket); + + app.post("/error_message/{employeeId}", errorMessageCreateAdapter::createErrorMessage); + app.patch("/error_message/{messageId}", errorMessageCloseAdapter::closeMessage); + } +} diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java new file mode 100644 index 0000000..f56a209 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -0,0 +1,58 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.employee.dto.GetEmployeeDTO; +import org.lab.application.employee.dto.CreateEmployeeDTO; +import org.lab.application.employee.use_cases.CreateEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class EmployeeCreateAdapter { + + private final ObjectMapper mapper; + private final CreateEmployeeUseCase useCase; + + @Inject + public EmployeeCreateAdapter( + ObjectMapper mapper, + CreateEmployeeUseCase useCase + ) { + this.mapper = mapper; + this.useCase = useCase; + } + + public Context createEmployee( + Context ctx + ) { + try { + int actorId = Integer.parseInt(ctx.pathParam("actorId")); + CreateEmployeeDTO dto = ctx.bodyAsClass(CreateEmployeeDTO.class); + Employee employee = mapper.mapToDomain(dto, Employee.class); + Employee createdEmployee = useCase.execute( + employee, + actorId + ); + GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( + createdEmployee, + GetEmployeeDTO.class + ); + return ctx.status(201).json(presentationEmployee); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java new file mode 100644 index 0000000..60bd14b --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -0,0 +1,42 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class EmployeeDeleteAdapter { + + private final DeleteEmployeeUseCase useCase; + + @Inject + public EmployeeDeleteAdapter( + DeleteEmployeeUseCase useCase + ) { + this.useCase = useCase; + } + + public Context deleteEmployee( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int actorId = Integer.parseInt(ctx.pathParam("actorId")); + this.useCase.execute(employeeId, actorId); + return ctx.status(201); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java new file mode 100644 index 0000000..651671e --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -0,0 +1,56 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.employee.dto.GetEmployeeDTO; +import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class EmployeeGetAdapter { + + private final GetEmployeeUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public EmployeeGetAdapter( + GetEmployeeUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context getEmployee( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int actorId = Integer.parseInt(ctx.pathParam("actorId")); + Employee receivedEmployee = useCase.execute( + employeeId, + actorId + ); + GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( + receivedEmployee, + GetEmployeeDTO.class + ); + return ctx.status(201).json(presentationEmployee); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java new file mode 100644 index 0000000..7d47b93 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java @@ -0,0 +1,45 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; + +public class ErrorMessageCloseAdapter { + + private final CloseErrorMessageUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ErrorMessageCloseAdapter( + CloseErrorMessageUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context closeMessage(Context ctx) { + try { + int messageId = Integer.parseInt(ctx.pathParam("messageId")); + ErrorMessage createdMessage = useCase.execute(messageId); + GetErrorMessageDTO presentationMessage = mapper.mapToPresentation( + createdMessage, + GetErrorMessageDTO.class + ); + return ctx.status(201).json(presentationMessage); + + } catch (MessageNotFoundException e) { + return ctx.status(409).json(Map.of("error", "Message doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java new file mode 100644 index 0000000..c73c2a5 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java @@ -0,0 +1,66 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.error_message.dto.CreateErrorMessageDTO; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class ErrorMessageCreateAdapter { + + private final CreateErrorMessageUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ErrorMessageCreateAdapter( + CreateErrorMessageUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context createErrorMessage( + Context ctx + ) { + try { + int actorId = Integer.parseInt(ctx.pathParam("employeeId")); + CreateErrorMessageDTO dto = ctx.bodyAsClass(CreateErrorMessageDTO.class); + ErrorMessage errorMessage = mapper.mapToDomain(dto, ErrorMessage.class); + ErrorMessage createdMessage = useCase.execute( + errorMessage, + actorId + ); + GetErrorMessageDTO presentationMessage = mapper.mapToPresentation( + createdMessage, + GetErrorMessageDTO.class + ); + return ctx.status(201).json(presentationMessage); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java new file mode 100644 index 0000000..aaaccf8 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -0,0 +1,54 @@ +package org.lab.api.adapters.project; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.project.dto.CreateProjectDTO; +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.CreateProjectUseCase; +import org.lab.domain.project.model.Project; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class ProjectCreateAdapter { + + private final ObjectMapper objectMapper; + private final CreateProjectUseCase useCase; + + @Inject + public ProjectCreateAdapter( + ObjectMapper objectMapper, + CreateProjectUseCase useCase + ) { + this.objectMapper = objectMapper; + this.useCase = useCase; + } + + public Context createProject( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + CreateProjectDTO dto = ctx.bodyAsClass(CreateProjectDTO.class); + Project project = objectMapper.mapToDomain(dto, Project.class); + Project createdProject = useCase.execute(project, employeeId); + GetProjectDTO presentationProject = objectMapper.mapToPresentation( + createdProject, + GetProjectDTO.class + ); + return ctx.status(201).json(presentationProject); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java new file mode 100644 index 0000000..5b8ff51 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -0,0 +1,51 @@ +package org.lab.api.adapters.project; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.project.use_cases.DeleteProjectUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class ProjectDeleteAdapter { + + private final DeleteProjectUseCase useCase; + + @Inject + public ProjectDeleteAdapter( + DeleteProjectUseCase useCase + ) { + this.useCase = useCase; + } + + public Context deleteProject( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + this.useCase.execute(employeeId, projectId); + return ctx.status(204); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesn't exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java new file mode 100644 index 0000000..10a31c8 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -0,0 +1,62 @@ +package org.lab.api.adapters.project; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class ProjectGetAdapter { + + private final GetProjectUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ProjectGetAdapter( + GetProjectUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context getProject(Context ctx) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + Project project = useCase.execute( + projectId, + employeeId + ); + GetProjectDTO presentationProject = mapper.mapToPresentation( + project, + GetProjectDTO.class + ); + return ctx.status(200).json(presentationProject); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java new file mode 100644 index 0000000..e32aa5a --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java @@ -0,0 +1,49 @@ +package org.lab.api.adapters.project; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.ListProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class ProjectListAdapter { + + private final ListProjectUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ProjectListAdapter( + ListProjectUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context listProjects( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + List projects = this.useCase.execute(employeeId); + List presentationProjects = new ArrayList<>(); + for (Project project : projects) { + GetProjectDTO presentationProject = this.mapper.mapToPresentation( + project, + GetProjectDTO.class + ); + presentationProjects.add(presentationProject); + } + return ctx.status(200).json(presentationProjects); + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java new file mode 100644 index 0000000..0f8bac8 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java @@ -0,0 +1,56 @@ +package org.lab.api.adapters.ticket; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CloseTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; + +public class TicketCloseAdapter { + + private final CloseTicketUseCase useCase; + private final ObjectMapper objectMapper; + + @Inject + public TicketCloseAdapter( + CloseTicketUseCase useCase, + ObjectMapper objectMapper + ) { + this.useCase = useCase; + this.objectMapper = objectMapper; + } + + public Context closeTicket( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int ticketId = Integer.parseInt(ctx.pathParam("ticketId")); + Ticket updatedTicket = useCase.execute( + ticketId, + employeeId + ); + GetTicketDTO presentationTicket = objectMapper.mapToPresentation( + updatedTicket, + GetTicketDTO.class + ); + return ctx.status(201).json(presentationTicket); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java new file mode 100644 index 0000000..4ff47ba --- /dev/null +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -0,0 +1,64 @@ +package org.lab.api.adapters.ticket; + +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.ticket.dto.CreateTicketDTO; +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CreateTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.ticket.model.Ticket; + +public class TicketCreateAdapter { + + private CreateTicketUseCase useCase; + private ObjectMapper objectMapper; + + @Inject + public TicketCreateAdapter( + CreateTicketUseCase useCase, + ObjectMapper objectMapper + ) { + this.useCase = useCase; + this.objectMapper = objectMapper; + } + + public Context createTicket( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + CreateTicketDTO dto = ctx.bodyAsClass(CreateTicketDTO.class); + Ticket ticket = objectMapper.mapToDomain(dto, Ticket.class); + Ticket createdTicket = useCase.execute( + ticket, + employeeId, + projectId + ); + GetTicketDTO presentationTicket = objectMapper.mapToPresentation( + createdTicket, + GetTicketDTO.class + ); + return ctx.status(201).json(presentationTicket); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java new file mode 100644 index 0000000..7449e4a --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java @@ -0,0 +1,11 @@ +package org.lab.application.employee.dto; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.interfaces.PresentationObject; + +public record CreateEmployeeDTO( + String name, + int age, + EmployeeType type +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java new file mode 100644 index 0000000..d1b1e92 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java @@ -0,0 +1,9 @@ +package org.lab.application.employee.dto; + +import org.lab.domain.interfaces.PresentationObject; + +public record DeleteEmployeeDTO( + int employeeId, + int deletedEmployeeId +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java new file mode 100644 index 0000000..9875ce6 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java @@ -0,0 +1,17 @@ +package org.lab.application.employee.dto; + +import java.util.Date; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.interfaces.PresentationObject; + +public record GetEmployeeDTO( + int id, + String name, + int age, + EmployeeType type, + int createdBy, + Date createdDate +) implements PresentationObject { +} + diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java new file mode 100644 index 0000000..b14a67d --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -0,0 +1,34 @@ +package org.lab.application.employee.use_cases; + +import com.google.inject.Inject; + +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class CreateEmployeeUseCase { + + private final EmployeeRepository employeeRepository; + private final EmployeePermissionValidator validator; + + @Inject + public CreateEmployeeUseCase( + EmployeeRepository employeeRepository, + EmployeePermissionValidator validator + ) { + this.employeeRepository = employeeRepository; + this.validator = validator; + } + + public Employee execute( + Employee employee, + int creatorId + ) { + validator.validate(creatorId); + Employee createdEmployee = employeeRepository.create( + employee, + creatorId + ); + return createdEmployee; + } +} diff --git a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java new file mode 100644 index 0000000..e21efc1 --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -0,0 +1,29 @@ +package org.lab.application.employee.use_cases; + +import com.google.inject.Inject; + +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.shared.services.EmployeePermissionValidator; + +public class DeleteEmployeeUseCase { + + private final EmployeeRepository employeeRepository; + private final EmployeePermissionValidator validator; + + @Inject + public DeleteEmployeeUseCase( + EmployeeRepository employeeRepository, + EmployeePermissionValidator validator + ) { + this.employeeRepository = employeeRepository; + this.validator = validator; + } + + public void execute( + int deleteEmployeeId, + int employeeId + ) { + validator.validate(employeeId); + employeeRepository.delete(deleteEmployeeId); + } +} diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java new file mode 100644 index 0000000..017a663 --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -0,0 +1,31 @@ +package org.lab.application.employee.use_cases; + +import com.google.inject.Inject; + +import org.lab.domain.emploee.model.Employee; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.EmployeePermissionValidator; + +public class GetEmployeeUseCase { + + private EmployeePermissionValidator employeePermissionValidator; + private EmployeeProvider employeeProvider; + + @Inject + public GetEmployeeUseCase( + EmployeePermissionValidator employeePermissionValidator, + EmployeeProvider employeeProvider + ) { + this.employeePermissionValidator = employeePermissionValidator; + this.employeeProvider = employeeProvider; + } + + public Employee execute( + int employeeId, + int actorId + ) { + employeePermissionValidator.validate(actorId); + Employee employee = this.employeeProvider.get(employeeId); + return employee; + } +} diff --git a/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java b/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java new file mode 100644 index 0000000..418e30a --- /dev/null +++ b/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java @@ -0,0 +1,9 @@ +package org.lab.application.error_message.dto; + +import org.lab.domain.interfaces.PresentationObject; + +public record CreateErrorMessageDTO( + int projectId, + String text +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java b/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java new file mode 100644 index 0000000..7bab17d --- /dev/null +++ b/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java @@ -0,0 +1,13 @@ +package org.lab.application.error_message.dto; + +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record GetErrorMessageDTO( + int id, + int projectId, + int createdBy, + String text, + ErrorMessageStatus status +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java new file mode 100644 index 0000000..9b8959a --- /dev/null +++ b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java @@ -0,0 +1,41 @@ +package org.lab.application.error_message.services; + +import com.google.inject.Inject; + +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectSpecProvider; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.spec.Specification; +import org.lab.application.project.services.UserSpecFactory; + +public class CreateErrorMessageValidator { + + private final EmployeeProvider employeeProvider; + private final ProjectSpecProvider projectSpecProvider; + private final UserSpecFactory userSpecFactory; + + @Inject + public CreateErrorMessageValidator( + EmployeeProvider employeeProvider, + ProjectSpecProvider projectSpecProvider, + UserSpecFactory userSpecFactory + ) { + this.employeeProvider = employeeProvider; + this.projectSpecProvider = projectSpecProvider; + this.userSpecFactory = userSpecFactory; + } + + public void validate( + int employeeId, + int projectId + ) { + Employee employee = this.employeeProvider.get(employeeId); + if (employee.getType() != EmployeeType.TESTER) { + throw new NotPermittedException("Only tester can create error messages"); + } + Specification employeeSpec = this.userSpecFactory.getForType(employee); + this.projectSpecProvider.get(projectId, employeeSpec); + } +} diff --git a/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java b/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java new file mode 100644 index 0000000..c49b944 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java @@ -0,0 +1,25 @@ +package org.lab.application.error_message.services; + +import com.google.inject.Inject; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +public class ErrorMessageValidator { + + private final ErrorMessageRepository errorMessageRepository; + + @Inject + public ErrorMessageValidator( + ErrorMessageRepository errorMessageRepository + ) { + this.errorMessageRepository = errorMessageRepository; + } + + public void validate(int messageId) { + ErrorMessage message = this.errorMessageRepository.get(messageId); + if (message == null) { + throw new MessageNotFoundException(); + } + } +} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java new file mode 100644 index 0000000..178858d --- /dev/null +++ b/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java @@ -0,0 +1,29 @@ +package org.lab.application.error_message.use_cases; + +import com.google.inject.Inject; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +public class CloseErrorMessageUseCase { + + private final ErrorMessageValidator errorMessageValidator; + private final ErrorMessageRepository errorMessageRepository; + + @Inject + public CloseErrorMessageUseCase( + ErrorMessageValidator errorMessageValidator, + ErrorMessageRepository errorMessageRepository + ) { + this.errorMessageValidator = errorMessageValidator; + this.errorMessageRepository = errorMessageRepository; + } + + public ErrorMessage execute( + int messageId + ) { + this.errorMessageValidator.validate(messageId); + ErrorMessage message = this.errorMessageRepository.close(messageId); + return message; + } +} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java new file mode 100644 index 0000000..3e11ce7 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java @@ -0,0 +1,36 @@ +package org.lab.application.error_message.use_cases; + +import com.google.inject.Inject; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; +import org.lab.application.error_message.services.CreateErrorMessageValidator; + +public class CreateErrorMessageUseCase { + + private final ErrorMessageRepository errorMessageRepository; + private final CreateErrorMessageValidator createErrorMessageValidator; + + @Inject + public CreateErrorMessageUseCase( + ErrorMessageRepository errorMessageRepository, + CreateErrorMessageValidator createErrorMessageValidator + ) { + this.errorMessageRepository = errorMessageRepository; + this.createErrorMessageValidator = createErrorMessageValidator; + } + + public ErrorMessage execute( + ErrorMessage message, + int employeeId + ) { + this.createErrorMessageValidator.validate( + employeeId, + message.getProjectId() + ); + ErrorMessage createdMessage = this.errorMessageRepository.create( + message, + employeeId + ); + return createdMessage; + } +} diff --git a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java new file mode 100644 index 0000000..b45dd09 --- /dev/null +++ b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java @@ -0,0 +1,14 @@ +package org.lab.application.project.dto; + +import org.lab.domain.interfaces.PresentationObject; + +import java.util.List; + +public record CreateProjectDTO( + String name, + String description, + Integer teamLeadId, + ListdeveloperIds, + List testerIds +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/project/dto/GetProjectDTO.java b/src/main/java/org/lab/application/project/dto/GetProjectDTO.java new file mode 100644 index 0000000..0b96a2c --- /dev/null +++ b/src/main/java/org/lab/application/project/dto/GetProjectDTO.java @@ -0,0 +1,32 @@ +package org.lab.application.project.dto; + +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.interfaces.PresentationObject; + +import java.util.Date; +import java.util.List; + +public record GetProjectDTO( + int id, + String name, + String description, + + int managerId, + Integer teamLeadId, + + ListdeveloperIds, + List testerIds, + + ProjectStatus status, + + Integer currentMilestoneId, + List milestoneIds, + + List bugReportIds, + + Date createdDate, + int createdBy, + Date updatedDate, + Integer updatedBy +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java new file mode 100644 index 0000000..dbccff3 --- /dev/null +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -0,0 +1,58 @@ +package org.lab.application.project.services; + +import java.util.concurrent.StructuredTaskScope; +import java.util.concurrent.ExecutionException; + +import com.google.inject.Inject; + +import org.lab.application.shared.services.ProjectProvider; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.application.shared.services.EmployeeProvider; + +public class GetValidator { + + private final ProjectProvider projectProvider; + private final EmployeeProvider currentEmployeeProvider; + + @Inject + public GetValidator( + ProjectProvider projectProvider, + EmployeeProvider currentEmployeeProvider + ) { + this.projectProvider = projectProvider; + this.currentEmployeeProvider = currentEmployeeProvider; + } + + public Pair validate( + int projectId, + int employeeId + ) + throws ProjectNotFoundException, + UserNotFoundException + { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + var projectFuture = scope.fork(() -> { + Project project = this.projectProvider.get(projectId); + return project; + }); + var employeeFuture = scope.fork(() -> { + Employee employee = this.currentEmployeeProvider.get(employeeId); + return employee; + }); + scope.join(); + scope.throwIfFailed(); + + Project project = projectFuture.get(); + Employee employee = employeeFuture.get(); + return new Pair(project, employee); + + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/project/services/Pair.java b/src/main/java/org/lab/application/project/services/Pair.java new file mode 100644 index 0000000..12787b7 --- /dev/null +++ b/src/main/java/org/lab/application/project/services/Pair.java @@ -0,0 +1,6 @@ +package org.lab.application.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; + +public record Pair(Project project, Employee employee) {} diff --git a/src/main/java/org/lab/application/project/services/UserSpecFactory.java b/src/main/java/org/lab/application/project/services/UserSpecFactory.java new file mode 100644 index 0000000..d961927 --- /dev/null +++ b/src/main/java/org/lab/application/project/services/UserSpecFactory.java @@ -0,0 +1,27 @@ +package org.lab.application.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.spec.TeamLeaderSpec; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.repository.employee.spec.DeveloperSpec; +import org.lab.infra.db.repository.employee.spec.ManagerSpec; +import org.lab.infra.db.spec.Specification; + +public class UserSpecFactory { + + public Specification getForType( + Employee employee + ) { + switch (employee.getType()) { + case MANAGER: + return new ManagerSpec(employee.getId()); + case TEAMLEAD: + return new TeamLeaderSpec(employee.getId()); + case PROGRAMMER: + return new DeveloperSpec(employee.getId()); + case TESTER: + return new TesterSpec(employee.getId()); + } + return null; + } +} diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java new file mode 100644 index 0000000..6ebcc8e --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -0,0 +1,31 @@ +package org.lab.application.project.use_cases; + +import com.google.inject.Inject; + +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.shared.services.EmployeePermissionValidator; + +public class CreateProjectUseCase { + + private final ProjectRepository projectRepository; + private final EmployeePermissionValidator employeePermissionValidator; + + @Inject + public CreateProjectUseCase( + ProjectRepository projectRepository, + EmployeePermissionValidator employeePermissionValidator + ) { + this.projectRepository = projectRepository; + this.employeePermissionValidator = employeePermissionValidator; + } + + public Project execute( + Project project, + int employeeId + ) { + this.employeePermissionValidator.validate(employeeId); + Project domainProject = this.projectRepository.create(project, employeeId); + return domainProject; + } +} diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java new file mode 100644 index 0000000..9fba328 --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -0,0 +1,39 @@ +package org.lab.application.project.use_cases; + +import com.google.inject.Inject; + +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.project.services.GetValidator; + +public class DeleteProjectUseCase { + + private final ProjectRepository projectRepository; + private final GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + + @Inject + public DeleteProjectUseCase( + ProjectRepository projectRepository, + GetValidator getValidator, + ProjectMembershipValidator projectMembershipValidator + ) { + this.projectRepository = projectRepository; + this.getValidator = getValidator; + this.projectMembershipValidator = projectMembershipValidator; + } + + public void execute( + int employeeId, + int projectId + ) { + Pair pair = this.getValidator.validate(projectId, employeeId); + Employee employee = pair.employee(); + Project project = pair.project(); + this.projectMembershipValidator.validate(employee, project); + this.projectRepository.delete(projectId); + } +} diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java new file mode 100644 index 0000000..d856b7a --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -0,0 +1,35 @@ +package org.lab.application.project.use_cases; + +import com.google.inject.Inject; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.project.services.ProjectMembershipValidator; + +public class GetProjectUseCase { + + private final GetValidator getValidator; + private final ProjectMembershipValidator projectMembershipValidator; + + @Inject + public GetProjectUseCase( + GetValidator getValidator, + ProjectMembershipValidator projectMembershipValidator + ) { + this.getValidator = getValidator; + this.projectMembershipValidator = projectMembershipValidator; + } + + public Project execute( + int projectId, + int employeeId + ) { + Pair pair = this.getValidator.validate(projectId, employeeId); + Project project = pair.project(); + Employee employee = pair.employee(); + this.projectMembershipValidator.validate(employee, project); + return project; + } +} diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java new file mode 100644 index 0000000..fb9cca9 --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -0,0 +1,39 @@ +package org.lab.application.project.use_cases; + +import java.util.List; + +import com.google.inject.Inject; + +import org.lab.application.project.services.UserSpecFactory; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + +public class ListProjectUseCase { + + private final ProjectRepository projectRepository; + private final EmployeeProvider currentEmployeeProvider; + private final UserSpecFactory userSpecFactory; + + @Inject + public ListProjectUseCase( + ProjectRepository projectRepository, + EmployeeProvider currentEmployeeProvider, + UserSpecFactory userSpecFactory + ) { + this.projectRepository = projectRepository; + this.currentEmployeeProvider = currentEmployeeProvider; + this.userSpecFactory = userSpecFactory; + } + + public List execute( + int employeeId + ) { + Employee employee = this.currentEmployeeProvider.get(employeeId); + Specification spec = this.userSpecFactory.getForType(employee); + List projects = this.projectRepository.list(spec); + return projects; + } +} diff --git a/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java new file mode 100644 index 0000000..08337ea --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java @@ -0,0 +1,27 @@ +package org.lab.application.shared.services; + +import com.google.inject.Inject; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class EmployeePermissionValidator { + + private final EmployeeRepository employeeRepository; + + @Inject + public EmployeePermissionValidator( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } + + public void validate(int employeeId) { + Employee creatorEmployee = employeeRepository.getById(employeeId); + if (creatorEmployee.getType() != EmployeeType.MANAGER) { + throw new NotPermittedException("Only manager can work with employees"); + } + } +} diff --git a/src/main/java/org/lab/application/shared/services/EmployeeProvider.java b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java new file mode 100644 index 0000000..6de0a53 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java @@ -0,0 +1,31 @@ +package org.lab.application.shared.services; + +import com.google.inject.Inject; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class EmployeeProvider { + + private final EmployeeRepository employeeRepository; + + @Inject + public EmployeeProvider( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } + + public Employee get( + int employeeId + ) throws + UserNotFoundException + { + Employee employee = employeeRepository.getById(employeeId); + if (employee == null) { + throw new UserNotFoundException(); + } + return employee; + } +} diff --git a/src/main/java/org/lab/application/shared/services/ProjectProvider.java b/src/main/java/org/lab/application/shared/services/ProjectProvider.java new file mode 100644 index 0000000..d1332a4 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/ProjectProvider.java @@ -0,0 +1,31 @@ +package org.lab.application.shared.services; + +import com.google.inject.Inject; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; + +public class ProjectProvider { + + private ProjectRepository projectRepository; + + @Inject + public ProjectProvider( + ProjectRepository projectRepository + ) { + this.projectRepository = projectRepository; + } + + public Project get( + int projectId + ) throws + ProjectNotFoundException + { + Project project = this.projectRepository.get(projectId); + if (project == null) { + throw new ProjectNotFoundException(); + } + return project; + } +} diff --git a/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java b/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java new file mode 100644 index 0000000..44cc602 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java @@ -0,0 +1,37 @@ +package org.lab.application.shared.services; + +import com.google.inject.Inject; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + +public class ProjectSpecProvider { + + private ProjectRepository projectRepository; + + @Inject + public ProjectSpecProvider( + ProjectRepository projectRepository + ) { + this.projectRepository = projectRepository; + } + + public Project get( + int projectId, + Specification spec + ) throws + ProjectNotFoundException + { + Project project = this.projectRepository.getWithSpec( + projectId, + spec + ); + if (project == null) { + throw new ProjectNotFoundException(); + } + return project; + } +} + diff --git a/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java new file mode 100644 index 0000000..77d208f --- /dev/null +++ b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java @@ -0,0 +1,11 @@ +package org.lab.application.ticket.dto; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record CreateTicketDTO( + int assignedTo, + String description, + TicketStatus status +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java new file mode 100644 index 0000000..6bff6d4 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java @@ -0,0 +1,18 @@ +package org.lab.application.ticket.dto; + +import java.util.Date; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record GetTicketDTO( + int id, + int createdBy, + int assignedTo, + int projectId, + String description, + TicketStatus status, + Date createdDate, + Date closedDate +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java new file mode 100644 index 0000000..18f99d7 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java @@ -0,0 +1,31 @@ +package org.lab.application.ticket.services; + +import com.google.inject.Inject; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; + +public class TicketCloseValidator { + + private TicketRepository ticketRepository; + + @Inject + public TicketCloseValidator( + TicketRepository ticketRepository + ) { + this.ticketRepository = ticketRepository; + } + + public void validate( + int ticketId, + int employeeId + ) throws + NotPermittedException + { + Ticket ticket = this.ticketRepository.get(ticketId, employeeId); + if (ticket == null) { + throw new NotPermittedException("Ticket with id " + ticketId + " does not exist"); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java new file mode 100644 index 0000000..1ac11e1 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java @@ -0,0 +1,46 @@ +package org.lab.application.ticket.services; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.StructuredTaskScope; + +import com.google.inject.Inject; + +import org.lab.application.shared.services.ProjectProvider; + +public class TicketCreateValidator { + + private final TicketPermissionValidator ticketPermissionValidator; + private final ProjectProvider projectProvider; + + @Inject + public TicketCreateValidator( + TicketPermissionValidator ticketPermissionValidator, + ProjectProvider projectProvider + ) { + this.ticketPermissionValidator = ticketPermissionValidator; + this.projectProvider = projectProvider; + } + + public void validate( + int employeeId, + int projectId + ) { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + scope.fork(() -> { + this.ticketPermissionValidator.validate(employeeId); + return null; + }); + scope.fork(() -> { + this.projectProvider.get(projectId); + return null; + }); + scope.join(); + scope.throwIfFailed(); + + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java new file mode 100644 index 0000000..157fc07 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java @@ -0,0 +1,31 @@ +package org.lab.application.ticket.services; + +import com.google.inject.Inject; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TicketPermissionValidator { + + private final EmployeeRepository employeeRepository; + + @Inject + public TicketPermissionValidator( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } + + public void validate(int employeeId) { + Employee creatorEmployee = employeeRepository.getById(employeeId); + System.out.println(creatorEmployee); + if ( + creatorEmployee.getType() != EmployeeType.MANAGER && + creatorEmployee.getType() != EmployeeType.TEAMLEAD + ) { + throw new NotPermittedException("Only manager or team lead can create tickets"); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java new file mode 100644 index 0000000..1dfb844 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java @@ -0,0 +1,31 @@ +package org.lab.application.ticket.use_cases; + +import com.google.inject.Inject; + +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCloseValidator; + +public class CloseTicketUseCase { + + private final TicketRepository ticketRepository; + private final TicketCloseValidator ticketCloseValidator; + + @Inject + public CloseTicketUseCase( + TicketRepository ticketRepository, + TicketCloseValidator ticketCloseValidator + ) { + this.ticketRepository = ticketRepository; + this.ticketCloseValidator = ticketCloseValidator; + } + + public Ticket execute( + int ticketId, + int employeeId + ) { + this.ticketCloseValidator.validate(ticketId, employeeId); + Ticket ticket = this.ticketRepository.close(ticketId); + return ticket; + } +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java new file mode 100644 index 0000000..21a35f8 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -0,0 +1,36 @@ +package org.lab.application.ticket.use_cases; + +import com.google.inject.Inject; + +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCreateValidator; + +public class CreateTicketUseCase { + + private final TicketRepository ticketRepository; + private final TicketCreateValidator ticketCreateValidator; + + @Inject + public CreateTicketUseCase( + TicketRepository ticketRepository, + TicketCreateValidator ticketCreateValidator + ) { + this.ticketRepository = ticketRepository; + this.ticketCreateValidator = ticketCreateValidator; + } + + public Ticket execute( + Ticket ticket, + int employeeId, + int projectId + ) { + this.ticketCreateValidator.validate(employeeId, projectId); + Ticket createdTicket = this.ticketRepository.create( + ticket, + employeeId, + projectId + ); + return createdTicket; + } +} diff --git a/src/main/java/org/lab/core/constants/employee/EmployeeType.java b/src/main/java/org/lab/core/constants/employee/EmployeeType.java new file mode 100644 index 0000000..24ca51c --- /dev/null +++ b/src/main/java/org/lab/core/constants/employee/EmployeeType.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.employee; + +public enum EmployeeType { + PROGRAMMER, + TESTER, + MANAGER, + TEAMLEAD +} diff --git a/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java b/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java new file mode 100644 index 0000000..8002ff0 --- /dev/null +++ b/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.error_message; + +public enum ErrorMessageStatus { + OPEN, + ACTIVE, + ON_HOLD, + CLOSED, +} \ No newline at end of file diff --git a/src/main/java/org/lab/core/constants/project/ProjectStatus.java b/src/main/java/org/lab/core/constants/project/ProjectStatus.java new file mode 100644 index 0000000..49a4591 --- /dev/null +++ b/src/main/java/org/lab/core/constants/project/ProjectStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.project; + +public enum ProjectStatus { + OPEN, + ACTIVE, + ON_HOLD, + DELETED +} diff --git a/src/main/java/org/lab/core/constants/ticket/TicketStatus.java b/src/main/java/org/lab/core/constants/ticket/TicketStatus.java new file mode 100644 index 0000000..ff35721 --- /dev/null +++ b/src/main/java/org/lab/core/constants/ticket/TicketStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.ticket; + +public enum TicketStatus { + OPEN, + ACTIVE, + ON_HOLD, + CLOSED, +} diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java new file mode 100644 index 0000000..ac42a33 --- /dev/null +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -0,0 +1,31 @@ +package org.lab.core.utils.mapper; + +import org.lab.domain.interfaces.DomainObject; +import org.lab.domain.interfaces.PresentationObject; + +public class ObjectMapper { + + private final com.fasterxml.jackson.databind.ObjectMapper mapper = + new com.fasterxml.jackson.databind.ObjectMapper(); + + public T mapToDomain( + PresentationObject presentationObject, + Class domainClass + ) { + return mapper.convertValue(presentationObject, domainClass); + } + + public T mapToPresentation( + DomainObject domainObject, + Class presentationObjectClass + ) { + return mapper.convertValue(domainObject, presentationObjectClass); + } + + public T mapFromRaw( + Object rawObject, + Class domainClass + ) { + return mapper.convertValue(rawObject, domainClass); + } +} diff --git a/src/main/java/org/lab/domain/emploee/model/Employee.java b/src/main/java/org/lab/domain/emploee/model/Employee.java new file mode 100644 index 0000000..8f3e85d --- /dev/null +++ b/src/main/java/org/lab/domain/emploee/model/Employee.java @@ -0,0 +1,18 @@ +package org.lab.domain.emploee.model; + +import java.util.Date; + +import lombok.Data; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.interfaces.DomainObject; + +@Data +public class Employee implements DomainObject { + private int id; + private String name; + private int age; + private EmployeeType type; + private int createdBy; + private Date createdDate; +} \ No newline at end of file diff --git a/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java b/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java new file mode 100644 index 0000000..7d86ef1 --- /dev/null +++ b/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java @@ -0,0 +1,15 @@ +package org.lab.domain.error_mesage.model; + +import lombok.Data; + +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.domain.interfaces.DomainObject; + +@Data +public class ErrorMessage implements DomainObject { + private int id; + private int projectId; + private int createdBy; + private String text; + private ErrorMessageStatus status; +} diff --git a/src/main/java/org/lab/domain/interfaces/AppObject.java b/src/main/java/org/lab/domain/interfaces/AppObject.java new file mode 100644 index 0000000..18d2f83 --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/AppObject.java @@ -0,0 +1,6 @@ +package org.lab.domain.interfaces; + +sealed interface AppObject + permits DomainObject, + PresentationObject { +} diff --git a/src/main/java/org/lab/domain/interfaces/DomainObject.java b/src/main/java/org/lab/domain/interfaces/DomainObject.java new file mode 100644 index 0000000..ef1f806 --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/DomainObject.java @@ -0,0 +1,5 @@ +package org.lab.domain.interfaces; + +public non-sealed interface DomainObject + extends AppObject { +} diff --git a/src/main/java/org/lab/domain/interfaces/PresentationObject.java b/src/main/java/org/lab/domain/interfaces/PresentationObject.java new file mode 100644 index 0000000..f21ef1f --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/PresentationObject.java @@ -0,0 +1,5 @@ +package org.lab.domain.interfaces; + +public non-sealed interface PresentationObject + extends AppObject { +} diff --git a/src/main/java/org/lab/domain/project/model/Project.java b/src/main/java/org/lab/domain/project/model/Project.java new file mode 100644 index 0000000..abd1d47 --- /dev/null +++ b/src/main/java/org/lab/domain/project/model/Project.java @@ -0,0 +1,34 @@ +package org.lab.domain.project.model; + +import java.util.Date; +import java.util.List; + +import lombok.Data; + +import org.lab.domain.interfaces.DomainObject; +import org.lab.core.constants.project.ProjectStatus; + +@Data +public class Project implements DomainObject { + private int id; + private String name; + private String description; + + private int managerId; + private Integer teamLeadId; + + private List developerIds; + private List testerIds; + + private ProjectStatus status; + + private Integer currentMilestoneId; + private List milestoneIds; + + private List bugReportIds; + + private Date createdDate; + private int createdBy; + private Date updatedDate; + private Integer updatedBy; +} \ No newline at end of file diff --git a/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java b/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java new file mode 100644 index 0000000..6f5cea7 --- /dev/null +++ b/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java @@ -0,0 +1,38 @@ +package org.lab.domain.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class ProjectMembershipValidator { + + public void validate( + Employee employee, + Project project + ) throws + NotPermittedException + { + switch (employee.getType()) { + case PROGRAMMER -> { + if (!project.getDeveloperIds().contains(employee.getId())) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case TESTER -> { + if (!project.getTesterIds().contains(employee.getId())) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case MANAGER -> { + if (project.getManagerId() != employee.getId()) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case TEAMLEAD -> { + if (project.getTeamLeadId() != employee.getId()) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + } + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java b/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java new file mode 100644 index 0000000..dc5b230 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class DatabaseException extends RuntimeException { + public DatabaseException() { + super("Something went wrong with database operation"); + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java new file mode 100644 index 0000000..0aa4362 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class MessageNotFoundException extends RuntimeException { + public MessageNotFoundException() { + super("Error message not found"); + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java b/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java new file mode 100644 index 0000000..745e299 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class NotPermittedException extends RuntimeException { + public NotPermittedException(String message) { + super(message); + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java new file mode 100644 index 0000000..e690d58 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class ProjectNotFoundException extends RuntimeException { + public ProjectNotFoundException() { + super("Project not found"); + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java b/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java new file mode 100644 index 0000000..1a03883 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class UserAlreadyExistsException extends RuntimeException { + public UserAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..ca1bec5 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException() { + super("Employee not found"); + } +} diff --git a/src/main/java/org/lab/domain/ticket/model/Ticket.java b/src/main/java/org/lab/domain/ticket/model/Ticket.java new file mode 100644 index 0000000..698a401 --- /dev/null +++ b/src/main/java/org/lab/domain/ticket/model/Ticket.java @@ -0,0 +1,20 @@ +package org.lab.domain.ticket.model; + +import java.util.Date; + +import lombok.Data; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.DomainObject; + +@Data +public class Ticket implements DomainObject { + private int id; + private int createdBy; + private int assignedTo; + private int projectId; + private String description; + private TicketStatus status; + private Date createdDate; + private Date closedDate; +} \ No newline at end of file diff --git a/src/main/java/org/lab/infra/db/client/DatabaseClient.java b/src/main/java/org/lab/infra/db/client/DatabaseClient.java new file mode 100644 index 0000000..61e3ae6 --- /dev/null +++ b/src/main/java/org/lab/infra/db/client/DatabaseClient.java @@ -0,0 +1,26 @@ +package org.lab.infra.db.client; + +import java.sql.Connection; +import java.sql.SQLException; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +public class DatabaseClient { + private static HikariDataSource dataSource; + + static { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:postgresql://postgres:5432/mydb"); + config.setUsername("postgres"); + config.setPassword("postgres"); + config.setMaximumPoolSize(10); + dataSource = new HikariDataSource(config); + } + + public static Connection getConnection() + throws SQLException + { + return dataSource.getConnection(); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java new file mode 100644 index 0000000..c269db3 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -0,0 +1,91 @@ +package org.lab.infra.db.repository.employee; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.infra.db.repository.employee.data_extractor.EmployeeRawDataExtractor; + +public class EmployeeRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final EmployeeRawDataExtractor employeeRawDataExtractor; + + public EmployeeRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + employeeRawDataExtractor = new EmployeeRawDataExtractor(); + } + + public Employee getById(int id) { + String sql = "SELECT * FROM employees where id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, id); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); + return objectMapper.mapFromRaw(row, Employee.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Employee create( + Employee employee, + int actorId + ) { + String sql = """ + INSERT INTO employees (name, age, type, "createdBy") + VALUES (?, ?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setString(1, employee.getName()); + stmt.setInt(2, employee.getAge()); + stmt.setString(3, employee.getType().name()); + stmt.setInt(4, actorId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); + return objectMapper.mapFromRaw(row, Employee.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Object delete(int id) { + String sql = "DELETE FROM employees WHERE id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, id); + return stmt.executeUpdate(); + } catch (SQLException e) { + System.err.println(e.getMessage()); + } + return null; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java new file mode 100644 index 0000000..4a91980 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java @@ -0,0 +1,24 @@ +package org.lab.infra.db.repository.employee.data_extractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class EmployeeRawDataExtractor { + + public Map extractEmployeeRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("name", rs.getString("name")); + row.put("age", rs.getInt("age")); + row.put("type", rs.getString("type")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("createdDate", rs.getTimestamp("createdDate")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java new file mode 100644 index 0000000..b21a4f8 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class DeveloperSpec implements SqlSpec { + + private final int developerId; + + public DeveloperSpec(int developerId) { + this.developerId = developerId; + } + + @Override + public String toSql() { + return "? = ANY(\"developerIds\")"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.developerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java new file mode 100644 index 0000000..bca2956 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class ManagerSpec implements SqlSpec { + + private final int managerId; + + public ManagerSpec(int managerId) { + this.managerId = managerId; + } + + @Override + public String toSql() { + return "\"managerId\" = ?"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.managerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java new file mode 100644 index 0000000..d283504 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class TeamLeaderSpec implements SqlSpec { + + private final int teamLeaderId; + + public TeamLeaderSpec(int teamLeaderId) { + this.teamLeaderId = teamLeaderId; + } + + @Override + public String toSql() { + return "\"teamLeadId\" = ?"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.teamLeaderId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java new file mode 100644 index 0000000..bc57571 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import java.util.ArrayList; +import java.util.List; + +import org.lab.infra.db.spec.SqlSpec; + +public class TesterSpec implements SqlSpec { + + private final int testerId; + + public TesterSpec(int testerId) { + this.testerId = testerId; + } + + @Override + public String toSql() { + return "? = ANY(\"testerIds\")"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.testerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java new file mode 100644 index 0000000..602e0ea --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java @@ -0,0 +1,106 @@ +package org.lab.infra.db.repository.error_message; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.error_message.data_extractor.ErrorMessageRawDataExtractor; + +public class ErrorMessageRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final ErrorMessageRawDataExtractor errorMessageRawDataExtractor; + + public ErrorMessageRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + errorMessageRawDataExtractor = new ErrorMessageRawDataExtractor(); + } + + public ErrorMessage get( + int messageId + ) { + String sql = """ + SELECT * FROM error_messages + WHERE id = ? + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, messageId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + return null; + } + + public ErrorMessage close( + int messageId + ) { + String sql = """ + UPDATE error_messages + SET status = 'CLOSED' + WHERE id = ? + RETURNING * + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, messageId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + return null; + } + + public ErrorMessage create( + ErrorMessage message, + int employeeId + ) { + String sql = """ + INSERT INTO error_messages ("projectId", "createdBy", "text") + VALUES (?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, message.getProjectId()); + stmt.setInt(2, employeeId); + stmt.setString(3, message.getText()); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } else { + throw new RuntimeException("Message creation failed"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } +} diff --git a/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java new file mode 100644 index 0000000..1537e47 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java @@ -0,0 +1,23 @@ +package org.lab.infra.db.repository.error_message.data_extractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class ErrorMessageRawDataExtractor { + + public Map extractErrorMessageRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("projectId", rs.getString("projectId")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("text", rs.getString("text")); + row.put("status", rs.getString("status")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java new file mode 100644 index 0000000..fd40020 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -0,0 +1,173 @@ +package org.lab.infra.db.repository.project; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.PreparedStatement; +import java.sql.Array; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.spec.Specification; +import org.lab.infra.db.spec.SqlSpec; +import org.lab.infra.db.repository.project.data_extractor.ProjectRawDataExtractor; + +public class ProjectRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final ProjectRawDataExtractor projectRawDataExtractor; + + public ProjectRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + projectRawDataExtractor = new ProjectRawDataExtractor(); + } + + public Project get( + int projectId + ) { + String sql = "SELECT * FROM projects WHERE id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Project getWithSpec( + int projectId, + Specification spec + ) { + SqlSpec sqlSpec = (SqlSpec) spec; + String sql = "SELECT * FROM projects WHERE id = ? AND " + sqlSpec.toSql(); + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + List params = sqlSpec.getParams(); + for (int i = 0; i < params.size(); i++) { + stmt.setObject(i+2, params.get(i)); + } + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Project create( + Project project, + int employeeId + ) { + String sql = """ + INSERT INTO projects ( + name, + description, + "managerId", + "teamLeadId", + "developerIds", + "testerIds", + "createdBy" + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setString(1, project.getName()); + stmt.setString(2, project.getDescription()); + stmt.setInt(3, employeeId); + stmt.setInt(4, project.getTeamLeadId()); + Array developerArray = conn.createArrayOf( + "INTEGER", + project.getDeveloperIds().toArray() + ); + stmt.setArray(5, developerArray); + + Array testerArray = conn.createArrayOf( + "INTEGER", + project.getTesterIds().toArray() + ); + stmt.setArray(6, testerArray); + stmt.setInt(7, project.getCreatedBy()); + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); + } else { + throw new RuntimeException("Project creation failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public List list( + Specification specification + ) { + SqlSpec spec = (SqlSpec) specification; + String sql = "SELECT * FROM projects WHERE " + spec.toSql(); + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + List params = spec.getParams(); + for (int i = 0; i < params.size(); i++) { + stmt.setObject(i+1, params.get(i)); + } + try (ResultSet rs = stmt.executeQuery()) { + List projects = new ArrayList<>(); + while (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + projects.add(objectMapper.mapFromRaw(row, Project.class)); + } + return projects; + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public int delete(int projectId) { + String sql = "DELETE FROM projects WHERE id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + return stmt.executeUpdate(); + } catch (SQLException e) { + throw new DatabaseException(); + } + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java new file mode 100644 index 0000000..1b817af --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java @@ -0,0 +1,68 @@ +package org.lab.infra.db.repository.project.data_extractor; + +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.lab.core.constants.project.ProjectStatus; + +public class ProjectRawDataExtractor { + + public Map extractRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("name", rs.getString("name")); + row.put("description", rs.getString("description")); + row.put("managerId", rs.getInt("managerId")); + + int teamLeadId = rs.getInt("teamLeadId"); + row.put("teamLeadId", rs.wasNull() ? null : teamLeadId); + + Array developerArrayRes = rs.getArray("developerIds"); + List developerIds = developerArrayRes != null + ? Arrays.asList((Integer[]) developerArrayRes.getArray()) + : List.of(); + row.put("developerIds", developerIds); + + Array testerArrayRes = rs.getArray("testerIds"); + List testerIds = testerArrayRes != null + ? Arrays.asList((Integer[]) testerArrayRes.getArray()) + : List.of(); + row.put("testerIds", testerIds); + + row.put("status", ProjectStatus.valueOf(rs.getString("status"))); + + int currentMilestoneId = rs.getInt("currentMilestoneId"); + row.put("currentMilestoneId", rs.wasNull() ? null : currentMilestoneId); + + Array milestoneArray = rs.getArray("milestoneIds"); + List milestoneIds = milestoneArray != null + ? Arrays.asList((Integer[]) milestoneArray.getArray()) + : List.of(); + row.put("milestoneIds", milestoneIds); + + Array bugArray = rs.getArray("bugReportIds"); + List bugReportIds = bugArray != null + ? Arrays.asList((Integer[]) bugArray.getArray()) + : List.of(); + row.put("bugReportIds", bugReportIds); + + row.put("createdBy", rs.getInt("createdBy")); + row.put("createdDate", rs.getTimestamp("createdDate")); + row.put( + "updatedBy", + rs.getObject("updatedBy") != null + ? rs.getInt("updatedBy") : null + ); + row.put("updatedDate", rs.getTimestamp("updatedDate")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java new file mode 100644 index 0000000..6d8e722 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -0,0 +1,109 @@ +package org.lab.infra.db.repository.ticket; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.ticket.data_extractor.TicketRawDataExtractor; + +public class TicketRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final TicketRawDataExtractor ticketRawDataExtractor; + + public TicketRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + ticketRawDataExtractor = new TicketRawDataExtractor(); + } + + public Ticket get( + int ticketId, + int employeeId + ) { + String sql = "SELECT * FROM tickets WHERE id = ? AND \"assignedTo\" = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, ticketId); + stmt.setInt(2, employeeId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Ticket create( + Ticket ticket, + int employeeId, + int projectId + ) { + String sql = """ + INSERT INTO tickets ("createdBy", "assignedTo", description, "projectId") + VALUES (?, ?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, employeeId); + stmt.setInt(2, ticket.getAssignedTo()); + stmt.setString(3, ticket.getDescription()); + stmt.setInt(4, projectId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); + } else { + throw new RuntimeException("Ticket creation failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + + public Ticket close( + int ticketId + ) { + String sql = """ + UPDATE tickets + SET status = 'CLOSED' + WHERE id = ? + RETURNING * + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, ticketId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); + } else { + throw new RuntimeException("Ticket update failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } +} diff --git a/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java new file mode 100644 index 0000000..5f36524 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java @@ -0,0 +1,26 @@ +package org.lab.infra.db.repository.ticket.data_extractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class TicketRawDataExtractor { + + public Map extractTicketRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("assignedTo", rs.getInt("assignedTo")); + row.put("projectId", rs.getInt("projectId")); + row.put("description", rs.getString("description")); + row.put("status", rs.getString("status")); + row.put("createdDate", rs.getTimestamp("createdDate")); + row.put("closedDate", rs.getTimestamp("closedDate")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/spec/Specification.java b/src/main/java/org/lab/infra/db/spec/Specification.java new file mode 100644 index 0000000..6f008e8 --- /dev/null +++ b/src/main/java/org/lab/infra/db/spec/Specification.java @@ -0,0 +1,10 @@ +package org.lab.infra.db.spec; + +import java.util.List; + +public sealed interface Specification + permits SqlSpec +{ + String toSql(); + List getParams(); +} diff --git a/src/main/java/org/lab/infra/db/spec/SqlSpec.java b/src/main/java/org/lab/infra/db/spec/SqlSpec.java new file mode 100644 index 0000000..fff7a55 --- /dev/null +++ b/src/main/java/org/lab/infra/db/spec/SqlSpec.java @@ -0,0 +1,9 @@ +package org.lab.infra.db.spec; + +import java.util.List; + +public non-sealed interface SqlSpec + extends Specification { + String toSql(); + List getParams(); +} diff --git a/src/main/java/org/lab/infra/di/AppModule.java b/src/main/java/org/lab/infra/di/AppModule.java new file mode 100644 index 0000000..98467a1 --- /dev/null +++ b/src/main/java/org/lab/infra/di/AppModule.java @@ -0,0 +1,71 @@ +package org.lab.infra.di; + +import com.google.inject.AbstractModule; +import org.lab.api.adapters.employee.*; +import org.lab.api.adapters.error_message.ErrorMessageCloseAdapter; +import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; +import org.lab.api.adapters.project.*; +import org.lab.api.adapters.ticket.*; +import org.lab.application.employee.use_cases.*; +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.application.project.use_cases.*; +import org.lab.application.ticket.use_cases.*; +import org.lab.application.shared.services.*; +import org.lab.application.ticket.services.*; +import org.lab.application.project.services.*; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.repository.ticket.TicketRepository; + + +public class AppModule extends AbstractModule { + + @Override + protected void configure() { + bind(EmployeeRepository.class).asEagerSingleton(); + bind(ProjectRepository.class).asEagerSingleton(); + bind(TicketRepository.class).asEagerSingleton(); + bind(ObjectMapper.class).asEagerSingleton(); + bind(EmployeePermissionValidator.class).asEagerSingleton(); + bind(ProjectProvider.class).asEagerSingleton(); + bind(EmployeeProvider.class).asEagerSingleton(); + bind(ProjectMembershipValidator.class).asEagerSingleton(); + bind(GetValidator.class).asEagerSingleton(); + bind(UserSpecFactory.class).asEagerSingleton(); + bind(TicketPermissionValidator.class).asEagerSingleton(); + bind(TicketCreateValidator.class).asEagerSingleton(); + bind(TicketCloseValidator.class).asEagerSingleton(); + bind(CreateErrorMessageValidator.class).asEagerSingleton(); + bind(ProjectSpecProvider.class).asEagerSingleton(); + bind(ErrorMessageValidator.class).asEagerSingleton(); + + bind(CreateEmployeeUseCase.class).asEagerSingleton(); + bind(DeleteEmployeeUseCase.class).asEagerSingleton(); + bind(GetEmployeeUseCase.class).asEagerSingleton(); + bind(CreateProjectUseCase.class).asEagerSingleton(); + bind(GetProjectUseCase.class).asEagerSingleton(); + bind(DeleteProjectUseCase.class).asEagerSingleton(); + bind(ListProjectUseCase.class).asEagerSingleton(); + bind(CreateTicketUseCase.class).asEagerSingleton(); + bind(CloseTicketUseCase.class).asEagerSingleton(); + bind(CreateErrorMessageUseCase.class).asEagerSingleton(); + bind(CloseErrorMessageUseCase.class).asEagerSingleton(); + + bind(EmployeeCreateAdapter.class).asEagerSingleton(); + bind(EmployeeDeleteAdapter.class).asEagerSingleton(); + bind(EmployeeGetAdapter.class).asEagerSingleton(); + bind(ProjectCreateAdapter.class).asEagerSingleton(); + bind(ProjectGetAdapter.class).asEagerSingleton(); + bind(ProjectDeleteAdapter.class).asEagerSingleton(); + bind(ProjectListAdapter.class).asEagerSingleton(); + bind(TicketCreateAdapter.class).asEagerSingleton(); + bind(TicketCloseAdapter.class).asEagerSingleton(); + bind(ErrorMessageCreateAdapter.class).asEagerSingleton(); + bind(ErrorMessageCloseAdapter.class).asEagerSingleton(); + } +} diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java new file mode 100644 index 0000000..e093095 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java @@ -0,0 +1,140 @@ +package org.lab.api.adapters.employee; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.employee.dto.CreateEmployeeDTO; +import org.lab.application.employee.dto.GetEmployeeDTO; +import org.lab.application.employee.use_cases.CreateEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.Map; + +public class TestEmployeeCreateAdapter { + + private ObjectMapper mapper; + private CreateEmployeeUseCase useCase; + private EmployeeCreateAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + mapper = Mockito.mock(ObjectMapper.class); + useCase = Mockito.mock(CreateEmployeeUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new EmployeeCreateAdapter(mapper, useCase); + } + + @Test + public void testCreateEmployeeSuccess() { + Mockito.when(ctx.pathParam("actorId")).thenReturn("99"); + + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER + ); + + Employee mappedEmployee = new Employee(); + mappedEmployee.setName("John"); + mappedEmployee.setAge(30); + mappedEmployee.setType(EmployeeType.PROGRAMMER); + + Employee createdEmployee = new Employee(); + createdEmployee.setId(5); + createdEmployee.setName("John"); + createdEmployee.setAge(30); + createdEmployee.setType(EmployeeType.PROGRAMMER); + createdEmployee.setCreatedBy(99); + createdEmployee.setCreatedDate(new Date()); + + GetEmployeeDTO presentation = new GetEmployeeDTO( + createdEmployee.getId(), + createdEmployee.getName(), + createdEmployee.getAge(), + createdEmployee.getType(), + createdEmployee.getCreatedBy(), + createdEmployee.getCreatedDate() + ); + + Mockito.when( + ctx.bodyAsClass(CreateEmployeeDTO.class) + ).thenReturn(dto); + Mockito.when( + mapper.mapToDomain(dto, Employee.class) + ).thenReturn(mappedEmployee); + Mockito.when( + useCase.execute(mappedEmployee, 99) + ).thenReturn(createdEmployee); + Mockito.when( + mapper.mapToPresentation( + createdEmployee, + GetEmployeeDTO.class) + ).thenReturn(presentation); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(201); + Mockito.verify(ctx).json(presentation); + } + + @Test + public void testCreateEmployeeNotPermitted() { + Mockito.when(ctx.pathParam("actorId")).thenReturn("99"); + + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER + ); + + Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); + Mockito.when(mapper.mapToDomain(dto, Employee.class)).thenReturn(new Employee()); + + Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(99))) + .thenThrow(new NotPermittedException( + "You do not have permission to perform this operation" + )); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of("error", "You do not have permission to perform this operation") + ); + } + + @Test + public void testCreateEmployeeUnexpectedError() { + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER + ); + + Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Employee.class)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java new file mode 100644 index 0000000..4eab84a --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java @@ -0,0 +1,134 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import io.javalin.http.Context; +import org.mockito.Mockito; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.domain.shared.exceptions.DatabaseException; + +public class TestEmployeeDeleteAdapter { + private DeleteEmployeeUseCase deleteEmployeeUseCase; + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator employeePermissionValidator; + private EmployeeDeleteAdapter employeeDeleteAdapter; + private Context ctx; + + @BeforeEach + public void setUp() { + ctx = Mockito.mock(Context.class); + employeeRepository = Mockito.mock(EmployeeRepository.class); + employeePermissionValidator = new EmployeePermissionValidator( + employeeRepository + ); + deleteEmployeeUseCase = new DeleteEmployeeUseCase( + employeeRepository, + employeePermissionValidator + ); + employeeDeleteAdapter = new EmployeeDeleteAdapter(deleteEmployeeUseCase); + } + + @Test + public void testDeleteEmployeeSuccess() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("Manager"); + notManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(notManagerEmployee.getId())).thenReturn(notManagerEmployee); + + Mockito.when(ctx.status(201)).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + Mockito.verify(ctx).status(201); + } + + @Test + public void testDeleteEmployeeFetchesNotPermittedException() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(notManagerEmployee.getId())).thenReturn(notManagerEmployee); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json(Map.of("error", "You do not have permission to perform this operation")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenDeletingEmployee() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).delete(testEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java new file mode 100644 index 0000000..ad3d07c --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -0,0 +1,179 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import io.javalin.http.Context; +import org.mockito.Mockito; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestEmployeeGetAdapter { + private GetEmployeeUseCase getEmployeeUseCase; + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator employeePermissionValidator; + private EmployeeGetAdapter employeeGetAdapter; + private EmployeeProvider employeeProvider; + private Context ctx; + private ObjectMapper mapper; + + @BeforeEach + public void setUp() { + ctx = Mockito.mock(Context.class); + employeeRepository = Mockito.mock(EmployeeRepository.class); + employeePermissionValidator = new EmployeePermissionValidator( + employeeRepository + ); + employeeProvider = new EmployeeProvider(employeeRepository); + getEmployeeUseCase = new GetEmployeeUseCase( + employeePermissionValidator, + employeeProvider + ); + mapper = new ObjectMapper(); + employeeGetAdapter = new EmployeeGetAdapter( + getEmployeeUseCase, + mapper + ); + } + + @Test + public void testGetEmployeeSuccess() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("Manager"); + ManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); + Mockito.when(employeeRepository + .getById(testEmployee.getId())) + .thenReturn(testEmployee); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + Mockito.verify(ctx).status(201); + } + + @Test + public void testGetEmployeeHandlesNotPermittedException() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("Manager"); + ManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); + Mockito.when(employeeRepository + .getById(testEmployee.getId())) + .thenReturn(testEmployee); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } + + @Test + public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(notManagerEmployee.getId()); + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(notManagerEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } + + @Test + public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("NotManager"); + ManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); + + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(testEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java new file mode 100644 index 0000000..21f372e --- /dev/null +++ b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java @@ -0,0 +1,93 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; + +public class TestErrorMessageCloseAdapter { + + private CloseErrorMessageUseCase useCase; + private ObjectMapper mapper; + private ErrorMessageCloseAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CloseErrorMessageUseCase.class); + mapper = mock(ObjectMapper.class); + adapter = new ErrorMessageCloseAdapter(useCase, mapper); + ctx = mock(Context.class); + } + + @Test + void closeMessage_success() { + when(ctx.pathParam("messageId")).thenReturn("1"); + + ErrorMessage msg = new ErrorMessage(); + msg.setId(1); + msg.setProjectId(100); + msg.setText("Some text"); + msg.setStatus(ErrorMessageStatus.CLOSED); + msg.setCreatedBy(999); + + GetErrorMessageDTO dto = new GetErrorMessageDTO( + 1, + 100, + 999, + "Some text", + ErrorMessageStatus.CLOSED + ); + + when(useCase.execute(1)).thenReturn(msg); + when(mapper.mapToPresentation(msg, GetErrorMessageDTO.class)).thenReturn(dto); + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + Context result = adapter.closeMessage(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + assertEquals(ctx, result); + } + + @Test + void closeMessage_messageNotFound() { + when(ctx.pathParam("messageId")).thenReturn("5"); + + when(useCase.execute(5)).thenThrow(new MessageNotFoundException()); + + when(ctx.status(409)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeMessage(ctx); + + verify(ctx).status(409); + verify(ctx).json(Map.of("error", "Message doesnt exist")); + } + + @Test + void closeMessage_internalServerError() { + when(ctx.pathParam("messageId")).thenReturn("10"); + + when(useCase.execute(10)).thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeMessage(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java new file mode 100644 index 0000000..4377b56 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java @@ -0,0 +1,185 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import static org.mockito.Mockito.*; +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.error_message.dto.CreateErrorMessageDTO; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class TestErrorMessageCreateAdapter { + + private CreateErrorMessageUseCase useCase; + private ObjectMapper mapper; + private ErrorMessageCreateAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CreateErrorMessageUseCase.class); + mapper = mock(ObjectMapper.class); + adapter = new ErrorMessageCreateAdapter(useCase, mapper); + ctx = mock(Context.class); + } + + @Test + void createErrorMessage_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + + CreateErrorMessageDTO bodyDto = new CreateErrorMessageDTO(1, "text"); + ErrorMessage domainMsg = new ErrorMessage(); + domainMsg.setId(5); + domainMsg.setProjectId(1); + domainMsg.setCreatedBy(10); + domainMsg.setText("text"); + domainMsg.setStatus(ErrorMessageStatus.OPEN); + + ErrorMessage created = new ErrorMessage(); + created.setId(5); + created.setProjectId(1); + created.setCreatedBy(10); + created.setText("text"); + created.setStatus(ErrorMessageStatus.OPEN); + GetErrorMessageDTO dto = new GetErrorMessageDTO( + 5, + 1, + 10, + "text", + ErrorMessageStatus.OPEN + ); + + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)).thenReturn(bodyDto); + when(mapper.mapToDomain(bodyDto, ErrorMessage.class)).thenReturn(domainMsg); + when(useCase.execute(domainMsg, 10)).thenReturn(created); + when(mapper.mapToPresentation(created, GetErrorMessageDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void createErrorMessage_userNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))).thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))).thenThrow(new UserNotFoundException()); + + when(ctx.status(409)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(409); + verify(ctx).json(Map.of("error", "User doesnt exist")); + } + + @Test + void createErrorMessage_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))).thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new NotPermittedException("not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", "You do not have permission to perform this operation" + )); + } + + @Test + void createErrorMessage_projectNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))) + .thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new ProjectNotFoundException()); + + when(ctx.status(404)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(404); + verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + void createErrorMessage_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))) + .thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java new file mode 100644 index 0000000..bc5253a --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java @@ -0,0 +1,155 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.CreateProjectDTO; +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.CreateProjectUseCase; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectCreateAdapter { + + private ObjectMapper mapper; + private CreateProjectUseCase useCase; + private ProjectCreateAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + mapper = Mockito.mock(ObjectMapper.class); + useCase = Mockito.mock(CreateProjectUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectCreateAdapter(mapper, useCase); + } + + @Test + void testCreateProjectSuccess() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + List.of(1, 2), + List.of(3, 4) + ); + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)).thenReturn(dto); + Mockito.when(ctx.pathParam("employeeId")).thenReturn("20"); + + Project mapped = new Project(); + Mockito.when(mapper.mapToDomain(dto, Project.class)).thenReturn(mapped); + + Project created = new Project(); + created.setId(111); + created.setName("TestProject"); + created.setDescription("Description"); + created.setManagerId(10); + created.setTeamLeadId(20); + created.setDeveloperIds(List.of(1, 2)); + created.setTesterIds(List.of(3, 4)); + + Mockito.when( + useCase.execute(Mockito.eq(mapped), + Mockito.eq(20)) + ).thenReturn(created); + + GetProjectDTO presentation = new GetProjectDTO( + created.getId(), + created.getName(), + created.getDescription(), + created.getManagerId(), + created.getTeamLeadId(), + created.getDeveloperIds(), + created.getTesterIds(), + created.getStatus(), + created.getCurrentMilestoneId(), + created.getMilestoneIds(), + created.getBugReportIds(), + created.getCreatedDate(), + created.getCreatedBy(), + created.getUpdatedDate(), + created.getUpdatedBy() + ); + Mockito.when( + mapper.mapToPresentation( + created, + GetProjectDTO.class) + ).thenReturn(presentation); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + adapter.createProject(ctx); + Mockito.verify(ctx).status(201); + Mockito.verify(ctx).json(presentation); + } + + @Test + public void testCreateProject_NotPermitted() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + List.of(), + List.of() + ); + + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)).thenReturn(dto); + Mockito.when(ctx.pathParam("employeeId")).thenReturn("20"); + + Project mappedProject = new Project(); + Mockito.when(mapper.mapToDomain(dto, Project.class)).thenReturn(mappedProject); + + Mockito.when(useCase.execute(Mockito.eq(mappedProject), Mockito.eq(20))) + .thenThrow(new NotPermittedException( + "You do not have permission to perform this operation" + )); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } + + @Test + public void testCreateProject_UnexpectedError() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + List.of(), + List.of() + ); + + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) + .thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Project.class)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java new file mode 100644 index 0000000..c2aa8d7 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java @@ -0,0 +1,116 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.use_cases.DeleteProjectUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Map; + +public class TestProjectDeleteAdapter { + + private DeleteProjectUseCase useCase; + private ProjectDeleteAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(DeleteProjectUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectDeleteAdapter(useCase); + } + + @Test + public void testDeleteProjectSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(ctx.status(204)).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(useCase).execute(10, 50); + Mockito.verify(ctx).status(204); + } + + @Test + public void testDeleteProject_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new UserNotFoundException()) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json( + Map.of("error", "User doesnt exist") + ); + } + + @Test + public void testDeleteProject_NotPermitted() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new NotPermittedException("denied")) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } + + @Test + public void testDeleteProject_ProjectNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new ProjectNotFoundException()) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(404)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(404); + Mockito.verify(ctx).json(Map.of("error", "Project doesn't exist")); + } + + @Test + public void testDeleteProject_UnexpectedError() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new RuntimeException("boom")) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java new file mode 100644 index 0000000..56eb0ce --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java @@ -0,0 +1,159 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectGetAdapter { + + private GetProjectUseCase useCase; + private ObjectMapper mapper; + private ProjectGetAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(GetProjectUseCase.class); + mapper = Mockito.mock(ObjectMapper.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectGetAdapter(useCase, mapper); + } + + @Test + public void testGetProjectSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Project project = new Project(); + project.setId(50); + project.setName("TestProject"); + project.setDescription("Description"); + project.setManagerId(10); + project.setTeamLeadId(20); + project.setDeveloperIds(List.of(1, 2)); + project.setTesterIds(List.of(3, 4)); + project.setStatus(ProjectStatus.OPEN); + project.setCreatedDate(new Date()); + project.setCreatedBy(99); + + Mockito.when( + useCase.execute( + Mockito.eq(50), + Mockito.eq(10) + ) + ).thenReturn(project); + + GetProjectDTO dto = new GetProjectDTO( + project.getId(), + project.getName(), + project.getDescription(), + project.getManagerId(), + project.getTeamLeadId(), + project.getDeveloperIds(), + project.getTesterIds(), + project.getStatus(), + project.getCurrentMilestoneId(), + project.getMilestoneIds(), + project.getBugReportIds(), + project.getCreatedDate(), + project.getCreatedBy(), + project.getUpdatedDate(), + project.getUpdatedBy() + ); + + Mockito.when(mapper.mapToPresentation(project, GetProjectDTO.class)).thenReturn(dto); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(200); + Mockito.verify(ctx).json(dto); + } + + @Test + public void testGetProject_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) + .thenThrow(new UserNotFoundException()); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json(Map.of("error", "User doesnt exist")); + } + + @Test + public void testGetProject_NotPermitted() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) + .thenThrow(new NotPermittedException("denied")); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of("error", "You do not have permission to perform this operation") + ); + } + + @Test + public void testGetProject_ProjectNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) + .thenThrow(new ProjectNotFoundException()); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(404); + Mockito.verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + public void testGetProject_UnexpectedError() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(10, 50)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java new file mode 100644 index 0000000..6308dee --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java @@ -0,0 +1,111 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.ListProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectListAdapter { + + private ListProjectUseCase useCase; + private ObjectMapper mapper; + private ProjectListAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(ListProjectUseCase.class); + mapper = Mockito.mock(ObjectMapper.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectListAdapter(useCase, mapper); + } + + @Test + public void testListProjectsSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + + Project project1 = new Project(); + project1.setId(1); + project1.setName("Project1"); + project1.setDescription("Desc1"); + project1.setManagerId(10); + project1.setStatus(ProjectStatus.OPEN); + project1.setCreatedDate(new Date()); + project1.setCreatedBy(99); + + Project project2 = new Project(); + project2.setId(2); + project2.setName("Project2"); + project2.setDescription("Desc2"); + project2.setManagerId(10); + project2.setStatus(ProjectStatus.OPEN); + project2.setCreatedDate(new Date()); + project2.setCreatedBy(99); + + List projects = List.of(project1, project2); + + Mockito.when(useCase.execute(10)).thenReturn(projects); + + GetProjectDTO dto1 = new GetProjectDTO( + project1.getId(), project1.getName(), project1.getDescription(), + project1.getManagerId(), project1.getTeamLeadId(), + project1.getDeveloperIds(), project1.getTesterIds(), + project1.getStatus(), + project1.getCurrentMilestoneId(), project1.getMilestoneIds(), + project1.getBugReportIds(), + project1.getCreatedDate(), project1.getCreatedBy(), + project1.getUpdatedDate(), project1.getUpdatedBy() + ); + + GetProjectDTO dto2 = new GetProjectDTO( + project2.getId(), project2.getName(), project2.getDescription(), + project2.getManagerId(), project2.getTeamLeadId(), + project2.getDeveloperIds(), project2.getTesterIds(), + project2.getStatus(), + project2.getCurrentMilestoneId(), project2.getMilestoneIds(), + project2.getBugReportIds(), + project2.getCreatedDate(), project2.getCreatedBy(), + project2.getUpdatedDate(), project2.getUpdatedBy() + ); + + Mockito.when(mapper.mapToPresentation(project1, GetProjectDTO.class)).thenReturn(dto1); + Mockito.when(mapper.mapToPresentation(project2, GetProjectDTO.class)).thenReturn(dto2); + + List dtoList = List.of(dto1, dto2); + + Mockito.when(ctx.status(200)).thenReturn(ctx); + Mockito.when(ctx.json(dtoList)).thenReturn(ctx); + + adapter.listProjects(ctx); + + Mockito.verify(ctx).status(200); + Mockito.verify(ctx).json(dtoList); + } + + @Test + public void testListProjects_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + + Mockito.when(useCase.execute(10)).thenThrow(new UserNotFoundException()); + + Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.listProjects(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json(Map.of("error", "User doesnt exist")); + } +} diff --git a/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java b/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java new file mode 100644 index 0000000..8ecb81e --- /dev/null +++ b/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java @@ -0,0 +1,106 @@ +package org.lab.api.adapters.ticket; + +import java.util.Date; +import java.util.Map; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CloseTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; + +public class TestTicketCloseAdapter { + + private CloseTicketUseCase useCase; + private ObjectMapper objectMapper; + private TicketCloseAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CloseTicketUseCase.class); + objectMapper = mock(ObjectMapper.class); + adapter = new TicketCloseAdapter(useCase, objectMapper); + ctx = mock(Context.class); + } + + @Test + void closeTicket_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + Ticket domainTicket = new Ticket(); + domainTicket.setId(5); + domainTicket.setCreatedBy(10); + domainTicket.setAssignedTo(11); + domainTicket.setProjectId(1); + domainTicket.setDescription("Fix bug"); + domainTicket.setStatus(TicketStatus.CLOSED); + domainTicket.setCreatedDate(new Date()); + domainTicket.setClosedDate(new Date()); + + GetTicketDTO dto = new GetTicketDTO( + 5, + 10, + 11, + 1, + "Fix bug", + TicketStatus.CLOSED, + domainTicket.getCreatedDate(), + domainTicket.getClosedDate() + ); + + when(useCase.execute(5, 10)).thenReturn(domainTicket); + when(objectMapper.mapToPresentation(domainTicket, GetTicketDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void closeTicket_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + when(useCase.execute(5, 10)) + .thenThrow(new NotPermittedException("you are not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", + "You do not have permission to perform this operation" + )); + } + + @Test + void closeTicket_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + when(useCase.execute(5, 10)).thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java b/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java new file mode 100644 index 0000000..111ad4a --- /dev/null +++ b/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java @@ -0,0 +1,171 @@ +package org.lab.api.adapters.ticket; + +import java.util.Date; +import java.util.Map; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +import org.lab.application.ticket.dto.CreateTicketDTO; +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CreateTicketUseCase; +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.ticket.model.Ticket; + +public class TestTicketCreateAdapter { + + private CreateTicketUseCase useCase; + private ObjectMapper objectMapper; + private TicketCreateAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CreateTicketUseCase.class); + objectMapper = mock(ObjectMapper.class); + adapter = new TicketCreateAdapter(useCase, objectMapper); + ctx = mock(Context.class); + } + + @Test + void createTicket_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + CreateTicketDTO bodyDto = new CreateTicketDTO( + 20, + "Fix memory leak", + TicketStatus.OPEN + ); + + Ticket domainTicket = new Ticket(); + domainTicket.setId(0); + domainTicket.setAssignedTo(20); + domainTicket.setCreatedBy(10); + domainTicket.setProjectId(3); + domainTicket.setDescription("Fix memory leak"); + domainTicket.setStatus(TicketStatus.OPEN); + + Ticket created = new Ticket(); + created.setId(7); + created.setAssignedTo(20); + created.setCreatedBy(10); + created.setProjectId(3); + created.setDescription("Fix memory leak"); + created.setStatus(TicketStatus.OPEN); + created.setCreatedDate(new Date()); + + GetTicketDTO dto = new GetTicketDTO( + 7, + 10, + 20, + 3, + "Fix memory leak", + TicketStatus.OPEN, + created.getCreatedDate(), + null + ); + + when(ctx.bodyAsClass(CreateTicketDTO.class)).thenReturn(bodyDto); + when(objectMapper.mapToDomain(bodyDto, Ticket.class)).thenReturn(domainTicket); + when(useCase.execute(domainTicket, 10, 3)).thenReturn(created); + when(objectMapper.mapToPresentation(created, GetTicketDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void createTicket_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new NotPermittedException("not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", + "You do not have permission to perform this operation" + )); + } + + @Test + void createTicket_projectNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new ProjectNotFoundException()); + + when(ctx.status(404)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(404); + verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + void createTicket_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java new file mode 100644 index 0000000..1e1cba1 --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -0,0 +1,98 @@ +package org.lab.application.employee.use_cases; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestCreateEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + private CreateEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + useCase = new CreateEmployeeUseCase(employeeRepository, validator); + } + + @Test + public void testCreateEmployeeSuccess() { + Employee input = new Employee(); + input.setName("Tim Cock"); + input.setAge(12); + input.setType(EmployeeType.PROGRAMMER); + + Employee saved = new Employee(); + saved.setId(100); + saved.setName("Tim Cock"); + + Mockito.when( + employeeRepository.create(Mockito.any(Employee.class), + Mockito.eq(1)) + ) + .thenReturn(saved); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.MANAGER); + Mockito.when(employeeRepository.getById(1)).thenReturn(creator); + + Employee result = useCase.execute(input, 1); + + Assertions.assertNotNull(result); + Assertions.assertEquals(100, result.getId()); + + Mockito.verify(employeeRepository) + .create(Mockito.any(Employee.class), + Mockito.eq(1) + ); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testCreateEmployeeRaisesNotPermittedException() { + Employee input = new Employee(); + input.setId(123); + input.setName("Tim Cock"); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(creator); + + Assertions.assertThrows( + NotPermittedException.class, + () -> useCase.execute(input, 1) + ); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testCreateEmployeeRaisesRuntimeException() { + Employee input = new Employee(); + input.setId(123); + input.setName("Tim Cock"); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenThrow(new RuntimeException()); + + Assertions.assertThrows( + RuntimeException.class, + () -> useCase.execute(input, 1) + ); + Mockito.verify(employeeRepository).getById(1); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java new file mode 100644 index 0000000..e70382f --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java @@ -0,0 +1,52 @@ +package org.lab.application.employee.use_cases; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestDeleteEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + private DeleteEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + useCase = new DeleteEmployeeUseCase(employeeRepository, validator); + } + + @Test + public void testDeleteEmployeeSuccess() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + useCase.execute(2, 1); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testDeleteEmployeeRaisesNotPermittedError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + Assertions.assertThrows( + NotPermittedException.class, + () -> useCase.execute(2, 1) + ); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java new file mode 100644 index 0000000..b2726e5 --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -0,0 +1,75 @@ +package org.lab.application.employee.use_cases; + +import org.mockito.Mockito; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestGetEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeeProvider employeeProvider; + private EmployeePermissionValidator validator; + private GetEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + employeeProvider = new EmployeeProvider(employeeRepository); + validator = new EmployeePermissionValidator(employeeRepository); + useCase = new GetEmployeeUseCase(validator, employeeProvider); + } + + @Test + public void testGetEmployeeSuccess() { + Employee programmer = new Employee(); + programmer.setId(2); + programmer.setType(EmployeeType.PROGRAMMER); + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + Mockito.when(employeeRepository.getById(2)).thenReturn(programmer); + + useCase.execute(2, 1); + Mockito.verify(employeeRepository).getById(2); + } + + @Test + public void testGetEmployeeRaisesNotPermittedError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + Assertions.assertThrows( + NotPermittedException.class, + () -> useCase.execute(2, 1) + ); + } + + @Test + public void testGetEmployeeRaisesEmployeeNotFoundError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + Mockito.when(employeeRepository.getById(2)).thenReturn(null); + + Assertions.assertThrows( + UserNotFoundException.class, + () -> useCase.execute(2, 1) + ); + } +} diff --git a/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java b/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java new file mode 100644 index 0000000..8ed67af --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java @@ -0,0 +1,69 @@ +package org.lab.application.error_messages.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectSpecProvider; +import org.lab.application.project.services.UserSpecFactory; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.spec.Specification; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +public class TestCreateErrorMessageValidator { + + private EmployeeProvider employeeProvider; + private ProjectSpecProvider projectSpecProvider; + private UserSpecFactory userSpecFactory; + private CreateErrorMessageValidator validator; + + @BeforeEach + void setup() { + employeeProvider = mock(EmployeeProvider.class); + projectSpecProvider = mock(ProjectSpecProvider.class); + userSpecFactory = mock(UserSpecFactory.class); + validator = new CreateErrorMessageValidator( + employeeProvider, + projectSpecProvider, + userSpecFactory + ); + } + + @Test + void validate_onlyTesterAllowed() { + Employee employee = new Employee(); + employee.setId(1); + employee.setType(EmployeeType.PROGRAMMER); + when(employeeProvider.get(1)).thenReturn(employee); + + NotPermittedException ex = assertThrows(NotPermittedException.class, () -> { + validator.validate(1, 10); + }); + + assertEquals("Only tester can create error messages", ex.getMessage()); + verifyNoInteractions(userSpecFactory, projectSpecProvider); + } + + @Test + void validate_testerCallsProjectSpecProvider() { + Employee employee = new Employee(); + employee.setId(2); + employee.setType(EmployeeType.TESTER); + + Specification spec = mock(TesterSpec.class); + + when(employeeProvider.get(2)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + + validator.validate(2, 10); + + verify(userSpecFactory).getForType(employee); + verify(projectSpecProvider).get(10, spec); + } +} diff --git a/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java b/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java new file mode 100644 index 0000000..a5f366c --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java @@ -0,0 +1,43 @@ +package org.lab.application.error_messages.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +class TestErrorMessageValidator { + + private ErrorMessageRepository repository; + private ErrorMessageValidator validator; + + @BeforeEach + void setup() { + repository = mock(ErrorMessageRepository.class); + validator = new ErrorMessageValidator(repository); + } + + @Test + void validate_messageExists() { + ErrorMessage message = new ErrorMessage(); + when(repository.get(1)).thenReturn(message); + + assertDoesNotThrow(() -> validator.validate(1)); + + verify(repository).get(1); + } + + @Test + void validate_messageNotFound() { + when(repository.get(2)).thenReturn(null); + + assertThrows(MessageNotFoundException.class, () -> validator.validate(2)); + + verify(repository).get(2); + } +} + diff --git a/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java b/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java new file mode 100644 index 0000000..3268d30 --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java @@ -0,0 +1,51 @@ +package org.lab.application.error_messages.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +class TestCloseErrorMessageUseCase { + + private ErrorMessageValidator validator; + private ErrorMessageRepository repository; + private CloseErrorMessageUseCase useCase; + + @BeforeEach + void setup() { + validator = mock(ErrorMessageValidator.class); + repository = mock(ErrorMessageRepository.class); + useCase = new CloseErrorMessageUseCase(validator, repository); + } + + @Test + void execute_success() { + int messageId = 1; + ErrorMessage closedMessage = new ErrorMessage(); + when(repository.close(messageId)).thenReturn(closedMessage); + + ErrorMessage result = useCase.execute(messageId); + + verify(validator).validate(messageId); + verify(repository).close(messageId); + assertEquals(closedMessage, result); + } + + @Test + void execute_messageNotFound() { + int messageId = 2; + doThrow(new MessageNotFoundException()).when(validator).validate(messageId); + + assertThrows(MessageNotFoundException.class, () -> useCase.execute(messageId)); + + verify(validator).validate(messageId); + verifyNoInteractions(repository); + } +} + diff --git a/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java b/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java new file mode 100644 index 0000000..a9c7eba --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java @@ -0,0 +1,72 @@ +package org.lab.application.error_messages.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; +import org.lab.domain.shared.exceptions.NotPermittedException; + +class CreateErrorMessageUseCaseTest { + + private ErrorMessageRepository repository; + private CreateErrorMessageValidator validator; + private CreateErrorMessageUseCase useCase; + + @BeforeEach + void setup() { + repository = mock(ErrorMessageRepository.class); + validator = mock(CreateErrorMessageValidator.class); + useCase = new CreateErrorMessageUseCase(repository, validator); + } + + @Test + void execute_success() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + ErrorMessage createdMessage = new ErrorMessage(); + when(repository.create(message, employeeId)).thenReturn(createdMessage); + + ErrorMessage result = useCase.execute(message, employeeId); + + verify(validator).validate(employeeId, message.getProjectId()); + verify(repository).create(message, employeeId); + assertEquals(createdMessage, result); + } + + @Test + void execute_notPermitted() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + doThrow(new NotPermittedException("not permitted")) + .when(validator) + .validate(employeeId, message.getProjectId()); + + assertThrows(NotPermittedException.class, () -> useCase.execute(message, employeeId)); + verify(validator).validate(employeeId, message.getProjectId()); + verifyNoInteractions(repository); + } + + @Test + void execute_runtimeExceptionFromRepository() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + doNothing().when(validator).validate(employeeId, message.getProjectId()); + when(repository.create(message, employeeId)).thenThrow(new RuntimeException("boom")); + + assertThrows(RuntimeException.class, () -> useCase.execute(message, employeeId)); + verify(validator).validate(employeeId, message.getProjectId()); + verify(repository).create(message, employeeId); + } +} + diff --git a/src/test/java/org/lab/application/project/services/TestGetValidator.java b/src/test/java/org/lab/application/project/services/TestGetValidator.java new file mode 100644 index 0000000..bc814d6 --- /dev/null +++ b/src/test/java/org/lab/application/project/services/TestGetValidator.java @@ -0,0 +1,86 @@ +package org.lab.application.project.services; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectProvider; + +import java.util.concurrent.ExecutionException; + +class TestGetValidator { + + private ProjectProvider projectProvider; + private EmployeeProvider employeeProvider; + private GetValidator validator; + + @BeforeEach + void setup() { + projectProvider = mock(ProjectProvider.class); + employeeProvider = mock(EmployeeProvider.class); + validator = new GetValidator(projectProvider, employeeProvider); + } + + @Test + void validate_success() throws Exception { + Project project = new Project(); + Employee employee = new Employee(); + + when(projectProvider.get(5)).thenReturn(project); + when(employeeProvider.get(10)).thenReturn(employee); + + Pair result = validator.validate(5, 10); + + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + assertSame(project, result.project()); + assertSame(employee, result.employee()); + } + + @Test + void validate_projectNotFound_throwsException() throws Exception { + when(projectProvider.get(5)).thenThrow(new ProjectNotFoundException()); + when(employeeProvider.get(10)).thenReturn(new Employee()); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> validator.validate(5, 10) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause() + ).getCause() instanceof ProjectNotFoundException + ); + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + } + + @Test + void validate_userNotFound_throwsException() throws Exception { + when(projectProvider.get(5)).thenReturn(new Project()); + when(employeeProvider.get(10)).thenThrow(new UserNotFoundException()); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> validator.validate(5, 10) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause() + ).getCause() instanceof UserNotFoundException + ); + + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + } +} diff --git a/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java b/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java new file mode 100644 index 0000000..2142efd --- /dev/null +++ b/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java @@ -0,0 +1,71 @@ +package org.lab.application.project.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.spec.DeveloperSpec; +import org.lab.infra.db.repository.employee.spec.ManagerSpec; +import org.lab.infra.db.repository.employee.spec.TeamLeaderSpec; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.spec.Specification; + +class TestUserSpecFactory { + + private UserSpecFactory factory; + + @BeforeEach + void setup() { + factory = new UserSpecFactory(); + } + + @Test + void getForType_manager_returnsManagerSpec() { + Employee emp = new Employee(); + emp.setId(1); + emp.setType(EmployeeType.MANAGER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof ManagerSpec); + assertEquals(1, ((ManagerSpec) spec).getParams().get(0)); + } + + @Test + void getForType_teamLead_returnsTeamLeaderSpec() { + Employee emp = new Employee(); + emp.setId(2); + emp.setType(EmployeeType.TEAMLEAD); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof TeamLeaderSpec); + assertEquals(2, ((TeamLeaderSpec) spec).getParams().get(0)); + } + + @Test + void getForType_programmer_returnsDeveloperSpec() { + Employee emp = new Employee(); + emp.setId(3); + emp.setType(EmployeeType.PROGRAMMER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof DeveloperSpec); + assertEquals(3, ((DeveloperSpec) spec).getParams().get(0)); + } + + @Test + void getForType_tester_returnsTesterSpec() { + Employee emp = new Employee(); + emp.setId(4); + emp.setType(EmployeeType.TESTER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof TesterSpec); + assertEquals(4, ((TesterSpec) spec).getParams().get(0)); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java new file mode 100644 index 0000000..2a1bec5 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java @@ -0,0 +1,58 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.shared.services.EmployeePermissionValidator; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +class TestCreateProjectUseCase { + + private ProjectRepository projectRepository; + private EmployeePermissionValidator employeePermissionValidator; + private CreateProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + employeePermissionValidator = mock(EmployeePermissionValidator.class); + useCase = new CreateProjectUseCase( + projectRepository, + employeePermissionValidator + ); + } + + @Test + void execute_success_createsProject() { + Project project = new Project(); + project.setId(1); + int employeeId = 10; + + when(projectRepository.create(project, employeeId)).thenReturn(project); + + Project result = useCase.execute(project, employeeId); + + verify(employeePermissionValidator).validate(employeeId); + verify(projectRepository).create(project, employeeId); + assertEquals(project, result); + } + + @Test + void execute_notPermitted_throwsException() { + Project project = new Project(); + int employeeId = 5; + + doThrow(new NotPermittedException("Not allowed")) + .when(employeePermissionValidator).validate(employeeId); + + assertThrows(NotPermittedException.class, () -> useCase.execute(project, employeeId)); + + verify(employeePermissionValidator).validate(employeeId); + verifyNoInteractions(projectRepository); + } +} + diff --git a/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java new file mode 100644 index 0000000..930814a --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java @@ -0,0 +1,90 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; + +class TestDeleteProjectUseCase { + + private ProjectRepository projectRepository; + private GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + private DeleteProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + getValidator = mock(GetValidator.class); + projectMembershipValidator = mock(ProjectMembershipValidator.class); + useCase = new DeleteProjectUseCase( + projectRepository, + getValidator, + projectMembershipValidator + ); + } + + @Test + void execute_success_deletesProject() throws Exception { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + + useCase.execute(employeeId, projectId); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + verify(projectRepository).delete(projectId); + } + + @Test + void execute_validatorThrows_exception() throws Exception { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new ProjectNotFoundException()); + + assertThrows( + ProjectNotFoundException.class, + () -> useCase.execute(employeeId, projectId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_membershipValidatorThrows_exception() throws Exception { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + doThrow( + new NotPermittedException("Not permitted") + ).when(projectMembershipValidator).validate(employee, project); + + assertThrows(NotPermittedException.class, () -> useCase.execute(employeeId, projectId)); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + verifyNoInteractions(projectRepository); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java new file mode 100644 index 0000000..36e22c0 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java @@ -0,0 +1,104 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class TestGetProjectUseCase { + private GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + private GetProjectUseCase useCase; + + @BeforeEach + void setup() { + getValidator = mock(GetValidator.class); + projectMembershipValidator = mock(ProjectMembershipValidator.class); + useCase = new GetProjectUseCase( + getValidator, + projectMembershipValidator + ); + } + + @Test + void execute_success_returnsProject() { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + + Project result = useCase.execute(projectId, employeeId); + + assertEquals(project, result); + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + } + + @Test + void execute_validatorThrowsProjectNotFoundException() { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new ProjectNotFoundException()); + + assertThrows( + ProjectNotFoundException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + } + + @Test + void execute_validatorThrowsUserNotFoundException() { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new UserNotFoundException()); + + assertThrows( + UserNotFoundException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + } + + @Test + void execute_membershipValidatorThrowsNotPermittedException() { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + doThrow( + new NotPermittedException("not permitted") + ).when(projectMembershipValidator).validate(employee, project); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java new file mode 100644 index 0000000..91f5c79 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java @@ -0,0 +1,111 @@ +package org.lab.application.project.use_cases; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.project.services.UserSpecFactory; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; +import org.lab.infra.db.spec.SqlSpec; + +public class TestListProjectUseCase { + + private ProjectRepository projectRepository; + private EmployeeProvider employeeProvider; + private UserSpecFactory userSpecFactory; + private ListProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + employeeProvider = mock(EmployeeProvider.class); + userSpecFactory = mock(UserSpecFactory.class); + useCase = new ListProjectUseCase( + projectRepository, + employeeProvider, + userSpecFactory + ); + } + + @Test + void execute_success_returnsProjects() { + int employeeId = 10; + Employee employee = new Employee(); + Specification spec = mock(SqlSpec.class); + Project project1 = new Project(); + Project project2 = new Project(); + List projects = List.of(project1, project2); + + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + when(projectRepository.list(spec)).thenReturn(projects); + + List result = useCase.execute(employeeId); + + assertEquals(projects, result); + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verify(projectRepository).list(spec); + } + + @Test + void execute_employeeProviderThrowsException_propagates() { + int employeeId = 10; + when(employeeProvider.get(employeeId)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verifyNoInteractions(userSpecFactory); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_userSpecFactoryThrowsException_propagates() { + int employeeId = 10; + Employee employee = new Employee(); + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_projectRepositoryThrowsException_propagates() { + int employeeId = 10; + Employee employee = new Employee(); + Specification spec = mock(SqlSpec.class); + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + when(projectRepository.list(spec)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verify(projectRepository).list(spec); + } +} diff --git a/src/test/java/org/lab/application/shared/services/DummySpec.java b/src/test/java/org/lab/application/shared/services/DummySpec.java new file mode 100644 index 0000000..b5e7bf1 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/DummySpec.java @@ -0,0 +1,18 @@ +package org.lab.application.shared.services; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.List; + +public final class DummySpec implements SqlSpec { + @Override + public String toSql() { + return "1=1"; + } + + @Override + public List getParams() { + return List.of(); + } +} + diff --git a/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java b/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java new file mode 100644 index 0000000..650ce0d --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java @@ -0,0 +1,56 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class TestEmployeePermissionValidator { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + + @BeforeEach + void setup() { + employeeRepository = mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + } + + @Test + void validate_manager_passes() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + when(employeeRepository.getById(1)).thenReturn(manager); + + validator.validate(1); + verify(employeeRepository).getById(1); + } + + @Test + void validate_nonManager_throwsException() { + Employee programmer = new Employee(); + programmer.setId(2); + programmer.setType(EmployeeType.PROGRAMMER); + + when(employeeRepository.getById(2)).thenReturn(programmer); + + assertThrows(NotPermittedException.class, () -> validator.validate(2)); + verify(employeeRepository).getById(2); + } + + @Test + void validate_nullEmployee_throwsException() { + when(employeeRepository.getById(3)).thenReturn(null); + + assertThrows(NullPointerException.class, () -> validator.validate(3)); + verify(employeeRepository).getById(3); + } +} + diff --git a/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java b/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java new file mode 100644 index 0000000..a50f9f5 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java @@ -0,0 +1,46 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +class TestEmployeeProvider { + + private EmployeeRepository repository; + private EmployeeProvider provider; + + @BeforeEach + void setup() { + repository = mock(EmployeeRepository.class); + provider = new EmployeeProvider(repository); + } + + @Test + void get_existingEmployee_returnsEmployee() throws UserNotFoundException { + int employeeId = 1; + Employee employee = new Employee(); + employee.setId(employeeId); + + when(repository.getById(employeeId)).thenReturn(employee); + + Employee result = provider.get(employeeId); + + assertEquals(employee, result); + verify(repository).getById(employeeId); + } + + @Test + void get_nonExistingEmployee_throwsException() { + int employeeId = 1; + + when(repository.getById(employeeId)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> provider.get(employeeId)); + verify(repository).getById(employeeId); + } +} diff --git a/src/test/java/org/lab/application/shared/services/TestProjectProvider.java b/src/test/java/org/lab/application/shared/services/TestProjectProvider.java new file mode 100644 index 0000000..52124bd --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestProjectProvider.java @@ -0,0 +1,50 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; + +class TestProjectProvider { + + private ProjectRepository repository; + private ProjectProvider provider; + + @BeforeEach + void setup() { + repository = mock(ProjectRepository.class); + provider = new ProjectProvider(repository); + } + + @Test + void get_existingEmployee_returnsEmployee() + throws + UserNotFoundException + { + int projectId = 1; + Project project = new Project(); + project.setId(projectId); + + when(repository.get(projectId)).thenReturn(project); + + Project result = provider.get(projectId); + + assertEquals(project, result); + verify(repository).get(projectId); + } + + @Test + void get_nonExistingProject_throwsException() { + int projectId = 1; + + when(repository.get(projectId)).thenReturn(null); + + assertThrows(ProjectNotFoundException.class, () -> provider.get(projectId)); + verify(repository).get(projectId); + } +} \ No newline at end of file diff --git a/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java b/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java new file mode 100644 index 0000000..f07e245 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java @@ -0,0 +1,52 @@ +package org.lab.application.shared.services; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + +class TestProjectSpecProvider { + + private ProjectRepository repository; + private ProjectSpecProvider provider; + private Specification spec; + + @BeforeEach + void setup() { + repository = mock(ProjectRepository.class); + provider = new ProjectSpecProvider(repository); + spec = mock(DummySpec.class); + } + + @Test + void get_existingProject_returnsProject() + throws + ProjectNotFoundException + { + int projectId = 1; + Project project = new Project(); + project.setId(projectId); + + when(repository.getWithSpec(projectId, spec)).thenReturn(project); + + Project result = provider.get(projectId, spec); + + assertEquals(project, result); + verify(repository).getWithSpec(projectId, spec); + } + + @Test + void get_nonExistingProject_throwsException() { + int projectId = 1; + + when(repository.getWithSpec(projectId, spec)).thenReturn(null); + + assertThrows(ProjectNotFoundException.class, () -> provider.get(projectId, spec)); + verify(repository).getWithSpec(projectId, spec); + } +} diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java new file mode 100644 index 0000000..943425f --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java @@ -0,0 +1,45 @@ +package org.lab.application.ticket.services; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; + +public class TestTicketCloseValidator { + + private TicketRepository ticketRepository; + private TicketCloseValidator validator; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + validator = new TicketCloseValidator(ticketRepository); + } + + @Test + void validate_existingTicket_passes() throws NotPermittedException { + Ticket ticket = new Ticket(); + ticket.setId(1); + ticket.setAssignedTo(10); + + when(ticketRepository.get(1, 10)).thenReturn(ticket); + + validator.validate(1, 10); + verify(ticketRepository).get(1, 10); + } + + @Test + void validate_nonExistingTicket_throwsException() { + when(ticketRepository.get(2, 10)).thenReturn(null); + + assertThrows( + NotPermittedException.class, + () -> validator.validate(2, 10) + ); + verify(ticketRepository).get(2, 10); + } +} diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java new file mode 100644 index 0000000..2be4d95 --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java @@ -0,0 +1,68 @@ +package org.lab.application.ticket.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.application.shared.services.ProjectProvider; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class TestTicketCreateValidator { + + private TicketPermissionValidator ticketPermissionValidator; + private ProjectProvider projectProvider; + private TicketCreateValidator validator; + + @BeforeEach + void setup() { + ticketPermissionValidator = mock(TicketPermissionValidator.class); + projectProvider = mock(ProjectProvider.class); + validator = new TicketCreateValidator(ticketPermissionValidator, projectProvider); + } + + @Test + void validate_success_noException() { + int employeeId = 10; + int projectId = 5; + + validator.validate(employeeId, projectId); + verify(ticketPermissionValidator).validate(employeeId); + verify(projectProvider).get(projectId); + } + + @Test + void validate_ticketPermissionThrows_exception() { + int employeeId = 10; + int projectId = 5; + + doThrow(new NotPermittedException("not allowed")) + .when(ticketPermissionValidator) + .validate(employeeId); + + assertThrows( + RuntimeException.class, + () -> validator.validate(employeeId, projectId) + ); + + verify(ticketPermissionValidator).validate(employeeId); + } + + @Test + void validate_projectProviderThrows_exception() { + int employeeId = 10; + int projectId = 5; + + doThrow(new RuntimeException("project not found")) + .when(projectProvider) + .get(projectId); + + assertThrows( + RuntimeException.class, + () -> validator.validate(employeeId, projectId) + ); + + verify(ticketPermissionValidator).validate(employeeId); + verify(projectProvider).get(projectId); + } +} diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java new file mode 100644 index 0000000..27a4acd --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java @@ -0,0 +1,56 @@ +package org.lab.application.ticket.services; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestTicketPermissionValidator { + + private EmployeeRepository employeeRepository; + private TicketPermissionValidator validator; + + @BeforeEach + void setup() { + employeeRepository = mock(EmployeeRepository.class); + validator = new TicketPermissionValidator(employeeRepository); + } + + @Test + void validate_managerAllowed() { + Employee manager = new Employee(); + manager.setType(EmployeeType.MANAGER); + when(employeeRepository.getById(1)).thenReturn(manager); + + validator.validate(1); + verify(employeeRepository).getById(1); + } + + @Test + void validate_teamLeadAllowed() { + Employee teamLead = new Employee(); + teamLead.setType(EmployeeType.TEAMLEAD); + when(employeeRepository.getById(2)).thenReturn(teamLead); + + validator.validate(2); + verify(employeeRepository).getById(2); + } + + @Test + void validate_notAllowed_throws() { + Employee tester = new Employee(); + tester.setType(EmployeeType.TESTER); + when(employeeRepository.getById(3)).thenReturn(tester); + + assertThrows( + NotPermittedException.class, + () -> validator.validate(3) + ); + verify(employeeRepository).getById(3); + } +} diff --git a/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java b/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java new file mode 100644 index 0000000..66dcd9a --- /dev/null +++ b/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java @@ -0,0 +1,54 @@ +package org.lab.application.ticket.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCloseValidator; + +class TestCloseTicketUseCase { + + private TicketRepository ticketRepository; + private TicketCloseValidator ticketCloseValidator; + private CloseTicketUseCase useCase; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + ticketCloseValidator = mock(TicketCloseValidator.class); + useCase = new CloseTicketUseCase(ticketRepository, ticketCloseValidator); + } + + @Test + void execute_success() { + Ticket ticket = new Ticket(); + ticket.setId(1); + when(ticketRepository.close(1)).thenReturn(ticket); + + Ticket result = useCase.execute(1, 10); + + verify(ticketCloseValidator).validate(1, 10); + verify(ticketRepository).close(1); + assertEquals(ticket, result); + } + + @Test + void execute_validatorThrows_notPermitted() { + doThrow( + new NotPermittedException("not permitted") + ).when(ticketCloseValidator).validate(1, 10); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(1, 10) + ); + + verify(ticketCloseValidator).validate(1, 10); + verify(ticketRepository, never()).close(anyInt()); + } +} diff --git a/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java b/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java new file mode 100644 index 0000000..51029fd --- /dev/null +++ b/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java @@ -0,0 +1,60 @@ +package org.lab.application.ticket.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCreateValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; + +class TestCreateTicketUseCase { + + private TicketRepository ticketRepository; + private TicketCreateValidator ticketCreateValidator; + private CreateTicketUseCase useCase; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + ticketCreateValidator = mock(TicketCreateValidator.class); + useCase = new CreateTicketUseCase(ticketRepository, ticketCreateValidator); + } + + @Test + void execute_success() { + Ticket ticket = new Ticket(); + Ticket createdTicket = new Ticket(); + createdTicket.setId(1); + + when(ticketRepository.create(ticket, 10, 5)) + .thenReturn(createdTicket); + + Ticket result = useCase.execute(ticket, 10, 5); + + verify(ticketCreateValidator).validate(10, 5); + verify(ticketRepository).create(ticket, 10, 5); + assertEquals(createdTicket, result); + } + + @Test + void execute_validatorThrows_notPermitted() { + Ticket ticket = new Ticket(); + doThrow( + new NotPermittedException("Not permitted") + ) + .when(ticketCreateValidator) + .validate(10, 5); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(ticket, 10, 5) + ); + + verify(ticketCreateValidator).validate(10, 5); + verify(ticketRepository, never()).create(any(), anyInt(), anyInt()); + } +} diff --git a/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java b/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java new file mode 100644 index 0000000..7b7b5d3 --- /dev/null +++ b/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java @@ -0,0 +1,77 @@ +package org.lab.core.utils.mapper; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.interfaces.DomainObject; +import org.lab.domain.interfaces.PresentationObject; + +public class TestObjectMapper { + private ObjectMapper mapper; + + interface TestDomain extends DomainObject {} + interface TestPresentation extends PresentationObject {} + + static class DomainImpl implements TestDomain { + public int id; + public String name; + + public DomainImpl() {} + public DomainImpl(int id, String name) { + this.id = id; + this.name = name; + } + } + + static class PresentationImpl implements TestPresentation { + public int id; + public String name; + + public PresentationImpl() {} + public PresentationImpl(int id, String name) { + this.id = id; + this.name = name; + } + } + + @BeforeEach + void setup() { + mapper = new ObjectMapper(); + } + + @Test + void mapToDomain_shouldConvertPresentationToDomain() { + PresentationImpl presentation = new PresentationImpl(1, "Alice"); + + DomainImpl domain = mapper.mapToDomain(presentation, DomainImpl.class); + + assertEquals(1, domain.id); + assertEquals("Alice", domain.name); + } + + @Test + void mapToPresentation_shouldConvertDomainToPresentation() { + DomainImpl domain = new DomainImpl(2, "Bob"); + + PresentationImpl presentation = mapper.mapToPresentation(domain, PresentationImpl.class); + + assertEquals(2, presentation.id); + assertEquals("Bob", presentation.name); + } + + @Test + void mapFromRaw_shouldConvertMapToDomain() { + Map raw = Map.of( + "id", 3, + "name", "Charlie" + ); + + DomainImpl domain = mapper.mapFromRaw(raw, DomainImpl.class); + + assertEquals(3, domain.id); + assertEquals("Charlie", domain.name); + } +} \ No newline at end of file diff --git a/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java b/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java new file mode 100644 index 0000000..a915ca2 --- /dev/null +++ b/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java @@ -0,0 +1,102 @@ +package org.lab.domain.project.services; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class TestProjectMembershipValidator { + + private ProjectMembershipValidator validator; + private Employee employee; + private Project project; + + @BeforeEach + void setup() { + validator = new ProjectMembershipValidator(); + + project = new Project(); + project.setManagerId(1); + project.setTeamLeadId(2); + project.setDeveloperIds(List.of(3, 4, 5)); + project.setTesterIds(List.of(6, 7)); + } + + @Test + void programmer_allowed() { + employee = new Employee(); + employee.setId(3); + employee.setType(EmployeeType.PROGRAMMER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void programmer_denied() { + employee = new Employee(); + employee.setId(99); + employee.setType(EmployeeType.PROGRAMMER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void tester_allowed() { + employee = new Employee(); + employee.setId(6); + employee.setType(EmployeeType.TESTER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void tester_denied() { + employee = new Employee(); + employee.setId(123); + employee.setType(EmployeeType.TESTER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void manager_allowed() { + employee = new Employee(); + employee.setId(1); + employee.setType(EmployeeType.MANAGER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void manager_denied() { + employee = new Employee(); + employee.setId(55); + employee.setType(EmployeeType.MANAGER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void teamlead_allowed() { + employee = new Employee(); + employee.setId(2); + employee.setType(EmployeeType.TEAMLEAD); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void teamlead_denied() { + employee = new Employee(); + employee.setId(666); + employee.setType(EmployeeType.TEAMLEAD); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } +}