chore(release): v1.1 #2
@ -11,15 +11,15 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>1.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath/>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>1.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +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.Optional;
|
|
||||||
|
|
||||||
public interface PriceRepositoryPort {
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@ -17,8 +20,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) {
|
||||||
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(
|
new NotFoundException(String.format(
|
||||||
"Price for product %d of brand %d not found for date %s", productId, brandId, dateTime)));
|
"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.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -38,31 +40,55 @@ class PriceServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnPriceWhenPriceExists() {
|
void shouldReturnPriceWhenPriceExists() {
|
||||||
Price expectedPrice = createPrice();
|
Price lowPriorityPrice = createPrice(0);
|
||||||
when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
Price highPriorityPrice = createPrice(2);
|
||||||
eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
List<Price> prices = Arrays.asList(lowPriorityPrice, highPriorityPrice);
|
||||||
.thenReturn(Optional.of(expectedPrice));
|
|
||||||
|
when(priceRepositoryPort.findPricesByProductIdAndBrandId(
|
||||||
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
|
.thenReturn(prices);
|
||||||
|
|
||||||
Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
Price result = priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
||||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
assertEquals(expectedPrice.getProductId(), result.getProductId());
|
assertEquals(highPriorityPrice, result);
|
||||||
assertEquals(expectedPrice.getPrice(), result.getPrice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowNotFoundExceptionWhenPriceDoesNotExist() {
|
void shouldThrowNotFoundExceptionWhenPriceListIsEmpty() {
|
||||||
when(priceRepositoryPort.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
when(priceRepositoryPort.findPricesByProductIdAndBrandId(
|
||||||
any(LocalDateTime.class), anyLong(), anyLong()))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(Optional.empty());
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
assertThrows(NotFoundException.class, () ->
|
assertThrows(NotFoundException.class, () ->
|
||||||
priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
priceService.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(
|
||||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID));
|
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()
|
return Price.builder()
|
||||||
.brandId(TEST_BRAND_ID)
|
.brandId(TEST_BRAND_ID)
|
||||||
.productId(TEST_PRODUCT_ID)
|
.productId(TEST_PRODUCT_ID)
|
||||||
@ -70,7 +96,7 @@ class PriceServiceTest {
|
|||||||
.endDate(TEST_DATE.plusDays(1))
|
.endDate(TEST_DATE.plusDays(1))
|
||||||
.priceList(1L)
|
.priceList(1L)
|
||||||
.price(BigDecimal.valueOf(35.50))
|
.price(BigDecimal.valueOf(35.50))
|
||||||
.priority(1)
|
.priority(priority)
|
||||||
.currency("EUR")
|
.currency("EUR")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
12
boot/pom.xml
12
boot/pom.xml
@ -7,8 +7,8 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>1.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
<relativePath/>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>boot</artifactId>
|
<artifactId>boot</artifactId>
|
||||||
@ -20,22 +20,22 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>1.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>1.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>1.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>1.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -13,5 +13,6 @@ spring.h2.console.path=/h2-console
|
|||||||
|
|
||||||
# Flyway
|
# Flyway
|
||||||
spring.flyway.enabled=true
|
spring.flyway.enabled=true
|
||||||
spring.flyway.locations=filesystem:infrastructure/out/sql-repository/sql/migration
|
spring.flyway.locations=filesystem:../infrastructure/out/sql-repository/src/main/resources/sql/migration
|
||||||
spring.flyway.baseline-on-migrate=true
|
spring.flyway.baseline-on-migrate=true
|
||||||
|
spring.flyway.fail-on-missing-locations=true
|
||||||
@ -11,6 +11,6 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>1.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>1.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>1.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>1.0-RELEASE</version>
|
<version>1.1-RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,16 @@ package com.techivw.webprice.infrastructure.in.controllers.handlers;
|
|||||||
|
|
||||||
import com.techivw.webprice.application.exceptions.NotFoundException;
|
import com.techivw.webprice.application.exceptions.NotFoundException;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@ -24,4 +27,27 @@ public class AdapterExceptionHandler {
|
|||||||
|
|
||||||
return new ResponseEntity<>(body, NOT_FOUND);
|
return new ResponseEntity<>(body, NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||||
|
public ResponseEntity<Map<String, Object>> handleMissingParamException(MissingServletRequestParameterException ex) {
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("timestamp", new Date());
|
||||||
|
body.put("status", BAD_REQUEST.value());
|
||||||
|
body.put("error", "Bad Request");
|
||||||
|
body.put("message", "Required parameter '" + ex.getParameterName() + "' is missing");
|
||||||
|
|
||||||
|
return new ResponseEntity<>(body, BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
public ResponseEntity<Map<String, Object>> handleTypeMismatchException(MethodArgumentTypeMismatchException ex) {
|
||||||
|
Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("timestamp", new Date());
|
||||||
|
body.put("status", BAD_REQUEST.value());
|
||||||
|
body.put("error", "Bad Request");
|
||||||
|
body.put("message", "Parameter '" + ex.getName() + "' has invalid format.");
|
||||||
|
|
||||||
|
return new ResponseEntity<>(body, BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
package com.techivw.webprice.infrastructure.in.controllers.adapters;
|
package com.techivw.webprice.infrastructure.in.controllers.adapters;
|
||||||
|
|
||||||
|
import com.techivw.webprice.application.exceptions.NotFoundException;
|
||||||
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
import com.techivw.webprice.application.ports.in.PriceServicePort;
|
||||||
import com.techivw.webprice.domain.Price;
|
import com.techivw.webprice.domain.Price;
|
||||||
|
import com.techivw.webprice.infrastructure.in.controllers.handlers.AdapterExceptionHandler;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
@ -23,6 +27,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
|
|
||||||
@WebMvcTest
|
@WebMvcTest
|
||||||
@ContextConfiguration(classes = PriceControllerAdapter.class)
|
@ContextConfiguration(classes = PriceControllerAdapter.class)
|
||||||
|
@Import(AdapterExceptionHandler.class)
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
public class PriceControllerAdapterTest {
|
public class PriceControllerAdapterTest {
|
||||||
|
|
||||||
@ -35,8 +40,71 @@ public class PriceControllerAdapterTest {
|
|||||||
private static final Long PRODUCT_ID = 35455L;
|
private static final Long PRODUCT_ID = 35455L;
|
||||||
private static final Long BRAND_ID = 1L;
|
private static final Long BRAND_ID = 1L;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotFound() throws Exception {
|
||||||
|
LocalDateTime date = LocalDateTime.parse("2020-06-14T10:00:00");
|
||||||
|
|
||||||
|
when(priceServicePort.getPriceWithHighestPriorityByDateTimeAndProductIdAndBrandId(date, PRODUCT_ID, BRAND_ID))
|
||||||
|
.thenThrow(new NotFoundException(""));
|
||||||
|
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("dateTime", date.toString())
|
||||||
|
.param("productId", String.valueOf(PRODUCT_ID))
|
||||||
|
.param("brandId", String.valueOf(BRAND_ID)))
|
||||||
|
.andExpect(status().isNotFound())
|
||||||
|
.andExpect(jsonPath("$.status").value(404))
|
||||||
|
.andExpect(jsonPath("$.error").value("Not Found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMissingParameter() throws Exception {
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("productId", String.valueOf(PRODUCT_ID))
|
||||||
|
.param("brandId", String.valueOf(BRAND_ID)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.status").value(400))
|
||||||
|
.andExpect(jsonPath("$.error").value("Bad Request"))
|
||||||
|
.andExpect(jsonPath("$.message").value("Required parameter 'dateTime' is missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidDateFormat() throws Exception {
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("dateTime", "2020-06T17:00")
|
||||||
|
.param("productId", String.valueOf(PRODUCT_ID))
|
||||||
|
.param("brandId", String.valueOf(BRAND_ID)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.status").value(400))
|
||||||
|
.andExpect(jsonPath("$.error").value("Bad Request"))
|
||||||
|
.andExpect(jsonPath("$.message").value("Parameter 'dateTime' has invalid format."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidProductIdFormat() throws Exception {
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("dateTime", "2020-06-14T10:00:00")
|
||||||
|
.param("productId", "testNaN")
|
||||||
|
.param("brandId", String.valueOf(BRAND_ID)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.status").value(400))
|
||||||
|
.andExpect(jsonPath("$.error").value("Bad Request"))
|
||||||
|
.andExpect(jsonPath("$.message").value("Parameter 'productId' has invalid format."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInvalidBrandIdFormat() throws Exception {
|
||||||
|
mockMvc.perform(get("/price")
|
||||||
|
.param("dateTime", "2020-06-14T10:00:00")
|
||||||
|
.param("productId", String.valueOf(PRODUCT_ID))
|
||||||
|
.param("brandId", "not-a-number")) // Invalid brandId format
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.status").value(400))
|
||||||
|
.andExpect(jsonPath("$.error").value("Bad Request"))
|
||||||
|
.andExpect(jsonPath("$.message").value("Parameter 'brandId' has invalid format."));
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("testCases")
|
@MethodSource("successfulTestCases")
|
||||||
void testGetPrice(String testName, String dateTime, double expectedPrice) throws Exception {
|
void testGetPrice(String testName, String dateTime, double expectedPrice) throws Exception {
|
||||||
LocalDateTime date = LocalDateTime.parse(dateTime);
|
LocalDateTime date = LocalDateTime.parse(dateTime);
|
||||||
|
|
||||||
@ -52,7 +120,7 @@ public class PriceControllerAdapterTest {
|
|||||||
.andExpect(jsonPath("$.price").value(expectedPrice));
|
.andExpect(jsonPath("$.price").value(expectedPrice));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Arguments> testCases() {
|
private static Stream<Arguments> successfulTestCases() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of("Test 1", "2020-06-14T10:00:00", 35.50),
|
Arguments.of("Test 1", "2020-06-14T10:00:00", 35.50),
|
||||||
Arguments.of("Test 2", "2020-06-14T16:00:00", 25.45),
|
Arguments.of("Test 2", "2020-06-14T16:00:00", 25.45),
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.techivw</groupId>
|
<groupId>com.techivw</groupId>
|
||||||
<artifactId>web-price</artifactId>
|
<artifactId>web-price</artifactId>
|
||||||
<version>1.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>1.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>1.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.Optional;
|
|
||||||
|
|
||||||
@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 " +
|
|
||||||
"ORDER BY p.priority DESC LIMIT 1")
|
|
||||||
Optional<PriceEntity> findPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,7 @@ 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.Optional;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@ -18,11 +17,10 @@ public class PriceRepositoryAdapter implements PriceRepositoryPort {
|
|||||||
private final PriceEntityJPARepository repository;
|
private final PriceEntityJPARepository repository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Price> findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(LocalDateTime dateTime, Long productId, Long brandId) {
|
public List<Price> findPricesByProductIdAndBrandId(Long productId, Long brandId) {
|
||||||
|
|
||||||
Optional<PriceEntity> priceEntity =
|
List<PriceEntity> priceEntityList = repository.findPricesByProductIdAndBrandId(productId, brandId);
|
||||||
repository.findPriceByDateTimeAndProductIdAndBrandId(dateTime, 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.domain.Price;
|
||||||
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
import com.techivw.webprice.infrastructure.repositories.model.PriceEntity;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.List;
|
||||||
|
|
||||||
public class PriceEntityMapper {
|
public class PriceEntityMapper {
|
||||||
|
|
||||||
@ -21,8 +21,8 @@ public class PriceEntityMapper {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<Price> toOptionalPrice(Optional<PriceEntity> priceEntity) {
|
public static List<Price> toPriceList(List<PriceEntity> priceEntityList) {
|
||||||
return priceEntity.map(PriceEntityMapper::toPrice);
|
return priceEntityList.stream().map(PriceEntityMapper::toPrice).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,4 +44,11 @@ public class PriceEntity {
|
|||||||
@Column(name = "currency")
|
@Column(name = "currency")
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|
||||||
|
@Column(name = "last_update")
|
||||||
|
@Temporal(TemporalType.TIMESTAMP)
|
||||||
|
private LocalDateTime lastUpdate;
|
||||||
|
|
||||||
|
@Column(name = "last_update_by")
|
||||||
|
private String lastUpdateBy;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,5 +7,7 @@ CREATE TABLE prices (
|
|||||||
product_id BIGINT NOT NULL,
|
product_id BIGINT NOT NULL,
|
||||||
priority BIGINT NOT NULL,
|
priority BIGINT NOT NULL,
|
||||||
price DECIMAL(10, 2) NOT NULL,
|
price DECIMAL(10, 2) NOT NULL,
|
||||||
currency VARCHAR(3)
|
currency VARCHAR(3),
|
||||||
|
last_update TIMESTAMP,
|
||||||
|
last_update_by VARCHAR(255)
|
||||||
);
|
);
|
||||||
@ -1,6 +1,6 @@
|
|||||||
INSERT INTO prices (brand_id, start_date, end_date, price_list, product_id, priority, price, currency)
|
INSERT INTO prices (brand_id, start_date, end_date, price_list, product_id, priority, price, currency, last_update, last_update_by)
|
||||||
VALUES
|
VALUES
|
||||||
(1, '2020-06-14 00:00:00', '2020-12-31 23:59:59', 1, 35455, 0, 35.50, 'EUR'),
|
(1, '2020-06-14 00:00:00', '2020-12-31 23:59:59', 1, 35455, 0, 35.50, 'EUR', '2020-03-26 14:49:07', 'user1'),
|
||||||
(1, '2020-06-14 15:00:00', '2020-06-14 18:30:00', 2, 35455, 1, 25.45, 'EUR'),
|
(1, '2020-06-14 15:00:00', '2020-06-14 18:30:00', 2, 35455, 1, 25.45, 'EUR', '2020-05-26 15:38:22', 'user1'),
|
||||||
(1, '2020-06-15 00:00:00', '2020-06-15 11:00:00', 3, 35455, 1, 30.50, 'EUR'),
|
(1, '2020-06-15 00:00:00', '2020-06-15 11:00:00', 3, 35455, 1, 30.50, 'EUR', '2020-05-26 15:39:22', 'user2'),
|
||||||
(1, '2020-06-15 16:00:00', '2020-12-31 23:59:59', 4, 35455, 1, 38.95, 'EUR');
|
(1, '2020-06-15 16:00:00', '2020-12-31 23:59:59', 4, 35455, 1, 38.95, 'EUR', '2020-06-02 10:14:00', 'user1')
|
||||||
@ -11,7 +11,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
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.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
@ -35,35 +37,35 @@ class PriceRepositoryAdapterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnPriceWhenPriceEntityExists() {
|
void shouldReturnPricesWhenPriceEntitiesExist() {
|
||||||
PriceEntity priceEntity = createSamplePriceEntity();
|
List<PriceEntity> priceEntities = Arrays.asList(
|
||||||
when(repository.findPriceByDateTimeAndProductIdAndBrandId(
|
createPriceEntity(1),
|
||||||
eq(TEST_DATE), eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
createPriceEntity(2)
|
||||||
.thenReturn(Optional.of(priceEntity));
|
);
|
||||||
|
|
||||||
Optional<Price> result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
when(repository.findPricesByProductIdAndBrandId(
|
||||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
|
.thenReturn(priceEntities);
|
||||||
|
|
||||||
assertTrue(result.isPresent());
|
List<Price> result = adapter.findPricesByProductIdAndBrandId(
|
||||||
Price price = result.get();
|
TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
assertEquals(TEST_PRODUCT_ID, price.getProductId());
|
|
||||||
assertEquals(TEST_BRAND_ID, price.getBrandId());
|
assertEquals(2, result.size());
|
||||||
assertEquals(BigDecimal.valueOf(35.50), price.getPrice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnEmptyOptionalWhenPriceEntityDoesNotExist() {
|
void shouldReturnEmptyListWhenNoPriceEntitiesExist() {
|
||||||
when(repository.findPriceByDateTimeAndProductIdAndBrandId(
|
when(repository.findPricesByProductIdAndBrandId(
|
||||||
any(LocalDateTime.class), anyLong(), anyLong()))
|
eq(TEST_PRODUCT_ID), eq(TEST_BRAND_ID)))
|
||||||
.thenReturn(Optional.empty());
|
.thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
Optional<Price> result = adapter.findHighestPriorityPriceByDateTimeAndProductIdAndBrandId(
|
List<Price> result = adapter.findPricesByProductIdAndBrandId(
|
||||||
TEST_DATE, TEST_PRODUCT_ID, TEST_BRAND_ID);
|
TEST_PRODUCT_ID, TEST_BRAND_ID);
|
||||||
|
|
||||||
assertFalse(result.isPresent());
|
assertTrue(result.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PriceEntity createSamplePriceEntity() {
|
private PriceEntity createPriceEntity(int priority) {
|
||||||
PriceEntity entity = new PriceEntity();
|
PriceEntity entity = new PriceEntity();
|
||||||
entity.setBrandId(TEST_BRAND_ID);
|
entity.setBrandId(TEST_BRAND_ID);
|
||||||
entity.setProductId(TEST_PRODUCT_ID);
|
entity.setProductId(TEST_PRODUCT_ID);
|
||||||
@ -71,7 +73,7 @@ class PriceRepositoryAdapterTest {
|
|||||||
entity.setEndDate(TEST_DATE.plusDays(1));
|
entity.setEndDate(TEST_DATE.plusDays(1));
|
||||||
entity.setPriceList(1L);
|
entity.setPriceList(1L);
|
||||||
entity.setPrice(BigDecimal.valueOf(35.50));
|
entity.setPrice(BigDecimal.valueOf(35.50));
|
||||||
entity.setPriority(1);
|
entity.setPriority(priority);
|
||||||
entity.setCurrency("EUR");
|
entity.setCurrency("EUR");
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|||||||
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>1.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