diff --git a/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java b/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java index b785a12..6e75ca5 100644 --- a/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java +++ b/application/src/main/java/com/techivw/webprice/application/ports/out/PriceRepositoryPort.java @@ -2,11 +2,9 @@ package com.techivw.webprice.application.ports.out; import com.techivw.webprice.domain.Price; -import java.time.LocalDateTime; -import java.util.Optional; +import java.util.List; public interface PriceRepositoryPort { - Optional findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId); - + List findPricesByProductIdAndBrandId(Long productId, Long brandId); } diff --git a/application/src/main/java/com/techivw/webprice/application/services/PriceService.java b/application/src/main/java/com/techivw/webprice/application/services/PriceService.java index eee1d41..ae5ae4d 100644 --- a/application/src/main/java/com/techivw/webprice/application/services/PriceService.java +++ b/application/src/main/java/com/techivw/webprice/application/services/PriceService.java @@ -8,6 +8,9 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; @Service @AllArgsConstructor @@ -17,8 +20,14 @@ public class PriceService implements PriceServicePort { @Override public Price getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) { - return priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId) - .orElseThrow(() -> + + List prices = priceRepositoryPort.findPricesByProductIdAndBrandId(productId, brandId); + + Optional maxPriorityPrice = prices.stream() + .filter(price -> !dateTime.isBefore(price.getStartDate()) && !dateTime.isAfter(price.getEndDate())) + .max(Comparator.comparing(Price::getPriority)); + + return maxPriorityPrice.orElseThrow(() -> new NotFoundException(String.format( "Price for product %d of brand %d not found for date %s", productId, brandId, dateTime))); } diff --git a/application/src/test/java/com/techivw/webprice/application/services/PriceServiceTest.java b/application/src/test/java/com/techivw/webprice/application/services/PriceServiceTest.java index 2a61a80..7952aa7 100644 --- a/application/src/test/java/com/techivw/webprice/application/services/PriceServiceTest.java +++ b/application/src/test/java/com/techivw/webprice/application/services/PriceServiceTest.java @@ -7,7 +7,9 @@ import com.techivw.webprice.domain.Price; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.Optional; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -38,31 +40,55 @@ class PriceServiceTest { @Test void shouldReturnPriceWhenPriceExists() { - Price expectedPrice = createPrice(); - when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId( - eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) - .thenReturn(Optional.of(expectedPrice)); + Price lowPriorityPrice = createPrice(0); + Price highPriorityPrice = createPrice(2); + List prices = Arrays.asList(lowPriorityPrice, highPriorityPrice); + + when(priceRepositoryPort.findPricesByProductIdAndBrandId( + eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) + .thenReturn(prices); Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId( TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID); assertNotNull(result); - assertEquals(expectedPrice.getProductId(), result.getProductId()); - assertEquals(expectedPrice.getPrice(), result.getPrice()); + assertEquals(highPriorityPrice, result); } @Test - void shouldThrowNotFoundExceptionWhenPriceDoesNotExist() { - when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId( - any(LocalDateTime.class), anyLong(), anyLong())) - .thenReturn(Optional.empty()); + void shouldThrowNotFoundExceptionWhenPriceListIsEmpty() { + when(priceRepositoryPort.findPricesByProductIdAndBrandId( + eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) + .thenReturn(Collections.emptyList()); assertThrows(NotFoundException.class, () -> priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId( TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID)); } - private Price createPrice() { + @Test + void shouldThrowNotFoundExceptionWhenNoPriceInDateRange() { + Price price = Price.builder() + .brandId(TEST_BRAND_ID) + .productId(TEST_PRODUCT_ID) + .startDate(TEST_DATE.plusDays(1)) + .endDate(TEST_DATE.plusDays(2)) + .priceList(1L) + .price(BigDecimal.valueOf(35.50)) + .priority(1) + .currency("EUR") + .build(); + + when(priceRepositoryPort.findPricesByProductIdAndBrandId( + eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) + .thenReturn(Collections.singletonList(price)); + + assertThrows(NotFoundException.class, () -> + priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId( + TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID)); + } + + private Price createPrice(int priority) { return Price.builder() .brandId(TEST_BRAND_ID) .productId(TEST_PRODUCT_ID) @@ -70,7 +96,7 @@ class PriceServiceTest { .endDate(TEST_DATE.plusDays(1)) .priceList(1L) .price(BigDecimal.valueOf(35.50)) - .priority(1) + .priority(priority) .currency("EUR") .build(); } diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java index f265993..b03c5da 100644 --- a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/PriceEntityJPARepository.java @@ -2,19 +2,12 @@ package com.techivw.webprice.infrastructure.repositories; import com.techivw.webprice.infrastructure.repositories.model.PriceEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import java.time.LocalDateTime; -import java.util.Optional; +import java.util.List; @Repository public interface PriceEntityJPARepository extends JpaRepository { - @Query("SELECT p FROM PriceEntity p WHERE " + - "p.productId = :productId " + - "AND p.brandId = :brandId " + - "AND :dateTime BETWEEN p.startDate AND p.endDate " + - "ORDER BY p.priority DESC LIMIT 1") - Optional findPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId); + List findPricesByProductIdAndBrandId(Long productId, Long brandId); } diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java index a30b161..4d9984e 100644 --- a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapter.java @@ -8,8 +8,7 @@ import com.techivw.webprice.infrastructure.repositories.model.PriceEntity; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; -import java.util.Optional; +import java.util.List; @Service @AllArgsConstructor @@ -18,11 +17,10 @@ public class PriceRepositoryAdapter implements PriceRepositoryPort { private final PriceEntityJPARepository repository; @Override - public Optional findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) { + public List findPricesByProductIdAndBrandId(Long productId, Long brandId) { - Optional priceEntity = - repository.findPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId); + List priceEntityList = repository.findPricesByProductIdAndBrandId(productId, brandId); - return PriceEntityMapper.toOptionalPrice(priceEntity); + return PriceEntityMapper.toPriceList(priceEntityList); } } diff --git a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java index 7445cd9..bd52b06 100644 --- a/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java +++ b/infrastructure/out/sql-repository/src/main/java/com/techivw/webprice/infrastructure/repositories/mappers/PriceEntityMapper.java @@ -3,7 +3,7 @@ package com.techivw.webprice.infrastructure.repositories.mappers; import com.techivw.webprice.domain.Price; import com.techivw.webprice.infrastructure.repositories.model.PriceEntity; -import java.util.Optional; +import java.util.List; public class PriceEntityMapper { @@ -21,8 +21,8 @@ public class PriceEntityMapper { .build(); } - public static Optional toOptionalPrice(Optional priceEntity) { - return priceEntity.map(PriceEntityMapper::toPrice); + public static List toPriceList(List priceEntityList) { + return priceEntityList.stream().map(PriceEntityMapper::toPrice).toList(); } } diff --git a/infrastructure/out/sql-repository/src/test/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapterTest.java b/infrastructure/out/sql-repository/src/test/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapterTest.java index a260ca5..21e4d5f 100644 --- a/infrastructure/out/sql-repository/src/test/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapterTest.java +++ b/infrastructure/out/sql-repository/src/test/java/com/techivw/webprice/infrastructure/repositories/adapters/PriceRepositoryAdapterTest.java @@ -11,7 +11,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.Optional; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -35,35 +37,35 @@ class PriceRepositoryAdapterTest { } @Test - void shouldReturnPriceWhenPriceEntityExists() { - PriceEntity priceEntity = createSamplePriceEntity(); - when(repository.findPriceByDateTimeAndProductIdAndBrandId( - eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) - .thenReturn(Optional.of(priceEntity)); + void shouldReturnPricesWhenPriceEntitiesExist() { + List priceEntities = Arrays.asList( + createPriceEntity(1), + createPriceEntity(2) + ); - Optional result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId( - TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID); + when(repository.findPricesByProductIdAndBrandId( + eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) + .thenReturn(priceEntities); - assertTrue(result.isPresent()); - Price price = result.get(); - assertEquals(TEST_PRODUCT_ID, price.getProductId()); - assertEquals(TEST_BRAND_ID, price.getBrandId()); - assertEquals(BigDecimal.valueOf(35.50), price.getPrice()); + List result = adapter.findPricesByProductIdAndBrandId( + TEST_PRODUCT_ID, TEST_BRAND_ID); + + assertEquals(2, result.size()); } @Test - void shouldReturnEmptyOptionalWhenPriceEntityDoesNotExist() { - when(repository.findPriceByDateTimeAndProductIdAndBrandId( - any(LocalDateTime.class), anyLong(), anyLong())) - .thenReturn(Optional.empty()); + void shouldReturnEmptyListWhenNoPriceEntitiesExist() { + when(repository.findPricesByProductIdAndBrandId( + eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID))) + .thenReturn(Collections.emptyList()); - Optional result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId( - TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID); + List result = adapter.findPricesByProductIdAndBrandId( + TEST_PRODUCT_ID, TEST_BRAND_ID); - assertFalse(result.isPresent()); + assertTrue(result.isEmpty()); } - private PriceEntity createSamplePriceEntity() { + private PriceEntity createPriceEntity(int priority) { PriceEntity entity = new PriceEntity(); entity.setBrandId(TEST_BRAND_ID); entity.setProductId(TEST_PRODUCT_ID); @@ -71,7 +73,7 @@ class PriceRepositoryAdapterTest { entity.setEndDate(TEST_DATE.plusDays(1)); entity.setPriceList(1L); entity.setPrice(BigDecimal.valueOf(35.50)); - entity.setPriority(1); + entity.setPriority(priority); entity.setCurrency("EUR"); return entity; }