diff --git a/.run/Test.run.xml b/.run/Test.run.xml index e33b35f..667c096 100644 --- a/.run/Test.run.xml +++ b/.run/Test.run.xml @@ -1,24 +1,12 @@ - - - - - - true - true - false - false - + + + \ No newline at end of file diff --git a/api/pmt.yml b/api/pmt.yml index 4640111..ba7ebd1 100644 --- a/api/pmt.yml +++ b/api/pmt.yml @@ -30,16 +30,16 @@ components: CreateProjectDTO: type: object properties: + name: + type: string + goal: + type: string customerId: type: integer format: int64 administratorId: type: integer format: int64 - title: - type: string - goal: - type: string start: type: string format: date-time @@ -52,16 +52,16 @@ components: id: type: integer format: int64 + name: + type: string + goal: + type: string customerId: type: integer format: int64 administratorId: type: integer format: int64 - title: - type: string - goal: - type: string start: type: string format: date-time @@ -69,14 +69,8 @@ components: type: string format: date-time responses: - UnAuthorized: - description: "Un Authorized" - InternalError: - description: "Internal Server Error" - content: - text/plain: - schema: - type: string + Unauthorized: + description: "Unauthorized" NotFound: description: "Not Found" content: @@ -89,6 +83,24 @@ components: text/plain: schema: type: string + UnprocessableContent: + description: "Unprocessable Content" + content: + text/plain: + schema: + type: string + InternalError: + description: "Internal Server Error" + content: + text/plain: + schema: + type: string + ServiceUnavailable: + description: "Service Unavailable" + content: + text/plain: + schema: + type: string paths: /project: get: @@ -102,7 +114,7 @@ paths: schema: $ref: "#/components/schemas/GetAllProjectsDTO" 401: - $ref: "#/components/responses/UnAuthorized" + $ref: "#/components/responses/Unauthorized" 500: $ref: "#/components/responses/InternalError" post: @@ -121,10 +133,39 @@ paths: schema: $ref: "#/components/schemas/CreatedProjectDTO" 401: - $ref: "#/components/responses/UnAuthorized" + $ref: "#/components/responses/Unauthorized" 404: $ref: "#/components/responses/NotFound" 409: - $ref: "#/components/responses/NotFound" + $ref: "#/components/responses/Conflict" + 422: + $ref: "#/components/responses/UnprocessableContent" 500: - $ref: "#/components/responses/InternalError" \ No newline at end of file + $ref: "#/components/responses/InternalError" + 503: + $ref: "#/components/responses/ServiceUnavailable" + /project/{id}: + delete: + operationId: "deleteProject" + description: "Delete a specific Project" + parameters: + - in: path + name: id + schema: + type: integer + format: int64 + required: true + responses: + 204: + description: "Deletes a specific Project" + 401: + $ref: "#/components/responses/Unauthorized" + 404: + description: "Project not found" + content: + text/plain: + schema: + type: string + 500: + $ref: "#/components/responses/InternalError" + diff --git a/gen/config-pmt.json b/gen/config-pmt.json index c2d6f24..80ab789 100644 --- a/gen/config-pmt.json +++ b/gen/config-pmt.json @@ -4,7 +4,7 @@ "invokerPackage": "de.hmmh.pmt", "java8": false, "java11": true, - "dateLibrary": "java11", + "dateLibrary": "java8-localdatetime", "library": "spring-boot3", "defaultInterfaces": false, "serializableModel": true diff --git a/src/main/java/de/hmmh/pmt/ApiController.java b/src/main/java/de/hmmh/pmt/ApiController.java index b28479e..51799b5 100644 --- a/src/main/java/de/hmmh/pmt/ApiController.java +++ b/src/main/java/de/hmmh/pmt/ApiController.java @@ -1,20 +1,22 @@ package de.hmmh.pmt; import com.fasterxml.jackson.databind.ObjectMapper; -import de.hmmh.pmt.dtos.CreateProjectDTO; -import de.hmmh.pmt.dtos.CreatedProjectDTO; -import de.hmmh.pmt.employee.ApiClientFactory; import de.hmmh.pmt.db.Project; import de.hmmh.pmt.db.ProjectRepository; -import de.hmmh.pmt.oas.DefaultApi; +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.employee.ApiClientFactory; +import de.hmmh.pmt.oas.DefaultApi; +import de.hmmh.pmt.util.Mapper; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClientException; import java.util.Optional; @@ -22,6 +24,8 @@ import java.util.Optional; @Controller @RequestMapping("${openapi.projectManagement.base-path:/api/v1}") public class ApiController implements DefaultApi { + @Autowired + private Mapper mapper; @Autowired private ApiClientFactory apiClientFactory; @Autowired @@ -37,11 +41,21 @@ public class ApiController implements DefaultApi { return Optional.empty(); } + @Override + public ResponseEntity deleteProject(Long id) { + if (!projectRepository.existsById(id)) { + return ResponseEntity.notFound().build(); + } + + projectRepository.deleteById(id); + return ResponseEntity.noContent().build(); + } + @Override public ResponseEntity getAllProjects() { GetAllProjectsDTO response = new GetAllProjectsDTO(); - for (Project project : this.projectRepository.findAll()){ + for (Project project : this.projectRepository.findAll()) { ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setId(project.getId()); projectInfo.setName(project.getName()); @@ -53,17 +67,29 @@ public class ApiController implements DefaultApi { @Override public ResponseEntity createProject(CreateProjectDTO body) { - if (projectRepository.existsByName(body.getTitle())){ + if (projectRepository.existsByName(body.getName())) { return new ResponseEntity<>(HttpStatus.CONFLICT); } - try { - if (apiClientFactory.getEmployeeApi().findById(body.getAdministratorId()).getId() == body.getAdministratorId()){ - } - } catch (RestClientException exception){ - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } + try { + apiClientFactory.getEmployeeApi().findById(body.getAdministratorId()); + } 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); + } - return null; + Project project = mapper.map(body); + if (!project.isValid()) { + return new ResponseEntity<>(HttpStatus.UNPROCESSABLE_ENTITY); + } + projectRepository.save(project); + + CreatedProjectDTO response = mapper.map(project); + return new ResponseEntity<>(response, HttpStatus.CREATED); } } diff --git a/src/main/java/de/hmmh/pmt/db/Project.java b/src/main/java/de/hmmh/pmt/db/Project.java index 9c9891f..0a70189 100644 --- a/src/main/java/de/hmmh/pmt/db/Project.java +++ b/src/main/java/de/hmmh/pmt/db/Project.java @@ -1,6 +1,9 @@ package de.hmmh.pmt.db; import jakarta.persistence.*; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -10,6 +13,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDateTime; +import java.util.Set; @NoArgsConstructor @AllArgsConstructor @@ -19,7 +23,7 @@ import java.time.LocalDateTime; @Table(name = "project") public class Project { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @@ -43,5 +47,16 @@ public class Project { private LocalDateTime plannedEnd; private LocalDateTime realEnd; // Cant be named just "end" because it's and SQL Keyword + + + public boolean isValid() { + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Set> violations = validator.validate(this); + + return violations.isEmpty() && + plannedEnd.isAfter(start) && + (realEnd == null || realEnd.isAfter(start)); + } + } diff --git a/src/main/java/de/hmmh/pmt/util/Mapper.java b/src/main/java/de/hmmh/pmt/util/Mapper.java new file mode 100644 index 0000000..d5d7591 --- /dev/null +++ b/src/main/java/de/hmmh/pmt/util/Mapper.java @@ -0,0 +1,32 @@ +package de.hmmh.pmt.util; + +import de.hmmh.pmt.db.Project; +import de.hmmh.pmt.dtos.CreateProjectDTO; +import de.hmmh.pmt.dtos.CreatedProjectDTO; +import org.springframework.stereotype.Component; + +@Component +public class Mapper { + public Project map(CreateProjectDTO dto) { + Project project = new Project(); + project.setName(dto.getName()); + project.setGoal(dto.getGoal()); + project.setCustomerId(dto.getCustomerId()); + project.setAdministratorId(dto.getAdministratorId()); + project.setStart(dto.getStart()); + project.setPlannedEnd(dto.getPlannedEnd()); + return project; + } + + public CreatedProjectDTO map(Project project) { + CreatedProjectDTO dto = new CreatedProjectDTO(); + dto.setId(project.getId()); + dto.setName(project.getName()); + dto.setGoal(project.getGoal()); + dto.setCustomerId(project.getCustomerId()); + dto.setAdministratorId(project.getAdministratorId()); + dto.setStart(project.getStart()); + dto.setPlannedEnd(project.getPlannedEnd()); + return dto; + } +} diff --git a/src/test/java/de/hmmh/pmt/IntegrationTest.java b/src/test/java/de/hmmh/pmt/IntegrationTest.java index 1bc1a0c..eb59826 100644 --- a/src/test/java/de/hmmh/pmt/IntegrationTest.java +++ b/src/test/java/de/hmmh/pmt/IntegrationTest.java @@ -1,17 +1,22 @@ package de.hmmh.pmt; +import com.fasterxml.jackson.databind.ObjectMapper; import de.hmmh.pmt.db.Project; import de.hmmh.pmt.db.ProjectRepository; +import de.hmmh.pmt.employee.api.EmployeeControllerApi; +import de.hmmh.pmt.employee.api.QualificationControllerApi; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDateTime; -import java.util.List; +import java.util.HashMap; +import java.util.Map; @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @@ -23,8 +28,15 @@ public abstract class IntegrationTest { @Autowired protected MockMvc mvc; @Autowired + protected ObjectMapper objectMapper; + @Autowired protected ProjectRepository projectRepository; + @MockBean + protected EmployeeControllerApi mockEmployeeApi; + @MockBean + protected QualificationControllerApi mockQualificationApi; + @BeforeEach void setUp() { projectRepository.deleteAll(); @@ -35,70 +47,65 @@ public abstract class IntegrationTest { projectRepository.deleteAll(); } - protected List createTestProjectData() { - Project project1 = new Project( - 1L, - "Build a Dream Space Station", - "Launch a self-sustaining space habitat!", - 42L, - 101L, - LocalDateTime.of(2024, 3, 1, 10, 0), - LocalDateTime.of(2028, 6, 30, 18, 0), - LocalDateTime.of(2029, 12, 15, 16, 30) - ); - Project project2 = new Project( - 2L, - "Underwater Research Lab", - "Discover new marine species!", - 73L, - 202L, - LocalDateTime.of(2025, 5, 22, 8, 45), - LocalDateTime.of(2027, 11, 5, 17, 0), - LocalDateTime.of(2027, 10, 20, 14, 0) - ); - Project project3 = new Project( - 3L, - "AI-Powered Smart City", - "Create the world's most advanced smart city!", - 89L, - 303L, - LocalDateTime.of(2026, 9, 14, 12, 0), - LocalDateTime.of(2030, 4, 1, 9, 30), - LocalDateTime.of(2030, 5, 2, 15, 0) - ); - Project project4 = new Project( - 4L, - "Renewable Energy Revolution", - "Replace all fossil fuels with renewables!", - 56L, - 404L, - LocalDateTime.of(2023, 7, 19, 11, 30), - LocalDateTime.of(2029, 12, 31, 20, 0), - LocalDateTime.of(2029, 10, 5, 18, 45) - ); - Project project5 = new Project( - 5L, - "Virtual Reality Theme Park", - "Build a fully immersive VR theme park!", - 99L, - 505L, - LocalDateTime.of(2024, 2, 28, 9, 15), - LocalDateTime.of(2026, 9, 30, 17, 0), - LocalDateTime.of(2026, 8, 15, 13, 45) - ); + protected Map createTestProjectData() { + Map projects = new HashMap<>(); + + Project researchLabProject = new Project(); + researchLabProject.setName("Underwater Research Lab"); + researchLabProject.setGoal( "Discover new marine species!"); + researchLabProject.setCustomerId(73L); + researchLabProject.setAdministratorId(202L); + researchLabProject.setStart(LocalDateTime.of(2025, 5, 22, 8, 45)); + researchLabProject.setPlannedEnd(LocalDateTime.of(2026, 5, 22, 8, 45)); + projects.put("research-lab", researchLabProject); + + Project spaceStationProject = new Project(); + spaceStationProject.setName("International Space Station Expansion"); + spaceStationProject.setGoal("Build new modules for extended research."); + spaceStationProject.setCustomerId(85L); + spaceStationProject.setAdministratorId(150L); + spaceStationProject.setStart(LocalDateTime.of(2024, 10, 15, 10, 0)); + spaceStationProject.setPlannedEnd(LocalDateTime.of(2025, 12, 15, 10, 0)); + projects.put("space-station", spaceStationProject); + + Project aiResearchProject = new Project(); + aiResearchProject.setName("AI Human Interaction Study"); + aiResearchProject.setGoal("Study AI interactions in healthcare."); + aiResearchProject.setCustomerId(63L); + aiResearchProject.setAdministratorId(180L); + aiResearchProject.setStart(LocalDateTime.of(2023, 11, 3, 9, 30)); + aiResearchProject.setPlannedEnd(LocalDateTime.of(2024, 6, 3, 9, 30)); + projects.put("ai-research", aiResearchProject); + + Project renewableEnergyProject = new Project(); + renewableEnergyProject.setName("Renewable Energy Storage System"); + renewableEnergyProject.setGoal("Develop new battery tech for solar power."); + renewableEnergyProject.setCustomerId(90L); + renewableEnergyProject.setAdministratorId(175L); + renewableEnergyProject.setStart(LocalDateTime.of(2024, 1, 10, 11, 0)); + renewableEnergyProject.setPlannedEnd(LocalDateTime.of(2025, 1, 10, 11, 0)); + projects.put("renewable-energy", renewableEnergyProject); + + Project climateChangeProject = new Project(); + climateChangeProject.setName("Climate Change Impact Analysis"); + climateChangeProject.setGoal("Study global warming effects on ecosystems."); + climateChangeProject.setCustomerId(72L); + climateChangeProject.setAdministratorId(190L); + 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.setStart(LocalDateTime.of(2024, 8, 20, 9, 15)); + medicalResearchProject.setPlannedEnd(LocalDateTime.of(2026, 8, 20, 9, 15)); + projects.put("medical-research", medicalResearchProject); - projectRepository.save(project1); - projectRepository.save(project2); - projectRepository.save(project3); - projectRepository.save(project4); - projectRepository.save(project5); - - return List.of( - project1, - project2, - project3, - project4, - project5 - ); + projectRepository.saveAllAndFlush(projects.values()); + return projects; } } diff --git a/src/test/java/de/hmmh/pmt/project/CreateTest.java b/src/test/java/de/hmmh/pmt/project/CreateTest.java new file mode 100644 index 0000000..2f62f0b --- /dev/null +++ b/src/test/java/de/hmmh/pmt/project/CreateTest.java @@ -0,0 +1,44 @@ +package de.hmmh.pmt.project; + +import de.hmmh.pmt.IntegrationTest; +import de.hmmh.pmt.dtos.CreateProjectDTO; +import de.hmmh.pmt.employee.dtos.EmployeeResponseDTO; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.time.LocalDateTime; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class CreateTest extends IntegrationTest { + + @Test + void successfullyCreate() throws Exception { + Mockito + .when(this.mockEmployeeApi.findById(Mockito.anyLong())) + .thenReturn(new EmployeeResponseDTO()); + + CreateProjectDTO createDTO = new CreateProjectDTO(); + createDTO.setName("Test"); + createDTO.setGoal("A Test Goal"); + createDTO.setCustomerId(10L); + createDTO.setAdministratorId(10L); + createDTO.setStart(LocalDateTime.of(2000, 1, 13, 12, 51)); + createDTO.setPlannedEnd(LocalDateTime.of(2002, 3, 21, 11, 42)); + + RequestBuilder requestBuilder = MockMvcRequestBuilders + .post(baseUri + "/project") + .accept(MediaType.APPLICATION_JSON) + .content(this.objectMapper.writeValueAsString(createDTO)) + .contentType(MediaType.APPLICATION_JSON); + + this.mvc + .perform(requestBuilder) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").exists()); + } +} diff --git a/src/test/java/de/hmmh/pmt/project/DeleteTest.java b/src/test/java/de/hmmh/pmt/project/DeleteTest.java new file mode 100644 index 0000000..25a95ba --- /dev/null +++ b/src/test/java/de/hmmh/pmt/project/DeleteTest.java @@ -0,0 +1,31 @@ +package de.hmmh.pmt.project; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import de.hmmh.pmt.IntegrationTest; +import de.hmmh.pmt.db.Project; + +public class DeleteTest extends IntegrationTest { + @Test + void projectNotFound() throws Exception { + mvc + .perform(delete(baseUri + "/project/1")) + .andExpect(status().isNotFound()) + ; + } + + @Test + void deletedSuccessfully() throws Exception { + Map allProjects = createTestProjectData(); + mvc + .perform(delete(baseUri + "/project/" + allProjects.get("space-station").getId())) + .andExpect(status().isNoContent()) + ; + } +} diff --git a/src/test/java/de/hmmh/pmt/project/GetAllTest.java b/src/test/java/de/hmmh/pmt/project/GetAllTest.java index ea8fb84..b71ecf0 100644 --- a/src/test/java/de/hmmh/pmt/project/GetAllTest.java +++ b/src/test/java/de/hmmh/pmt/project/GetAllTest.java @@ -5,7 +5,7 @@ import de.hmmh.pmt.IntegrationTest; import de.hmmh.pmt.db.Project; import org.junit.jupiter.api.Test; -import java.util.List; +import java.util.Map; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; @@ -25,7 +25,7 @@ public class GetAllTest extends IntegrationTest { @Test void multipleProjects() throws Exception { - List allProjects = createTestProjectData(); + Map allProjects = createTestProjectData(); mvc .perform(get(baseUri + "/project"))