diff --git a/pom.xml b/pom.xml index 17fd7ad..57032fd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,63 +1,78 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.1.3.RELEASE - - - com.zoomcare - java-candidate-challenge - 0.0.1-SNAPSHOT - candidate-challenge - ZoomCare Candidate Code Challenge + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.5 + + + com.example + ZoomCareCodeChallenge + 0.0.1-SNAPSHOT + ZoomCareCodeChallenge + ZoomCareCodeChallenge + + 17 + + + + org.hamcrest + hamcrest + 2.2 + test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + true + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-data-jdbc - - - org.springframework.boot - spring-boot-starter-web - - - - org.flywaydb - flyway-core - runtime - - - - com.h2database - h2 - runtime - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + diff --git a/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java new file mode 100644 index 0000000..ac9fd14 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -0,0 +1,34 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.dto.EmployeeDTO; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@AllArgsConstructor +@RequestMapping("/employees") +public class EmployeeController { + + private final EmployeeService employeeService; + + + @GetMapping("/") + public ResponseEntity> getTopLevelEmployees() { + List topLevelEmployees = employeeService.getTopLevelEmployees(); + return new ResponseEntity<>(topLevelEmployees, HttpStatus.OK); + } + + @GetMapping("/{id}") + public ResponseEntity getEmployee(@PathVariable Long id) { + EmployeeDTO employee = employeeService.getEmployee(id); + return new ResponseEntity<>(employee, HttpStatus.OK); + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java new file mode 100644 index 0000000..8e0ed01 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java @@ -0,0 +1,24 @@ +package com.zoomcare.candidatechallenge.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +public class EmployeeDTO { + + private Long id; + private List properties; + private List reports; + + public EmployeeDTO() { + + } + + public EmployeeDTO(long l) { + } + + // Getters and setters +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java new file mode 100644 index 0000000..ab4d590 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java @@ -0,0 +1,17 @@ +package com.zoomcare.candidatechallenge.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PropertiesDTO { + + private String key; + private String value; + + + // Getters and setters +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java b/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java new file mode 100644 index 0000000..863e8c5 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java @@ -0,0 +1,11 @@ +package com.zoomcare.candidatechallenge.exceptions; + +public class EmployeeNotFoundException extends RuntimeException { + + public EmployeeNotFoundException(Long id) { + super("Could not find employee with id: " + id); + } + + } + + diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java new file mode 100644 index 0000000..fa6de52 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java @@ -0,0 +1,40 @@ +package com.zoomcare.candidatechallenge.model; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +@Data +@AllArgsConstructor +@Entity +@Table(name = "EMPLOYEE") +public class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SUPERVISOR_ID") + private Employee supervisor; + + @OneToMany(mappedBy = "supervisor", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List reports = new ArrayList<>(); + + @OneToMany(mappedBy = "employee", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List properties = new ArrayList<>(); + + public Employee() { + + } + + public Employee(long l, Object o, Object o1) { + } + + public Employee(long l) { + } + + // Getters and setters +} \ No newline at end of file diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java new file mode 100644 index 0000000..b1c8a2a --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java @@ -0,0 +1,40 @@ +package com.zoomcare.candidatechallenge.model; + + +import jakarta.persistence.*; +import lombok.Data; + +@Data +@Entity +@Table(name = "PROPERTIES") +public class Properties { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "EMPLOYEE_ID") + private Employee employee; + + + + @Column(name = "SUPERVISOR_ID") + private Long supervisorId; + @Column(name = "property_key") + private String key; + @Column(name = "property_value") + private String value; + + public Properties(String key1, String value1) { + } + + public Properties() { + + } + + + // Getters and setters + +} + diff --git a/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java new file mode 100644 index 0000000..48d6be1 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java @@ -0,0 +1,14 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.Employee; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface EmployeeRepository extends JpaRepository { + + List findBySupervisorIsNull(); + +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java new file mode 100644 index 0000000..2b1e396 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -0,0 +1,68 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.dto.EmployeeDTO; +import com.zoomcare.candidatechallenge.dto.PropertiesDTO; +import com.zoomcare.candidatechallenge.exceptions.EmployeeNotFoundException; +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.Properties; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class EmployeeService { + + private final EmployeeRepository employeeRepository; + + public EmployeeService(EmployeeRepository employeeRepository) { + this.employeeRepository = employeeRepository; + } + + public List getTopLevelEmployees() { + List topLevelEmployees = employeeRepository.findBySupervisorIsNull(); + return convertToDTO(topLevelEmployees); + } + + public EmployeeDTO getEmployee(Long id) { + Optional employee = employeeRepository.findById(id); + if (employee.isPresent()) { + return convertToDTO(employee.get()); + } else { + throw new EmployeeNotFoundException(id); + } + } + + private List convertToDTO(List employees) { + List employeeDTOs = new ArrayList<>(); + for (Employee employee : employees) { + employeeDTOs.add(convertToDTO(employee)); + } + return employeeDTOs; + } + + public EmployeeDTO convertToDTO(Employee employee) { + EmployeeDTO employeeDTO = new EmployeeDTO(); + employeeDTO.setId(employee.getId()); + employeeDTO.setProperties(convertPropertiesToDTO(employee.getProperties())); + employeeDTO.setReports((List) convertToDTO(employee.getReports())); + return employeeDTO; + } + + public List convertPropertiesToDTO(List properties) { + List propertiesDTOs = new ArrayList<>(); + for (Properties property : properties) { + propertiesDTOs.add(convertToDTO(property)); + } + return propertiesDTOs; + } + + private PropertiesDTO convertToDTO(Properties property) { + PropertiesDTO propertiesDTO = new PropertiesDTO(); + propertiesDTO.setKey(property.getKey()); + propertiesDTO.setValue(property.getValue()); + return propertiesDTO; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..83cae53 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=8890 +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true + +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 4408d17..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,12 +0,0 @@ -spring: - h2: - console: - enabled: true -management: - endpoints: - web: - exposure: - include: "*" - endpoint: - health: - show-details: always \ No newline at end of file diff --git a/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java new file mode 100644 index 0000000..717a122 --- /dev/null +++ b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java @@ -0,0 +1,91 @@ +package com.example.zoomcarecodechallenge; + +import com.example.zoomcarecodechallenge.controller.EmployeeController; +import com.example.zoomcarecodechallenge.dto.EmployeeDTO; +import com.example.zoomcarecodechallenge.dto.PropertiesDTO; +import com.example.zoomcarecodechallenge.model.Employee; +import com.example.zoomcarecodechallenge.model.Properties; +import com.example.zoomcarecodechallenge.repository.EmployeeRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.hamcrest.MockitoHamcrest; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.web.server.ResponseStatusException; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.*; + +import com.example.zoomcarecodechallenge.dto.EmployeeDTO; +import com.example.zoomcarecodechallenge.service.EmployeeService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class EmployeeControllerTest { + + @Mock + private EmployeeService employeeService; + @InjectMocks + private EmployeeController employeeController; + + @Test + public void testGetTopLevelEmployees() { + EmployeeDTO employeeDTO1 = new EmployeeDTO(); + employeeDTO1.setId(1L); + + EmployeeDTO employeeDTO2 = new EmployeeDTO(); + employeeDTO2.setId(2L); + + List expectedEmployees = Arrays.asList(employeeDTO1, employeeDTO2); + + when(employeeService.getTopLevelEmployees()) + .thenReturn(expectedEmployees); + + ResponseEntity> responseEntity = employeeController.getTopLevelEmployees(); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals(expectedEmployees, responseEntity.getBody()); + } + + @Test + public void testGetEmployee() { + EmployeeDTO expectedEmployee = new EmployeeDTO(); + expectedEmployee.setId(1L); + + when(employeeService.getEmployee(1L)) + .thenReturn(expectedEmployee); + + ResponseEntity responseEntity = employeeController.getEmployee(1L); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals(expectedEmployee, responseEntity.getBody()); + } +} diff --git a/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java new file mode 100644 index 0000000..cece2f0 --- /dev/null +++ b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java @@ -0,0 +1,130 @@ +package com.example.zoomcarecodechallenge; + +import com.example.zoomcarecodechallenge.dto.EmployeeDTO; +import com.example.zoomcarecodechallenge.dto.PropertiesDTO; +import com.example.zoomcarecodechallenge.exceptions.EmployeeNotFoundException; +import com.example.zoomcarecodechallenge.model.Employee; +import com.example.zoomcarecodechallenge.model.Properties; +import com.example.zoomcarecodechallenge.repository.EmployeeRepository; +import com.example.zoomcarecodechallenge.service.EmployeeService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +public class EmployeeServiceTest { + + @Mock + private EmployeeRepository employeeRepository; + + private EmployeeService employeeService; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + employeeService = new EmployeeService(employeeRepository); + } + + @Test + public void testGetTopLevelEmployees() { + Employee employee1 = new Employee(); + employee1.setId(1L); + employee1.setSupervisor(null); + employee1.setReports(Arrays.asList(new Employee(), new Employee())); + + Employee employee2 = new Employee(); + employee2.setId(2L); + employee2.setSupervisor(null); + employee2.setReports(Arrays.asList(new Employee())); + + when(employeeRepository.findBySupervisorIsNull()) + .thenReturn(Arrays.asList(employee1, employee2)); + + List topLevelEmployees = employeeService.getTopLevelEmployees(); + + assertEquals(2, topLevelEmployees.size()); + assertEquals(1L, topLevelEmployees.get(0).getId()); + assertEquals(2L, topLevelEmployees.get(1).getId()); + } + + @Test + public void testGetEmployee() { + Employee employee = new Employee(); + employee.setId(1L); + employee.setSupervisor(new Employee()); + employee.setReports(new ArrayList<>()); + employee.setProperties(Arrays.asList(new Properties(), new Properties())); + + when(employeeRepository.findById(1L)) + .thenReturn(Optional.of(employee)); + + EmployeeDTO employeeDTO = employeeService.getEmployee(1L); + + assertEquals(1L, employeeDTO.getId()); + assertEquals(2, employeeDTO.getProperties().size()); + } + + @Test + public void testGetEmployeeNotFound() { + when(employeeRepository.findById(1L)) + .thenReturn(Optional.empty()); + + assertThrows(EmployeeNotFoundException.class, () -> employeeService.getEmployee(1L)); + } + + @Test + public void testConvertToDTO() { + Employee employee = new Employee(); + employee.setId(1L); + employee.setSupervisor(new Employee()); + employee.setReports(new ArrayList<>()); + employee.setProperties(Arrays.asList(new Properties(), new Properties())); + + EmployeeDTO employeeDTO = employeeService.convertToDTO(employee); + + assertEquals(1L, employeeDTO.getId()); + assertEquals(2, employeeDTO.getProperties().size()); + } + + @Test + public void testConvertPropertiesToDTO() { + Properties property1 = new Properties(); + property1.setKey("key1"); + property1.setValue("value1"); + + Properties property2 = new Properties(); + property2.setKey("key2"); + property2.setValue("value2"); + + List properties = Arrays.asList(property1, property2); + + List propertiesDTOs = employeeService.convertPropertiesToDTO(properties); + + assertEquals(2, propertiesDTOs.size()); + assertEquals("key1", propertiesDTOs.get(0).getKey()); + assertEquals("value2", propertiesDTOs.get(1).getValue()); + } + + @Test + public void testConvertToDTOWithNullSupervisor() { + Employee employee = new Employee(); + employee.setId(1L); + employee.setSupervisor(null); + employee.setReports(Arrays.asList(new Employee(), new Employee())); + employee.setProperties(Arrays.asList(new Properties(), new Properties())); + + EmployeeDTO employeeDTO = employeeService.convertToDTO(employee); + + assertEquals(1L, employeeDTO.getId()); + assertEquals(2, employeeDTO.getProperties().size()); + assertEquals(2, employeeDTO.getReports().size());} +} +