diff --git a/pom.xml b/pom.xml index 17fd7ad..8098d29 100644 --- a/pom.xml +++ b/pom.xml @@ -31,24 +31,30 @@ 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.projectlombok + lombok + 1.18.26 + + + org.springframework.boot + spring-boot-starter-data-jpa + diff --git a/src/main/java/com/zoomcare/candidatechallenge/.DS_Store b/src/main/java/com/zoomcare/candidatechallenge/.DS_Store new file mode 100644 index 0000000..e75b544 Binary files /dev/null and b/src/main/java/com/zoomcare/candidatechallenge/.DS_Store differ diff --git a/src/main/java/com/zoomcare/candidatechallenge/CandidateChallengeApplication.java b/src/main/java/com/zoomcare/candidatechallenge/CandidateChallengeApplication.java index 0114259..e5f98cb 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/CandidateChallengeApplication.java +++ b/src/main/java/com/zoomcare/candidatechallenge/CandidateChallengeApplication.java @@ -4,12 +4,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class CandidateChallengeApplication -{ - - public static void main(String[] args) - { - SpringApplication.run(CandidateChallengeApplication.class, args); - } - +public class CandidateChallengeApplication { + public static void main(String[] args) { + SpringApplication.run(CandidateChallengeApplication.class, args); + } } 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..14eee76 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -0,0 +1,33 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.model.EmployeeResponse; +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 javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/v1/employees") +@AllArgsConstructor +public class EmployeeController { + private EmployeeService service; + + @GetMapping("/{id}") + public ResponseEntity getEmployee(@PathVariable @NotNull Long id) { + Optional employee = service.getEmployee(id); + return employee.isPresent() ? ResponseEntity.ok(employee.get()) : new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + } + + @GetMapping + public List getEmployees() { + return service.getEmployees(); + } +} 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..868fc1f --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java @@ -0,0 +1,20 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; + +@Data +@Entity +@Table(name = "employee") +public class Employee implements Serializable { + @Id + @Column + private Long id; + @Column(name = "supervisor_id") + private Long supervisorId; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeResponse.java b/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeResponse.java new file mode 100644 index 0000000..fbf267f --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeResponse.java @@ -0,0 +1,15 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class EmployeeResponse { + private Long id; + private Long supervisorId; + private List propertyList; + private List reporterList; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Property.java b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java new file mode 100644 index 0000000..e3f2424 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java @@ -0,0 +1,25 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; +import java.io.Serializable; + +@Data +@Entity +@IdClass(PropertyId.class) +@Table(name = "property") +public class Property implements Serializable { + @Id + @Column(name = "employee_id") + private Long employeeId; + @Id + @Column + private String key; + @Column + private String value; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/PropertyId.java b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyId.java new file mode 100644 index 0000000..d126439 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyId.java @@ -0,0 +1,11 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class PropertyId implements Serializable { + private Long employeeId; + private String key; +} 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..2b1c3ba --- /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 javax.transaction.Transactional; +import java.util.List; + +@Repository +@Transactional +public interface EmployeeRepository extends JpaRepository { + List findAllBySupervisorId(Long id); +} \ No newline at end of file diff --git a/src/main/java/com/zoomcare/candidatechallenge/repository/PropertyRepository.java b/src/main/java/com/zoomcare/candidatechallenge/repository/PropertyRepository.java new file mode 100644 index 0000000..e3e6210 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/repository/PropertyRepository.java @@ -0,0 +1,14 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.Property; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import javax.transaction.Transactional; +import java.util.List; + +@Repository +@Transactional +public interface PropertyRepository extends JpaRepository { + List findAllPropertyByEmployeeId(Long employee_id); +} \ No newline at end of file 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..aec215e --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -0,0 +1,12 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.model.EmployeeResponse; + +import java.util.List; +import java.util.Optional; + +public interface EmployeeService { + Optional getEmployee(Long id); + + List getEmployees(); +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java new file mode 100644 index 0000000..b1747cd --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java @@ -0,0 +1,55 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.EmployeeResponse; +import com.zoomcare.candidatechallenge.model.Property; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import com.zoomcare.candidatechallenge.repository.PropertyRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + EmployeeRepository employeeRepository; + PropertyRepository propertyRepository; + + @Override + public Optional getEmployee(Long id) { + Optional employeeOptional = employeeRepository.findById(id); + if (employeeOptional.isPresent()) { + Employee employee = employeeOptional.get(); + return Optional.of(EmployeeResponse.builder() + .id(employee.getId()) + .supervisorId(employee.getSupervisorId()) + .propertyList(propertyRepository.findAllPropertyByEmployeeId(id)) + .reporterList(employeeRepository.findAllBySupervisorId(employee.getId())) + .build()); + } + return Optional.empty(); + } + + @Override + public List getEmployees() { + Set supervisorIds = employeeRepository.findAll().stream() + .filter(e -> Objects.nonNull(e.getSupervisorId())) + .map(e -> e.getSupervisorId()).collect(Collectors.toSet()); + List employeeList = employeeRepository.findAllById(supervisorIds); + List propertyList = propertyRepository.findAll(); + return employeeList.stream().map(employee -> EmployeeResponse.builder() + .id(employee.getId()) + .supervisorId(employee.getSupervisorId()) + .propertyList(propertyList.stream() + .filter(property -> Objects.equals(property.getEmployeeId(), employee.getId())) + .collect(Collectors.toList())) + .reporterList(employeeRepository.findAllBySupervisorId(employee.getId())) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4408d17..d6bd5c9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,11 @@ spring: h2: console: enabled: true + datasource: + url: jdbc:h2:mem:testdb + username: sa + main: + allow-bean-definition-overriding: true management: endpoints: web: @@ -9,4 +14,4 @@ management: include: "*" endpoint: health: - show-details: always \ No newline at end of file + show-details: always diff --git a/src/test/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerTest.java b/src/test/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerTest.java new file mode 100644 index 0000000..a6298b0 --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerTest.java @@ -0,0 +1,88 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.EmployeeResponse; +import com.zoomcare.candidatechallenge.model.Property; +import com.zoomcare.candidatechallenge.service.EmployeeServiceImpl; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class EmployeeControllerTest { + + private EmployeeController controller; + EmployeeServiceImpl service; + + @Before + public void setUp() { + service = Mockito.mock(EmployeeServiceImpl.class); + controller = new EmployeeController(service); + } + + @Test + public void test_getEmployee_success() { + EmployeeResponse employeeResponse = getEmployeeResponse(1L, null, "title", "CEO"); + Mockito.when(service.getEmployee(Mockito.any())).thenReturn(Optional.ofNullable(employeeResponse)); + ResponseEntity controllerResponse = controller.getEmployee(1L); + assertNotNull(controllerResponse); + assertEquals(employeeResponse, controllerResponse.getBody()); + assertEquals(HttpStatus.OK, controllerResponse.getStatusCode()); + } + + @Test + public void test_getEmployees_success() { + List employeesResponse = new ArrayList<>(); + Mockito.when(service.getEmployees()).thenReturn(employeesResponse); + List controllerResponse = controller.getEmployees(); + assertNotNull(employeesResponse); + assertEquals(employeesResponse, controllerResponse); + } + + @Test + public void test_getEmployee_notFound() { + Mockito.when(service.getEmployee(Mockito.any())).thenReturn(Optional.empty()); + ResponseEntity controllerResponse = controller.getEmployee(0L); + assertNull(controllerResponse.getBody()); + assertEquals(HttpStatus.NOT_FOUND, controllerResponse.getStatusCode()); + } + + private EmployeeResponse getEmployeeResponse(Long id, Long supervisorId, String key, String title) { + Property property = new Property(); + property.setEmployeeId(id); + property.setKey(key); + property.setValue(title); + List propertyByEmployeeId = new ArrayList<>(); + propertyByEmployeeId.add(property); + List reporterList = new ArrayList<>(); + Employee employee2 = new Employee(); + employee2.setId(2L); + employee2.setSupervisorId(1L); + Employee employee6 = new Employee(); + employee6.setId(6L); + employee6.setSupervisorId(1L); + Employee employee7 = new Employee(); + employee7.setId(7L); + employee7.setSupervisorId(1L); + reporterList.add(employee2); + reporterList.add(employee6); + reporterList.add(employee7); + EmployeeResponse employeeResponse = EmployeeResponse.builder() + .id(id) + .supervisorId(supervisorId) + .propertyList(propertyByEmployeeId) + .reporterList(reporterList) + .build(); + return employeeResponse; + } + +} \ No newline at end of file diff --git a/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImplTest.java b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImplTest.java new file mode 100644 index 0000000..ca3bfc7 --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImplTest.java @@ -0,0 +1,231 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.EmployeeResponse; +import com.zoomcare.candidatechallenge.model.Property; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import com.zoomcare.candidatechallenge.repository.PropertyRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class EmployeeServiceImplTest { + + private EmployeeServiceImpl service; + EmployeeRepository employeeRepository; + PropertyRepository propertyRepository; + private static final String TITLE = "title"; + private static final String REGION = "region"; + + @Before + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + propertyRepository = Mockito.mock(PropertyRepository.class); + service = new EmployeeServiceImpl(employeeRepository, propertyRepository); + } + + @Test + public void test_getEmployee_no_subordinate_success() { + Long employeeId = 5L; + Optional employeeOptional = Optional.of(getEmployee(employeeId, 2L)); + Map propertyMap = new HashMap<>(); + propertyMap.put(REGION, "Europe"); + propertyMap.put(TITLE, "Regional Director of Sales"); + List propertyList = getPropertyList(employeeId, propertyMap); + Mockito.when(employeeRepository.findById(Mockito.any())).thenReturn(employeeOptional); + Mockito.when(employeeRepository.findAllBySupervisorId(Mockito.any())).thenReturn(new ArrayList<>()); + Mockito.when(propertyRepository.findAllPropertyByEmployeeId(Mockito.any())).thenReturn(propertyList); + Optional employee = service.getEmployee(employeeId); + assertNotNull(employee); + assertTrue(employee.get().getReporterList().isEmpty()); + assertFalse(employee.get().getPropertyList().isEmpty()); + } + + @Test + public void test_getEmployee_with_subordinate_success() { + Long employeeId = 3L; + Optional employeeOptional = Optional.of(getEmployee(employeeId, 2L)); + List reporterList = getReporterList(employeeId); + Map propertyMap = new HashMap<>(); + propertyMap.put(REGION, "North America"); + propertyMap.put(TITLE, "Regional Director of Sales"); + List propertyList = getPropertyList(employeeId, propertyMap); + Mockito.when(employeeRepository.findById(Mockito.any())).thenReturn(employeeOptional); + Mockito.when(employeeRepository.findAllBySupervisorId(Mockito.any())).thenReturn(reporterList); + Mockito.when(propertyRepository.findAllPropertyByEmployeeId(Mockito.any())).thenReturn(propertyList); + Optional employee = service.getEmployee(employeeId); + assertNotNull(employee); + assertFalse(employee.get().getReporterList().isEmpty()); + assertFalse(employee.get().getPropertyList().isEmpty()); + } + + @Test + public void test_getEmployees_success() { + List employeeList = getAllEmployee(); + List propertyList = getAllProperty(); + Set supervisorIds = getSupervisorIdList(); + List employeeBySupervisorIdList = getAllEmployeeBySupervisorId(supervisorIds); + Mockito.when(employeeRepository.findAll()).thenReturn(employeeList); + Mockito.when(employeeRepository.findAllById(supervisorIds)).thenReturn(employeeBySupervisorIdList); + Mockito.when(propertyRepository.findAll()).thenReturn(propertyList); + Mockito.when(employeeRepository.findAllBySupervisorId(Mockito.any())).thenReturn(new ArrayList<>()); + List employeeResponseList = service.getEmployees(); + assertFalse(employeeResponseList.get(0).getPropertyList().isEmpty()); + assertEquals(4, employeeResponseList.size()); + assertNotNull(employeeResponseList); + } + + private List getAllEmployeeBySupervisorId(Set supervisorIds) { + List employeesBySupervisorId = new ArrayList<>(); + for (Long id : supervisorIds) { + Employee employee = new Employee(); + employee.setId(id); + Long supervisorId = null; + if (id == 2 || id == 7) supervisorId = 1L; + if (id == 3) supervisorId = 2L; + employee.setSupervisorId(supervisorId); + employeesBySupervisorId.add(employee); + } + return employeesBySupervisorId; + } + + private List getAllProperty() { + List propertyList = new ArrayList<>(); + Property property1 = new Property(); + property1.setEmployeeId(1L); + property1.setKey(TITLE); + property1.setValue("CEO"); + propertyList.add(property1); + Property property2 = new Property(); + property2.setEmployeeId(2L); + property2.setKey(TITLE); + property2.setValue("Vice President of Sales"); + propertyList.add(property2); + Property property3 = new Property(); + property3.setEmployeeId(3L); + property3.setKey(TITLE); + property3.setValue("Regional Director of Sales"); + propertyList.add(property3); + Property property4 = new Property(); + property4.setEmployeeId(3L); + property4.setKey(REGION); + property4.setValue("North America"); + propertyList.add(property4); + Property property5 = new Property(); + property5.setEmployeeId(4L); + property5.setKey(TITLE); + property5.setValue("Sales Representative"); + propertyList.add(property5); + Property property6 = new Property(); + property6.setEmployeeId(5L); + property6.setKey(TITLE); + property6.setValue("Regional Director of Sales"); + propertyList.add(property6); + Property property7 = new Property(); + property7.setEmployeeId(5L); + property7.setKey(REGION); + property7.setValue("Europe"); + propertyList.add(property7); + Property property8 = new Property(); + property8.setEmployeeId(6L); + property8.setKey(TITLE); + property8.setValue("Vice President of People"); + propertyList.add(property8); + Property property9 = new Property(); + property9.setEmployeeId(7L); + property9.setKey(TITLE); + property9.setValue("Vice President of Marketing"); + propertyList.add(property9); + Property property10 = new Property(); + property10.setEmployeeId(8L); + property10.setKey(TITLE); + property10.setValue("Regional Director of Marketing"); + propertyList.add(property10); + Property property11 = new Property(); + property11.setEmployeeId(8L); + property11.setKey(REGION); + property11.setValue("North America"); + propertyList.add(property11); + Property property12 = new Property(); + property12.setEmployeeId(9L); + property12.setKey(TITLE); + property12.setValue("Regional Director of Marketing"); + propertyList.add(property12); + Property property13 = new Property(); + property13.setEmployeeId(9L); + property13.setKey(REGION); + property13.setValue("Europe"); + propertyList.add(property13); + return propertyList; + } + + private List getAllEmployee() { + List employeeList = new ArrayList<>(); + for (int i = 1; i < 10; i++) { + Employee employee = new Employee(); + employee.setId(Long.valueOf(i)); + if (i > 1 && i < 5) { + employee.setSupervisorId(Long.valueOf(i - 1)); + } else if (i == 5) { + employee.setSupervisorId(Long.valueOf(i - 3)); + } else if (i > 5 && i < 8) { + employee.setSupervisorId(1L); + } else if (i > 7) { + employee.setSupervisorId(7L); + } + employeeList.add(employee); + } + return employeeList; + } + + private Set getSupervisorIdList() { + Set supervisorIds = new HashSet<>(); + supervisorIds.add(1L); + supervisorIds.add(2L); + supervisorIds.add(3L); + supervisorIds.add(7L); + return supervisorIds; + } + + private Employee getEmployee(Long id, Long supervisorId) { + Employee employee = new Employee(); + employee.setId(id); + employee.setSupervisorId(supervisorId); + return employee; + } + + private List getReporterList(Long supervisorId) { + List reporterList = new ArrayList<>(); + Employee employee = new Employee(); + employee.setId(4L); + employee.setSupervisorId(supervisorId); + reporterList.add(employee); + return reporterList; + } + + private List getPropertyList(Long employeeId, Map propertyMap) { + List propertyList = new ArrayList<>(); + for (Map.Entry entry : propertyMap.entrySet()) { + Property property = new Property(); + property.setEmployeeId(employeeId); + property.setKey(entry.getKey()); + property.setValue(entry.getValue()); + propertyList.add(property); + } + return propertyList; + } + +} \ No newline at end of file