diff --git a/time-logging-service/src/test/java/com/techtorque/time_logging_service/controller/TimeLogControllerIntegrationTest.java b/time-logging-service/src/test/java/com/techtorque/time_logging_service/controller/TimeLogControllerIntegrationTest.java new file mode 100644 index 0000000..606ec0a --- /dev/null +++ b/time-logging-service/src/test/java/com/techtorque/time_logging_service/controller/TimeLogControllerIntegrationTest.java @@ -0,0 +1,204 @@ +package com.techtorque.time_logging_service.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.techtorque.time_logging_service.dto.request.TimeLogRequest; +import com.techtorque.time_logging_service.dto.request.TimeLogUpdateRequest; +import com.techtorque.time_logging_service.dto.response.TimeLogResponse; +import com.techtorque.time_logging_service.service.TimeLogService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +@ActiveProfiles("test") +class TimeLogControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private TimeLogService timeLogService; + + private TimeLogResponse testResponse; + + @BeforeEach + void setUp() { + testResponse = new TimeLogResponse(); + testResponse.setId("log123"); + testResponse.setEmployeeId("employee123"); + testResponse.setServiceId("service456"); + testResponse.setProjectId("project789"); + testResponse.setHours(8.0); + testResponse.setDate(LocalDate.of(2025, 11, 21)); + testResponse.setDescription("Worked on feature"); + testResponse.setWorkType("Development"); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testCreateTimeLog_Success() throws Exception { + TimeLogRequest request = new TimeLogRequest(); + request.setServiceId("service456"); + request.setProjectId("project789"); + request.setHours(8.0); + request.setDate(LocalDate.of(2025, 11, 21)); + request.setDescription("Worked on feature"); + request.setWorkType("Development"); + + when(timeLogService.createTimeLog(anyString(), any(TimeLogRequest.class))) + .thenReturn(testResponse); + + mockMvc.perform(post("/time-logs") + .header("X-User-Subject", "employee123") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value("log123")) + .andExpect(jsonPath("$.hours").value(8.0)); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testGetMyTimeLogs_Employee() throws Exception { + when(timeLogService.getAllTimeLogsByEmployee("employee123")) + .thenReturn(Arrays.asList(testResponse)); + + mockMvc.perform(get("/time-logs") + .header("X-User-Subject", "employee123") + .header("X-User-Roles", "ROLE_EMPLOYEE")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].id").value("log123")); + } + + @Test + @WithMockUser(roles = "ADMIN") + void testGetMyTimeLogs_Admin() throws Exception { + when(timeLogService.getAllTimeLogs()) + .thenReturn(Arrays.asList(testResponse)); + + mockMvc.perform(get("/time-logs") + .header("X-User-Subject", "admin123") + .header("X-User-Roles", "ROLE_ADMIN")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testGetTimeLogById_Success() throws Exception { + when(timeLogService.getTimeLogByIdWithAuthorization(anyString(), anyString(), anyString())) + .thenReturn(testResponse); + + mockMvc.perform(get("/time-logs/{logId}", "log123") + .header("X-User-Subject", "employee123") + .header("X-User-Role", "ROLE_EMPLOYEE")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value("log123")); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testUpdateTimeLog_Success() throws Exception { + TimeLogUpdateRequest updateRequest = new TimeLogUpdateRequest(); + updateRequest.setHours(10.0); + updateRequest.setDescription("Updated description"); + + when(timeLogService.updateTimeLogWithAuthorization(anyString(), anyString(), any(TimeLogUpdateRequest.class))) + .thenReturn(testResponse); + + mockMvc.perform(put("/time-logs/{logId}", "log123") + .header("X-User-Subject", "employee123") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value("log123")); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testDeleteTimeLog_Success() throws Exception { + mockMvc.perform(delete("/time-logs/{logId}", "log123") + .header("X-User-Subject", "employee123") + .header("X-User-Roles", "ROLE_EMPLOYEE")) + .andExpect(status().isNoContent()); + } + + @Test + @WithMockUser(roles = "CUSTOMER") + void testGetTimeLogsForService_Success() throws Exception { + when(timeLogService.getTimeLogsByServiceId("service456")) + .thenReturn(Arrays.asList(testResponse)); + + mockMvc.perform(get("/time-logs/service/{serviceId}", "service456")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].serviceId").value("service456")); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testGetTimeLogsForProject_Success() throws Exception { + when(timeLogService.getTimeLogsByProjectId("project789")) + .thenReturn(Arrays.asList(testResponse)); + + mockMvc.perform(get("/time-logs/project/{projectId}", "project789")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].projectId").value("project789")); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testGetEmployeeStats_Success() throws Exception { + Map stats = new HashMap<>(); + stats.put("employeeId", "employee123"); + stats.put("totalHours", 40.0); + stats.put("totalLogs", 5); + + when(timeLogService.getEmployeeStatistics("employee123")) + .thenReturn(stats); + + mockMvc.perform(get("/time-logs/stats") + .header("X-User-Subject", "employee123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.employeeId").value("employee123")) + .andExpect(jsonPath("$.totalHours").value(40.0)); + } + + @Test + @WithMockUser(roles = "EMPLOYEE") + void testGetMyTimeLogs_WithDateRange() throws Exception { + when(timeLogService.getTimeLogsByDateRange(anyString(), any(LocalDate.class), any(LocalDate.class))) + .thenReturn(Arrays.asList(testResponse)); + + mockMvc.perform(get("/time-logs") + .header("X-User-Subject", "employee123") + .header("X-User-Roles", "ROLE_EMPLOYEE") + .param("from", "2025-11-01") + .param("to", "2025-11-30")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } +} diff --git a/time-logging-service/src/test/java/com/techtorque/time_logging_service/repository/TimeLogRepositoryTest.java b/time-logging-service/src/test/java/com/techtorque/time_logging_service/repository/TimeLogRepositoryTest.java new file mode 100644 index 0000000..f10bf50 --- /dev/null +++ b/time-logging-service/src/test/java/com/techtorque/time_logging_service/repository/TimeLogRepositoryTest.java @@ -0,0 +1,235 @@ +package com.techtorque.time_logging_service.repository; + +import com.techtorque.time_logging_service.entity.TimeLog; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class TimeLogRepositoryTest { + + @Autowired + private TimeLogRepository timeLogRepository; + + private TimeLog testTimeLog; + + @BeforeEach + void setUp() { + timeLogRepository.deleteAll(); + + testTimeLog = TimeLog.builder() + .employeeId("employee123") + .serviceId("service456") + .projectId("project789") + .hours(8.0) + .date(LocalDate.of(2025, 11, 21)) + .description("Worked on feature implementation") + .workType("Development") + .build(); + } + + @Test + void testSaveTimeLog() { + TimeLog saved = timeLogRepository.save(testTimeLog); + + assertThat(saved).isNotNull(); + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getEmployeeId()).isEqualTo("employee123"); + assertThat(saved.getHours()).isEqualTo(8.0); + assertThat(saved.getDate()).isEqualTo(LocalDate.of(2025, 11, 21)); + } + + @Test + void testFindById() { + timeLogRepository.save(testTimeLog); + + Optional found = timeLogRepository.findById(testTimeLog.getId()); + + assertThat(found).isPresent(); + assertThat(found.get().getEmployeeId()).isEqualTo("employee123"); + } + + @Test + void testFindByEmployeeId() { + TimeLog timeLog2 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service999") + .hours(4.0) + .date(LocalDate.of(2025, 11, 22)) + .description("Code review") + .workType("Review") + .build(); + + timeLogRepository.save(testTimeLog); + timeLogRepository.save(timeLog2); + + List employeeLogs = timeLogRepository.findByEmployeeId("employee123"); + + assertThat(employeeLogs).hasSize(2); + assertThat(employeeLogs).allMatch(log -> log.getEmployeeId().equals("employee123")); + } + + @Test + void testFindByServiceId() { + timeLogRepository.save(testTimeLog); + + List serviceLogs = timeLogRepository.findByServiceId("service456"); + + assertThat(serviceLogs).hasSize(1); + assertThat(serviceLogs.get(0).getServiceId()).isEqualTo("service456"); + } + + @Test + void testFindByProjectId() { + timeLogRepository.save(testTimeLog); + + List projectLogs = timeLogRepository.findByProjectId("project789"); + + assertThat(projectLogs).hasSize(1); + assertThat(projectLogs.get(0).getProjectId()).isEqualTo("project789"); + } + + @Test + void testFindByIdAndEmployeeId() { + timeLogRepository.save(testTimeLog); + + Optional found = timeLogRepository.findByIdAndEmployeeId( + testTimeLog.getId(), "employee123"); + + assertThat(found).isPresent(); + assertThat(found.get().getId()).isEqualTo(testTimeLog.getId()); + } + + @Test + void testFindByIdAndEmployeeId_DifferentEmployee() { + timeLogRepository.save(testTimeLog); + + Optional found = timeLogRepository.findByIdAndEmployeeId( + testTimeLog.getId(), "differentEmployee"); + + assertThat(found).isEmpty(); + } + + @Test + void testFindByEmployeeIdAndDateBetween() { + TimeLog log1 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .hours(8.0) + .date(LocalDate.of(2025, 11, 20)) + .description("Day 1") + .build(); + + TimeLog log2 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service2") + .hours(7.0) + .date(LocalDate.of(2025, 11, 21)) + .description("Day 2") + .build(); + + TimeLog log3 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service3") + .hours(6.0) + .date(LocalDate.of(2025, 11, 25)) + .description("Day 3 - outside range") + .build(); + + timeLogRepository.save(log1); + timeLogRepository.save(log2); + timeLogRepository.save(log3); + + List logsInRange = timeLogRepository.findByEmployeeIdAndDateBetween( + "employee123", + LocalDate.of(2025, 11, 20), + LocalDate.of(2025, 11, 22) + ); + + assertThat(logsInRange).hasSize(2); + assertThat(logsInRange).noneMatch(log -> log.getDate().equals(LocalDate.of(2025, 11, 25))); + } + + @Test + void testGetTotalHoursByEmployeeId() { + TimeLog log1 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .hours(8.0) + .date(LocalDate.of(2025, 11, 20)) + .description("Day 1") + .build(); + + TimeLog log2 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service2") + .hours(7.5) + .date(LocalDate.of(2025, 11, 21)) + .description("Day 2") + .build(); + + timeLogRepository.save(log1); + timeLogRepository.save(log2); + + Double totalHours = timeLogRepository.getTotalHoursByEmployeeId("employee123"); + + assertThat(totalHours).isEqualTo(15.5); + } + + @Test + void testGetTotalHoursByEmployeeId_NoLogs() { + Double totalHours = timeLogRepository.getTotalHoursByEmployeeId("nonexistent"); + + assertThat(totalHours).isNull(); + } + + @Test + void testUpdateTimeLog() { + timeLogRepository.save(testTimeLog); + + testTimeLog.setHours(10.0); + testTimeLog.setDescription("Updated description"); + TimeLog updated = timeLogRepository.save(testTimeLog); + + assertThat(updated.getHours()).isEqualTo(10.0); + assertThat(updated.getDescription()).isEqualTo("Updated description"); + } + + @Test + void testDeleteTimeLog() { + timeLogRepository.save(testTimeLog); + String logId = testTimeLog.getId(); + + timeLogRepository.deleteById(logId); + + Optional deleted = timeLogRepository.findById(logId); + assertThat(deleted).isEmpty(); + } + + @Test + void testTimeLogWithNullProjectId() { + TimeLog logWithoutProject = TimeLog.builder() + .employeeId("employee123") + .serviceId("service456") + .hours(5.0) + .date(LocalDate.of(2025, 11, 21)) + .description("Service only work") + .build(); + + TimeLog saved = timeLogRepository.save(logWithoutProject); + + assertThat(saved.getProjectId()).isNull(); + assertThat(saved.getServiceId()).isNotNull(); + } +} diff --git a/time-logging-service/src/test/java/com/techtorque/time_logging_service/service/TimeLoggingServiceImplTest.java b/time-logging-service/src/test/java/com/techtorque/time_logging_service/service/TimeLoggingServiceImplTest.java new file mode 100644 index 0000000..d533f63 --- /dev/null +++ b/time-logging-service/src/test/java/com/techtorque/time_logging_service/service/TimeLoggingServiceImplTest.java @@ -0,0 +1,282 @@ +package com.techtorque.time_logging_service.service; + +import com.techtorque.time_logging_service.dto.request.TimeLogRequest; +import com.techtorque.time_logging_service.dto.request.TimeLogUpdateRequest; +import com.techtorque.time_logging_service.dto.response.TimeLogSummaryResponse; +import com.techtorque.time_logging_service.entity.TimeLog; +import com.techtorque.time_logging_service.repository.TimeLogRepository; +import com.techtorque.time_logging_service.service.impl.TimeLoggingServiceImpl; +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.junit.jupiter.MockitoExtension; +import org.springframework.security.access.AccessDeniedException; + +import java.time.LocalDate; +import java.util.Arrays; +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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TimeLoggingServiceImplTest { + + @Mock + private TimeLogRepository timeLogRepository; + + @InjectMocks + private TimeLoggingServiceImpl timeLoggingService; + + private TimeLog testTimeLog; + private TimeLogRequest testRequest; + + @BeforeEach + void setUp() { + testTimeLog = TimeLog.builder() + .id("log123") + .employeeId("employee123") + .serviceId("service456") + .projectId("project789") + .hours(8.0) + .date(LocalDate.of(2025, 11, 21)) + .description("Worked on feature") + .workType("Development") + .build(); + + testRequest = new TimeLogRequest(); + testRequest.setServiceId("service456"); + testRequest.setProjectId("project789"); + testRequest.setHours(8.0); + testRequest.setDate(LocalDate.of(2025, 11, 21)); + testRequest.setDescription("Worked on feature"); + testRequest.setWorkType("Development"); + } + + @Test + void testLogWorkTime_Success() { + when(timeLogRepository.save(any(TimeLog.class))).thenReturn(testTimeLog); + + TimeLog result = timeLoggingService.logWorkTime(testRequest, "employee123"); + + assertThat(result).isNotNull(); + assertThat(result.getEmployeeId()).isEqualTo("employee123"); + assertThat(result.getHours()).isEqualTo(8.0); + verify(timeLogRepository, times(1)).save(any(TimeLog.class)); + } + + @Test + void testGetLogsForService() { + TimeLog log2 = TimeLog.builder() + .id("log456") + .employeeId("employee999") + .serviceId("service456") + .hours(4.0) + .date(LocalDate.of(2025, 11, 22)) + .description("Another task") + .build(); + + when(timeLogRepository.findByServiceId("service456")) + .thenReturn(Arrays.asList(testTimeLog, log2)); + + List results = timeLoggingService.getLogsForService("service456"); + + assertThat(results).hasSize(2); + assertThat(results).allMatch(log -> log.getServiceId().equals("service456")); + verify(timeLogRepository, times(1)).findByServiceId("service456"); + } + + @Test + void testGetLogDetails_Success() { + when(timeLogRepository.findByIdAndEmployeeId("log123", "employee123")) + .thenReturn(Optional.of(testTimeLog)); + + Optional result = timeLoggingService.getLogDetails("log123", "employee123"); + + assertThat(result).isPresent(); + assertThat(result.get().getId()).isEqualTo("log123"); + } + + @Test + void testGetLogDetails_AccessDenied() { + when(timeLogRepository.findByIdAndEmployeeId("log123", "wrongEmployee")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> timeLoggingService.getLogDetails("log123", "wrongEmployee")) + .isInstanceOf(AccessDeniedException.class) + .hasMessageContaining("Access denied"); + } + + @Test + void testUpdateLog_Success() { + TimeLogUpdateRequest updateRequest = new TimeLogUpdateRequest(); + updateRequest.setHours(10.0); + updateRequest.setDescription("Updated description"); + + when(timeLogRepository.findByIdAndEmployeeId("log123", "employee123")) + .thenReturn(Optional.of(testTimeLog)); + when(timeLogRepository.save(any(TimeLog.class))).thenReturn(testTimeLog); + + TimeLog result = timeLoggingService.updateLog("log123", updateRequest, "employee123"); + + assertThat(result).isNotNull(); + verify(timeLogRepository, times(1)).save(any(TimeLog.class)); + } + + @Test + void testUpdateLog_AccessDenied() { + TimeLogUpdateRequest updateRequest = new TimeLogUpdateRequest(); + updateRequest.setHours(10.0); + + when(timeLogRepository.findByIdAndEmployeeId("log123", "wrongEmployee")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> timeLoggingService.updateLog("log123", updateRequest, "wrongEmployee")) + .isInstanceOf(AccessDeniedException.class); + } + + @Test + void testUpdateLog_PartialUpdate() { + TimeLogUpdateRequest updateRequest = new TimeLogUpdateRequest(); + updateRequest.setHours(10.0); + // Other fields are null + + when(timeLogRepository.findByIdAndEmployeeId("log123", "employee123")) + .thenReturn(Optional.of(testTimeLog)); + when(timeLogRepository.save(any(TimeLog.class))).thenAnswer(invocation -> { + TimeLog saved = invocation.getArgument(0); + assertThat(saved.getHours()).isEqualTo(10.0); + assertThat(saved.getDescription()).isEqualTo("Worked on feature"); // unchanged + return saved; + }); + + timeLoggingService.updateLog("log123", updateRequest, "employee123"); + + verify(timeLogRepository, times(1)).save(any(TimeLog.class)); + } + + @Test + void testDeleteLog_Success() { + when(timeLogRepository.findByIdAndEmployeeId("log123", "employee123")) + .thenReturn(Optional.of(testTimeLog)); + doNothing().when(timeLogRepository).delete(any(TimeLog.class)); + + timeLoggingService.deleteLog("log123", "employee123"); + + verify(timeLogRepository, times(1)).delete(testTimeLog); + } + + @Test + void testDeleteLog_AccessDenied() { + when(timeLogRepository.findByIdAndEmployeeId("log123", "wrongEmployee")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> timeLoggingService.deleteLog("log123", "wrongEmployee")) + .isInstanceOf(AccessDeniedException.class); + } + + @Test + void testGetEmployeeSummary_Month() { + TimeLog log1 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .projectId("project1") + .hours(8.0) + .date(LocalDate.of(2025, 11, 15)) + .build(); + + TimeLog log2 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .projectId("project2") + .hours(6.0) + .date(LocalDate.of(2025, 11, 20)) + .build(); + + TimeLog log3 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service2") + .projectId("project1") + .hours(4.0) + .date(LocalDate.of(2025, 11, 25)) + .build(); + + when(timeLogRepository.findByEmployeeIdAndDateBetween( + eq("employee123"), + eq(LocalDate.of(2025, 11, 1)), + eq(LocalDate.of(2025, 11, 30)) + )).thenReturn(Arrays.asList(log1, log2, log3)); + + TimeLogSummaryResponse summary = timeLoggingService.getEmployeeSummary( + "employee123", "month", "2025-11"); + + assertThat(summary).isNotNull(); + assertThat(summary.getTotalHours()).isEqualTo(18.0); + assertThat(summary.getCount()).isEqualTo(3); + assertThat(summary.getByService()).hasSize(2); + assertThat(summary.getByService().get("service1")).isEqualTo(14.0); + assertThat(summary.getByService().get("service2")).isEqualTo(4.0); + assertThat(summary.getByProject()).hasSize(2); + assertThat(summary.getByProject().get("project1")).isEqualTo(12.0); + assertThat(summary.getByProject().get("project2")).isEqualTo(6.0); + } + + @Test + void testGetEmployeeSummary_Week() { + TimeLog log1 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .hours(8.0) + .date(LocalDate.of(2025, 11, 21)) + .build(); + + TimeLog log2 = TimeLog.builder() + .employeeId("employee123") + .serviceId("service1") + .hours(7.0) + .date(LocalDate.of(2025, 11, 22)) + .build(); + + when(timeLogRepository.findByEmployeeIdAndDateBetween( + eq("employee123"), + eq(LocalDate.of(2025, 11, 21)), + eq(LocalDate.of(2025, 11, 27)) + )).thenReturn(Arrays.asList(log1, log2)); + + TimeLogSummaryResponse summary = timeLoggingService.getEmployeeSummary( + "employee123", "week", "2025-11-21"); + + assertThat(summary).isNotNull(); + assertThat(summary.getTotalHours()).isEqualTo(15.0); + assertThat(summary.getCount()).isEqualTo(2); + } + + @Test + void testGetEmployeeSummary_InvalidPeriod() { + assertThatThrownBy(() -> timeLoggingService.getEmployeeSummary( + "employee123", "invalid", "2025-11-21")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid period"); + } + + @Test + void testGetEmployeeSummary_EmptyResult() { + when(timeLogRepository.findByEmployeeIdAndDateBetween( + anyString(), any(LocalDate.class), any(LocalDate.class) + )).thenReturn(Arrays.asList()); + + TimeLogSummaryResponse summary = timeLoggingService.getEmployeeSummary( + "employee123", "month", "2025-11"); + + assertThat(summary.getTotalHours()).isEqualTo(0.0); + assertThat(summary.getCount()).isEqualTo(0); + assertThat(summary.getByService()).isEmpty(); + assertThat(summary.getByProject()).isEmpty(); + } +}