From 77440fd6365b2a8e5fb6b4a47618c996fd072f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20K=C3=BCck?= Date: Mon, 21 Oct 2024 12:58:41 +0200 Subject: [PATCH 1/7] PMT-4: Define Endpoint --- api/pmt.yml | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/api/pmt.yml b/api/pmt.yml index 3b22c8e..e2318d9 100644 --- a/api/pmt.yml +++ b/api/pmt.yml @@ -68,6 +68,15 @@ components: plannedEnd: type: string format: date-time + AddEmployeeDTO: + type: object + properties: + employeeId: + type: integer + format: int64 + qualificationId: + type: integer + format: int64 responses: Unauthorized: description: "Unauthorized" @@ -147,6 +156,37 @@ paths: /project/{id}: + post: + operationId: "addEmployee" + description: "Adds an employee to a specific Project" + parameters: + - in: path + name: id + schema: + type: integer + format: int64 + required: true + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/AddEmployeeDTO" + responses: + 204: + description: "Employee successfully added to the specific Project" + 401: + $ref: "#/components/responses/Unauthorized" + 404: + $ref: "#/components/responses/NotFound" + 409: + $ref: "#/components/responses/Conflict" + 422: + $ref: "#/components/responses/UnprocessableContent" + 500: + $ref: "#/components/responses/InternalError" + 503: + $ref: "#/components/responses/ServiceUnavailable" + delete: operationId: "deleteProject" description: "Delete a specific Project" @@ -170,4 +210,3 @@ paths: type: string 500: $ref: "#/components/responses/InternalError" - -- 2.45.2 From 17e20ee8309d4d908b084178cfdd16dd46830904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=A4ume?= Date: Mon, 21 Oct 2024 13:04:25 +0200 Subject: [PATCH 2/7] PMT-4: Add Database Model for Allocations --- src/main/java/de/hmmh/pmt/db/Allocation.java | 34 +++++++++++++++++++ .../java/de/hmmh/pmt/db/AllocationId.java | 18 ++++++++++ src/main/resources/spotbugs-exclude.xml | 4 +++ 3 files changed, 56 insertions(+) create mode 100644 src/main/java/de/hmmh/pmt/db/Allocation.java create mode 100644 src/main/java/de/hmmh/pmt/db/AllocationId.java diff --git a/src/main/java/de/hmmh/pmt/db/Allocation.java b/src/main/java/de/hmmh/pmt/db/Allocation.java new file mode 100644 index 0000000..3f2ae9b --- /dev/null +++ b/src/main/java/de/hmmh/pmt/db/Allocation.java @@ -0,0 +1,34 @@ +package de.hmmh.pmt.db; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Entity +@IdClass(AllocationId.class) +@Table(name = "allocation") +public class Allocation { + + @Id + @Setter(AccessLevel.NONE) + private Long projectId; + + @ManyToOne + @JoinColumn(name = "allocation_project", referencedColumnName = "id", insertable = false, updatable = false) + private Project project; + + @Id + private Long employeeId; + + @NotNull + private Long role; // This is a QualificationId + + public void setProject(Project project) { + this.project = project; + this.projectId = project.getId(); + } +} diff --git a/src/main/java/de/hmmh/pmt/db/AllocationId.java b/src/main/java/de/hmmh/pmt/db/AllocationId.java new file mode 100644 index 0000000..355cfa9 --- /dev/null +++ b/src/main/java/de/hmmh/pmt/db/AllocationId.java @@ -0,0 +1,18 @@ +package de.hmmh.pmt.db; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AllocationId implements Serializable { + private static final long serialVersionUID = 1L; + private Long projectId; + private Long employeeId; +} diff --git a/src/main/resources/spotbugs-exclude.xml b/src/main/resources/spotbugs-exclude.xml index af92a63..6fab22b 100644 --- a/src/main/resources/spotbugs-exclude.xml +++ b/src/main/resources/spotbugs-exclude.xml @@ -4,6 +4,10 @@ + + + + -- 2.45.2 From d5a0483293d379d1247d047ce73888f3966dec48 Mon Sep 17 00:00:00 2001 From: Rajbir Singh Date: Mon, 21 Oct 2024 13:06:38 +0200 Subject: [PATCH 3/7] PMT-4: Implement Repository --- src/main/java/de/hmmh/pmt/db/AllocationRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/de/hmmh/pmt/db/AllocationRepository.java diff --git a/src/main/java/de/hmmh/pmt/db/AllocationRepository.java b/src/main/java/de/hmmh/pmt/db/AllocationRepository.java new file mode 100644 index 0000000..5d4bc07 --- /dev/null +++ b/src/main/java/de/hmmh/pmt/db/AllocationRepository.java @@ -0,0 +1,10 @@ +package de.hmmh.pmt.db; + +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface AllocationRepository extends CrudRepository { + + List findAllocationsByEmployeeId(Long employeeId); +} -- 2.45.2 From 954ad8237ca8b06d597ac66b3671f7b0d4d53445 Mon Sep 17 00:00:00 2001 From: Rajbir Singh Date: Mon, 21 Oct 2024 13:07:03 +0200 Subject: [PATCH 4/7] PMT-4: Implement Endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ole Kück --- src/main/java/de/hmmh/pmt/ApiController.java | 59 ++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/hmmh/pmt/ApiController.java b/src/main/java/de/hmmh/pmt/ApiController.java index 51799b5..36761f9 100644 --- a/src/main/java/de/hmmh/pmt/ApiController.java +++ b/src/main/java/de/hmmh/pmt/ApiController.java @@ -1,13 +1,13 @@ package de.hmmh.pmt; import com.fasterxml.jackson.databind.ObjectMapper; +import de.hmmh.pmt.db.Allocation; +import de.hmmh.pmt.db.AllocationRepository; import de.hmmh.pmt.db.Project; import de.hmmh.pmt.db.ProjectRepository; -import de.hmmh.pmt.dtos.CreateProjectDTO; -import de.hmmh.pmt.dtos.CreatedProjectDTO; -import de.hmmh.pmt.dtos.GetAllProjectsDTO; -import de.hmmh.pmt.dtos.ProjectInfo; +import de.hmmh.pmt.dtos.*; import de.hmmh.pmt.employee.ApiClientFactory; +import de.hmmh.pmt.employee.dtos.EmployeeResponseDTO; import de.hmmh.pmt.oas.DefaultApi; import de.hmmh.pmt.util.Mapper; import jakarta.servlet.http.HttpServletRequest; @@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClientException; +import java.time.ZoneOffset; +import java.util.List; import java.util.Optional; @Controller @@ -30,6 +32,8 @@ public class ApiController implements DefaultApi { private ApiClientFactory apiClientFactory; @Autowired private ProjectRepository projectRepository; + @Autowired + AllocationRepository allocationRepository; @Override public Optional getObjectMapper() { @@ -92,4 +96,51 @@ public class ApiController implements DefaultApi { CreatedProjectDTO response = mapper.map(project); return new ResponseEntity<>(response, HttpStatus.CREATED); } + + @Override + public ResponseEntity addEmployee(Long id, AddEmployeeDTO body) { + Optional optionalProject = projectRepository.findById(id); + if (optionalProject.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + Project project = optionalProject.get(); + + EmployeeResponseDTO employee; + try { + employee = apiClientFactory.getEmployeeApi().findById(body.getEmployeeId()); + } catch (HttpClientErrorException exception) { + return new ResponseEntity<>(exception.getStatusCode().equals(HttpStatus.NOT_FOUND) + ? HttpStatus.NOT_FOUND + : HttpStatus.SERVICE_UNAVAILABLE); + } catch (RestClientException exception) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + + if (employee.getSkillSet() + .stream() + .noneMatch(qualification -> qualification.getId().equals(body.getQualificationId()))) { + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + long start = project.getStart().toEpochSecond(ZoneOffset.UTC); + long plannedEnd = project.getPlannedEnd().toEpochSecond(ZoneOffset.UTC); + List allocations = allocationRepository.findAllocationsByEmployeeId(body.getEmployeeId()); + if (allocations.stream() + .map(Allocation::getProject) + .anyMatch(allocatedProject -> { + long allocatedStart = allocatedProject.getStart().toEpochSecond(null); + long allocatedPlannedEnd = allocatedProject.getPlannedEnd().toEpochSecond(null); + return Math.max(start, allocatedStart) <= Math.min(plannedEnd, allocatedPlannedEnd); + })) { + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + + Allocation allocation = new Allocation(); + allocation.setEmployeeId(employee.getId()); + allocation.setRole(body.getQualificationId()); + allocation.setProject(project); + allocationRepository.save(allocation); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } } -- 2.45.2 From 1b036ca6f0beda9d2e96720d87c7812870af65d2 Mon Sep 17 00:00:00 2001 From: Rajbir Singh Date: Mon, 21 Oct 2024 13:09:31 +0200 Subject: [PATCH 5/7] PMT-4: Write Happy Path Test --- .../de/hmmh/pmt/project/AddEmployeeTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java diff --git a/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java b/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java new file mode 100644 index 0000000..d9d9011 --- /dev/null +++ b/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java @@ -0,0 +1,52 @@ +package de.hmmh.pmt.project; + +import de.hmmh.pmt.IntegrationTest; +import de.hmmh.pmt.db.Project; +import de.hmmh.pmt.dtos.AddEmployeeDTO; +import de.hmmh.pmt.employee.dtos.EmployeeResponseDTO; +import de.hmmh.pmt.employee.dtos.QualificationGetDTO; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; + +public class AddEmployeeTest extends IntegrationTest { + + @Test + void addValidEmployee() throws Exception { + EmployeeResponseDTO employee= new EmployeeResponseDTO(); + employee.setId(2L); + employee.setSkillSet(List.of(newQualification(1L))); + when(mockEmployeeApi.findById(anyLong())) + .thenReturn(employee); + + Map allProjects = createTestProjectData(); + AddEmployeeDTO addEmployeeDTO = new AddEmployeeDTO(); + addEmployeeDTO.setEmployeeId(1L); + addEmployeeDTO.setQualificationId(1L); + RequestBuilder request = MockMvcRequestBuilders + .post(baseUri + "/project/" + allProjects.get("research-lab").getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(this.objectMapper.writeValueAsString(addEmployeeDTO)); + + this.mvc + .perform(request) + .andExpect(status().isNoContent()); + } + + + private static QualificationGetDTO newQualification(Long id){ + QualificationGetDTO qualificationGetDTO = new QualificationGetDTO(); + qualificationGetDTO.setId(id); + return qualificationGetDTO; + } + + +} -- 2.45.2 From 9009c7c79eb79e033aa1a77fa205b9376567d081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=A4ume?= Date: Mon, 21 Oct 2024 14:25:51 +0200 Subject: [PATCH 6/7] PMT-4: Fix Repository --- src/main/java/de/hmmh/pmt/ApiController.java | 6 +++--- src/main/java/de/hmmh/pmt/db/Allocation.java | 3 ++- src/main/java/de/hmmh/pmt/db/AllocationRepository.java | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hmmh/pmt/ApiController.java b/src/main/java/de/hmmh/pmt/ApiController.java index 36761f9..44205b5 100644 --- a/src/main/java/de/hmmh/pmt/ApiController.java +++ b/src/main/java/de/hmmh/pmt/ApiController.java @@ -124,12 +124,12 @@ public class ApiController implements DefaultApi { long start = project.getStart().toEpochSecond(ZoneOffset.UTC); long plannedEnd = project.getPlannedEnd().toEpochSecond(ZoneOffset.UTC); - List allocations = allocationRepository.findAllocationsByEmployeeId(body.getEmployeeId()); + List allocations = allocationRepository.findAllByEmployeeId(body.getEmployeeId()); if (allocations.stream() .map(Allocation::getProject) .anyMatch(allocatedProject -> { - long allocatedStart = allocatedProject.getStart().toEpochSecond(null); - long allocatedPlannedEnd = allocatedProject.getPlannedEnd().toEpochSecond(null); + long allocatedStart = allocatedProject.getStart().toEpochSecond(ZoneOffset.UTC); + long allocatedPlannedEnd = allocatedProject.getPlannedEnd().toEpochSecond(ZoneOffset.UTC); return Math.max(start, allocatedStart) <= Math.min(plannedEnd, allocatedPlannedEnd); })) { return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); diff --git a/src/main/java/de/hmmh/pmt/db/Allocation.java b/src/main/java/de/hmmh/pmt/db/Allocation.java index 3f2ae9b..78f41e8 100644 --- a/src/main/java/de/hmmh/pmt/db/Allocation.java +++ b/src/main/java/de/hmmh/pmt/db/Allocation.java @@ -14,11 +14,12 @@ import lombok.*; public class Allocation { @Id + @Column(name = "project_id") @Setter(AccessLevel.NONE) private Long projectId; @ManyToOne - @JoinColumn(name = "allocation_project", referencedColumnName = "id", insertable = false, updatable = false) + @JoinColumn(name = "project_id", referencedColumnName = "id", insertable = false, updatable = false) private Project project; @Id diff --git a/src/main/java/de/hmmh/pmt/db/AllocationRepository.java b/src/main/java/de/hmmh/pmt/db/AllocationRepository.java index 5d4bc07..dcbc9d3 100644 --- a/src/main/java/de/hmmh/pmt/db/AllocationRepository.java +++ b/src/main/java/de/hmmh/pmt/db/AllocationRepository.java @@ -1,10 +1,10 @@ package de.hmmh.pmt.db; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -public interface AllocationRepository extends CrudRepository { +public interface AllocationRepository extends JpaRepository { - List findAllocationsByEmployeeId(Long employeeId); + List findAllByEmployeeId(Long employeeId); } -- 2.45.2 From 32d895c47e26c55edb26e05fcc657df8a127ff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20S=C3=A4ume?= Date: Mon, 21 Oct 2024 14:37:34 +0200 Subject: [PATCH 7/7] PMT-4: Add Bad Path Tests --- .../java/de/hmmh/pmt/IntegrationTest.java | 74 +++++++++- .../de/hmmh/pmt/project/AddEmployeeTest.java | 139 ++++++++++++++++-- 2 files changed, 192 insertions(+), 21 deletions(-) diff --git a/src/test/java/de/hmmh/pmt/IntegrationTest.java b/src/test/java/de/hmmh/pmt/IntegrationTest.java index eb59826..91852a4 100644 --- a/src/test/java/de/hmmh/pmt/IntegrationTest.java +++ b/src/test/java/de/hmmh/pmt/IntegrationTest.java @@ -1,6 +1,8 @@ package de.hmmh.pmt; import com.fasterxml.jackson.databind.ObjectMapper; +import de.hmmh.pmt.db.Allocation; +import de.hmmh.pmt.db.AllocationRepository; import de.hmmh.pmt.db.Project; import de.hmmh.pmt.db.ProjectRepository; import de.hmmh.pmt.employee.api.EmployeeControllerApi; @@ -24,13 +26,19 @@ import java.util.Map; public abstract class IntegrationTest { protected final static String baseUri = "/api/v1"; + protected final static long TEST_EMPLOYEE_A_ID = 1L; + protected final static long TEST_QUALIFICATION_A_ID = 10L; + protected final static long TEST_QUALIFICATION_B_ID = 11L; @Autowired protected MockMvc mvc; @Autowired protected ObjectMapper objectMapper; + @Autowired protected ProjectRepository projectRepository; + @Autowired + protected AllocationRepository allocationRepository; @MockBean protected EmployeeControllerApi mockEmployeeApi; @@ -39,11 +47,13 @@ public abstract class IntegrationTest { @BeforeEach void setUp() { + allocationRepository.deleteAll(); projectRepository.deleteAll(); } @AfterEach void cleanUp() { + allocationRepository.deleteAll(); projectRepository.deleteAll(); } @@ -54,7 +64,7 @@ public abstract class IntegrationTest { researchLabProject.setName("Underwater Research Lab"); researchLabProject.setGoal( "Discover new marine species!"); researchLabProject.setCustomerId(73L); - researchLabProject.setAdministratorId(202L); + researchLabProject.setAdministratorId(TEST_EMPLOYEE_A_ID); researchLabProject.setStart(LocalDateTime.of(2025, 5, 22, 8, 45)); researchLabProject.setPlannedEnd(LocalDateTime.of(2026, 5, 22, 8, 45)); projects.put("research-lab", researchLabProject); @@ -63,7 +73,7 @@ public abstract class IntegrationTest { spaceStationProject.setName("International Space Station Expansion"); spaceStationProject.setGoal("Build new modules for extended research."); spaceStationProject.setCustomerId(85L); - spaceStationProject.setAdministratorId(150L); + spaceStationProject.setAdministratorId(TEST_EMPLOYEE_A_ID); spaceStationProject.setStart(LocalDateTime.of(2024, 10, 15, 10, 0)); spaceStationProject.setPlannedEnd(LocalDateTime.of(2025, 12, 15, 10, 0)); projects.put("space-station", spaceStationProject); @@ -72,7 +82,7 @@ public abstract class IntegrationTest { aiResearchProject.setName("AI Human Interaction Study"); aiResearchProject.setGoal("Study AI interactions in healthcare."); aiResearchProject.setCustomerId(63L); - aiResearchProject.setAdministratorId(180L); + aiResearchProject.setAdministratorId(TEST_EMPLOYEE_A_ID); aiResearchProject.setStart(LocalDateTime.of(2023, 11, 3, 9, 30)); aiResearchProject.setPlannedEnd(LocalDateTime.of(2024, 6, 3, 9, 30)); projects.put("ai-research", aiResearchProject); @@ -81,7 +91,7 @@ public abstract class IntegrationTest { renewableEnergyProject.setName("Renewable Energy Storage System"); renewableEnergyProject.setGoal("Develop new battery tech for solar power."); renewableEnergyProject.setCustomerId(90L); - renewableEnergyProject.setAdministratorId(175L); + renewableEnergyProject.setAdministratorId(TEST_EMPLOYEE_A_ID); renewableEnergyProject.setStart(LocalDateTime.of(2024, 1, 10, 11, 0)); renewableEnergyProject.setPlannedEnd(LocalDateTime.of(2025, 1, 10, 11, 0)); projects.put("renewable-energy", renewableEnergyProject); @@ -90,22 +100,72 @@ public abstract class IntegrationTest { climateChangeProject.setName("Climate Change Impact Analysis"); climateChangeProject.setGoal("Study global warming effects on ecosystems."); climateChangeProject.setCustomerId(72L); - climateChangeProject.setAdministratorId(190L); + climateChangeProject.setAdministratorId(TEST_EMPLOYEE_A_ID); climateChangeProject.setStart(LocalDateTime.of(2025, 3, 12, 8, 0)); climateChangeProject.setPlannedEnd(LocalDateTime.of(2026, 3, 12, 8, 0)); projects.put("climate change", climateChangeProject); - Project medicalResearchProject = new Project(); medicalResearchProject.setName("Cancer Treatment Innovation"); medicalResearchProject.setGoal("Develop new gene therapy techniques."); medicalResearchProject.setCustomerId(95L); - medicalResearchProject.setAdministratorId(155L); + medicalResearchProject.setAdministratorId(TEST_EMPLOYEE_A_ID); medicalResearchProject.setStart(LocalDateTime.of(2024, 8, 20, 9, 15)); medicalResearchProject.setPlannedEnd(LocalDateTime.of(2026, 8, 20, 9, 15)); projects.put("medical-research", medicalResearchProject); + Project overlappingProjectA = new Project(); + overlappingProjectA.setName("Overlap A"); + overlappingProjectA.setGoal("A Project That Overlaps with another one for Testing"); + overlappingProjectA.setCustomerId(19L); + overlappingProjectA.setAdministratorId(TEST_EMPLOYEE_A_ID); + overlappingProjectA.setStart(LocalDateTime.of(1970, 6, 20, 9, 15)); + overlappingProjectA.setPlannedEnd(LocalDateTime.of(2025, 8, 20, 9, 15)); + projects.put("overlap-a", overlappingProjectA); + + Project overlappingProjectB = new Project(); + overlappingProjectB.setName("Overlap B"); + overlappingProjectB.setGoal("B Project That Overlaps with another one for Testing"); + overlappingProjectB.setCustomerId(23L); + overlappingProjectB.setAdministratorId(TEST_EMPLOYEE_A_ID); + overlappingProjectB.setStart(LocalDateTime.of(2024, 7, 20, 9, 15)); + overlappingProjectB.setPlannedEnd(LocalDateTime.of(2026, 12, 20, 9, 15)); + projects.put("overlap-b", overlappingProjectB); + + Project overlappingProjectC = new Project(); + overlappingProjectC.setName("Overlap C"); + overlappingProjectC.setGoal("C Project That Overlaps with another one for Testing"); + overlappingProjectC.setCustomerId(19L); + overlappingProjectC.setAdministratorId(TEST_EMPLOYEE_A_ID); + overlappingProjectC.setStart(LocalDateTime.of(2024, 6, 20, 9, 15)); + overlappingProjectC.setPlannedEnd(LocalDateTime.of(2026, 8, 20, 9, 15)); + projects.put("overlap-c", overlappingProjectC); + + + Project nonOverlappingProject = new Project(); + nonOverlappingProject.setName("Non Overlapping to Overlap a-c"); + nonOverlappingProject.setGoal("Project That doesnt overlap with another one of the Overlap ones for Testing"); + nonOverlappingProject.setCustomerId(19L); + nonOverlappingProject.setAdministratorId(TEST_EMPLOYEE_A_ID); + nonOverlappingProject.setStart(LocalDateTime.of(1970, 1, 20, 9, 15)); + nonOverlappingProject.setPlannedEnd(LocalDateTime.of(1970, 2, 20, 9, 15)); + projects.put("non-overlap", nonOverlappingProject); + projectRepository.saveAllAndFlush(projects.values()); return projects; } + + + protected Map createTestAllocationData(Map allProjects) { + Map allocations = new HashMap<>(); + + Allocation allocation1ToOverlapA = new Allocation(); + allocation1ToOverlapA.setProject(allProjects.get("overlap-a")); + allocation1ToOverlapA.setEmployeeId(TEST_EMPLOYEE_A_ID); + allocation1ToOverlapA.setRole(TEST_QUALIFICATION_A_ID); + allocations.put("1>overlap-a", allocation1ToOverlapA); + + allocationRepository.saveAllAndFlush(allocations.values()); + return allocations; + } } diff --git a/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java b/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java index d9d9011..f557441 100644 --- a/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java +++ b/src/test/java/de/hmmh/pmt/project/AddEmployeeTest.java @@ -1,52 +1,163 @@ package de.hmmh.pmt.project; import de.hmmh.pmt.IntegrationTest; +import de.hmmh.pmt.db.Allocation; import de.hmmh.pmt.db.Project; import de.hmmh.pmt.dtos.AddEmployeeDTO; import de.hmmh.pmt.employee.dtos.EmployeeResponseDTO; import de.hmmh.pmt.employee.dtos.QualificationGetDTO; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; import java.util.List; import java.util.Map; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class AddEmployeeTest extends IntegrationTest { @Test void addValidEmployee() throws Exception { - EmployeeResponseDTO employee= new EmployeeResponseDTO(); - employee.setId(2L); - employee.setSkillSet(List.of(newQualification(1L))); when(mockEmployeeApi.findById(anyLong())) - .thenReturn(employee); + .thenReturn(getEmployee()); Map allProjects = createTestProjectData(); - AddEmployeeDTO addEmployeeDTO = new AddEmployeeDTO(); - addEmployeeDTO.setEmployeeId(1L); - addEmployeeDTO.setQualificationId(1L); - RequestBuilder request = MockMvcRequestBuilders - .post(baseUri + "/project/" + allProjects.get("research-lab").getId()) - .contentType(MediaType.APPLICATION_JSON) - .content(this.objectMapper.writeValueAsString(addEmployeeDTO)); this.mvc - .perform(request) + .perform(getRequest(allProjects.get("research-lab").getId(), getAddEmployeeDTO())) .andExpect(status().isNoContent()); } + @Test + void shouldNotAddEmployeeWhenProjectIsNotFound() throws Exception { + this.mvc + .perform(getRequest(0L, getAddEmployeeDTO())) + .andExpect(status().isNotFound()); + } - private static QualificationGetDTO newQualification(Long id){ + @Test + void shouldNotAddEmployeeWhenEmployeeDoesNotExist() throws Exception { + when(this.mockEmployeeApi.findById(Mockito.anyLong())) + .thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)); + + Map allProjects = createTestProjectData(); + + this.mvc + .perform(getRequest(allProjects.get("research-lab").getId(), getAddEmployeeDTO())) + .andExpect(status().isNotFound()); + } + + @Test + void shouldReturnUnavailableWhenEmployeeApiIsDown() throws Exception { + when(this.mockEmployeeApi.findById(Mockito.anyLong())) + .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR)); + + Map allProjects = createTestProjectData(); + + this.mvc + .perform(getRequest(allProjects.get("research-lab").getId(), getAddEmployeeDTO())) + .andExpect(status().isServiceUnavailable()); + } + + @Test + void shouldReturnInternalServerErrorOnApiClientCrash() throws Exception { + when(this.mockEmployeeApi.findById(Mockito.anyLong())) + .thenThrow(new RestClientException("Api Client crash")); + + Map allProjects = createTestProjectData(); + + this.mvc + .perform(getRequest(allProjects.get("research-lab").getId(), getAddEmployeeDTO())) + .andExpect(status().isInternalServerError()); + } + + @Test + void shouldReturnUnprocessableWhenEmployeeDoesNotHaveTheQualification() throws Exception { + when(mockEmployeeApi.findById(anyLong())) + .thenReturn(getEmployee()); + + Map allProjects = createTestProjectData(); + + AddEmployeeDTO addEmployeeDTO = getAddEmployeeDTO(); + addEmployeeDTO.setQualificationId(TEST_QUALIFICATION_B_ID); + + this.mvc + .perform(getRequest(allProjects.get("research-lab").getId(), addEmployeeDTO)) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + void shouldReturnUnprocessableWhenEmployeesProjectTimeRangesOverlapsBackwards() throws Exception { + when(mockEmployeeApi.findById(anyLong())) + .thenReturn(getEmployee()); + + Map allProjects = createTestProjectData(); + createTestAllocationData(allProjects); + + this.mvc + .perform(getRequest(allProjects.get("overlap-b").getId(), getAddEmployeeDTO())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + void shouldReturnUnprocessableWhenEmployeesProjectTimeRangesOverlapsForwards() throws Exception { + when(mockEmployeeApi.findById(anyLong())) + .thenReturn(getEmployee()); + + Map allProjects = createTestProjectData(); + createTestAllocationData(allProjects); + + this.mvc + .perform(getRequest(allProjects.get("overlap-c").getId(), getAddEmployeeDTO())) + .andExpect(status().isUnprocessableEntity()); + } + + @Test + void shouldNotReturnUnprocessableWhenAllocationsDontOverlap() throws Exception { + when(mockEmployeeApi.findById(anyLong())) + .thenReturn(getEmployee()); + + Map allProjects = createTestProjectData(); + createTestAllocationData(allProjects); + + this.mvc + .perform(getRequest(allProjects.get("non-overlap").getId(), getAddEmployeeDTO())) + .andExpect(status().isNoContent()); + } + + private QualificationGetDTO newQualification(Long id) { QualificationGetDTO qualificationGetDTO = new QualificationGetDTO(); qualificationGetDTO.setId(id); return qualificationGetDTO; } + private EmployeeResponseDTO getEmployee() { + EmployeeResponseDTO employee = new EmployeeResponseDTO(); + employee.setId(TEST_EMPLOYEE_A_ID); + employee.setSkillSet(List.of(newQualification(TEST_QUALIFICATION_A_ID))); + return employee; + } + + private AddEmployeeDTO getAddEmployeeDTO() { + AddEmployeeDTO addEmployeeDTO = new AddEmployeeDTO(); + addEmployeeDTO.setEmployeeId(TEST_EMPLOYEE_A_ID); + addEmployeeDTO.setQualificationId(TEST_QUALIFICATION_A_ID); + return addEmployeeDTO; + } + + private RequestBuilder getRequest(Long projectId, AddEmployeeDTO addEmployeeDTO) throws Exception { + return MockMvcRequestBuilders + .post(baseUri + "/project/" + projectId) + .content(this.objectMapper.writeValueAsString(addEmployeeDTO)) + .contentType(MediaType.APPLICATION_JSON); + } } -- 2.45.2