Compare commits
No commits in common. "510325d7af483c5334609d8274827d2e198cac21" and "b3906155eceb67fa7fabab99de78d451c7a2273e" have entirely different histories.
510325d7af
...
b3906155ec
@ -22,7 +22,7 @@ Web service for obtaining the price of a product given a date, the product id an
|
|||||||
```
|
```
|
||||||
mvn clean spring-boot:run -Dspring-boot.run.profiles=local
|
mvn clean spring-boot:run -Dspring-boot.run.profiles=local
|
||||||
```
|
```
|
||||||
3. Service should be ready to receive requests at http://localhost:8080/price, with OpenAPI docs available at http://localhost:8080/swagger-ui/index.html
|
3. Service should be ready to receive requests at http://localhost:8080/price with OpenAPI docs available at http://localhost:8080/swagger-ui/index.html
|
||||||
|
|
||||||
## Running Tests
|
## Running Tests
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,9 @@ package com.techivw.webprice.application.ports.out;
|
|||||||
|
|
||||||
import com.techivw.webprice.domain.Price;
|
import com.techivw.webprice.domain.Price;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface PriceRepositoryPort {
|
public interface PriceRepositoryPort {
|
||||||
|
|
||||||
List<Price> findPricesByProductIdAndBrandIdAndDateTimeBetween(Long productId, Long brandId, LocalDateTime dateTime);
|
List<Price> findPricesByProductIdAndBrandId(Long productId, Long brandId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,12 +21,14 @@ public class PriceService implements PriceServicePort {
|
|||||||
@Override
|
@Override
|
||||||
public Price getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
public Price getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
||||||
|
|
||||||
List<Price> prices = priceRepositoryPort.findPricesByProductIdAndBrandIdAndDateTimeBetween(productId, brandId, dateTime);
|
List<Price> prices = priceRepositoryPort.findPricesByProductIdAndBrandId(productId, brandId);
|
||||||
|
|
||||||
return prices.stream()
|
Optional<Price> maxPriorityPrice = prices.stream()
|
||||||
.max(Comparator.comparing(Price::getPriority))
|
.filter(price -> !dateTime.isBefore(price.getStartDate()) && !dateTime.isAfter(price.getEndDate()))
|
||||||
.orElseThrow(() -> new NotFoundException(
|
.max(Comparator.comparing(Price::getPriority));
|
||||||
String.format("Price for product %d of brand %d not found for date %s",
|
|
||||||
productId, brandId, dateTime)));
|
return maxPriorityPrice.orElseThrow(() ->
|
||||||
|
new NotFoundException(String.format(
|
||||||
|
"Price for product %d of brand %d not found for date %s", productId, brandId, dateTime)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class PriceServiceTest {
|
|||||||
|
|
||||||
private PriceServicePort priceService;
|
private PriceServicePort priceService;
|
||||||
|
|
||||||
private final LocalDateTime TEST_DATE = LocalDateTime.parse("2020-06-14T16:00:00");
|
private final LocalDateTime TEST_DATE = LocalDateTime.parse("2023-01-01T12:00:00");
|
||||||
private final Long TEST_PRODUCT_ID = 35455L;
|
private final Long TEST_PRODUCT_ID = 35455L;
|
||||||
private final Long TEST_BRAND_ID = 1L;
|
private final Long TEST_BRAND_ID = 1L;
|
||||||
|
|
||||||
@ -44,8 +44,8 @@ class PriceServiceTest {
|
|||||||
Price highPriorityPrice = createPrice(2);
|
Price highPriorityPrice = createPrice(2);
|
||||||
List<Price> prices = Arrays.asList(lowPriorityPrice, highPriorityPrice);
|
List<Price> prices = Arrays.asList(lowPriorityPrice, highPriorityPrice);
|
||||||
|
|
||||||
when(priceRepositoryPort.findPricesByProductIdAndBrandIdAndDateTimeBetween(
|
when(priceRepositoryPort.findPricesByProductIdAndBrandId(
|
||||||
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID), eq(TEST_DATE)))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(prices);
|
.thenReturn(prices);
|
||||||
|
|
||||||
Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
||||||
@ -57,8 +57,8 @@ class PriceServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowNotFoundExceptionWhenPriceListIsEmpty() {
|
void shouldThrowNotFoundExceptionWhenPriceListIsEmpty() {
|
||||||
when(priceRepositoryPort.findPricesByProductIdAndBrandIdAndDateTimeBetween(
|
when(priceRepositoryPort.findPricesByProductIdAndBrandId(
|
||||||
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID), eq(TEST_DATE)))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(Collections.emptyList());
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
assertThrows(NotFoundException.class, () ->
|
assertThrows(NotFoundException.class, () ->
|
||||||
@ -66,6 +66,28 @@ class PriceServiceTest {
|
|||||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID));
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
private Price createPrice(int priority) {
|
||||||
return Price.builder()
|
return Price.builder()
|
||||||
.brandId(TEST_BRAND_ID)
|
.brandId(TEST_BRAND_ID)
|
||||||
@ -78,4 +100,4 @@ class PriceServiceTest {
|
|||||||
.currency("EUR")
|
.currency("EUR")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
boot/pom.xml
10
boot/pom.xml
@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -20,22 +20,22 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>application</artifactId>
|
<artifactId>application</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>rest-api</artifactId>
|
<artifactId>rest-api</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>sql-repository</artifactId>
|
<artifactId>sql-repository</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,6 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</parent>
|
</parent>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -19,12 +19,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>application</artifactId>
|
<artifactId>application</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ public class PriceControllerAdapter {
|
|||||||
@ApiResponse(responseCode = "400", description = "Invalid request parameters")
|
@ApiResponse(responseCode = "400", description = "Invalid request parameters")
|
||||||
@ApiResponse(responseCode = "404", description = "Price information not found for the given parameters")
|
@ApiResponse(responseCode = "404", description = "Price information not found for the given parameters")
|
||||||
public ResponseEntity<PriceResponse> getPrice(
|
public ResponseEntity<PriceResponse> getPrice(
|
||||||
@Parameter(description = "Date and time for price lookup (ISO format)", example = "2020-06-14T16:00:00")
|
@Parameter(description = "Date and time for price lookup (ISO format)", example = "2025-04-30T12:00:00")
|
||||||
@RequestParam(name = "dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime,
|
@RequestParam(name = "dateTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime,
|
||||||
@Parameter(description = "Product ID", example = "35455")
|
@Parameter(description = "Product ID", example = "35455")
|
||||||
@RequestParam(name = "productId") Long productId,
|
@RequestParam(name = "productId") Long productId,
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -19,12 +19,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>application</artifactId>
|
<artifactId>application</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
|
|||||||
@ -2,19 +2,12 @@ package com.techivw.webprice.infrastructure.repositories;
|
|||||||
|
|
||||||
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PriceEntityJPARepository extends JpaRepository<PriceEntity, Long> {
|
public interface PriceEntityJPARepository extends JpaRepository<PriceEntity, Long> {
|
||||||
|
|
||||||
@Query("SELECT p FROM PriceEntity p WHERE " +
|
List<PriceEntity> findPricesByProductIdAndBrandId(Long productId, Long brandId);
|
||||||
"p.productId = :productId " +
|
|
||||||
"AND p.brandId = :brandId " +
|
|
||||||
"AND :dateTime BETWEEN p.startDate AND p.endDate")
|
|
||||||
List<PriceEntity> findPricesByProductIdAndBrandIdAndBetweenDateTime(
|
|
||||||
Long productId, Long brandId, LocalDateTime dateTime);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -18,9 +17,9 @@ public class PriceRepositoryAdapter implements PriceRepositoryPort {
|
|||||||
private final PriceEntityJPARepository repository;
|
private final PriceEntityJPARepository repository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Price> findPricesByProductIdAndBrandIdAndDateTimeBetween(Long productId, Long brandId, LocalDateTime dateTime) {
|
public List<Price> findPricesByProductIdAndBrandId(Long productId, Long brandId) {
|
||||||
|
|
||||||
List<PriceEntity> priceEntityList = repository.findPricesByProductIdAndBrandIdAndBetweenDateTime(productId, brandId, dateTime);
|
List<PriceEntity> priceEntityList = repository.findPricesByProductIdAndBrandId(productId, brandId);
|
||||||
|
|
||||||
return PriceEntityMapper.toPriceList(priceEntityList);
|
return PriceEntityMapper.toPriceList(priceEntityList);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,24 +43,24 @@ class PriceRepositoryAdapterTest {
|
|||||||
createPriceEntity(2)
|
createPriceEntity(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
when(repository.findPricesByProductIdAndBrandIdAndBetweenDateTime(
|
when(repository.findPricesByProductIdAndBrandId(
|
||||||
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID), eq(TEST_DATE)))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(priceEntities);
|
.thenReturn(priceEntities);
|
||||||
|
|
||||||
List<Price> result = adapter.findPricesByProductIdAndBrandIdAndDateTimeBetween(
|
List<Price> result = adapter.findPricesByProductIdAndBrandId(
|
||||||
TEST_PRODUCT_ID, TEST_BRAND_ID, TEST_DATE);
|
TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
assertEquals(2, result.size());
|
assertEquals(2, result.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnEmptyListWhenNoPriceEntitiesExist() {
|
void shouldReturnEmptyListWhenNoPriceEntitiesExist() {
|
||||||
when(repository.findPricesByProductIdAndBrandIdAndBetweenDateTime(
|
when(repository.findPricesByProductIdAndBrandId(
|
||||||
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID), eq(TEST_DATE)))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(Collections.emptyList());
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
List<Price> result = adapter.findPricesByProductIdAndBrandIdAndDateTimeBetween(
|
List<Price> result = adapter.findPricesByProductIdAndBrandId(
|
||||||
TEST_PRODUCT_ID, TEST_BRAND_ID, TEST_DATE);
|
TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
assertTrue(result.isEmpty());
|
assertTrue(result.isEmpty());
|
||||||
}
|
}
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>2.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>web-price</name>
|
<name>web-price</name>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user