refactor: move price business logic from repository to service
This commit is contained in:
parent
1382675860
commit
d133e9eeea
@ -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<Price> findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
||||
|
||||
List<Price> findPricesByProductIdAndBrandId(Long productId, Long brandId);
|
||||
}
|
||||
|
||||
@ -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<Price> prices = priceRepositoryPort.findPricesByProductIdAndBrandId(productId, brandId);
|
||||
|
||||
Optional<Price> 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)));
|
||||
}
|
||||
|
||||
@ -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<Price> 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();
|
||||
}
|
||||
|
||||
@ -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<PriceEntity, Long> {
|
||||
|
||||
@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<PriceEntity> findPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
||||
List<PriceEntity> findPricesByProductIdAndBrandId(Long productId, Long brandId);
|
||||
}
|
||||
|
||||
@ -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<Price> findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
||||
public List<Price> findPricesByProductIdAndBrandId(Long productId, Long brandId) {
|
||||
|
||||
Optional<PriceEntity> priceEntity =
|
||||
repository.findPriceByDateTimeAndProductIdAndBrandId(dateTime, productId, brandId);
|
||||
List<PriceEntity> priceEntityList = repository.findPricesByProductIdAndBrandId(productId, brandId);
|
||||
|
||||
return PriceEntityMapper.toOptionalPrice(priceEntity);
|
||||
return PriceEntityMapper.toPriceList(priceEntityList);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Price> toOptionalPrice(Optional<PriceEntity> priceEntity) {
|
||||
return priceEntity.map(PriceEntityMapper::toPrice);
|
||||
public static List<Price> toPriceList(List<PriceEntity> priceEntityList) {
|
||||
return priceEntityList.stream().map(PriceEntityMapper::toPrice).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<PriceEntity> priceEntities = Arrays.asList(
|
||||
createPriceEntity(1),
|
||||
createPriceEntity(2)
|
||||
);
|
||||
|
||||
Optional<Price> 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<Price> 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<Price> result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||
List<Price> 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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user