Add Domain Test Classes

This commit is contained in:
Ali CANLI
2022-07-10 18:11:53 +03:00
parent 56cdf7191a
commit 1238b3ae6b
20 changed files with 387 additions and 41 deletions

View File

@@ -47,12 +47,13 @@ public class Money {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money money)) return false;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.equals(money.amount);
}
@Override
public int hashCode() {
return amount.hashCode();
return Objects.hash(amount);
}
}

View File

@@ -36,6 +36,16 @@
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -31,7 +31,7 @@ public class OrderCreateHelper {
@Transactional
public OrderCreatedEvent persistOrder(CreateOrderCommand createOrderCommand) {
log.info("createOrder: {}", createOrderCommand);
checkCustomer(createOrderCommand.getCustomerId());
checkCustomer(createOrderCommand.customerId());
Restaurant restaurant = checkRestaurant(createOrderCommand);
var order = orderDataMapper.createOrderCommandToOrder(createOrderCommand);
var createdEventOrder = orderDomainService.validateAndInitiateOrder(order, restaurant);
@@ -44,7 +44,7 @@ public class OrderCreateHelper {
return restaurantRepository.findRestaurantInformation
(orderDataMapper.createOrderCommandToRestaurant(createOrderCommand))
.orElseThrow(() -> new OrderDomainException("Restaurant not found. " +
"Please check restaurant id: " + createOrderCommand.getRestaurantId()));
"Please check restaurant id: " + createOrderCommand.restaurantId()));
}

View File

@@ -2,15 +2,29 @@ package com.food.order.domain;
import com.food.order.domain.dto.track.TrackOrderQuery;
import com.food.order.domain.dto.track.TrackOrderResponse;
import com.food.order.domain.mapper.OrderDataMapper;
import com.food.order.domain.ports.output.repository.OrderRepository;
import com.food.order.system.domain.exception.OrderNotFoundException;
import com.food.order.system.domain.valueobject.TrackingId;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
@RequiredArgsConstructor
public class OrderTrackCommandHandler {
private final OrderDataMapper orderDataMapper;
private final OrderRepository orderRepository;
@Transactional(readOnly = true)
public TrackOrderResponse trackOrder(TrackOrderQuery trackOrderQuery) {
log.info("trackOrder: {}", trackOrderQuery);
return null;
var order = orderRepository.findByTrackingId
(new TrackingId(trackOrderQuery.orderTrackingId()))
.orElseThrow(() -> new OrderNotFoundException
("Order not found with tracking id : + " + trackOrderQuery.orderTrackingId()));
return orderDataMapper.orderToTrackOrderResponse(order);
}
}

View File

@@ -0,0 +1,25 @@
package com.food.order.domain;
import com.food.order.domain.dto.message.PaymentResponse;
import com.food.order.domain.ports.input.message.listener.payment.PaymentResponseMessageListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@Service
@Validated
@Slf4j
@RequiredArgsConstructor
public class PaymentResponseMessageListenerImpl implements PaymentResponseMessageListener {
@Override
public void paymentCompleted(PaymentResponse paymentResponse) {
}
@Override
public void paymentCancelled(PaymentResponse paymentResponse) {
}
}

View File

@@ -0,0 +1,23 @@
package com.food.order.domain;
import com.food.order.domain.dto.message.RestaurantApprovalResponse;
import com.food.order.domain.ports.input.message.listener.restaurantapproval.RestaurantApprovalResponseMessageListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class RestaurantApprovalResponseMessageListenerImpl implements RestaurantApprovalResponseMessageListener {
@Override
public void orderApproved(RestaurantApprovalResponse restaurantApprovalResponse) {
}
@Override
public void orderRejected(RestaurantApprovalResponse restaurantApprovalResponse) {
}
}

View File

@@ -9,9 +9,7 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@AllArgsConstructor
public record CreateOrderCommand(@NotNull UUID customerId,
@NotNull UUID restaurantId,
@NotNull BigDecimal price,

View File

@@ -3,14 +3,11 @@ package com.food.order.domain.dto.create;
import com.food.order.domain.valueobject.OrderStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@Getter
@Builder
@AllArgsConstructor
public record CreateOrderResponse(@NotNull UUID orderTrackingId,
@NotNull OrderStatus orderStatus,
@NotNull String message) {

View File

@@ -7,8 +7,6 @@ import lombok.Getter;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
@Getter
@AllArgsConstructor
@Builder
public record OrderAddress(@NotNull @Max(value = 50) String street,
@NotNull @Max(value = 50) String city,

View File

@@ -1,6 +1,5 @@
package com.food.order.domain.dto.create;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@@ -8,8 +7,6 @@ import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.UUID;
@Getter
@AllArgsConstructor
@Builder
public record OrderItem(@NotNull UUID productId,
@NotNull Integer quantity,

View File

@@ -2,13 +2,10 @@ package com.food.order.domain.dto.track;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@Getter
@Builder
@AllArgsConstructor
public record TrackOrderQuery(@NotNull UUID orderTrackingId) {
}

View File

@@ -3,15 +3,12 @@ package com.food.order.domain.dto.track;
import com.food.order.domain.valueobject.OrderStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.UUID;
@Getter
@Builder
@AllArgsConstructor
public record TrackOrderResponse(@NotNull UUID orderTrackingId,
@NotNull OrderStatus orderStatus,
List<String> failureMessages) {

View File

@@ -3,6 +3,7 @@ package com.food.order.domain.mapper;
import com.food.order.domain.dto.create.CreateOrderCommand;
import com.food.order.domain.dto.create.CreateOrderResponse;
import com.food.order.domain.dto.create.OrderAddress;
import com.food.order.domain.dto.track.TrackOrderResponse;
import com.food.order.domain.valueobject.CustomerId;
import com.food.order.domain.valueobject.Money;
import com.food.order.domain.valueobject.ProductId;
@@ -21,24 +22,34 @@ import java.util.stream.Collectors;
@Component
public class OrderDataMapper {
public TrackOrderResponse orderToTrackOrderResponse(Order order) {
return TrackOrderResponse.builder()
.orderTrackingId(order.getTrackingId().getValue())
.orderStatus(order.getStatus())
.failureMessages(order.getFailureMessages())
.build();
}
public Restaurant createOrderCommandToRestaurant(CreateOrderCommand createOrderCommand) {
return Restaurant.builder()
.id(new RestaurantId(createOrderCommand.getRestaurantId()))
.products(createOrderCommand.getOrderItems().stream()
.id(new RestaurantId(createOrderCommand.restaurantId()))
.products(createOrderCommand.orderItems().stream()
.map(orderItem ->
new Product(new ProductId(orderItem.getProductId())))
.collect(Collectors.toList())
new Product(new ProductId(orderItem.productId())))
.toList()
)
.build();
}
public Order createOrderCommandToOrder(CreateOrderCommand createOrderCommand) {
return Order.builder()
.customerId(new CustomerId(createOrderCommand.getCustomerId()))
.restaurantId(new RestaurantId(createOrderCommand.getRestaurantId()))
.deliveryAddress(orderAddressToStreetAddress(createOrderCommand.getOrderAddress()))
.price(new Money(createOrderCommand.getPrice()))
.items(orderItemsToOrderItemEntities(createOrderCommand.getOrderItems()))
.customerId(new CustomerId(createOrderCommand.customerId()))
.restaurantId(new RestaurantId(createOrderCommand.restaurantId()))
.deliveryAddress(orderAddressToStreetAddress(createOrderCommand.orderAddress()))
.price(new Money(createOrderCommand.price()))
.items(orderItemsToOrderItemEntities(createOrderCommand.orderItems()))
.build();
}
@@ -46,10 +57,10 @@ public class OrderDataMapper {
return orderItems.stream()
.map(orderItem ->
OrderItem.builder()
.product(new Product(new ProductId(orderItem.getProductId())))
.price(new Money(orderItem.getPrice()))
.quantity(orderItem.getQuantity())
.subTotal(new Money(orderItem.getSubTotal()))
.product(new Product(new ProductId(orderItem.productId())))
.price(new Money(orderItem.price()))
.quantity(orderItem.quantity())
.subTotal(new Money(orderItem.subTotal()))
.build())
.collect(Collectors.toList());
}
@@ -57,9 +68,9 @@ public class OrderDataMapper {
private StreetAddress orderAddressToStreetAddress(OrderAddress orderAddress) {
return new StreetAddress(
UUID.randomUUID(),
orderAddress.getStreet(),
orderAddress.getCity(),
orderAddress.getPostalCode()
orderAddress.street(),
orderAddress.city(),
orderAddress.postalCode()
);
}

View File

@@ -1,6 +1,7 @@
package com.food.order.domain.ports.output.repository;
import com.food.order.system.domain.entity.Order;
import com.food.order.system.domain.valueobject.TrackingId;
import java.util.Optional;
@@ -8,7 +9,7 @@ public interface OrderRepository {
Order save(Order order);
Optional<Order> findByTrackingId(String trackingId);
Optional<Order> findByTrackingId(TrackingId trackingId);
}

View File

@@ -0,0 +1,200 @@
package com.food.order.domain;
import com.food.order.domain.dto.create.CreateOrderCommand;
import com.food.order.domain.dto.create.OrderAddress;
import com.food.order.domain.dto.create.OrderItem;
import com.food.order.domain.mapper.OrderDataMapper;
import com.food.order.domain.ports.input.service.OrderApplicationService;
import com.food.order.domain.ports.output.repository.CustomerRepository;
import com.food.order.domain.ports.output.repository.OrderRepository;
import com.food.order.domain.ports.output.repository.RestaurantRepository;
import com.food.order.domain.valueobject.*;
import com.food.order.system.domain.entity.Customer;
import com.food.order.system.domain.entity.Order;
import com.food.order.system.domain.entity.Product;
import com.food.order.system.domain.entity.Restaurant;
import com.food.order.system.domain.exception.OrderDomainException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest(classes = OrderTestConfiguration.class)
class OrderApplicationTest {
@Autowired
private OrderApplicationService orderApplicationService;
@Autowired
private OrderDataMapper orderDataMapper;
@Autowired
private OrderRepository orderRepository;
@Autowired
private CustomerRepository customerRepository;
@Autowired
private RestaurantRepository restaurantRepository;
private CreateOrderCommand createOrderCommand;
private CreateOrderCommand createOrderCommandWrongPrice;
private CreateOrderCommand createOrderCommandWrongProductPrice;
private final UUID CUSTOMER_ID = UUID.randomUUID();
private final UUID RESTAURANT_ID = UUID.randomUUID();
private final Money PRODUCT_PRICE = new Money(BigDecimal.valueOf(50));
private final UUID PRODUCT_ID = UUID.randomUUID();
private final UUID ORDER_ID = UUID.randomUUID();
private final BigDecimal PRICE = new BigDecimal("200.00");
@BeforeAll
public void init(){
createOrderCommand = CreateOrderCommand.builder()
.customerId(CUSTOMER_ID)
.restaurantId(RESTAURANT_ID)
.orderAddress(OrderAddress.builder()
.street("street")
.city("city")
.postalCode("41780")
.build())
.price(PRICE)
.orderItems(List.of(
OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("50.00"))
.subTotal(new BigDecimal("50.00"))
.quantity(1)
.build(),
OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("50.00"))
.subTotal(new BigDecimal("150.00"))
.quantity(3)
.build()))
.build();
createOrderCommandWrongPrice = CreateOrderCommand.builder()
.customerId(CUSTOMER_ID)
.restaurantId(RESTAURANT_ID)
.orderAddress(OrderAddress.builder()
.street("street")
.city("city")
.postalCode("41780")
.build())
.price(PRICE)
.orderItems(List.of(OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("50.00"))
.subTotal(new BigDecimal("100.00"))
.quantity(2)
.build(),
OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("50.00"))
.subTotal(new BigDecimal("150.00"))
.quantity(3)
.build()))
.build();
createOrderCommandWrongProductPrice = CreateOrderCommand.builder()
.customerId(CUSTOMER_ID)
.restaurantId(RESTAURANT_ID)
.orderAddress(OrderAddress.builder()
.street("street")
.city("city")
.postalCode("41780")
.build())
.price(new BigDecimal("210.00"))
.orderItems(List.of(OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("60.00"))
.subTotal(new BigDecimal("60.00"))
.quantity(1)
.build(),
OrderItem.builder()
.productId(PRODUCT_ID)
.price(new BigDecimal("50.00"))
.subTotal(new BigDecimal("150.00"))
.quantity(3)
.build()))
.build();
Customer customer = new Customer();
customer.setId(new CustomerId(CUSTOMER_ID));
Restaurant restaurant = Restaurant.builder()
.id(new RestaurantId(RESTAURANT_ID))
.products(List.of(
new Product(new ProductId(PRODUCT_ID), "product-1", PRODUCT_PRICE),
new Product(new ProductId(UUID.randomUUID()), "product-2", new Money(BigDecimal.valueOf(50)))
))
.isActive(Boolean.TRUE)
.build();
Order order = orderDataMapper.createOrderCommandToOrder(createOrderCommand);
order.setId(new OrderId(ORDER_ID));
Mockito.when(customerRepository.findCustomer(CUSTOMER_ID)).thenReturn(Optional.of(customer));
Mockito.when(restaurantRepository.findRestaurantInformation
(orderDataMapper.createOrderCommandToRestaurant(createOrderCommand)))
.thenReturn(Optional.of(restaurant));
Mockito.when(orderRepository.save(Mockito.any(Order.class))).thenReturn(order);
}
@Test
void testCreateOrder(){
var response = orderApplicationService.createOrder(createOrderCommand);
assertEquals(OrderStatus.PENDING, response.orderStatus());
assertEquals("Order created successfully", response.message());
assertNotNull(response.orderTrackingId());
}
@Test
void testCreateOrderWrongPrice(){
OrderDomainException exception = Assertions.assertThrows(OrderDomainException.class,
() -> orderApplicationService.createOrder(createOrderCommandWrongPrice));
assertEquals("Order total price is not equal to the sum of order items prices", exception.getMessage());
}
@Test
void testCreateOrderWrongProductPrice(){
OrderDomainException exception = Assertions.assertThrows(OrderDomainException.class,
() -> orderApplicationService.createOrder(createOrderCommandWrongProductPrice));
assertEquals("Order item price is not valid", exception.getMessage());
}
@Test
void testCreateOrderWithPassiveRestaurant(){
Restaurant restaurant = Restaurant.builder()
.id(new RestaurantId(RESTAURANT_ID))
.products(List.of(
new Product(new ProductId(PRODUCT_ID), "product-1", PRODUCT_PRICE),
new Product(new ProductId(UUID.randomUUID()), "product-2", new Money(BigDecimal.valueOf(50)))
))
.isActive(Boolean.FALSE)
.build();
Mockito.when(restaurantRepository.findRestaurantInformation
(orderDataMapper.createOrderCommandToRestaurant(createOrderCommand)))
.thenReturn(Optional.of(restaurant));
OrderDomainException exception = Assertions.assertThrows(OrderDomainException.class,
() -> orderApplicationService.createOrder(createOrderCommand));
assertEquals("Restaurant is not active, please try again later. Restaurant id: "+ restaurant.getId(), exception.getMessage());
}
}

View File

@@ -0,0 +1,56 @@
package com.food.order.domain;
import com.food.order.domain.ports.output.message.publisher.payment.OrderCancelledPaymentRequestMessagePublisher;
import com.food.order.domain.ports.output.message.publisher.payment.OrderCreatedPaymentRequestMessagePublisher;
import com.food.order.domain.ports.output.message.publisher.restaurantapproval.OrderPaidRestaurantRequestMessagePublisher;
import com.food.order.domain.ports.output.repository.CustomerRepository;
import com.food.order.domain.ports.output.repository.OrderRepository;
import com.food.order.domain.ports.output.repository.RestaurantRepository;
import com.food.order.system.domain.service.OrderDomainService;
import com.food.order.system.domain.service.impl.OrderDomainServiceImpl;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication(scanBasePackages = "com.food.order.domain")
public class OrderTestConfiguration {
@Bean
public OrderCreatedPaymentRequestMessagePublisher orderCreatedPaymentRequestMessagePublisher() {
return Mockito.mock(OrderCreatedPaymentRequestMessagePublisher.class);
}
@Bean
public OrderCancelledPaymentRequestMessagePublisher orderCancelledPaymentRequestMessagePublisher() {
return Mockito.mock(OrderCancelledPaymentRequestMessagePublisher.class);
}
@Bean
public OrderPaidRestaurantRequestMessagePublisher orderPaidRestaurantRequestMessagePublisher() {
return Mockito.mock(OrderPaidRestaurantRequestMessagePublisher.class);
}
@Bean
public OrderRepository orderRepository() {
return Mockito.mock(OrderRepository.class);
}
@Bean
public CustomerRepository customerRepository() {
return Mockito.mock(CustomerRepository.class);
}
@Bean
public RestaurantRepository restaurantRepository() {
return Mockito.mock(RestaurantRepository.class);
}
@Bean
public OrderDomainService orderDomainService() {
return new OrderDomainServiceImpl();
}
}

View File

@@ -20,7 +20,7 @@ public class OrderItem extends BaseEntity<OrderItemId> {
boolean isPriceValid() {
return price.isGreaterThanZero() &&
price.equals(product.getPrice()) &&
price.getAmount().compareTo(product.getPrice().getAmount()) == 0 &&
price.multiply(quantity).equals(subTotal);
}

View File

@@ -0,0 +1,14 @@
package com.food.order.system.domain.exception;
import com.food.order.domain.exception.DomainException;
public class OrderNotFoundException extends DomainException {
public OrderNotFoundException(String message) {
super(message);
}
public OrderNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -41,7 +41,7 @@ public class OrderDomainServiceImpl implements OrderDomainService {
private void validateRestaurant(Restaurant restaurant) {
if (Boolean.FALSE.equals(restaurant.isActive())) {
throw new OrderDomainException("Restaurant is not active, please try again later. " +
"Restaurant id: {} " + restaurant.getId());
"Restaurant id: " + restaurant.getId());
}
}

View File

@@ -23,6 +23,7 @@
<properties>
<maven-compiler-plugin.version>3.9.0</maven-compiler-plugin.version>
<mockito.version>4.5.1</mockito.version>
</properties>
<dependencyManagement>
@@ -70,6 +71,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>