diff --git a/pom.xml b/pom.xml index 17fd7ad..0a6b1bb 100644 --- a/pom.xml +++ b/pom.xml @@ -2,20 +2,23 @@ 4.0.0 + org.springframework.boot spring-boot-starter-parent - 2.1.3.RELEASE - + 2.6.7 + + com.zoomcare java-candidate-challenge - 0.0.1-SNAPSHOT + 0.0.1 candidate-challenge - ZoomCare Candidate Code Challenge + Habib De León Baños - ZoomCare Candidate Code Challenge - 1.8 + 11 + 1.4.2.Final @@ -25,7 +28,7 @@ org.springframework.boot - spring-boot-starter-data-jdbc + spring-boot-starter-data-jpa org.springframework.boot @@ -49,6 +52,19 @@ spring-boot-starter-test test + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + org.springdoc + springdoc-openapi-ui + 1.5.12 + diff --git a/src/main/java/com/zoomcare/candidatechallenge/controller/ControllerExceptionHandler.java b/src/main/java/com/zoomcare/candidatechallenge/controller/ControllerExceptionHandler.java new file mode 100644 index 0000000..9f3978d --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/ControllerExceptionHandler.java @@ -0,0 +1,39 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.exception.DataNotFoundException; +import com.zoomcare.candidatechallenge.exception.UnexpectedException; +import com.zoomcare.candidatechallenge.model.dto.CommonResponse; +import com.zoomcare.candidatechallenge.model.enums.ResponseCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class ControllerExceptionHandler { + + @ExceptionHandler({Exception.class}) + public ResponseEntity handleException(Exception exception) { + + if (exception instanceof DataNotFoundException) { + return handleDataNotFound((DataNotFoundException) exception); + } else if (exception instanceof UnexpectedException) { + return handleUnexpectedException((UnexpectedException) exception); + } else { + return handleUnexpectedException(new UnexpectedException("Something went wrong", exception)); + } + } + + private ResponseEntity handleDataNotFound(DataNotFoundException exception) { + return createBasicResponse(ResponseCode.DATA_NOT_FOUND, exception, HttpStatus.NOT_FOUND); + } + + private ResponseEntity handleUnexpectedException(UnexpectedException exception) { + return createBasicResponse(ResponseCode.UNEXPECTED_ERROR, exception, HttpStatus.INTERNAL_SERVER_ERROR); + } + + private ResponseEntity createBasicResponse(ResponseCode code, Exception exception, + HttpStatus status) { + return new ResponseEntity<>(new CommonResponse(code, exception.getMessage()), status); + } +} 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..dcb180f --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -0,0 +1,89 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.model.dto.CommonResponse; +import com.zoomcare.candidatechallenge.model.dto.EmployeeDto; +import com.zoomcare.candidatechallenge.model.entity.Employee; +import com.zoomcare.candidatechallenge.model.enums.ResponseCode; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +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; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("employees") +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class EmployeeController { + + private final EmployeeService employeeService; + + @GetMapping("/top-level") + @Operation(summary = "Get the nested organization structure starting from the top level employees") + public ResponseEntity getAllTopLevelEmployees() { + final List employees = employeeService.getAllTopLevelEmployees(); + + final List result = employees.stream().map(e -> employeeToDto(e)).collect(Collectors.toList()); + + return new ResponseEntity<>( + new CommonResponse( + ResponseCode.OPERATION_SUCCESSFUL, "Nested organization structure", result + ), HttpStatus.OK + ); + } + + @GetMapping("/{employeeId}") + @Operation(summary = "Get the nested organization structure starting from the specified employee") + public ResponseEntity getEmployeeById(@PathVariable("employeeId") Long employeeId) { + final Employee employee = employeeService.findById(employeeId); + + final EmployeeDto result = employeeToDto(employee); + + return new ResponseEntity<>( + new CommonResponse( + ResponseCode.OPERATION_SUCCESSFUL, "Found employee with id " + employeeId, result + ), HttpStatus.OK + ); + } + + + private EmployeeDto employeeToDto(Employee employee) { + if (employee == null) { + return null; + } + + final EmployeeDto employeeDto = new EmployeeDto(); + + final Employee supervisor = employee.getSupervisor(); + final Long supervisorId; + if (supervisor != null) { + supervisorId = supervisor.getId(); + } else { + supervisorId = null; + } + + employeeDto.setSupervisorId(supervisorId); + employeeDto.setEmployeeId(employee.getId()); + employeeDto.setProperties(employee.getProperties()); + + final List directReports = employee.getDirectReports(); + final List directReportsDtos; + + if (directReports != null && !directReports.isEmpty()) { + directReportsDtos = directReports.stream().map(dr -> employeeToDto(dr)).collect(Collectors.toList()); + } else { + directReportsDtos = null; + } + + employeeDto.setDirectReports(directReportsDtos); + + return employeeDto; + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/BusinessException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/BusinessException.java new file mode 100644 index 0000000..6aa4a9a --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/exception/BusinessException.java @@ -0,0 +1,19 @@ +package com.zoomcare.candidatechallenge.exception; + +public abstract class BusinessException extends RuntimeException { + + public BusinessException() { + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(Throwable cause) { + super(cause); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/DataNotFoundException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/DataNotFoundException.java new file mode 100644 index 0000000..241e2d3 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/exception/DataNotFoundException.java @@ -0,0 +1,19 @@ +package com.zoomcare.candidatechallenge.exception; + +public class DataNotFoundException extends BusinessException { + + public DataNotFoundException() { + } + + public DataNotFoundException(String message) { + super(message); + } + + public DataNotFoundException(Throwable cause) { + super(cause); + } + + public DataNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/exception/UnexpectedException.java b/src/main/java/com/zoomcare/candidatechallenge/exception/UnexpectedException.java new file mode 100644 index 0000000..53a67cb --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/exception/UnexpectedException.java @@ -0,0 +1,19 @@ +package com.zoomcare.candidatechallenge.exception; + +public class UnexpectedException extends BusinessException{ + + public UnexpectedException() { + } + + public UnexpectedException(String message) { + super(message); + } + + public UnexpectedException(Throwable cause) { + super(cause); + } + + public UnexpectedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/dto/CommonResponse.java b/src/main/java/com/zoomcare/candidatechallenge/model/dto/CommonResponse.java new file mode 100644 index 0000000..dd458c3 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/dto/CommonResponse.java @@ -0,0 +1,39 @@ +package com.zoomcare.candidatechallenge.model.dto; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.zoomcare.candidatechallenge.model.enums.ResponseCode; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@JsonPropertyOrder(value = {"code", "message", "data"}) +public class CommonResponse { + private final ResponseCode code; + private final String message; + private final T data; + + public CommonResponse(ResponseCode code, String message) { + this.code = code; + this.message = message; + this.data = null; + } + + @JsonGetter("code") + public String getJsonCode() { + return code.toString(); + } + + @JsonIgnore + public ResponseCode getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public T getData() { + return data; + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/dto/EmployeeDto.java b/src/main/java/com/zoomcare/candidatechallenge/model/dto/EmployeeDto.java new file mode 100644 index 0000000..36a9152 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/dto/EmployeeDto.java @@ -0,0 +1,17 @@ +package com.zoomcare.candidatechallenge.model.dto; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonPropertyOrder(value = {"employeeId", "supervisorId", "properties", "directReports"}) +public class EmployeeDto { + + private Long employeeId; + private Long supervisorId; + private Map properties; + private List directReports; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/entity/Employee.java b/src/main/java/com/zoomcare/candidatechallenge/model/entity/Employee.java new file mode 100644 index 0000000..dbbbe00 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/entity/Employee.java @@ -0,0 +1,44 @@ +package com.zoomcare.candidatechallenge.model.entity; + +import lombok.Data; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import java.util.List; +import java.util.Map; + +@Entity +@Data +public class Employee { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + @JoinColumn(name = "supervisor_id") + private Employee supervisor; + + @ElementCollection + @MapKeyColumn(name = "key") + @Column(name = "value") + @CollectionTable + ( + name = "property", + joinColumns = {@JoinColumn(name = "employee_id", referencedColumnName = "id")} + ) + private Map properties; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "supervisor") + private List directReports; + +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/enums/ResponseCode.java b/src/main/java/com/zoomcare/candidatechallenge/model/enums/ResponseCode.java new file mode 100644 index 0000000..b2419d9 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/enums/ResponseCode.java @@ -0,0 +1,18 @@ +package com.zoomcare.candidatechallenge.model.enums; + +public enum ResponseCode { + OPERATION_SUCCESSFUL("challenge-0001"), + UNEXPECTED_ERROR("challenge-0002"), + DATA_NOT_FOUND("challenge-0003"); + + private final String code; + + ResponseCode(String code) { + this.code = code; + } + + @Override + public String toString() { + return code; + } +} 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..48c1df2 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java @@ -0,0 +1,15 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.entity.Employee; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface EmployeeRepository extends CrudRepository { + + @Query("SELECT e FROM Employee e WHERE e.supervisor IS NULL") + List getAllTopLevelEmployees(); +} 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..7618d15 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -0,0 +1,14 @@ +package com.zoomcare.candidatechallenge.service; + + +import com.zoomcare.candidatechallenge.exception.BusinessException; +import com.zoomcare.candidatechallenge.model.entity.Employee; + +import java.util.List; + +public interface EmployeeService { + + public List getAllTopLevelEmployees() throws BusinessException; + + public Employee findById(Long id) throws BusinessException; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/implementation/EmployeeServiceImpl.java b/src/main/java/com/zoomcare/candidatechallenge/service/implementation/EmployeeServiceImpl.java new file mode 100644 index 0000000..06e1735 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/implementation/EmployeeServiceImpl.java @@ -0,0 +1,74 @@ +package com.zoomcare.candidatechallenge.service.implementation; + + +import com.zoomcare.candidatechallenge.exception.BusinessException; +import com.zoomcare.candidatechallenge.exception.DataNotFoundException; +import com.zoomcare.candidatechallenge.exception.UnexpectedException; +import com.zoomcare.candidatechallenge.model.entity.Employee; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + @Override + public List getAllTopLevelEmployees() throws BusinessException { + try { + + final List allTopLevelEmployees = employeeRepository.getAllTopLevelEmployees(); + + if (allTopLevelEmployees == null || allTopLevelEmployees.isEmpty()) { + throw new DataNotFoundException("Top-level employees not found"); + } + + return allTopLevelEmployees; + + } catch (Exception exception) { + if (exception instanceof BusinessException) { + throw exception; + } + + final String errorMessage = "Something went wrong trying to get all top-level employees"; + + log.error(errorMessage, exception); + + throw new UnexpectedException(errorMessage, exception); + } + } + + @Override + public Employee findById(Long id) throws BusinessException { + + try { + + final Employee employee = employeeRepository.findById(id).orElse(null); + + if (employee == null) { + throw new DataNotFoundException("Employee with id " + id + " does not exist"); + } + + return employee; + + } catch (Exception exception) { + if (exception instanceof BusinessException) { + throw exception; + } + + final String errorMessage = "Something went wrong trying to get the employee with id " + id; + + log.error(errorMessage, exception); + + throw new UnexpectedException(errorMessage, exception); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4408d17..16ccd98 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,4 +9,8 @@ management: include: "*" endpoint: health: - show-details: always \ No newline at end of file + show-details: always +springdoc: + packages-to-scan: com.zoomcare.candidatechallenge.controller + swagger-ui: + path: /api-docs 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..5e8b417 --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerTest.java @@ -0,0 +1,68 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.exception.DataNotFoundException; +import com.zoomcare.candidatechallenge.model.entity.Employee; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(EmployeeController.class) +public class EmployeeControllerTest { + + @MockBean + private EmployeeService employeeService; + + @Autowired + private MockMvc mockMvc; + + @Test + void getAllTopLevelEmployees_EmployeesFound_ReturnsStatusOk() throws Exception { + doReturn(new ArrayList<>()).when(employeeService).getAllTopLevelEmployees(); + + mockMvc.perform(get("/employees/top-level")).andExpect(status().isOk()); + + verify(employeeService, times(1)).getAllTopLevelEmployees(); + } + + @Test + void getAllTopLevelEmployees_EmployeesNotFound_ReturnsStatusNotFound() throws Exception { + doThrow(new DataNotFoundException()).when(employeeService).getAllTopLevelEmployees(); + + mockMvc.perform(get("/employees/top-level")).andExpect(status().isNotFound()); + + verify(employeeService, times(1)).getAllTopLevelEmployees(); + } + + @Test + void findById_EmployeeFound_ReturnsStatusOk() throws Exception { + + final Long id = 1L; + + doReturn(new Employee()).when(employeeService).findById(id); + + mockMvc.perform(get("/employees/" + id)).andExpect(status().isOk()); + + verify(employeeService, times(1)).findById(id); + } + + @Test + void findById_EmployeeNotFound_ReturnsStatusNotFound() throws Exception { + + final Long id = 1L; + + doThrow(new DataNotFoundException()).when(employeeService).findById(id); + + mockMvc.perform(get("/employees/" + id)).andExpect(status().isNotFound()); + + verify(employeeService, times(1)).findById(id); + } +} diff --git a/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java b/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java new file mode 100644 index 0000000..22b8827 --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/repository/EmployeeRepositoryTest.java @@ -0,0 +1,51 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.entity.Employee; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +public class EmployeeRepositoryTest { + + @Autowired + private EmployeeRepository employeeRepository; + + + @Test + void getAllTopLevelEmployees_NonExistentTopLevelEmployees_ReturnsEmptyList() { + final Employee supervisor = new Employee(); + supervisor.setId(2L); + + employeeRepository.findById(1L).get().setSupervisor(supervisor); + + List employees = employeeRepository.getAllTopLevelEmployees(); + + assertThat(employees.isEmpty()).isTrue(); + } + + + @Test + void getAllTopLevelEmployees_ExistentTopLevelEmployees_ReturnsSuchEmployees() { + List employees = employeeRepository.getAllTopLevelEmployees(); + + assertThat(employees.size()).isEqualTo(1); + } + + @Test + void findById_ExistentId_EmployeeIsPresent() { + Optional employee = employeeRepository.findById(1L); + assertThat(employee.isPresent()).isTrue(); + } + + @Test + void findById_NonExistentId_EmployeeIsNotPresent() { + Optional employee = employeeRepository.findById(100L); + assertThat(employee.isPresent()).isFalse(); + } +} 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..a713aa2 --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImplTest.java @@ -0,0 +1,128 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.exception.DataNotFoundException; +import com.zoomcare.candidatechallenge.exception.UnexpectedException; +import com.zoomcare.candidatechallenge.model.entity.Employee; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import com.zoomcare.candidatechallenge.service.implementation.EmployeeServiceImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class EmployeeServiceImplTest { + + @Mock + private EmployeeRepository employeeRepository; + + @InjectMocks + private EmployeeServiceImpl employeeServiceImpl; + + @Test + public void getAllTopLevelEmployees_NonBusinessExceptionThrown_ThrowsUnexpectedException() { + + doThrow(new RuntimeException()).when(employeeRepository).getAllTopLevelEmployees(); + + assertThatThrownBy(() -> employeeServiceImpl.getAllTopLevelEmployees()).isInstanceOf(UnexpectedException.class); + + verify(employeeRepository, times(1)).getAllTopLevelEmployees(); + } + + @Test + public void getAllTopLevelEmployees_BusinessExceptionThrown_ThrowsSuchException() { + + doThrow(new DataNotFoundException()).when(employeeRepository).getAllTopLevelEmployees(); + + assertThatThrownBy(() -> employeeServiceImpl.getAllTopLevelEmployees()).isInstanceOf(DataNotFoundException.class); + + verify(employeeRepository, times(1)).getAllTopLevelEmployees(); + } + + @Test + public void getAllTopLevelEmployees_EmployeesNotFound_ThrowsDataNotFoundException() { + + final List mockedEmployees = new ArrayList<>(); + + doReturn(mockedEmployees).when(employeeRepository).getAllTopLevelEmployees(); + + assertThatThrownBy(() -> employeeServiceImpl.getAllTopLevelEmployees()).isInstanceOf(DataNotFoundException.class); + + verify(employeeRepository, times(1)).getAllTopLevelEmployees(); + } + + @Test + public void getAllTopLevelEmployees_EmployeesFound_ReturnsSuchEmployees() { + + final List mockedEmployees = List.of(new Employee()); + + doReturn(mockedEmployees).when(employeeRepository).getAllTopLevelEmployees(); + + final List employees = employeeServiceImpl.getAllTopLevelEmployees(); + + assertThat(employees).isEqualTo(mockedEmployees); + + verify(employeeRepository, times(1)).getAllTopLevelEmployees(); + } + + @Test + public void findById_NonBusinessExceptionThrown_ThrowsUnexpectedException() { + + final Long id = 1L; + + doThrow(new RuntimeException()).when(employeeRepository).findById(id); + + assertThatThrownBy(() -> employeeServiceImpl.findById(id)).isInstanceOf(UnexpectedException.class); + + verify(employeeRepository, times(1)).findById(id); + } + + @Test + public void findById_BusinessExceptionThrown_ThrowsSuchException() { + + final Long id = 1L; + + doThrow(new DataNotFoundException()).when(employeeRepository).findById(id); + + assertThatThrownBy(() -> employeeServiceImpl.findById(id)).isInstanceOf(DataNotFoundException.class); + + verify(employeeRepository, times(1)).findById(id); + } + + @Test + public void findById_EmployeeNotFound_ThrowsDataNotFoundException() { + + final Long id = 1L; + + doReturn(Optional.empty()).when(employeeRepository).findById(id); + + assertThatThrownBy(() -> employeeServiceImpl.findById(id)).isInstanceOf(DataNotFoundException.class); + + verify(employeeRepository, times(1)).findById(id); + } + + @Test + public void findById_EmployeeFound_ReturnsSuchEmployee() { + + final Long id = 1L; + + final Employee mockedEmployee = new Employee(); + + doReturn(Optional.of(mockedEmployee)).when(employeeRepository).findById(id); + + final Employee employee = employeeServiceImpl.findById(id); + + assertThat(employee).isEqualTo(mockedEmployee); + + verify(employeeRepository, times(1)).findById(id); + } +}