diff --git a/docker-compose.yml b/docker-compose.yml index 57ea10d..b44bc1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ services: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/analytics_db_dev - SPRING_DATASOURCE_USERNAME: analytics_user - SPRING_DATASOURCE_PASSWORD: password + SPRING_DATASOURCE_USERNAME: postgres + SPRING_DATASOURCE_PASSWORD: 123 SPRING_RABBITMQ_HOST: rabbitmq # RabbitMQ container name or use its IP if needed SPRING_RABBITMQ_PORT: 5672 SPRING_RABBITMQ_USERNAME: guest @@ -23,8 +23,8 @@ services: - "5432:5432" environment: POSTGRES_DB: analytics_db_dev - POSTGRES_USER: analytics_user - POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 123 volumes: - db_data:/var/lib/postgresql/data @@ -47,4 +47,3 @@ services: volumes: db_data: - \ No newline at end of file diff --git a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java index 6fdaf48..eae7c5e 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java @@ -23,7 +23,6 @@ public interface CourierRepository extends JpaRepository { + "LEFT JOIN orders o " + "ON c.id = o.courier_id " + "AND o.final_status_timestamp BETWEEN :startDate AND :endDate " - + "AND o.status IN ('COMPLETED', 'FAILED') " + "GROUP BY c.id, c.name " + "ORDER BY courierId", nativeQuery = true) List findCourierPerformanceBetweenDates( diff --git a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java index 9a70a67..8376613 100644 --- a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java @@ -53,7 +53,7 @@ public List getCourierSuccessRate( .courierId(data.getCourierId()) .courierName(data.getCourierName()) .successRate( - MetricCalculator.calculatePercentage( + MetricCalculator.calculateRate( data.getCompletedCount(), data.getDeliveryCount())) .build()) @@ -81,7 +81,7 @@ public List getCourierPerformanceReport( .courierName(data.getCourierName()) .deliveryCount(data.getDeliveryCount()) .successRate( - MetricCalculator.calculatePercentage( + MetricCalculator.calculateRate( data.getCompletedCount(), data.getDeliveryCount())) .averageRating(data.getAverageRating()) diff --git a/src/main/java/com/Podzilla/analytics/util/MetricCalculator.java b/src/main/java/com/Podzilla/analytics/util/MetricCalculator.java index 1e91256..a32839b 100644 --- a/src/main/java/com/Podzilla/analytics/util/MetricCalculator.java +++ b/src/main/java/com/Podzilla/analytics/util/MetricCalculator.java @@ -6,8 +6,6 @@ public final class MetricCalculator { private static final int DEFAULT_SCALE = 2; - private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); - private MetricCalculator() { throw new UnsupportedOperationException( "This is a utility class and cannot be instantiated"); @@ -25,7 +23,7 @@ private MetricCalculator() { * @return The calculated percentage as a BigDecimal, or BigDecimal.ZERO * if the denominator is zero. */ - public static BigDecimal calculatePercentage(final long numerator, + public static BigDecimal calculateRate(final long numerator, final long denominator, final int scale, final RoundingMode roundingMode) { if (denominator == 0) { @@ -35,7 +33,6 @@ public static BigDecimal calculatePercentage(final long numerator, return BigDecimal.ZERO; } return BigDecimal.valueOf(numerator) - .multiply(ONE_HUNDRED) .divide(BigDecimal.valueOf(denominator), scale, roundingMode); } @@ -47,9 +44,9 @@ public static BigDecimal calculatePercentage(final long numerator, * @return The calculated percentage (scale 2, HALF_UP rounding), or * BigDecimal.ZERO if denominator is zero. */ - public static BigDecimal calculatePercentage(final long numerator, + public static BigDecimal calculateRate(final long numerator, final long denominator) { - return calculatePercentage(numerator, denominator, DEFAULT_SCALE, + return calculateRate(numerator, denominator, DEFAULT_SCALE, RoundingMode.HALF_UP); } } diff --git a/src/test/java/com/Podzilla/analytics/api/controllers/FulfillmentReportControllerTest.java b/src/test/java/com/Podzilla/analytics/api/controllers/FulfillmentReportControllerTest.java deleted file mode 100644 index 3d87b5f..0000000 --- a/src/test/java/com/Podzilla/analytics/api/controllers/FulfillmentReportControllerTest.java +++ /dev/null @@ -1,335 +0,0 @@ -// package com.Podzilla.analytics.api.controllers; - -// import static org.junit.jupiter.api.Assertions.assertEquals; -// import static org.junit.jupiter.api.Assertions.assertNotNull; -// import static org.junit.jupiter.api.Assertions.assertTrue; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.Mockito.mock; -// import static org.mockito.Mockito.when; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; - -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.springframework.http.HttpStatus; -// import org.springframework.http.ResponseEntity; - -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; -// import com.Podzilla.analytics.services.FulfillmentAnalyticsService; - -// public class FulfillmentReportControllerTest { - -// private FulfillmentReportController controller; -// private FulfillmentAnalyticsService mockService; - -// private LocalDate startDate; -// private LocalDate endDate; -// private List overallTimeResponses; -// private List regionTimeResponses; -// private List courierTimeResponses; - -// @BeforeEach -// public void setup() { -// mockService = mock(FulfillmentAnalyticsService.class); -// controller = new FulfillmentReportController(mockService); - -// startDate = LocalDate.of(2024, 1, 1); -// endDate = LocalDate.of(2024, 1, 31); - -// // Setup test data -// overallTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("OVERALL") -// .averageDuration(BigDecimal.valueOf(24.5)) -// .build()); - -// regionTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("RegionID_1") -// .averageDuration(BigDecimal.valueOf(20.2)) -// .build(), -// FulfillmentTimeResponse.builder() -// .groupByValue("RegionID_2") -// .averageDuration(BigDecimal.valueOf(28.7)) -// .build()); - -// courierTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("CourierID_1") -// .averageDuration(BigDecimal.valueOf(18.3)) -// .build(), -// FulfillmentTimeResponse.builder() -// .groupByValue("CourierID_2") -// .averageDuration(BigDecimal.valueOf(22.1)) -// .build()); -// } - -// @Test -// public void testGetPlaceToShipTime_Overall() { -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// assertEquals(PlaceToShipGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); -// assertEquals(BigDecimal.valueOf(24.5), response.getBody().get(0).getAverageDuration()); -// } - -// @Test -// public void testGetPlaceToShipTime_ByRegion() { -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.REGION)) -// .thenReturn(regionTimeResponses); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.REGION); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(regionTimeResponses, response.getBody()); -// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); -// } - -// @Test -// public void testGetShipToDeliverTime_Overall() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// assertEquals(ShipToDeliverGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); -// } - -// @Test -// public void testGetShipToDeliverTime_ByRegion() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.REGION)) -// .thenReturn(regionTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.REGION); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(regionTimeResponses, response.getBody()); -// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); -// } - -// @Test -// public void testGetShipToDeliverTime_ByCourier() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.COURIER)) -// .thenReturn(courierTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.COURIER); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(courierTimeResponses, response.getBody()); -// assertEquals("CourierID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("CourierID_2", response.getBody().get(1).getGroupByValue()); -// } - -// // Edge case tests - -// @Test -// public void testGetPlaceToShipTime_EmptyResponse() { -// // Configure mock service to return empty list -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// @Test -// public void testGetShipToDeliverTime_EmptyResponse() { -// // Configure mock service to return empty list -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// // @Test -// // public void testGetPlaceToShipTime_InvalidGroupBy() { -// // // Create request with invalid groupBy -// // FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// // startDate, endDate, null); - -// // // Execute the method - should return bad request due to validation error -// // ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // // Verify response -// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); -// // } - -// // @Test -// // public void testGetShipToDeliverTime_InvalidGroupBy() { -// // // Create request with invalid groupBy -// // FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// // startDate, endDate, null); - -// // // Execute the method - should return bad request due to validation error -// // ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // // Verify response -// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); -// // } - -// @Test -// public void testGetPlaceToShipTime_SameDayRange() { -// // Test same start and end date -// LocalDate sameDate = LocalDate.of(2024, 1, 1); - -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request with same start and end date -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// sameDate, sameDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// } - -// @Test -// public void testGetShipToDeliverTime_SameDayRange() { -// // Test same start and end date -// LocalDate sameDate = LocalDate.of(2024, 1, 1); - -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request with same start and end date -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// } - -// @Test -// public void testGetPlaceToShipTime_FutureDates() { -// // Test future dates -// LocalDate futureStart = LocalDate.now().plusDays(1); -// LocalDate futureEnd = LocalDate.now().plusDays(30); - -// // Configure mock service - should return empty for future dates -// when(mockService.getPlaceToShipTimeResponse( -// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request with future dates -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// @Test -// public void testGetShipToDeliverTime_ServiceException() { -// // Configure mock service to throw exception -// when(mockService.getShipToDeliverTimeResponse( -// any(), any(), any())) -// .thenThrow(new RuntimeException("Service error")); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method - controller should handle exception -// // Note: Actual behavior depends on how controller handles exceptions -// // This might need adjustment based on actual implementation -// try { -// controller.getShipToDeliverTime(request); -// } catch (RuntimeException e) { -// assertEquals("Service error", e.getMessage()); -// } -// } -// } \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java new file mode 100644 index 0000000..f383c56 --- /dev/null +++ b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java @@ -0,0 +1,321 @@ +package com.Podzilla.analytics.controllers; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.AfterEach; +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.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.transaction.annotation.Transactional; + +import com.Podzilla.analytics.models.Courier; +import com.Podzilla.analytics.models.Customer; +import com.Podzilla.analytics.models.Order; +import com.Podzilla.analytics.models.Region; +import com.Podzilla.analytics.repositories.CourierRepository; +import com.Podzilla.analytics.repositories.CustomerRepository; +import com.Podzilla.analytics.repositories.OrderRepository; +import com.Podzilla.analytics.repositories.RegionRepository; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +@ActiveProfiles("test") +public class CourierAnalyticsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private CourierRepository courierRepository; + @Autowired + private CustomerRepository customerRepository; + @Autowired + private RegionRepository regionRepository; + @Autowired + private OrderRepository orderRepository; + + @Autowired + private EntityManager entityManager; + + private static final DateTimeFormatter ISO_LOCAL_DATE = DateTimeFormatter.ISO_LOCAL_DATE; + + private Customer customer1; + private Region region1; + private Courier courierJane; + private Courier courierJohn; + + @BeforeEach + void setUp() { + orderRepository.deleteAll(); + courierRepository.deleteAll(); + customerRepository.deleteAll(); + regionRepository.deleteAll(); + + entityManager.flush(); + entityManager.clear(); + + customer1 = customerRepository.save(Customer.builder().name("John Doe").build()); + region1 = regionRepository.save(Region.builder() + .city("Sample City") + .state("Sample State") + .country("Sample Country") + .postalCode("12345") + .build()); + + courierJane = courierRepository.save(Courier.builder() + .name("Jane Smith") + .status(Courier.CourierStatus.ACTIVE) + .build()); + + courierJohn = courierRepository.save(Courier.builder() + .name("John Doe") + .status(Courier.CourierStatus.ACTIVE) + .build()); + + orderRepository.save(Order.builder() + .totalAmount(new BigDecimal("50.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("4.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .totalAmount(new BigDecimal("75.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("4.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .totalAmount(new BigDecimal("120.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(2) + .courierRating(new BigDecimal("5.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .totalAmount(new BigDecimal("30.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) + .status(Order.OrderStatus.FAILED) + .numberOfItems(1) + .courierRating(null) + .customer(customer1) + .courier(courierJohn) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .totalAmount(new BigDecimal("90.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("3.0")) + .customer(customer1) + .courier(courierJohn) + .region(region1) + .build()); + + entityManager.flush(); + entityManager.clear(); + } + + @AfterEach + void tearDown() { + orderRepository.deleteAll(); + courierRepository.deleteAll(); + customerRepository.deleteAll(); + regionRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + } + + @Test + void contextLoads() { + } + + @Test + void getCourierDeliveryCounts_shouldReturnCountsForSpecificDateRange() throws Exception { + LocalDate startDate = LocalDate.now().minusDays(4); + LocalDate endDate = LocalDate.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))); + } + + @Test + void getCourierDeliveryCounts_shouldReturnZeroCountsWhenNoDeliveriesInDateRange() + throws Exception { + LocalDate startDate = LocalDate.now().plusDays(1); + LocalDate endDate = LocalDate.now().plusDays(2); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].courierName").value(hasItem("Jane Smith"))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(0))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].courierName").value(hasItem("John Doe"))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(0))); + } + + @Test + void getCourierDeliveryCounts_shouldHandleInvalidDateRange_startDateAfterEndDate() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(1); + LocalDateTime endDate = LocalDateTime.now().minusDays(3); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isBadRequest()); + } + + @Test + void getCourierSuccessRate_shouldReturnSuccessRatesForSpecificDateRange() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/success-rate") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect( + jsonPath("$[?(@.courierName == 'Jane Smith')].successRate").value(hasItem(closeTo(1.0, 0.001)))) + .andExpect( + jsonPath("$[?(@.courierName == 'John Doe')].successRate").value(hasItem(closeTo(0.5, 0.001)))); + } + + @Test + void getCourierAverageRating_shouldReturnAllAverageRatingsWhenNoDateRangeProvided() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now(); + mockMvc.perform(get("/courier-analytics/average-rating") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.333, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierAverageRating_shouldReturnAverageRatingsForSpecificDateRange() + throws Exception { + + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/average-rating") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierPerformanceReport_shouldReturnAllPerformanceReportsWhenNoDateRangeProvided() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now(); + mockMvc.perform(get("/courier-analytics/performance-report") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(3))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") + .value(hasItem(closeTo(1.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.333, 0.001)))) + + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") + .value(hasItem(closeTo(0.5, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierPerformanceReport_shouldReturnPerformanceReportsForSpecificDateRange() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/performance-report") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") + .value(hasItem(closeTo(1.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.0, 0.001)))) + + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") + .value(hasItem(closeTo(0.5, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } +} diff --git a/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java new file mode 100644 index 0000000..61e3254 --- /dev/null +++ b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java @@ -0,0 +1,339 @@ +package com.Podzilla.analytics.controllers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import com.Podzilla.analytics.api.controllers.FulfillmentReportController; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; +import com.Podzilla.analytics.services.FulfillmentAnalyticsService; + +public class FulfillmentReportControllerTest { + + private FulfillmentReportController controller; + private FulfillmentAnalyticsService mockService; + + private LocalDate startDate; + private LocalDate endDate; + private List overallTimeResponses; + private List regionTimeResponses; + private List courierTimeResponses; + + @BeforeEach + public void setup() { + mockService = mock(FulfillmentAnalyticsService.class); + controller = new FulfillmentReportController(mockService); + + startDate = LocalDate.of(2024, 1, 1); + endDate = LocalDate.of(2024, 1, 31); + + // Setup test data + overallTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("OVERALL") + .averageDuration(BigDecimal.valueOf(24.5)) + .build()); + + regionTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("RegionID_1") + .averageDuration(BigDecimal.valueOf(20.2)) + .build(), + FulfillmentTimeResponse.builder() + .groupByValue("RegionID_2") + .averageDuration(BigDecimal.valueOf(28.7)) + .build()); + + courierTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("CourierID_1") + .averageDuration(BigDecimal.valueOf(18.3)) + .build(), + FulfillmentTimeResponse.builder() + .groupByValue("CourierID_2") + .averageDuration(BigDecimal.valueOf(22.1)) + .build()); + } + + @Test + public void testGetPlaceToShipTime_Overall() { + // Configure mock service + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Create request + FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + startDate, endDate, PlaceToShipGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getPlaceToShipTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(overallTimeResponses, response.getBody()); + assertEquals(PlaceToShipGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); + assertEquals(BigDecimal.valueOf(24.5), response.getBody().get(0).getAverageDuration()); + } + + @Test + public void testGetPlaceToShipTime_ByRegion() { + // Configure mock service + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.REGION)) + .thenReturn(regionTimeResponses); + + // Create request + FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + startDate, endDate, PlaceToShipGroupBy.REGION); + + // Execute the method + ResponseEntity> response = controller.getPlaceToShipTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(regionTimeResponses, response.getBody()); + assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); + assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); + } + + @Test + public void testGetShipToDeliverTime_Overall() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Create request + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + startDate, endDate, ShipToDeliverGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getShipToDeliverTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(overallTimeResponses, response.getBody()); + assertEquals(ShipToDeliverGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); + } + + @Test + public void testGetShipToDeliverTime_ByRegion() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.REGION)) + .thenReturn(regionTimeResponses); + + // Create request + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + startDate, endDate, ShipToDeliverGroupBy.REGION); + + // Execute the method + ResponseEntity> response = controller.getShipToDeliverTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(regionTimeResponses, response.getBody()); + assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); + assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); + } + + @Test + public void testGetShipToDeliverTime_ByCourier() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.COURIER)) + .thenReturn(courierTimeResponses); + + // Create request + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + startDate, endDate, ShipToDeliverGroupBy.COURIER); + + // Execute the method + ResponseEntity> response = controller.getShipToDeliverTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(courierTimeResponses, response.getBody()); + assertEquals("CourierID_1", response.getBody().get(0).getGroupByValue()); + assertEquals("CourierID_2", response.getBody().get(1).getGroupByValue()); + } + + // Edge case tests + + @Test + public void testGetPlaceToShipTime_EmptyResponse() { + // Configure mock service to return empty list + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Create request + FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + startDate, endDate, PlaceToShipGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getPlaceToShipTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isEmpty()); + } + + @Test + public void testGetShipToDeliverTime_EmptyResponse() { + // Configure mock service to return empty list + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Create request + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + startDate, endDate, ShipToDeliverGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getShipToDeliverTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isEmpty()); + } + + // @Test + // public void testGetPlaceToShipTime_InvalidGroupBy() { + // // Create request with invalid groupBy + // FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + // startDate, endDate, null); + + // // Execute the method - should return bad request due to validation error + // ResponseEntity> response = + // controller.getPlaceToShipTime(request); + + // // Verify response + // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + // } + + // @Test + // public void testGetShipToDeliverTime_InvalidGroupBy() { + // // Create request with invalid groupBy + // FulfillmentShipToDeliverRequest request = new + // FulfillmentShipToDeliverRequest( + // startDate, endDate, null); + + // // Execute the method - should return bad request due to validation error + // ResponseEntity> response = + // controller.getShipToDeliverTime(request); + + // // Verify response + // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + // } + + @Test + public void testGetPlaceToShipTime_SameDayRange() { + // Test same start and end date + LocalDate sameDate = LocalDate.of(2024, 1, 1); + + // Configure mock service + when(mockService.getPlaceToShipTimeResponse( + sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Create request with same start and end date + FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + sameDate, sameDate, PlaceToShipGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getPlaceToShipTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(overallTimeResponses, response.getBody()); + } + + @Test + public void testGetShipToDeliverTime_SameDayRange() { + // Test same start and end date + LocalDate sameDate = LocalDate.of(2024, 1, 1); + + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Create request with same start and end date + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + sameDate, sameDate, ShipToDeliverGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getShipToDeliverTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(overallTimeResponses, response.getBody()); + } + + @Test + public void testGetPlaceToShipTime_FutureDates() { + // Test future dates + LocalDate futureStart = LocalDate.now().plusDays(1); + LocalDate futureEnd = LocalDate.now().plusDays(30); + + // Configure mock service - should return empty for future dates + when(mockService.getPlaceToShipTimeResponse( + futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Create request with future dates + FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( + futureStart, futureEnd, PlaceToShipGroupBy.OVERALL); + + // Execute the method + ResponseEntity> response = controller.getPlaceToShipTime(request); + + // Verify response + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isEmpty()); + } + + @Test + public void testGetShipToDeliverTime_ServiceException() { + // Configure mock service to throw exception + when(mockService.getShipToDeliverTimeResponse( + any(), any(), any())) + .thenThrow(new RuntimeException("Service error")); + + // Create request + FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( + startDate, endDate, ShipToDeliverGroupBy.OVERALL); + + // Execute the method - controller should handle exception + // Note: Actual behavior depends on how controller handles exceptions + // This might need adjustment based on actual implementation + try { + controller.getShipToDeliverTime(request); + } catch (RuntimeException e) { + assertEquals("Service error", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java new file mode 100644 index 0000000..1cb8c82 --- /dev/null +++ b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java @@ -0,0 +1,345 @@ +package com.Podzilla.analytics.services; + +import com.Podzilla.analytics.api.dtos.courier.CourierAverageRatingResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierDeliveryCountResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; +import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; +import com.Podzilla.analytics.repositories.CourierRepository; + +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CourierAnalyticsServiceTest { + + @Mock + private CourierRepository courierRepository; + + @InjectMocks + private CourierAnalyticsService courierAnalyticsService; + + private LocalDate testStartDate; + private LocalDate testEndDate; + private LocalDateTime expectedStartDateTime; + private LocalDateTime expectedEndDateTime; + + @BeforeEach + void setUp() { + testStartDate = LocalDate.of(2024, 1, 1); + testEndDate = LocalDate.of(2024, 1, 31); + expectedStartDateTime = testStartDate.atStartOfDay(); + expectedEndDateTime = testEndDate.atTime(LocalTime.MAX); + } + + private CourierPerformanceProjection createMockProjection( + Long courierId, String courierName, Long deliveryCount, Long completedCount, BigDecimal averageRating) { + CourierPerformanceProjection mockProjection = Mockito.mock(CourierPerformanceProjection.class); + Mockito.lenient().when(mockProjection.getCourierId()).thenReturn(courierId); + Mockito.lenient().when(mockProjection.getCourierName()).thenReturn(courierName); + Mockito.lenient().when(mockProjection.getDeliveryCount()).thenReturn(deliveryCount); + Mockito.lenient().when(mockProjection.getCompletedCount()).thenReturn(completedCount); + Mockito.lenient().when(mockProjection.getAverageRating()).thenReturn(averageRating); + return mockProjection; + } + + @Test + void getCourierDeliveryCounts_shouldReturnCorrectCountsForMultipleCouriers() { + // Arrange + CourierPerformanceProjection janeData = createMockProjection( + 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); + CourierPerformanceProjection johnData = createMockProjection( + 2L, "John", 5L, 5L, new BigDecimal("4.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData)); + + // Act + List result = courierAnalyticsService + .getCourierDeliveryCounts(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + + CourierDeliveryCountResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(1L, janeResponse.getCourierId()); + assertEquals(10, janeResponse.getDeliveryCount()); + + CourierDeliveryCountResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(2L, johnResponse.getCourierId()); + assertEquals(5, johnResponse.getDeliveryCount()); + + // Verify repository method was called with correct LocalDateTime arguments + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierDeliveryCounts_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierDeliveryCounts(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldReturnCorrectRates() { + // Arrange + // Jane: 8 completed out of 10 deliveries = 80% + CourierPerformanceProjection janeData = createMockProjection( + 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); + // John: 5 completed out of 5 deliveries = 100% + CourierPerformanceProjection johnData = createMockProjection( + 2L, "John", 5L, 5L, new BigDecimal("4.0")); + // Peter: 0 completed out of 2 deliveries = 0% + CourierPerformanceProjection peterData = createMockProjection( + 3L, "Peter", 2L, 0L, new BigDecimal("3.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData, peterData)); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + + CourierSuccessRateResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(1L, janeResponse.getCourierId()); + assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); + + CourierSuccessRateResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(2L, johnResponse.getCourierId()); + assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); + + CourierSuccessRateResponse peterResponse = result.stream() + .filter(r -> r.getCourierName().equals("Peter")) + .findFirst().orElse(null); + assertNotNull(peterResponse); + assertEquals(3L, peterResponse.getCourierId()); + assertTrue(peterResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldHandleZeroDeliveryCountGracefully() { + // Arrange + // Mark: 0 completed out of 0 deliveries. MetricCalculator should handle this + // (e.g., return 0 or null) + CourierPerformanceProjection markData = createMockProjection( + 4L, "Mark", 0L, 0L, new BigDecimal("0.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.singletonList(markData)); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + CourierSuccessRateResponse markResponse = result.get(0); + assertEquals(4L, markResponse.getCourierId()); + assertTrue(markResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierAverageRating_shouldReturnCorrectAverageRatings() { + // Arrange + CourierPerformanceProjection janeData = createMockProjection( + 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); + CourierPerformanceProjection johnData = createMockProjection( + 2L, "John", 5L, 5L, new BigDecimal("4.0")); + // Peter: No rating available or 0.0 rating (depends on projection and database) + CourierPerformanceProjection peterData = createMockProjection( + 3L, "Peter", 2L, 0L, null); // Assuming null for no rating + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData, peterData)); + + // Act + List result = courierAnalyticsService + .getCourierAverageRating(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + + CourierAverageRatingResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(1L, janeResponse.getCourierId()); + assertEquals(new BigDecimal("4.5"), janeResponse.getAverageRating()); + + CourierAverageRatingResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(2L, johnResponse.getCourierId()); + assertEquals(new BigDecimal("4.0"), johnResponse.getAverageRating()); + + CourierAverageRatingResponse peterResponse = result.stream() + .filter(r -> r.getCourierName().equals("Peter")) + .findFirst().orElse(null); + assertNotNull(peterResponse); + assertNull(peterResponse.getAverageRating()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierAverageRating_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierAverageRating(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierPerformanceReport_shouldReturnComprehensiveReport() { + // Arrange + // Jane: 8 completed out of 10 deliveries = 80%, Avg Rating 4.5 + CourierPerformanceProjection janeData = createMockProjection( + 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); + // John: 5 completed out of 5 deliveries = 100%, Avg Rating 4.0 + CourierPerformanceProjection johnData = createMockProjection( + 2L, "John", 5L, 5L, new BigDecimal("4.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData)); + + // Act + List result = courierAnalyticsService + .getCourierPerformanceReport(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + + CourierPerformanceReportResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(1L, janeResponse.getCourierId()); + assertEquals(10, janeResponse.getDeliveryCount()); + assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); + assertTrue(janeResponse.getAverageRating().compareTo(new BigDecimal("4.5")) == 0); + + CourierPerformanceReportResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(2L, johnResponse.getCourierId()); + assertEquals(5, johnResponse.getDeliveryCount()); + assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); + assertTrue(johnResponse.getAverageRating().compareTo(new BigDecimal("4.0")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierPerformanceReport_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierPerformanceReport(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } +} \ No newline at end of file diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..454c37d --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,7 @@ +# H2 Database Configuration for Tests +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect \ No newline at end of file