diff --git a/README.md b/README.md index eb45bbb33..f37676c9d 100644 --- a/README.md +++ b/README.md @@ -4,62 +4,66 @@ 식당에서 사용하는 주문관리 시스템을 구축한다. - 상품 - - [ ] `이름`과 `가격`이 있다. - - [ ] 등록된 상품들을 조회할 수 있다. - - [ ] 이름에 비속어를 사용할 수 없다. - - [ ] 가격을 변경할 수 있다. - - [ ] 가격은 0원 이상이어야 한다. - - [ ] 가격이 해당 상품을 포함하는 메뉴의 가격보다 크면 메뉴를 진열하지 않는다. + - [x] `이름`과 `가격`이 있다. + - [x] 등록된 상품들을 조회할 수 있다. + - [x] 이름에 비속어를 사용할 수 없다. + - [x] 가격은 0원 이상이어야 한다. + - [x] 가격을 변경할 수 있다. + - [x] 0원 이하의 가격으로 변경할 수 없다. + - [x] 등록되지 않은 상품은 가격을 변경할 수 없다. + - [x] 가격이 해당 상품을 포함하는 메뉴의 가격보다 크면 메뉴를 진열하지 않는다. - 메뉴 - - [ ] `이름`, `가격`, `메뉴 그룹`, `진열 여부`, `1개 이상의 상품`이 있다. - - [ ] 포함된 상품에는 수량이 있다. - - [ ] 등록된 메뉴들을 조회할 수 있다. - - [ ] 진열 여부를 변경할 수 있다. - - [ ] 이름에 비속어를 사용할 수 없다. - - [ ] 가격을 변경할 수 있다. - - [ ] 가격은 0원 이상이어야 한다. - - [ ] 가격은 메뉴에 포함된 상품별 총 금액(상품 가격 * 상품 수량)의 합보다 작아야 한다. - - [ ] 하나의 메뉴 그룹에만 속한다. - - [ ] 1개 이상의 상품을 포함한다. - - [ ] 등록되지 않은 상품은 포함할 수 없다. + - [x] `이름`, `가격`, `메뉴 그룹`, `진열 여부`, `1개 이상의 상품`이 있다. + - [x] 포함된 상품에는 수량이 있다. + - [x] 등록된 메뉴들을 조회할 수 있다. + - [x] 진열 여부를 변경할 수 있다. + - [x] 메뉴에 포함된 상품별 총 금액(상품 가격 * 상품 수량)의 합보다 작아야 진열할 수 있다. + - [x] 이름에 비속어를 사용할 수 없다. + - [x] 가격을 변경할 수 있다. + - [x] 가격은 0원 이상이어야 한다. + - [x] 가격은 메뉴에 포함된 상품별 총 금액(상품 가격 * 상품 수량)의 합보다 작아야 한다. + - [x] 하나의 메뉴 그룹에만 속한다. + - [x] 1개 이상의 상품을 포함한다. + - [x] 등록되지 않은 상품은 포함할 수 없다. - 메뉴 그룹 - - [ ] `이름`이 있다. - - [ ] 등록된 모든 메뉴 그룹을 조회할 수 있다. + - [x] `이름`이 있다. + - [x] 등록된 모든 메뉴 그룹을 조회할 수 있다. - 식탁 - - [ ] `이름`, `손님 수`, `착석 여부`가 있다. - - [ ] 식탁의 기본 상태는 0명의 손님이며 비어있다. - - [ ] 모든 식탁을 조회할 수 있다. - - [ ] 손님이 앉을 수 있다. - - [ ] 손님이 앉으면 찬 상태다 - - [ ] 앉은 손님의 수를 변경할 수 있다. - - [ ] 주문 완료된 식탁만 정리할 수 있다. - - [ ] 정리된 식탁은 기본 상태가 된다. + - [x] `이름`, `손님 수`, `착석 여부`가 있다. + - [x] 식탁의 기본 상태는 0명의 손님이며 비어있다. + - [x] 모든 식탁을 조회할 수 있다. + - [x] 손님이 앉을 수 있다. + - [x] 손님이 앉으면 착석 상태다 + - [x] 앉은 손님의 수를 변경할 수 있다. + - [x] 주문 완료된 식탁만 정리할 수 있다. + - [x] 정리된 식탁은 기본 상태가 된다. - 주문 - - [ ] `주문 유형`, `주문 메뉴의 수량 및 가격`이 있다. - - [ ] 주문 유형은 `배달`, `포장`, `식당 내 식사`다. - - [ ] 배달 주문의 `상태`는 `대기`, `수락`, `조리 완료`, `배달 중`, `배달 완료`, `주문 완료` 순이다. - - [ ] 배달 주문은 `주소`가 있어야 한다. - - [ ] 포장 또는 식당 내 식사 주문의 `상태`는 `대기`, `수락`, `조리 완료`, `주문 완료` 순이다. - - [ ] 식당 내 식사는 `식탁`에 착석 후 가능하다. - - [ ] `주문 시간`은 실시간으로 지정한다. - - [ ] 등록된 메뉴가 진열되어야 한다. - - [ ] 1개 이상의 메뉴를 주문해야 한다. - - [ ] 지불한 금액이 주문한 메뉴의 총 금액과 일치해야 한다. - - [ ] 주문의 기본 상태는 대기다. + - [x] `주문 유형`, `주문 메뉴의 수량 및 가격`이 있다. + - [x] 주문 유형은 `배달`, `포장`, `식당 내 식사`다. + - [x] 배달 주문의 `상태`는 `대기`, `수락`, `조리 완료`, `배달 중`, `배달 완료`, `주문 완료` 순이다. + - [x] 배달 주문은 `주소`가 있어야 한다. + - [x] 포장 또는 식당 내 식사 주문의 `상태`는 `대기`, `수락`, `조리 완료`, `주문 완료` 순이다. + - [x] 식당 내 식사는 `식탁`에 착석 후 가능하다. + - [x] `주문 시간`은 실시간으로 지정한다. + - [x] 등록된 메뉴가 진열되어야 한다. + - [x] 1개 이상의 메뉴를 주문해야 한다. + - [x] 지불한 금액이 주문한 메뉴의 총 금액과 일치해야 한다. + - [x] 주문의 기본 상태는 대기다. + - [x] 식당 내 식사 주문은 음수를 활용해 주문을 취소할 수 있다. - 주문 수락 - - [ ] 대기 중인 주문만 수락할 수 있다. - - [ ] 배달 주문은 배달 요청한다. + - [x] 대기 중인 주문만 수락할 수 있다. + - [x] 배달 주문은 배달 요청한다. - 조리 완료 - - [ ] 수락된 주문만 조리 완료할 수 있다. + - [x] 수락된 주문만 조리 완료할 수 있다. - 배달 중 - - [ ] 배달 주문, 조리 완료 주문만 배달 시작할 수 있다. + - [x] 배달 주문, 조리 완료 주문만 배달 시작할 수 있다. - 배달 완료 - - [ ] 배달 중인 주문만 주문 완료할 수 있다. + - [x] 배달 중인 주문만 주문 완료할 수 있다. - 주문 완료 - - [ ] 배달 완료된 배달 주문을 주문 완료할 수 있다. - - [ ] 조리 완료된 포장 주문 또는 식당 내 식사 주문을 주문 완료할 수 있다. - - [ ] 식당 내 식사 주문이 완료되면 식탁을 정리한다. - - [ ] 모든 주문을 조회할 수 있다. + - [x] 배달 완료된 배달 주문을 주문 완료할 수 있다. + - [x] 조리 완료된 포장 주문 또는 식당 내 식사 주문을 주문 완료할 수 있다. + - [x] 식당 내 식사 주문이 완료되면 식탁을 정리한다. + - [x] 모든 주문을 조회할 수 있다. ## 용어 사전 @@ -110,6 +114,8 @@ - [x] `가격, 진열 여부를 변경할 수 있다.` - 여러 항목에 대한 공통 행위를 가지더라도 행위의 조건이 다르다면 분리하는 것이 요구사항을 명확하게 전달할 수 있다. +- [x] 진열여부를 변경할 수 있다. + - 진열시 예외 조건 누락됨. - [x] `식탁을 정리할 수 있다.` - 행위에 대한 조건이 누락됨. - [x] `식당 내 식사는 빈 식탁이 있어야 한다.` diff --git a/src/main/java/kitchenpos/application/DefaultMenuGroupService.java b/src/main/java/kitchenpos/application/DefaultMenuGroupService.java new file mode 100644 index 000000000..165973767 --- /dev/null +++ b/src/main/java/kitchenpos/application/DefaultMenuGroupService.java @@ -0,0 +1,38 @@ +package kitchenpos.application; + +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuGroupRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +@Service +public class DefaultMenuGroupService implements MenuGroupService { + private final MenuGroupRepository menuGroupRepository; + + public DefaultMenuGroupService(final MenuGroupRepository menuGroupRepository) { + this.menuGroupRepository = menuGroupRepository; + } + + @Override + @Transactional + public MenuGroup create(final MenuGroup request) { + final String name = request.getName(); + if (Objects.isNull(name) || name.isEmpty()) { + throw new IllegalArgumentException(); + } + final MenuGroup menuGroup = new MenuGroup(); + menuGroup.setId(UUID.randomUUID()); + menuGroup.setName(name); + return menuGroupRepository.save(menuGroup); + } + + @Override + @Transactional(readOnly = true) + public List findAll() { + return menuGroupRepository.findAll(); + } +} diff --git a/src/main/java/kitchenpos/application/MenuGroupService.java b/src/main/java/kitchenpos/application/MenuGroupService.java index 84add81f5..7963d96e6 100644 --- a/src/main/java/kitchenpos/application/MenuGroupService.java +++ b/src/main/java/kitchenpos/application/MenuGroupService.java @@ -1,36 +1,12 @@ package kitchenpos.application; -import kitchenpos.domain.MenuGroup; -import kitchenpos.domain.MenuGroupRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.List; -import java.util.Objects; -import java.util.UUID; +import kitchenpos.domain.MenuGroup; -@Service -public class MenuGroupService { - private final MenuGroupRepository menuGroupRepository; +public interface MenuGroupService { - public MenuGroupService(final MenuGroupRepository menuGroupRepository) { - this.menuGroupRepository = menuGroupRepository; - } + MenuGroup create(final MenuGroup request); - @Transactional - public MenuGroup create(final MenuGroup request) { - final String name = request.getName(); - if (Objects.isNull(name) || name.isEmpty()) { - throw new IllegalArgumentException(); - } - final MenuGroup menuGroup = new MenuGroup(); - menuGroup.setId(UUID.randomUUID()); - menuGroup.setName(name); - return menuGroupRepository.save(menuGroup); - } + List findAll(); - @Transactional(readOnly = true) - public List findAll() { - return menuGroupRepository.findAll(); - } } diff --git a/src/main/java/kitchenpos/application/MenuService.java b/src/main/java/kitchenpos/application/MenuService.java index 0f3e4b7bc..e3445798f 100644 --- a/src/main/java/kitchenpos/application/MenuService.java +++ b/src/main/java/kitchenpos/application/MenuService.java @@ -1,44 +1,58 @@ package kitchenpos.application; -import kitchenpos.domain.*; -import kitchenpos.infra.PurgomalumClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuGroupRepository; +import kitchenpos.domain.MenuProduct; +import kitchenpos.domain.MenuRepository; +import kitchenpos.domain.Product; +import kitchenpos.domain.ProductRepository; +import kitchenpos.domain.exception.MenuMarginException; +import kitchenpos.domain.exception.MenuPriceException; +import kitchenpos.domain.exception.MenuProductException; +import kitchenpos.domain.exception.MenuProductNotExistException; +import kitchenpos.domain.exception.MenuProductQuantityException; +import kitchenpos.infra.ProfanityClient; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class MenuService { private final MenuRepository menuRepository; private final MenuGroupRepository menuGroupRepository; private final ProductRepository productRepository; - private final PurgomalumClient purgomalumClient; + private final ProfanityClient profanityClient; public MenuService( final MenuRepository menuRepository, final MenuGroupRepository menuGroupRepository, final ProductRepository productRepository, - final PurgomalumClient purgomalumClient + final ProfanityClient profanityClient ) { this.menuRepository = menuRepository; this.menuGroupRepository = menuGroupRepository; this.productRepository = productRepository; - this.purgomalumClient = purgomalumClient; + this.profanityClient = profanityClient; } @Transactional public Menu create(final Menu request) { final BigDecimal price = request.getPrice(); if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); + throw new MenuPriceException(); } final MenuGroup menuGroup = menuGroupRepository.findById(request.getMenuGroupId()) .orElseThrow(NoSuchElementException::new); final List menuProductRequests = request.getMenuProducts(); if (Objects.isNull(menuProductRequests) || menuProductRequests.isEmpty()) { - throw new IllegalArgumentException(); + throw new MenuProductNotExistException(); } final List products = productRepository.findAllByIdIn( menuProductRequests.stream() @@ -46,14 +60,14 @@ public Menu create(final Menu request) { .collect(Collectors.toList()) ); if (products.size() != menuProductRequests.size()) { - throw new IllegalArgumentException(); + throw new MenuProductException(menuProductRequests.size(), products.size()); } final List menuProducts = new ArrayList<>(); BigDecimal sum = BigDecimal.ZERO; for (final MenuProduct menuProductRequest : menuProductRequests) { final long quantity = menuProductRequest.getQuantity(); if (quantity < 0) { - throw new IllegalArgumentException(); + throw new MenuProductQuantityException(quantity); } final Product product = productRepository.findById(menuProductRequest.getProductId()) .orElseThrow(NoSuchElementException::new); @@ -67,10 +81,10 @@ public Menu create(final Menu request) { menuProducts.add(menuProduct); } if (price.compareTo(sum) > 0) { - throw new IllegalArgumentException(); + throw new MenuMarginException(price, sum); } final String name = request.getName(); - if (Objects.isNull(name) || purgomalumClient.containsProfanity(name)) { + if (Objects.isNull(name) || profanityClient.containsProfanity(name)) { throw new IllegalArgumentException(); } final Menu menu = new Menu(); @@ -87,17 +101,18 @@ public Menu create(final Menu request) { public Menu changePrice(final UUID menuId, final Menu request) { final BigDecimal price = request.getPrice(); if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); + throw new MenuPriceException(); } final Menu menu = menuRepository.findById(menuId) .orElseThrow(NoSuchElementException::new); + BigDecimal sum = BigDecimal.ZERO; for (final MenuProduct menuProduct : menu.getMenuProducts()) { - final BigDecimal sum = menuProduct.getProduct() + sum = sum.add(menuProduct.getProduct() .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())); - if (price.compareTo(sum) > 0) { - throw new IllegalArgumentException(); - } + .multiply(BigDecimal.valueOf(menuProduct.getQuantity()))); + } + if (price.compareTo(sum) > 0) { + throw new MenuMarginException(price, sum); } menu.setPrice(price); return menu; @@ -107,13 +122,14 @@ public Menu changePrice(final UUID menuId, final Menu request) { public Menu display(final UUID menuId) { final Menu menu = menuRepository.findById(menuId) .orElseThrow(NoSuchElementException::new); + BigDecimal sum = BigDecimal.ZERO; for (final MenuProduct menuProduct : menu.getMenuProducts()) { - final BigDecimal sum = menuProduct.getProduct() + sum = sum.add(menuProduct.getProduct() .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())); - if (menu.getPrice().compareTo(sum) > 0) { - throw new IllegalStateException(); - } + .multiply(BigDecimal.valueOf(menuProduct.getQuantity()))); + } + if (menu.getPrice().compareTo(sum) > 0) { + throw new MenuMarginException(menu.getPrice(), sum); } menu.setDisplayed(true); return menu; diff --git a/src/main/java/kitchenpos/application/OrderService.java b/src/main/java/kitchenpos/application/OrderService.java index 9fb0d2d65..fbe91174d 100644 --- a/src/main/java/kitchenpos/application/OrderService.java +++ b/src/main/java/kitchenpos/application/OrderService.java @@ -1,17 +1,37 @@ package kitchenpos.application; -import kitchenpos.domain.*; -import kitchenpos.infra.KitchenridersClient; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuRepository; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderLineItem; +import kitchenpos.domain.OrderRepository; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.OrderTableRepository; +import kitchenpos.domain.OrderType; +import kitchenpos.domain.exception.OrderDeliveryAddressException; +import kitchenpos.domain.exception.OrderDisplayException; +import kitchenpos.domain.exception.OrderFromEmptyOrderTableException; +import kitchenpos.domain.exception.OrderInvalidQuantityException; +import kitchenpos.domain.exception.OrderLineItemNotExistException; +import kitchenpos.domain.exception.OrderLineItemNotMatchException; +import kitchenpos.domain.exception.OrderLineItemPriceException; +import kitchenpos.domain.exception.OrderTypeNotExistException; +import kitchenpos.infra.KitchenridersClient; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { + private final OrderRepository orderRepository; private final MenuRepository menuRepository; private final OrderTableRepository orderTableRepository; @@ -33,11 +53,11 @@ public OrderService( public Order create(final Order request) { final OrderType type = request.getType(); if (Objects.isNull(type)) { - throw new IllegalArgumentException(); + throw new OrderTypeNotExistException(); } final List orderLineItemRequests = request.getOrderLineItems(); if (Objects.isNull(orderLineItemRequests) || orderLineItemRequests.isEmpty()) { - throw new IllegalArgumentException(); + throw new OrderLineItemNotExistException(); } final List menus = menuRepository.findAllByIdIn( orderLineItemRequests.stream() @@ -45,23 +65,24 @@ public Order create(final Order request) { .collect(Collectors.toList()) ); if (menus.size() != orderLineItemRequests.size()) { - throw new IllegalArgumentException(); + throw new OrderLineItemNotMatchException(); } final List orderLineItems = new ArrayList<>(); for (final OrderLineItem orderLineItemRequest : orderLineItemRequests) { final long quantity = orderLineItemRequest.getQuantity(); if (type != OrderType.EAT_IN) { if (quantity < 0) { - throw new IllegalArgumentException(); + throw new OrderInvalidQuantityException(quantity); } } final Menu menu = menuRepository.findById(orderLineItemRequest.getMenuId()) .orElseThrow(NoSuchElementException::new); if (!menu.isDisplayed()) { - throw new IllegalStateException(); + throw new OrderDisplayException(); } if (menu.getPrice().compareTo(orderLineItemRequest.getPrice()) != 0) { - throw new IllegalArgumentException(); + throw new OrderLineItemPriceException(menu.getName(), menu.getPrice().longValue(), + orderLineItemRequest.getPrice().longValue()); } final OrderLineItem orderLineItem = new OrderLineItem(); orderLineItem.setMenu(menu); @@ -77,7 +98,7 @@ public Order create(final Order request) { if (type == OrderType.DELIVERY) { final String deliveryAddress = request.getDeliveryAddress(); if (Objects.isNull(deliveryAddress) || deliveryAddress.isEmpty()) { - throw new IllegalArgumentException(); + throw new OrderDeliveryAddressException(); } order.setDeliveryAddress(deliveryAddress); } @@ -85,7 +106,7 @@ public Order create(final Order request) { final OrderTable orderTable = orderTableRepository.findById(request.getOrderTableId()) .orElseThrow(NoSuchElementException::new); if (orderTable.isEmpty()) { - throw new IllegalStateException(); + throw new OrderFromEmptyOrderTableException(); } order.setOrderTable(orderTable); } @@ -102,9 +123,9 @@ public Order accept(final UUID orderId) { if (order.getType() == OrderType.DELIVERY) { BigDecimal sum = BigDecimal.ZERO; for (final OrderLineItem orderLineItem : order.getOrderLineItems()) { - sum = orderLineItem.getMenu() + sum = sum.add(orderLineItem.getMenu() .getPrice() - .multiply(BigDecimal.valueOf(orderLineItem.getQuantity())); + .multiply(BigDecimal.valueOf(orderLineItem.getQuantity()))); } kitchenridersClient.requestDelivery(orderId, sum, order.getDeliveryAddress()); } @@ -179,4 +200,5 @@ public Order complete(final UUID orderId) { public List findAll() { return orderRepository.findAll(); } + } diff --git a/src/main/java/kitchenpos/application/ProductService.java b/src/main/java/kitchenpos/application/ProductService.java index 88cac9dad..37b63aeb3 100644 --- a/src/main/java/kitchenpos/application/ProductService.java +++ b/src/main/java/kitchenpos/application/ProductService.java @@ -1,7 +1,7 @@ package kitchenpos.application; import kitchenpos.domain.*; -import kitchenpos.infra.PurgomalumClient; +import kitchenpos.infra.ProfanityClient; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,12 +15,12 @@ public class ProductService { private final ProductRepository productRepository; private final MenuRepository menuRepository; - private final PurgomalumClient purgomalumClient; + private final ProfanityClient purgomalumClient; public ProductService( final ProductRepository productRepository, final MenuRepository menuRepository, - final PurgomalumClient purgomalumClient + final ProfanityClient purgomalumClient ) { this.productRepository = productRepository; this.menuRepository = menuRepository; @@ -48,7 +48,7 @@ public Product create(final Product request) { public Product changePrice(final UUID productId, final Product request) { final BigDecimal price = request.getPrice(); if (Objects.isNull(price) || price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("0원 미만의 가격으로 등록할 수 없습니다."); } final Product product = productRepository.findById(productId) .orElseThrow(NoSuchElementException::new); @@ -57,9 +57,9 @@ public Product changePrice(final UUID productId, final Product request) { for (final Menu menu : menus) { BigDecimal sum = BigDecimal.ZERO; for (final MenuProduct menuProduct : menu.getMenuProducts()) { - sum = menuProduct.getProduct() + sum = sum.add(menuProduct.getProduct() .getPrice() - .multiply(BigDecimal.valueOf(menuProduct.getQuantity())); + .multiply(BigDecimal.valueOf(menuProduct.getQuantity()))); } if (menu.getPrice().compareTo(sum) > 0) { menu.setDisplayed(false); diff --git a/src/main/java/kitchenpos/domain/JpaMenuGroupRepository.java b/src/main/java/kitchenpos/domain/JpaMenuGroupRepository.java new file mode 100644 index 000000000..1752fc1e4 --- /dev/null +++ b/src/main/java/kitchenpos/domain/JpaMenuGroupRepository.java @@ -0,0 +1,8 @@ +package kitchenpos.domain; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface JpaMenuGroupRepository extends MenuGroupRepository, JpaRepository { + +} diff --git a/src/main/java/kitchenpos/domain/JpaMenuRepository.java b/src/main/java/kitchenpos/domain/JpaMenuRepository.java new file mode 100644 index 000000000..7a0f0c149 --- /dev/null +++ b/src/main/java/kitchenpos/domain/JpaMenuRepository.java @@ -0,0 +1,15 @@ +package kitchenpos.domain; + +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +interface JpaMenuRepository extends MenuRepository, JpaRepository { + + @Override + @Query("select m from Menu m, MenuProduct mp where mp.product.id = :productId") + List findAllByProductId(@Param("productId") UUID productId); + +} diff --git a/src/main/java/kitchenpos/domain/JpaOrderRepository.java b/src/main/java/kitchenpos/domain/JpaOrderRepository.java new file mode 100644 index 000000000..b82636f53 --- /dev/null +++ b/src/main/java/kitchenpos/domain/JpaOrderRepository.java @@ -0,0 +1,8 @@ +package kitchenpos.domain; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface JpaOrderRepository extends OrderRepository, JpaRepository { + +} diff --git a/src/main/java/kitchenpos/domain/JpaOrderTableRepository.java b/src/main/java/kitchenpos/domain/JpaOrderTableRepository.java new file mode 100644 index 000000000..d44c7df6a --- /dev/null +++ b/src/main/java/kitchenpos/domain/JpaOrderTableRepository.java @@ -0,0 +1,8 @@ +package kitchenpos.domain; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface JpaOrderTableRepository extends OrderTableRepository, JpaRepository { + +} diff --git a/src/main/java/kitchenpos/domain/JpaProductRepository.java b/src/main/java/kitchenpos/domain/JpaProductRepository.java new file mode 100644 index 000000000..90b560fbb --- /dev/null +++ b/src/main/java/kitchenpos/domain/JpaProductRepository.java @@ -0,0 +1,8 @@ +package kitchenpos.domain; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +interface JpaProductRepository extends ProductRepository, JpaRepository { + +} diff --git a/src/main/java/kitchenpos/domain/MenuGroupRepository.java b/src/main/java/kitchenpos/domain/MenuGroupRepository.java index 13fa29eda..2b0cbaef3 100644 --- a/src/main/java/kitchenpos/domain/MenuGroupRepository.java +++ b/src/main/java/kitchenpos/domain/MenuGroupRepository.java @@ -1,8 +1,14 @@ package kitchenpos.domain; -import org.springframework.data.jpa.repository.JpaRepository; - +import java.util.List; +import java.util.Optional; import java.util.UUID; -public interface MenuGroupRepository extends JpaRepository { +public interface MenuGroupRepository { + + MenuGroup save(MenuGroup menuGroup); + + List findAll(); + + Optional findById(UUID menuGroupId); } diff --git a/src/main/java/kitchenpos/domain/MenuRepository.java b/src/main/java/kitchenpos/domain/MenuRepository.java index 544493ac7..558aafd81 100644 --- a/src/main/java/kitchenpos/domain/MenuRepository.java +++ b/src/main/java/kitchenpos/domain/MenuRepository.java @@ -1,15 +1,18 @@ package kitchenpos.domain; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - import java.util.List; +import java.util.Optional; import java.util.UUID; -public interface MenuRepository extends JpaRepository { +public interface MenuRepository { List findAllByIdIn(List ids); - @Query("select m from Menu m, MenuProduct mp where mp.product.id = :productId") - List findAllByProductId(@Param("productId") UUID productId); + List findAllByProductId(UUID productId); + + Optional findById(UUID menuId); + + Menu save(Menu menu); + + List findAll(); + } diff --git a/src/main/java/kitchenpos/domain/OrderRepository.java b/src/main/java/kitchenpos/domain/OrderRepository.java index d3c51d32c..37012e654 100644 --- a/src/main/java/kitchenpos/domain/OrderRepository.java +++ b/src/main/java/kitchenpos/domain/OrderRepository.java @@ -1,9 +1,16 @@ package kitchenpos.domain; -import org.springframework.data.jpa.repository.JpaRepository; - +import java.util.List; +import java.util.Optional; import java.util.UUID; -public interface OrderRepository extends JpaRepository { +public interface OrderRepository { + + Optional findById(UUID orderId); + boolean existsByOrderTableAndStatusNot(OrderTable orderTable, OrderStatus status); + + Order save(Order order); + + List findAll(); } diff --git a/src/main/java/kitchenpos/domain/OrderTableRepository.java b/src/main/java/kitchenpos/domain/OrderTableRepository.java index 0e732b0fc..2ca9da812 100644 --- a/src/main/java/kitchenpos/domain/OrderTableRepository.java +++ b/src/main/java/kitchenpos/domain/OrderTableRepository.java @@ -1,8 +1,14 @@ package kitchenpos.domain; -import org.springframework.data.jpa.repository.JpaRepository; - +import java.util.List; +import java.util.Optional; import java.util.UUID; -public interface OrderTableRepository extends JpaRepository { +public interface OrderTableRepository { + + Optional findById(UUID orderTableId); + + OrderTable save(OrderTable orderTable); + + List findAll(); } diff --git a/src/main/java/kitchenpos/domain/ProductRepository.java b/src/main/java/kitchenpos/domain/ProductRepository.java index b19420297..db20db39e 100644 --- a/src/main/java/kitchenpos/domain/ProductRepository.java +++ b/src/main/java/kitchenpos/domain/ProductRepository.java @@ -1,10 +1,15 @@ package kitchenpos.domain; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import java.util.Optional; import java.util.UUID; -public interface ProductRepository extends JpaRepository { +public interface ProductRepository { List findAllByIdIn(List ids); + + Optional findById(UUID productId); + + Product save(Product product); + + List findAll(); } diff --git a/src/main/java/kitchenpos/domain/exception/MenuMarginException.java b/src/main/java/kitchenpos/domain/exception/MenuMarginException.java new file mode 100644 index 000000000..999f045df --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/MenuMarginException.java @@ -0,0 +1,11 @@ +package kitchenpos.domain.exception; + +import java.math.BigDecimal; + +public class MenuMarginException extends IllegalArgumentException { + + public MenuMarginException(BigDecimal price, BigDecimal sum) { + super("메뉴의 가격은 주문하는 상품들의 가격의 합보다 작아야 합니다. " + + "가격 : " + price.longValue() + ", 주문하는 상품들의 가격의 합 : " + sum.longValue()); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/MenuPriceException.java b/src/main/java/kitchenpos/domain/exception/MenuPriceException.java new file mode 100644 index 000000000..409217aad --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/MenuPriceException.java @@ -0,0 +1,9 @@ +package kitchenpos.domain.exception; + +public class MenuPriceException extends IllegalArgumentException { + + public MenuPriceException() { + super("메뉴의 가격은 0원 이상이어야 합니다."); + } + +} diff --git a/src/main/java/kitchenpos/domain/exception/MenuProductException.java b/src/main/java/kitchenpos/domain/exception/MenuProductException.java new file mode 100644 index 000000000..a235f54cd --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/MenuProductException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class MenuProductException extends IllegalArgumentException { + + public MenuProductException(int requestProductSize, int productSize) { + super("주문하는 메뉴 상품과 실제 메뉴 상품의 수가 일치하지 않습니다. 주문 : " + requestProductSize + ", 실제 : " + productSize); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/MenuProductNotExistException.java b/src/main/java/kitchenpos/domain/exception/MenuProductNotExistException.java new file mode 100644 index 000000000..024cd92dc --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/MenuProductNotExistException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class MenuProductNotExistException extends IllegalArgumentException { + + public MenuProductNotExistException() { + super("하나 이상의 메뉴 상품을 포함해야 합니다."); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/MenuProductQuantityException.java b/src/main/java/kitchenpos/domain/exception/MenuProductQuantityException.java new file mode 100644 index 000000000..74234ba32 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/MenuProductQuantityException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class MenuProductQuantityException extends IllegalArgumentException { + + public MenuProductQuantityException(long quantity) { + super("주문하는 상품의 수량은 0 이상이어야 합니다. 주문하려는 상품의 수량 : " + quantity); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderDeliveryAddressException.java b/src/main/java/kitchenpos/domain/exception/OrderDeliveryAddressException.java new file mode 100644 index 000000000..2074eb200 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderDeliveryAddressException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderDeliveryAddressException extends IllegalArgumentException { + + public OrderDeliveryAddressException() { + super("배달 주소가 없습니다."); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderDisplayException.java b/src/main/java/kitchenpos/domain/exception/OrderDisplayException.java new file mode 100644 index 000000000..844ee2c35 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderDisplayException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderDisplayException extends IllegalStateException { + + public OrderDisplayException() { + super("진열되지 않은 메뉴는 주문할 수 없습니다."); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderFromEmptyOrderTableException.java b/src/main/java/kitchenpos/domain/exception/OrderFromEmptyOrderTableException.java new file mode 100644 index 000000000..ecfd8dbdd --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderFromEmptyOrderTableException.java @@ -0,0 +1,10 @@ +package kitchenpos.domain.exception; + +public class OrderFromEmptyOrderTableException extends IllegalStateException { + + private static final String DEFAULT_MESSAGE = "착석 후 주문 가능합니다."; + + public OrderFromEmptyOrderTableException() { + super(DEFAULT_MESSAGE); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderInvalidQuantityException.java b/src/main/java/kitchenpos/domain/exception/OrderInvalidQuantityException.java new file mode 100644 index 000000000..35ab58435 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderInvalidQuantityException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderInvalidQuantityException extends IllegalStateException { + + public OrderInvalidQuantityException(long quantity) { + super("최소 주문 수량은 0개 이상입니다. 주문 수량 : " + quantity); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderLineItemNotExistException.java b/src/main/java/kitchenpos/domain/exception/OrderLineItemNotExistException.java new file mode 100644 index 000000000..9fe210c88 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderLineItemNotExistException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderLineItemNotExistException extends IllegalStateException { + + public OrderLineItemNotExistException() { + super("주문 상품이 없습니다."); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderLineItemNotMatchException.java b/src/main/java/kitchenpos/domain/exception/OrderLineItemNotMatchException.java new file mode 100644 index 000000000..361acf201 --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderLineItemNotMatchException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderLineItemNotMatchException extends IllegalStateException { + + public OrderLineItemNotMatchException() { + super("등록되지 않은 메뉴는 주문할 수 없습니다."); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderLineItemPriceException.java b/src/main/java/kitchenpos/domain/exception/OrderLineItemPriceException.java new file mode 100644 index 000000000..b03f54a6c --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderLineItemPriceException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderLineItemPriceException extends IllegalArgumentException { + + public OrderLineItemPriceException(String menu, long menuPrice, long requestPrice) { + super("가격이 일치하지 않습니다. 메뉴명: " + menu + ", 메뉴 가격: " + menuPrice + ", 지불 가격: " + requestPrice); + } +} diff --git a/src/main/java/kitchenpos/domain/exception/OrderTypeNotExistException.java b/src/main/java/kitchenpos/domain/exception/OrderTypeNotExistException.java new file mode 100644 index 000000000..875dd876a --- /dev/null +++ b/src/main/java/kitchenpos/domain/exception/OrderTypeNotExistException.java @@ -0,0 +1,8 @@ +package kitchenpos.domain.exception; + +public class OrderTypeNotExistException extends IllegalStateException { + + public OrderTypeNotExistException() { + super("주문 유형이 올바르지 않습니다"); + } +} diff --git a/src/main/java/kitchenpos/infra/ProfanityClient.java b/src/main/java/kitchenpos/infra/ProfanityClient.java new file mode 100644 index 000000000..38ed04476 --- /dev/null +++ b/src/main/java/kitchenpos/infra/ProfanityClient.java @@ -0,0 +1,7 @@ +package kitchenpos.infra; + +public interface ProfanityClient { + + boolean containsProfanity(final String text); + +} diff --git a/src/main/java/kitchenpos/infra/PurgomalumClient.java b/src/main/java/kitchenpos/infra/PurgomalumClient.java index 2ed085bed..70655bd29 100644 --- a/src/main/java/kitchenpos/infra/PurgomalumClient.java +++ b/src/main/java/kitchenpos/infra/PurgomalumClient.java @@ -8,13 +8,15 @@ import java.net.URI; @Component -public class PurgomalumClient { +public class PurgomalumClient implements ProfanityClient { + private final RestTemplate restTemplate; public PurgomalumClient(final RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); } + @Override public boolean containsProfanity(final String text) { final URI url = UriComponentsBuilder.fromUriString("https://www.purgomalum.com/service/containsprofanity") .queryParam("text", text) diff --git a/src/test/java/kitchenpos/application/DefaultMenuGroupServiceTest.java b/src/test/java/kitchenpos/application/DefaultMenuGroupServiceTest.java new file mode 100644 index 000000000..f200ebade --- /dev/null +++ b/src/test/java/kitchenpos/application/DefaultMenuGroupServiceTest.java @@ -0,0 +1,79 @@ +package kitchenpos.application; + +import static kitchenpos.application.MenuGroupFixture.세트메뉴; +import static kitchenpos.application.MenuGroupFixture.추천메뉴; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuGroupRepository; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +@DisplayName("메뉴 그룹") +class DefaultMenuGroupServiceTest { + + private final MenuGroupRepository menuGroupRepository = new InMemoryMenuGroupRepository(); + private MenuGroupService menuGroupService; + + @BeforeEach + void setUp() { + menuGroupService = new DefaultMenuGroupService(menuGroupRepository); + } + + @DisplayName("이름이 없으면 예외 발생") + @ParameterizedTest(name = "이름: [{arguments}]") + @NullAndEmptySource + void createNonameException(String name) { + //given + MenuGroup 이름_없는_메뉴_그룹 = 메뉴_그룹_생성(name); + + //when + ThrowingCallable actual = () -> menuGroupService.create(이름_없는_메뉴_그룹); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("메뉴 그룹 생성") + @Test + void create() { + //given + MenuGroup 신규_메뉴_그룹 = 메뉴_그룹_생성(세트메뉴.getName()); + + //when + MenuGroup menuGroup = menuGroupService.create(신규_메뉴_그룹); + + //then + assertAll( + () -> assertThat(menuGroup.getId()).isNotNull(), + () -> assertThat(menuGroup.getName()).isEqualTo(세트메뉴.getName()) + ); + } + + @DisplayName("모든 메뉴 그룹 조회") + @Test + void findAll() { + //given + menuGroupRepository.save(추천메뉴); + menuGroupRepository.save(세트메뉴); + + //when + List menuGroups = menuGroupService.findAll(); + + //then + assertThat(menuGroups).containsExactlyInAnyOrder(세트메뉴, 추천메뉴); + } + + private MenuGroup 메뉴_그룹_생성(String name) { + MenuGroup menuGroup = new MenuGroup(); + menuGroup.setName(name); + return menuGroup; + } +} diff --git a/src/test/java/kitchenpos/application/FakeProfanityClient.java b/src/test/java/kitchenpos/application/FakeProfanityClient.java new file mode 100644 index 000000000..33642622f --- /dev/null +++ b/src/test/java/kitchenpos/application/FakeProfanityClient.java @@ -0,0 +1,22 @@ +package kitchenpos.application; + +import java.util.HashSet; +import java.util.Set; +import kitchenpos.infra.ProfanityClient; + +public class FakeProfanityClient implements ProfanityClient { + + private static final Set profanityDictionary = new HashSet<>(); + + static { + profanityDictionary.add("욕"); + profanityDictionary.add("비속어"); + profanityDictionary.add("나쁜말"); + } + + @Override + public boolean containsProfanity(final String text) { + return profanityDictionary.stream() + .anyMatch(text::contains); + } +} diff --git a/src/test/java/kitchenpos/application/InMemoryMenuGroupRepository.java b/src/test/java/kitchenpos/application/InMemoryMenuGroupRepository.java new file mode 100644 index 000000000..ca872adb9 --- /dev/null +++ b/src/test/java/kitchenpos/application/InMemoryMenuGroupRepository.java @@ -0,0 +1,35 @@ +package kitchenpos.application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuGroupRepository; + +public class InMemoryMenuGroupRepository implements MenuGroupRepository { + + private final Map menuGroups = new HashMap<>(); + + @Override + public MenuGroup save(MenuGroup menuGroup) { + if (Objects.isNull(menuGroup.getId())) { + menuGroup.setId(UUID.randomUUID()); + } + menuGroups.put(menuGroup.getId(), menuGroup); + return menuGroup; + } + + @Override + public List findAll() { + return new ArrayList<>(menuGroups.values()); + } + + @Override + public Optional findById(UUID menuGroupId) { + return Optional.ofNullable(menuGroups.get(menuGroupId)); + } +} diff --git a/src/test/java/kitchenpos/application/InMemoryMenuRepository.java b/src/test/java/kitchenpos/application/InMemoryMenuRepository.java new file mode 100644 index 000000000..72afa42e8 --- /dev/null +++ b/src/test/java/kitchenpos/application/InMemoryMenuRepository.java @@ -0,0 +1,54 @@ +package kitchenpos.application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuRepository; + +public class InMemoryMenuRepository implements MenuRepository { + + private final Map menus = new HashMap<>(); + + @Override + public List findAllByIdIn(List ids) { + return menus.values().stream() + .filter(menu -> ids.contains(menu.getId())) + .collect(Collectors.toList()); + } + + @Override + public List findAllByProductId(UUID productId) { + return menus.values().stream() + .filter( + menu -> menu.getMenuProducts() + .stream() + .anyMatch(menuProduct -> Objects.equals(menuProduct.getProductId(), productId)) + ) + .collect(Collectors.toList()); + } + + @Override + public Optional findById(UUID menuId) { + return Optional.ofNullable(menus.get(menuId)); + } + + @Override + public Menu save(Menu menu) { + if (Objects.isNull(menu.getId())) { + menu.setId(UUID.randomUUID()); + } + menus.put(menu.getId(), menu); + return menu; + } + + @Override + public List findAll() { + return new ArrayList<>(menus.values()); + } +} diff --git a/src/test/java/kitchenpos/application/InMemoryOrderRepository.java b/src/test/java/kitchenpos/application/InMemoryOrderRepository.java new file mode 100644 index 000000000..1f40c7ac9 --- /dev/null +++ b/src/test/java/kitchenpos/application/InMemoryOrderRepository.java @@ -0,0 +1,44 @@ +package kitchenpos.application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderRepository; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; + +public class InMemoryOrderRepository implements OrderRepository { + + private final Map orders = new HashMap<>(); + + @Override + public Optional findById(UUID orderId) { + return Optional.ofNullable(orders.get(orderId)); + } + + @Override + public boolean existsByOrderTableAndStatusNot(OrderTable orderTable, OrderStatus status) { + return orders.values().stream() + .anyMatch( + order -> order.getOrderTableId().equals(orderTable.getId()) && order.getStatus() != status); + } + + @Override + public Order save(Order order) { + if (Objects.isNull(order.getId())) { + order.setId(UUID.randomUUID()); + } + orders.put(order.getId(), order); + return order; + } + + @Override + public List findAll() { + return new ArrayList<>(orders.values()); + } +} diff --git a/src/test/java/kitchenpos/application/InMemoryOrderTableRepository.java b/src/test/java/kitchenpos/application/InMemoryOrderTableRepository.java new file mode 100644 index 000000000..937446312 --- /dev/null +++ b/src/test/java/kitchenpos/application/InMemoryOrderTableRepository.java @@ -0,0 +1,35 @@ +package kitchenpos.application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.OrderTableRepository; + +public class InMemoryOrderTableRepository implements OrderTableRepository { + + private final Map orderTables = new HashMap<>(); + + @Override + public Optional findById(UUID orderTableId) { + return Optional.ofNullable(orderTables.get(orderTableId)); + } + + @Override + public OrderTable save(OrderTable orderTable) { + if (Objects.isNull(orderTable.getId())) { + orderTable.setId(UUID.randomUUID()); + } + orderTables.put(orderTable.getId(), orderTable); + return orderTable; + } + + @Override + public List findAll() { + return new ArrayList<>(orderTables.values()); + } +} diff --git a/src/test/java/kitchenpos/application/InMemoryProductRepository.java b/src/test/java/kitchenpos/application/InMemoryProductRepository.java new file mode 100644 index 000000000..226b7047c --- /dev/null +++ b/src/test/java/kitchenpos/application/InMemoryProductRepository.java @@ -0,0 +1,43 @@ +package kitchenpos.application; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import kitchenpos.domain.Product; +import kitchenpos.domain.ProductRepository; + +public class InMemoryProductRepository implements ProductRepository { + + private final Map products = new HashMap<>(); + + @Override + public List findAllByIdIn(List ids) { + return products.values().stream() + .filter(product -> ids.contains(product.getId())) + .collect(Collectors.toList()); + } + + @Override + public Optional findById(UUID productId) { + return Optional.ofNullable(products.get(productId)); + } + + @Override + public Product save(Product product) { + if (Objects.isNull(product.getId())) { + product.setId(UUID.randomUUID()); + } + products.put(product.getId(), product); + return product; + } + + @Override + public List findAll() { + return new ArrayList<>(products.values()); + } +} diff --git a/src/test/java/kitchenpos/application/MenuBuilder.java b/src/test/java/kitchenpos/application/MenuBuilder.java new file mode 100644 index 000000000..0737ebd32 --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuBuilder.java @@ -0,0 +1,70 @@ +package kitchenpos.application; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuProduct; + +public class MenuBuilder { + + private String name; + private BigDecimal price; + private MenuGroup menuGroup; + private boolean displayed; + private List menuProducts; + private UUID menuGroupId; + + public MenuBuilder withName(String name) { + this.name = name; + return this; + } + + public MenuBuilder withPrice(long price) { + this.price = BigDecimal.valueOf(price); + return this; + } + + public MenuBuilder withPrice(BigDecimal price) { + this.price = price; + return this; + } + + public MenuBuilder withMenuGroup(MenuGroup menuGroup) { + this.menuGroup = menuGroup; + return this; + } + + public MenuBuilder withDisplayed(boolean displayed) { + this.displayed = displayed; + return this; + } + + public MenuBuilder withMenuProducts(List menuProducts) { + this.menuProducts = menuProducts; + return this; + } + + public MenuBuilder withMenuProducts(MenuProduct... menuProducts) { + this.menuProducts = Arrays.asList(menuProducts); + return this; + } + + public MenuBuilder withMenuGroupId(UUID menuGroupId) { + this.menuGroupId = menuGroupId; + return this; + } + + public Menu build() { + Menu menu = new Menu(); + menu.setName(name); + menu.setPrice(price); + menu.setMenuGroup(menuGroup); + menu.setDisplayed(displayed); + menu.setMenuProducts(menuProducts); + menu.setMenuGroupId(menuGroupId); + return menu; + } +} diff --git a/src/test/java/kitchenpos/application/MenuFixture.java b/src/test/java/kitchenpos/application/MenuFixture.java new file mode 100644 index 000000000..167d70c22 --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuFixture.java @@ -0,0 +1,32 @@ +package kitchenpos.application; + +import static kitchenpos.application.MenuGroupFixture.세트메뉴; +import static kitchenpos.application.MenuProductFixture.맛초킹_1개; +import static kitchenpos.application.MenuProductFixture.뿌링클_1개; +import static kitchenpos.application.MenuProductFixture.콜라_1개; + +import java.math.BigDecimal; +import java.util.Arrays; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuProduct; + +public class MenuFixture { + + public static final Menu 뿌링클_세트 = new Menu(); + public static final Menu 맛초킹_세트 = new Menu(); + + static { + initialize(뿌링클_세트, "뿌링클 세트", 뿌링클_1개); + initialize(맛초킹_세트, "맛초킹 세트", 맛초킹_1개); + } + + private static void initialize(Menu menu, String name, MenuProduct menuProduct) { + menu.setName(name); + menu.setMenuGroup(세트메뉴); + menu.setMenuGroupId(세트메뉴.getId()); + menu.setMenuProducts(Arrays.asList(menuProduct, 콜라_1개)); + menu.setPrice(BigDecimal.valueOf(11_000L)); + menu.setDisplayed(true); + } + +} diff --git a/src/test/java/kitchenpos/application/MenuGroupFixture.java b/src/test/java/kitchenpos/application/MenuGroupFixture.java new file mode 100644 index 000000000..3a7afb9b4 --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuGroupFixture.java @@ -0,0 +1,20 @@ +package kitchenpos.application; + +import java.util.UUID; +import kitchenpos.domain.MenuGroup; + +public class MenuGroupFixture { + + public static final MenuGroup 세트메뉴 = new MenuGroup(); + public static final MenuGroup 추천메뉴 = new MenuGroup(); + + static { + initialize(세트메뉴, "세트메뉴"); + initialize(추천메뉴, "추천메뉴"); + } + + private static void initialize(MenuGroup menuGroup, String name) { + menuGroup.setId(UUID.randomUUID()); + menuGroup.setName(name); + } +} diff --git a/src/test/java/kitchenpos/application/MenuProductFixture.java b/src/test/java/kitchenpos/application/MenuProductFixture.java new file mode 100644 index 000000000..19fef9d2e --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuProductFixture.java @@ -0,0 +1,34 @@ +package kitchenpos.application; + +import static kitchenpos.application.ProductFixture.맛초킹; +import static kitchenpos.application.ProductFixture.뿌링클; +import static kitchenpos.application.ProductFixture.콜라; + +import kitchenpos.domain.MenuProduct; +import kitchenpos.domain.Product; + +public class MenuProductFixture { + + public static final MenuProduct 뿌링클_1개 = new MenuProduct(); + public static final MenuProduct 맛초킹_1개 = new MenuProduct(); + public static final MenuProduct 콜라_1개 = new MenuProduct(); + public static final MenuProduct 콜라_수량_오류 = new MenuProduct(); + + static { + initialize(뿌링클_1개, 뿌링클); + initialize(맛초킹_1개, 맛초킹); + initialize(콜라_1개, 콜라); + initialize(콜라_수량_오류, 콜라, -1L); + } + + private static void initialize(MenuProduct menuProduct, Product product) { + initialize(menuProduct, product, 1L); + } + + private static void initialize(MenuProduct menuProduct, Product product, long quantity) { + menuProduct.setProduct(product); + menuProduct.setProductId(product.getId()); + menuProduct.setQuantity(quantity); + } + +} diff --git a/src/test/java/kitchenpos/application/MenuServiceTest.java b/src/test/java/kitchenpos/application/MenuServiceTest.java new file mode 100644 index 000000000..a5372a685 --- /dev/null +++ b/src/test/java/kitchenpos/application/MenuServiceTest.java @@ -0,0 +1,339 @@ +package kitchenpos.application; + +import static kitchenpos.application.MenuProductFixture.맛초킹_1개; +import static kitchenpos.application.MenuProductFixture.콜라_1개; +import static kitchenpos.application.MenuProductFixture.콜라_수량_오류; +import static kitchenpos.application.ProductFixture.맛초킹; +import static kitchenpos.application.ProductFixture.콜라; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuGroup; +import kitchenpos.domain.MenuGroupRepository; +import kitchenpos.domain.MenuRepository; +import kitchenpos.domain.ProductRepository; +import kitchenpos.domain.exception.MenuMarginException; +import kitchenpos.domain.exception.MenuPriceException; +import kitchenpos.domain.exception.MenuProductException; +import kitchenpos.domain.exception.MenuProductNotExistException; +import kitchenpos.domain.exception.MenuProductQuantityException; +import kitchenpos.infra.ProfanityClient; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayName("메뉴 관리") +class MenuServiceTest { + + private final MenuRepository menuRepository = new InMemoryMenuRepository(); + private final MenuGroupRepository menuGroupRepository = new InMemoryMenuGroupRepository(); + private final ProductRepository productRepository = new InMemoryProductRepository(); + private final ProfanityClient profanityClient = new FakeProfanityClient(); + + private MenuService menuService; + private MenuGroup 세트메뉴; + + @BeforeEach + void setUp() { + menuService = new MenuService(menuRepository, menuGroupRepository, productRepository, profanityClient); + productRepository.save(맛초킹); + productRepository.save(콜라); + 세트메뉴 = menuGroupRepository.save(MenuGroupFixture.세트메뉴); + } + + @DisplayName("메뉴의 가격은 0원 이상이어야 한다.") + @ParameterizedTest + @ValueSource(strings = {"-1"}) + @NullSource + void createPriceUnderZeroException(BigDecimal price) { + //given + Menu 잘못된_가격의_메뉴 = 메뉴생성() + .withPrice(price) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(잘못된_가격의_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuPriceException.class); + } + + @DisplayName("메뉴는 메뉴 그룹에 속해야 한다.") + @Test + void createMenuGroupNotExistException() { + //given + Menu 메뉴_그룹에_속하지_않은_메뉴 = 메뉴생성() + .withMenuGroupId(null) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(메뉴_그룹에_속하지_않은_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(NoSuchElementException.class); + } + + @DisplayName("메뉴는 1개 이상의 상품을 포함해야 한다.") + @Test + void createMenuProductsNotExistException() { + //given + Menu 상품_없는_메뉴 = 메뉴생성() + .withMenuProducts(Collections.emptyList()) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(상품_없는_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuProductNotExistException.class); + } + + @DisplayName("중복된 메뉴상품을 가질 수 없다.") + @Test + void createMenuProductsMismatchException() { + //given + Menu 중복된_상품을_포함하는_메뉴 = 메뉴생성() + .withMenuProducts(맛초킹_1개, 맛초킹_1개, 콜라_1개) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(중복된_상품을_포함하는_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuProductException.class); + } + + @DisplayName("메뉴는 수량이 부족한 메뉴상품을 포함할 수 없다.") + @Test + void createMenuProductsLackQuantityException() { + //given + Menu 메뉴상품_수량_오류_메뉴 = 메뉴생성() + .withMenuProducts(맛초킹_1개, 콜라_수량_오류) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(메뉴상품_수량_오류_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuProductQuantityException.class); + } + + @DisplayName("메뉴의 가격은 상품들의 합산 가격과 같거나 작아야 한다.") + @Test + void createPriceException() { + //given + Menu 비싼_메뉴 = 메뉴생성() + .withPrice(15_000L) + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(비싼_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuMarginException.class); + } + + @DisplayName("메뉴 이름에는 비속어가 포함될 수 없다.") + @Test + void createNameException() { + //given + Menu 비속어_메뉴 = 메뉴생성() + .withName("비속어") + .build(); + + //when + ThrowingCallable actual = () -> menuService.create(비속어_메뉴); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("메뉴 생성") + @Test + void create() { + //given + Menu 맛초킹_세트 = 메뉴생성().build(); + + //when + Menu menu = menuService.create(맛초킹_세트); + + //then + assertAll( + () -> assertThat(menu.getName()).isEqualTo(맛초킹_세트.getName()), + () -> assertThat(menu.getMenuProducts()).hasSize(2), + () -> assertThat(menu.getPrice()).isEqualTo(맛초킹_세트.getPrice()), + () -> assertThat(menu.isDisplayed()).isEqualTo(맛초킹_세트.isDisplayed()) + ); + } + + @DisplayName("가격을 0원 미만으로 변경할 수 없다.") + @ParameterizedTest(name = "변경 가격: [{arguments}]") + @ValueSource(strings = {"-1"}) + @NullSource + void changePriceException(BigDecimal price) { + //given + Menu 맛초킹_세트 = 맛초킹_세트_생성(); + + Menu 가격_변경 = 가격_변경(price); + + //when + ThrowingCallable actual = () -> menuService.changePrice(맛초킹_세트.getId(), 가격_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuPriceException.class); + } + + @DisplayName("상품들의 총 가격보다 높은 가격으로 변경할 수 없다.") + @Test + void changePriceGreaterThanProductsPricesException() { + //given + Menu 맛초킹_세트 = 맛초킹_세트_생성(); + + BigDecimal 인상된_가격 = 맛초킹_세트.getPrice().add(BigDecimal.valueOf(10_000L)); + Menu 가격_변경 = 가격_변경(인상된_가격); + + //when + ThrowingCallable actual = () -> menuService.changePrice(맛초킹_세트.getId(), 가격_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuMarginException.class); + } + + @DisplayName("가격 변경") + @Test + void changePrice() { + //given + Menu 맛초킹_세트 = 맛초킹_세트_생성(); + + BigDecimal 인하된_가격 = 맛초킹_세트.getPrice().subtract(BigDecimal.valueOf(10_000L)); + Menu 가격_인하 = 가격_변경(인하된_가격); + + //when + Menu menu = menuService.changePrice(맛초킹_세트.getId(), 가격_인하); + BigDecimal actual = menu.getPrice(); + + //then + assertThat(actual).isEqualTo(인하된_가격); + } + + @DisplayName("등록되지 않은 메뉴는 진열할 수 없다.") + @Test + void displayNotExistException() { + //given + Menu 없는_메뉴 = 등록_되지_않은_메뉴(); + + //when + ThrowingCallable actual = () -> menuService.display(없는_메뉴.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(NoSuchElementException.class); + } + + @DisplayName("상품들의 총 가격보다 높은 메뉴는 진열할 수 없다.") + @Test + void displayException() { + //given + Menu 마진_대박_맛초킹_세트 = 맛초킹_세트_생성(50_000); + + //when + ThrowingCallable actual = () -> menuService.display(마진_대박_맛초킹_세트.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(MenuMarginException.class); + } + + @DisplayName("메뉴를 진열한다.") + @Test + void display() { + //given + Menu 맛초킹_세트 = 맛초킹_세트_생성(); + + //when + Menu menu = menuService.display(맛초킹_세트.getId()); + + //then + assertThat(menu.isDisplayed()).isTrue(); + } + + @DisplayName("등록되지 않은 메뉴는 진열에서 제외할 수 없다.") + @Test + void hideException() { + //given + Menu 없는_메뉴 = 등록_되지_않은_메뉴(); + + //when + ThrowingCallable actual = () -> menuService.hide(없는_메뉴.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(NoSuchElementException.class); + } + + @DisplayName("메뉴를 숨긴다.") + @Test + void hide() { + //given + Menu 맛초킹_세트 = 맛초킹_세트_생성(); + + //when + Menu actual = menuService.hide(맛초킹_세트.getId()); + + //then + assertThat(actual.isDisplayed()).isFalse(); + } + + @DisplayName("모든 메뉴 조회") + @Test + void findAll() { + //given + menuRepository.save(메뉴생성().build()); + menuRepository.save(메뉴생성().build()); + + //when + List actual = menuService.findAll(); + + //then + assertThat(actual).hasSize(2); + } + + private Menu 등록_되지_않은_메뉴() { + return new MenuBuilder().build(); + } + + private Menu 맛초킹_세트_생성() { + return 맛초킹_세트_생성(11_000); + } + + private Menu 맛초킹_세트_생성(int price) { + Menu 맛초킹_세트 = 메뉴생성() + .withPrice(price) + .build(); + return menuRepository.save(맛초킹_세트); + } + + private Menu 가격_변경(BigDecimal price) { + return new MenuBuilder() + .withPrice(price) + .build(); + } + + private MenuBuilder 메뉴생성() { + return new MenuBuilder() + .withMenuGroup(세트메뉴) + .withMenuGroupId(세트메뉴.getId()) + .withName("맛초킹 세트") + .withDisplayed(true) + .withMenuProducts(Arrays.asList(맛초킹_1개, 콜라_1개)) + .withPrice(BigDecimal.valueOf(11_000L)); + } +} diff --git a/src/test/java/kitchenpos/application/OrderBuilder.java b/src/test/java/kitchenpos/application/OrderBuilder.java new file mode 100644 index 000000000..7a84154f2 --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderBuilder.java @@ -0,0 +1,68 @@ +package kitchenpos.application; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderLineItem; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.OrderType; + +public class OrderBuilder { + + private OrderType type; + private OrderStatus status; + private LocalDateTime orderDateTime; + private List orderLineItems; + private String deliveryAddress; + private OrderTable orderTable; + private UUID orderTableId; + + public OrderBuilder withType(OrderType type) { + this.type = type; + return this; + } + + public OrderBuilder withStatus(OrderStatus status) { + this.status = status; + return this; + } + + public OrderBuilder withOrderDateTime(LocalDateTime orderDateTime) { + this.orderDateTime = orderDateTime; + return this; + } + + public OrderBuilder withOrderLineItems(List orderLineItems) { + this.orderLineItems = orderLineItems; + return this; + } + + public OrderBuilder withDeliveryAddress(String deliveryAddress) { + this.deliveryAddress = deliveryAddress; + return this; + } + + public OrderBuilder withOrderTable(OrderTable orderTable) { + this.orderTable = orderTable; + return this; + } + + public OrderBuilder withOrderTableId(UUID orderTableId) { + this.orderTableId = orderTableId; + return this; + } + + public Order build() { + Order order = new Order(); + order.setType(type); + order.setStatus(status); + order.setOrderDateTime(orderDateTime); + order.setOrderLineItems(orderLineItems); + order.setDeliveryAddress(deliveryAddress); + order.setOrderTable(orderTable); + order.setOrderTableId(orderTableId); + return order; + } +} diff --git a/src/test/java/kitchenpos/application/OrderFixture.java b/src/test/java/kitchenpos/application/OrderFixture.java new file mode 100644 index 000000000..56ae0bfa5 --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderFixture.java @@ -0,0 +1,48 @@ +package kitchenpos.application; + +import static kitchenpos.application.MenuFixture.맛초킹_세트; +import static kitchenpos.application.MenuFixture.뿌링클_세트; +import static kitchenpos.application.OrderTableFixture.일번_식탁; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderLineItem; +import kitchenpos.domain.OrderType; + +public class OrderFixture { + + public static final Order 매장_주문 = new Order(); + public static final Order 포장_주문 = new Order(); + public static final Order 배달_주문 = new Order(); + + static { + 매장_주문.setId(UUID.randomUUID()); + 매장_주문.setType(OrderType.EAT_IN); + 매장_주문.setOrderLineItems(Collections.singletonList(주문_항목_뿌링클_세트())); + 매장_주문.setOrderTable(일번_식탁); + 매장_주문.setOrderTableId(일번_식탁.getId()); + + 포장_주문.setId(UUID.randomUUID()); + 포장_주문.setType(OrderType.TAKEOUT); + 포장_주문.setOrderLineItems(Collections.singletonList(주문_항목_맛초킹_세트())); + + 배달_주문.setId(UUID.randomUUID()); + 배달_주문.setType(OrderType.DELIVERY); + 배달_주문.setOrderLineItems(Arrays.asList(주문_항목_뿌링클_세트(), 주문_항목_맛초킹_세트())); + 배달_주문.setDeliveryAddress("ADDRESS"); + } + + private static OrderLineItem 주문_항목_뿌링클_세트() { + OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setMenu(뿌링클_세트); + return orderLineItem; + } + + private static OrderLineItem 주문_항목_맛초킹_세트() { + OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setMenu(맛초킹_세트); + return orderLineItem; + } +} diff --git a/src/test/java/kitchenpos/application/OrderServiceTest.java b/src/test/java/kitchenpos/application/OrderServiceTest.java new file mode 100644 index 000000000..f11b354f2 --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderServiceTest.java @@ -0,0 +1,640 @@ +package kitchenpos.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuRepository; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderLineItem; +import kitchenpos.domain.OrderRepository; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.OrderTableRepository; +import kitchenpos.domain.OrderType; +import kitchenpos.domain.exception.OrderDeliveryAddressException; +import kitchenpos.domain.exception.OrderDisplayException; +import kitchenpos.domain.exception.OrderFromEmptyOrderTableException; +import kitchenpos.domain.exception.OrderInvalidQuantityException; +import kitchenpos.domain.exception.OrderLineItemNotExistException; +import kitchenpos.domain.exception.OrderLineItemNotMatchException; +import kitchenpos.domain.exception.OrderLineItemPriceException; +import kitchenpos.domain.exception.OrderTypeNotExistException; +import kitchenpos.infra.KitchenridersClient; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +@DisplayName("주문 관리") +class OrderServiceTest { + + private final OrderRepository orderRepository = new InMemoryOrderRepository(); + private final MenuRepository menuRepository = new InMemoryMenuRepository(); + private final OrderTableRepository orderTableRepository = new InMemoryOrderTableRepository(); + private final KitchenridersClient kitchenridersClient = new KitchenridersClient(); + + private OrderService orderService; + + private Menu 맛초킹_세트; + private Menu 뿌링클_세트; + + @BeforeEach + void setUp() { + orderService = new OrderService(orderRepository, menuRepository, orderTableRepository, kitchenridersClient); + 뿌링클_세트 = menuRepository.save(MenuFixture.뿌링클_세트); + 맛초킹_세트 = menuRepository.save(MenuFixture.맛초킹_세트); + } + + @DisplayName("주문 유형이 반드시 있어야 한다.") + @Test + void createOrderTypeException() { + //given + Order 주문_유형_없는_주문 = 신규_주문() + .withType(null) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(주문_유형_없는_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderTypeNotExistException.class); + } + + @DisplayName("상품없이 주문할 수 없다.") + @ParameterizedTest(name = "주문 메뉴: [{arguments}]") + @NullAndEmptySource + void createOrderHasNoMenuException(List orderLineItems) { + //given + Order 상품_없는_주문 = 신규_배달_주문() + .withOrderLineItems(orderLineItems) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(상품_없는_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderLineItemNotExistException.class); + } + + @DisplayName("등록되지 않은 상품은 주문할 수 없다.") + @Test + void createOrderInvalidMenuException() { + //given + Menu 등록되지_않은_메뉴 = new Menu(); + OrderLineItem 등록_되지_않은_상품 = 주문_항목_1개(등록되지_않은_메뉴); + + Order 등록되지_않은_상품_주문 = 신규_배달_주문() + .withOrderLineItems(Collections.singletonList(등록_되지_않은_상품)) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(등록되지_않은_상품_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderLineItemNotMatchException.class); + } + + @DisplayName("포장 또는 배달 주문인 경우 0개 미만의 수량으로 주문할 수 없다.") + @ParameterizedTest(name = "메뉴 유형: [{arguments}]") + @EnumSource(value = OrderType.class, names = {"DELIVERY", "TAKEOUT"}) + void createOrderInvalidQuantityException(OrderType orderType) { + //given + OrderLineItem 모자란_수량 = 주문_항목(뿌링클_세트, -1); + Order 신규_배달_주문 = 신규_주문() + .withType(orderType) + .withOrderLineItems(Collections.singletonList(모자란_수량)) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(신규_배달_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderInvalidQuantityException.class); + } + + @DisplayName("진열되지 않은 상품은 주문할 수 없다.") + @Test + void createOrderNotDisplayedMenuException() { + //given + List 진열되지_않은_메뉴_상품 = 메뉴_뿌링클_1개(new Menu()); + Order 진열되지_않은_상품_주문 = 신규_배달_주문() + .withOrderLineItems(진열되지_않은_메뉴_상품) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(진열되지_않은_상품_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderDisplayException.class); + } + + @DisplayName("상품가격과 일치하지 않은 금액으로 주문할 수 없다.") + @Test + void createOrderMismatchPriceException() { + //given + List 모자란_금액 = Collections.singletonList(주문_항목_1개(뿌링클_세트, 8_000)); + Order 모자란_금액_주문 = 신규_배달_주문() + .withOrderLineItems(모자란_금액) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(모자란_금액_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderLineItemPriceException.class); + } + + @DisplayName("배달 주문인 경우 배달 주소가 반드시 있어야 한다.") + @ParameterizedTest(name = "배달 주소: [{arguments}]") + @NullAndEmptySource + void createDeliveryOrderHasNoAddressException(String deliveryAddress) { + //given + Order 주소_없는_배달_주문 = 신규_배달_주문() + .withDeliveryAddress(deliveryAddress) + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(주소_없는_배달_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderDeliveryAddressException.class); + } + + @DisplayName("식탁에 착석하지 않으면 매장 주문을 할 수 없다.") + @Test + void createEatInException() { + //given + OrderTable 착석하지_않은_식탁 = 착석하지_않은_식탁(); + Order 착석하지_않고_주문 = 신규_매장_주문() + .withOrderTableId(착석하지_않은_식탁.getId()) + + .build(); + + //when + ThrowingCallable actual = () -> orderService.create(착석하지_않고_주문); + + //then + assertThatThrownBy(actual).isInstanceOf(OrderFromEmptyOrderTableException.class); + } + + @DisplayName("매장 식당 주문") + @Test + void createOrderEatIn() { + //given + OrderTable 착석한_식탁 = 착석한_식탁(); + + Order 매장_식사_주문 = 신규_매장_주문() + .withOrderTableId(착석한_식탁.getId()) + .build(); + + //when + Order order = orderService.create(매장_식사_주문); + + //then + assertAll( + () -> assertThat(order.getStatus()).isEqualTo(OrderStatus.WAITING), + () -> assertThat(order.getType()).isEqualTo(OrderType.EAT_IN), + () -> assertThat(order.getOrderTable().getId()).isEqualTo(착석한_식탁.getId()) + ); + } + + @DisplayName("포장 주문 성공") + @Test + void createOrderTakeout() { + //given + Order 포장_식사_주문 = 신규_포장_주문().build(); + + //when + Order order = orderService.create(포장_식사_주문); + + //then + assertAll( + () -> assertThat(order.getStatus()).isEqualTo(OrderStatus.WAITING), + () -> assertThat(order.getType()).isEqualTo(OrderType.TAKEOUT) + ); + } + + @DisplayName("배달 주문 성공") + @Test + void createOrderDelivery() { + //given + String deliveryAddress = "우리집"; + + Order 배달_식사_주문 = 신규_배달_주문() + .withDeliveryAddress(deliveryAddress) + .build(); + + //when + Order order = orderService.create(배달_식사_주문); + + //then + assertAll( + () -> assertThat(order.getStatus()).isEqualTo(OrderStatus.WAITING), + () -> assertThat(order.getType()).isEqualTo(OrderType.DELIVERY), + () -> assertThat(order.getDeliveryAddress()).isEqualTo(deliveryAddress) + ); + } + + @DisplayName("대기중인 주문만 수락할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING"}, mode = Mode.EXCLUDE) + void accept(OrderStatus orderStatus) { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withDeliveryAddress("우리집") + .withStatus(orderStatus) + .build(); + + Order 대기중이_아닌_배달_식사_주문 = orderRepository.save(신규_배달_주문); + + //when + ThrowingCallable actual = () -> orderService.accept(대기중이_아닌_배달_식사_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("대기 중인 배달 주문만 수락할 수 있다.") + @Test + void acceptOrderDelivery() { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withDeliveryAddress("우리집") + .build(); + + Order 대기중인_배달_식사_주문 = orderRepository.save(신규_배달_주문); + + //when + Order actual = orderService.accept(대기중인_배달_식사_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.ACCEPTED); + } + + @DisplayName("대기 중인 주문만 수락할 수 있다.") + @ParameterizedTest(name = "주문 유형: [{arguments}]") + @EnumSource(value = OrderType.class, names = {"EAT_IN", "TAKEOUT"}) + void accept(OrderType orderType) { + //given + Order 신규_주문 = 신규_주문() + .withType(orderType) + .build(); + + Order 대기중인_주문 = orderRepository.save(신규_주문); + + //when + Order actual = orderService.accept(대기중인_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.ACCEPTED); + } + + @DisplayName("수락된 주문만 조리 완료할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"ACCEPTED"}, mode = Mode.EXCLUDE) + void serveException(OrderStatus orderStatus) { + //given + Order 신규_주문 = 신규_주문() + .withStatus(orderStatus) + .build(); + + Order 수락되지_않은_주문 = orderRepository.save(신규_주문); + + //when + ThrowingCallable actual = () -> orderService.serve(수락되지_않은_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("조리 완료") + @Test + void serve() { + //given + Order 신규_주문 = 신규_주문() + .withStatus(OrderStatus.ACCEPTED) + .build(); + + Order 수락된_주문 = orderRepository.save(신규_주문); + + //when + Order actual = orderService.serve(수락된_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.SERVED); + } + + @DisplayName("배달 주문만 배달 시작할 수 있다.") + @ParameterizedTest(name = "주문 유형: [{arguments}]") + @EnumSource(value = OrderType.class, names = {"EAT_IN", "TAKEOUT"}) + void startDeliveryOrderTypeException(OrderType orderType) { + //given + Order 신규_주문 = 신규_주문() + .withStatus(OrderStatus.SERVED) + .withType(orderType) + .build(); + + Order 배달이_아닌_주문 = orderRepository.save(신규_주문); + + //when + ThrowingCallable actual = () -> orderService.startDelivery(배달이_아닌_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("조리완료된 주문만 배달을 시작할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING", "ACCEPTED", "DELIVERING", "DELIVERED", "COMPLETED"}) + void startDeliveryOrderStatusException(OrderStatus orderStatus) { + //given + Order 배달_주문 = 신규_배달_주문() + .withStatus(orderStatus) + .build(); + + Order 조리_완료되지_않은_배달_주문 = orderRepository.save(배달_주문); + + //when + ThrowingCallable actual = () -> orderService.startDelivery(조리_완료되지_않은_배달_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("배달 중") + @Test + void startDelivery() { + //given + Order 배달_주문 = 신규_배달_주문() + .withStatus(OrderStatus.SERVED) + .build(); + + Order 조리_완료된_배달_주문 = orderRepository.save(배달_주문); + + //when + Order actual = orderService.startDelivery(조리_완료된_배달_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.DELIVERING); + } + + @DisplayName("배달중인 주문만 가능 배달완료할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING", "ACCEPTED", "SERVED", "DELIVERED", "COMPLETED"}) + void completeDeliveryOrderStatusException(OrderStatus orderStatus) { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withStatus(orderStatus) + .build(); + + Order 배달중인_배달_주문 = orderRepository.save(신규_배달_주문); + + //when + ThrowingCallable actual = () -> orderService.completeDelivery(배달중인_배달_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("배달 완료") + @Test + void completeDelivery() { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withStatus(OrderStatus.DELIVERING) + .build(); + + Order 배달중인_배달_주문 = orderRepository.save(신규_배달_주문); + + //when + Order actual = orderService.completeDelivery(배달중인_배달_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.DELIVERED); + } + + @DisplayName("배달 완료 주문만 주문 완료할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING", "ACCEPTED", "SERVED", "DELIVERING", "COMPLETED"}) + void completeDeliveryStatusException(OrderStatus orderStatus) { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withStatus(orderStatus) + .build(); + + Order 배달완료되지_않은_배달_주문 = orderRepository.save(신규_배달_주문); + + //when + ThrowingCallable actual = () -> orderService.complete(배달완료되지_않은_배달_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("배달 주문 완료") + @Test + void completeDeliveryOrder() { + //given + Order 신규_배달_주문 = 신규_배달_주문() + .withStatus(OrderStatus.DELIVERED) + .build(); + + Order 배달완료된_배달_주문 = orderRepository.save(신규_배달_주문); + + //when + Order actual = orderService.complete(배달완료된_배달_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED); + } + + @DisplayName("조리완료된 포장주문만 주문완료할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING", "ACCEPTED", "DELIVERED", "DELIVERING", "COMPLETED"}) + void completeTakeoutOrderStatusException(OrderStatus orderStatus) { + //given + Order 신규_포장_주문 = 신규_포장_주문() + .withStatus(orderStatus) + .build(); + + Order 조리완료되지_않은_포장_주문 = orderRepository.save(신규_포장_주문); + + //when + ThrowingCallable actual = () -> orderService.complete(조리완료되지_않은_포장_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("조리완료된 매장주문만 주문완료할 수 있다.") + @ParameterizedTest(name = "주문 상태: [{arguments}]") + @EnumSource(value = OrderStatus.class, names = {"WAITING", "ACCEPTED", "DELIVERED", "DELIVERING", "COMPLETED"}) + void completeEatInStatusException(OrderStatus orderStatus) { + //given + Order 신규_포장_주문 = 신규_포장_주문() + .withStatus(orderStatus) + .build(); + + Order 조리완료되지_않은_매장_주문 = orderRepository.save(신규_포장_주문); + + //when + ThrowingCallable actual = () -> orderService.complete(조리완료되지_않은_매장_주문.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("주문 완료 - 포장, 배달") + @ParameterizedTest(name = "주문 유형: [{0}], 주문 상태: [{1}]") + @MethodSource("completeTakeoutOrDelivery") + void completeTakeoutOrDeliveryOrder(OrderType orderType, OrderStatus orderStatus) { + //given + Order 신규_주문 = 신규_주문() + .withType(orderType) + .withStatus(orderStatus) + .build(); + + Order 조리완료된_주문 = orderRepository.save(신규_주문); + + //when + Order actual = orderService.complete(조리완료된_주문.getId()); + + //then + assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED); + } + + private static Stream completeTakeoutOrDelivery() { + return Stream.of( + Arguments.of(OrderType.DELIVERY, OrderStatus.DELIVERED), + Arguments.of(OrderType.TAKEOUT, OrderStatus.SERVED) + ); + } + + @DisplayName("매장 주문 완료") + @Test + void completeEatInOrder() { + //given + OrderTable 착석한_식탁 = 착석한_식탁(); + Order 신규_주문 = 신규_매장_주문() + .withOrderTableId(착석한_식탁.getId()) + .withOrderTable(착석한_식탁) + .withStatus(OrderStatus.SERVED) + .build(); + + Order 조리완료된_주문 = orderRepository.save(신규_주문); + + //when + Order actual = orderService.complete(조리완료된_주문.getId()); + + //then + assertAll( + () -> assertThat(actual.getStatus()).isEqualTo(OrderStatus.COMPLETED), + () -> assertThat(actual.getOrderTable().isEmpty()).isTrue(), + () -> assertThat(actual.getOrderTable().getNumberOfGuests()).isZero() + ); + } + + @DisplayName("모든 주문 조회") + @Test + void findAll() { + //given + Order 매장_주문 = 신규_매장_주문().build(); + Order 포장_주문 = 신규_포장_주문().build(); + Order 배달_주문 = 신규_배달_주문().build(); + + orderRepository.save(매장_주문); + orderRepository.save(포장_주문); + orderRepository.save(배달_주문); + + //when + List actual = orderService.findAll(); + + //then + assertThat(actual).hasSize(3); + } + + + private OrderTable 착석하지_않은_식탁() { + OrderTable orderTable = new OrderTable(); + orderTable.setEmpty(true); + return orderTableRepository.save(orderTable); + } + + private OrderTable 착석한_식탁() { + OrderTable orderTable = new OrderTable(); + orderTable.setEmpty(false); + return orderTableRepository.save(orderTable); + } + + public static OrderLineItem 주문_항목_1개(Menu menu) { + return 주문_항목(menu, 1); + } + + public static OrderLineItem 주문_항목_1개(Menu menu, int price) { + return 주문_항목(menu, 1, price); + } + + public static OrderLineItem 주문_항목(Menu menu, int quantity) { + return 주문_항목(menu, quantity, 10_000); + } + + public static OrderLineItem 주문_항목(Menu menu, int quantity, int price) { + OrderLineItem orderLineItem = new OrderLineItem(); + orderLineItem.setMenu(menu); + orderLineItem.setMenuId(menu.getId()); + orderLineItem.setQuantity(quantity); + orderLineItem.setPrice(BigDecimal.valueOf(price)); + return orderLineItem; + } + + private List 메뉴_뿌링클_1개(Menu menu) { + Menu 뿌링클_세트 = menuRepository.save(menu); + return Collections.singletonList(주문_항목_1개(뿌링클_세트)); + } + + private OrderBuilder 신규_매장_주문() { + return 신규_주문() + .withType(OrderType.EAT_IN) + .withDeliveryAddress(null); + } + + private OrderBuilder 신규_포장_주문() { + return 신규_주문() + .withType(OrderType.TAKEOUT) + .withOrderTableId(null) + .withDeliveryAddress(null); + } + + private OrderBuilder 신규_배달_주문() { + return 신규_주문() + .withType(OrderType.DELIVERY) + .withOrderTableId(null); + } + + private OrderBuilder 신규_주문() { + return new OrderBuilder() + .withOrderDateTime(LocalDateTime.now()) + .withStatus(OrderStatus.WAITING) + .withOrderLineItems(Arrays.asList( + 주문_항목(뿌링클_세트, 1, 11_000), + 주문_항목(맛초킹_세트, 1, 11_000)) + ) + .withDeliveryAddress("독도") + .withOrderTableId(UUID.randomUUID()); + } +} diff --git a/src/test/java/kitchenpos/application/OrderTableFixture.java b/src/test/java/kitchenpos/application/OrderTableFixture.java new file mode 100644 index 000000000..30ef8ba0c --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderTableFixture.java @@ -0,0 +1,28 @@ +package kitchenpos.application; + +import java.util.UUID; +import kitchenpos.domain.OrderTable; + +public class OrderTableFixture { + + public static final OrderTable 일번_식탁 = new OrderTable(); + public static final OrderTable 삼번_식탁 = new OrderTable(); + public static final OrderTable 착석_식탁 = new OrderTable(); + + static { + initialize(일번_식탁, "1번"); + initialize(삼번_식탁, "3번"); + initialize(착석_식탁, "5번", false); + } + + private static void initialize(OrderTable orderTable, String name) { + initialize(orderTable, name, true); + } + + private static void initialize(OrderTable orderTable, String name, boolean empty) { + orderTable.setId(UUID.randomUUID()); + orderTable.setName(name); + orderTable.setNumberOfGuests(0); + orderTable.setEmpty(empty); + } +} diff --git a/src/test/java/kitchenpos/application/OrderTableServiceTest.java b/src/test/java/kitchenpos/application/OrderTableServiceTest.java new file mode 100644 index 000000000..70baddb26 --- /dev/null +++ b/src/test/java/kitchenpos/application/OrderTableServiceTest.java @@ -0,0 +1,209 @@ +package kitchenpos.application; + + +import static kitchenpos.application.OrderTableFixture.삼번_식탁; +import static kitchenpos.application.OrderTableFixture.일번_식탁; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import kitchenpos.domain.Order; +import kitchenpos.domain.OrderRepository; +import kitchenpos.domain.OrderStatus; +import kitchenpos.domain.OrderTable; +import kitchenpos.domain.OrderTableRepository; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +@DisplayName("식탁 관리") +class OrderTableServiceTest { + + private final OrderTableRepository orderTableRepository = new InMemoryOrderTableRepository(); + private final OrderRepository orderRepository = new InMemoryOrderRepository(); + private OrderTableService orderTableService; + + @BeforeEach + void setUp() { + orderTableService = new OrderTableService(orderTableRepository, orderRepository); + } + + @DisplayName("식탁은 이름이 있어야 한다.") + @ParameterizedTest(name = "식탁 이름: [{arguments}]") + @NullAndEmptySource + void createException(String orderTableName) { + //given + OrderTable orderTable = 식탁_생성(orderTableName); + + //when + ThrowingCallable actual = () -> orderTableService.create(orderTable); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("식탁을 생성한다. 식탁의 기본 상태는 0명의 손님이며 비어있다.") + @Test + void create() { + //given + OrderTable 신규_식탁 = 식탁_생성("3번"); + + //when + OrderTable orderTable = orderTableService.create(신규_식탁); + + //then + assertAll( + () -> assertThat(orderTable.getName()).isEqualTo(신규_식탁.getName()), + () -> assertThat(orderTable.getNumberOfGuests()).isZero(), + () -> assertThat(orderTable.isEmpty()).isTrue() + ); + } + + @DisplayName("손님이 앉을 수 있다. 손님이 앉으면 착석 상태다.") + @Test + void sit() { + //given + OrderTable 빈_식탁 = 빈_식탁(); + orderTableRepository.save(빈_식탁); + + //when + OrderTable actual = orderTableService.sit(빈_식탁.getId()); + + //then + assertThat(actual.isEmpty()).isFalse(); + } + + + @DisplayName("착석한 손님의 수를 0명 미만으로 변경할 수 없다.") + @Test + void changeNumberOfGuestsException() { + //given + OrderTable 손님_수_변경 = 식탁_생성(-1); + + //when + ThrowingCallable actual = () -> orderTableService.changeNumberOfGuests(일번_식탁.getId(), 손님_수_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("착석하지 않은 식탁은 손님 수를 변경할 수 없다.") + @Test + void tableIsEmptyThenChangeNumberOfGuestsException() { + //given + OrderTable 빈_식탁 = 빈_식탁(); + orderTableRepository.save(빈_식탁); + + OrderTable 손님_수_변경 = 식탁_생성(3); + + //when + ThrowingCallable actual = () -> orderTableService.changeNumberOfGuests(빈_식탁.getId(), 손님_수_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("착석한 손님의 수를 변경할 수 있다.") + @Test + void changeNumberOfGuests() { + //given + OrderTable 삼번_식탁 = 식탁_생성("3번 식탁", 3, false); + orderTableRepository.save(삼번_식탁); + OrderTable 손님_수_변경 = 식탁_생성(4); + + //when + OrderTable orderTable = orderTableService.changeNumberOfGuests(삼번_식탁.getId(), 손님_수_변경); + + //then + assertAll( + () -> assertThat(orderTable.getNumberOfGuests()).isEqualTo(4), + () -> assertThat(orderTable.isEmpty()).isFalse() + ); + } + + @DisplayName("주문완료되지 않은 식탁은 정리할 수 없다.") + @ParameterizedTest + @EnumSource(value = OrderStatus.class, names = {"COMPLETED"}, mode = Mode.EXCLUDE) + void clearException(OrderStatus orderStatus) { + //given + OrderTable 삼번_식탁 = 식탁_생성("3번 식탁", 3, false); + orderTableRepository.save(삼번_식탁); + + Order 매장_주문 = new Order(); + 매장_주문.setOrderTableId(삼번_식탁.getId()); + 매장_주문.setStatus(orderStatus); + orderRepository.save(매장_주문); + + //when + ThrowingCallable actual = () -> orderTableService.clear(삼번_식탁.getId()); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalStateException.class); + } + + @DisplayName("주문 완료된 식탁은 정리할 수 있다. 정리된 식탁은 기본 상태가 된다.") + @Test + void clear() { + //given + OrderTable 삼번_식탁 = 식탁_생성("3번 식탁", 3, false); + orderTableRepository.save(삼번_식탁); + + Order 매장_주문 = new Order(); + 매장_주문.setOrderTableId(삼번_식탁.getId()); + 매장_주문.setStatus(OrderStatus.COMPLETED); + orderRepository.save(매장_주문); + + //when + OrderTable cleanTable = orderTableService.clear(삼번_식탁.getId()); + + //then + assertAll( + () -> Assertions.assertThat(cleanTable.getNumberOfGuests()).isZero(), + () -> Assertions.assertThat(cleanTable.isEmpty()).isTrue() + ); + } + + @DisplayName("모든 식탁 조회") + @Test + void findAll() { + //given + orderTableRepository.save(일번_식탁); + orderTableRepository.save(삼번_식탁); + + //when + List orderTables = orderTableService.findAll(); + + //then + assertAll( + () -> assertThat(orderTables).hasSize(2), + () -> assertThat(orderTables).containsExactlyInAnyOrder(일번_식탁, 삼번_식탁) + ); + } + + private OrderTable 식탁_생성(String name) { + return 식탁_생성(name, 0, true); + } + + private OrderTable 빈_식탁() { + return 식탁_생성("빈 식탁", 0, true); + } + + private OrderTable 식탁_생성(int numberOfGuests) { + return 식탁_생성("이름없는 식탁", numberOfGuests, false); + } + + private OrderTable 식탁_생성(String name, int numberOfGuests, boolean empty) { + OrderTable orderTable = new OrderTable(); + orderTable.setName(name); + orderTable.setNumberOfGuests(numberOfGuests); + orderTable.setEmpty(empty); + return orderTable; + } +} diff --git a/src/test/java/kitchenpos/application/ProductFixture.java b/src/test/java/kitchenpos/application/ProductFixture.java new file mode 100644 index 000000000..a865df880 --- /dev/null +++ b/src/test/java/kitchenpos/application/ProductFixture.java @@ -0,0 +1,25 @@ +package kitchenpos.application; + +import java.math.BigDecimal; +import java.util.UUID; +import kitchenpos.domain.Product; + +public class ProductFixture { + + public static final Product 뿌링클 = new Product(); + public static final Product 맛초킹 = new Product(); + public static final Product 콜라 = new Product(); + + static { + initialize(뿌링클, "뿌링클", 10_000L); + initialize(맛초킹, "맛초킹", 10_000L); + initialize(콜라, "콜라", 2_000L); + } + + private static void initialize(Product product, String name, long price) { + product.setName(name); + product.setId(UUID.randomUUID()); + product.setPrice(BigDecimal.valueOf(price)); + } + +} diff --git a/src/test/java/kitchenpos/application/ProductServiceTest.java b/src/test/java/kitchenpos/application/ProductServiceTest.java new file mode 100644 index 000000000..d659b36b7 --- /dev/null +++ b/src/test/java/kitchenpos/application/ProductServiceTest.java @@ -0,0 +1,190 @@ +package kitchenpos.application; + +import static kitchenpos.application.MenuProductFixture.콜라_1개; +import static kitchenpos.application.ProductFixture.뿌링클; +import static kitchenpos.application.ProductFixture.콜라; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import kitchenpos.domain.Menu; +import kitchenpos.domain.MenuProduct; +import kitchenpos.domain.MenuRepository; +import kitchenpos.domain.Product; +import kitchenpos.domain.ProductRepository; +import kitchenpos.infra.ProfanityClient; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayName("상품 관리") +class ProductServiceTest { + + private final ProductRepository productRepository = new InMemoryProductRepository(); + private final MenuRepository menuRepository = new InMemoryMenuRepository(); + private final ProfanityClient profanityClient = new FakeProfanityClient(); + + private ProductService productService; + + @BeforeEach + void setUp() { + productService = new ProductService(productRepository, menuRepository, profanityClient); + } + + @DisplayName("가격은 0원 이상이어야 한다.") + @ParameterizedTest(name = "상품금액: [{arguments}]") + @ValueSource(strings = {"-1"}) + @NullSource + void createPriceException(BigDecimal price) { + //given + Product product = 상품_생성(price); + + //when + ThrowingCallable actual = () -> productService.create(product); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("이름에 비속어를 사용할 수 없다.") + @ParameterizedTest(name = "상품 이름: [{arguments}]") + @ValueSource(strings = {"비속어", "욕"}) + @NullSource + void createNameException(String name) { + //given + Product product = 상품_생성(name); + + //when + ThrowingCallable actual = () -> productService.create(product); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("가격을 변경할 수 있다") + @ParameterizedTest(name = "변경할 가격: [{0}]") + @CsvSource(value = { + "8000", + "15000" + }) + void changePrice(long price) { + //given + Product 신규_상품 = productRepository.save(상품_생성(12_000)); + + Product 변경할_금액 = 상품_생성(price); + + //when + Product product = productService.changePrice(신규_상품.getId(), 변경할_금액); + + //then + assertThat(product.getPrice()).isEqualTo(BigDecimal.valueOf(price)); + } + + @DisplayName("상품의 가격이 해당 상품을 포함하는 메뉴의 가격보다 크면 메뉴를 진열하지 않는다.") + @ParameterizedTest(name = "변경할 가격: [{0}], 진열 여부: [{1}]") + @CsvSource(value = { + "8000, false", + "15000, true" + }) + void changePriceThenMenuDisplayed(long price, boolean expectedDisplayed) { + //given + Product 신규_상품 = productRepository.save(상품_생성(12_000)); + + MenuProduct menuProduct = new MenuProduct(); + menuProduct.setProduct(신규_상품); + menuProduct.setProductId(신규_상품.getId()); + menuProduct.setQuantity(1); + + Menu menu = new MenuBuilder().build(); + menu.setDisplayed(true); + menu.setMenuProducts(Arrays.asList(menuProduct, 콜라_1개)); + menu.setPrice(BigDecimal.valueOf(11_000L)); + menuRepository.save(menu); + + Product 변경할_금액 = 상품_생성(price); + + //when + productService.changePrice(신규_상품.getId(), 변경할_금액); + + //then + assertThat(menu.isDisplayed()).isEqualTo(expectedDisplayed); + } + + @DisplayName("0원 미만의 가격으로 변경할 수 없다.") + @ParameterizedTest(name = "변경할 가격: [{arguments}]") + @ValueSource(strings = {"-1"}) + @NullSource + void changePriceException(BigDecimal price) { + //given + Product 사이다 = 상품_생성(2_000L); + productRepository.save(사이다); + + Product 사이다_가격_변경 = 상품_생성(price); + + //when + ThrowingCallable actual = () -> productService.changePrice(사이다.getId(), 사이다_가격_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(IllegalArgumentException.class); + + } + + @DisplayName("등록되지 않은 상품은 가격을 변경할 수 없다.") + @Test + void changePriceNotExistProductException() { + //given + UUID 등록되지_않은_상품_ID = UUID.randomUUID(); + + Product 뿌링클_가격_변경 = 상품_생성(10_000L); + + //when + ThrowingCallable actual = () -> productService.changePrice(등록되지_않은_상품_ID, 뿌링클_가격_변경); + + //then + assertThatThrownBy(actual).isInstanceOf(NoSuchElementException.class); + + } + + @DisplayName("등록된 상품들을 조회할 수 있다.") + @Test + void findAll() { + //given + productRepository.save(뿌링클); + productRepository.save(콜라); + + //when + List products = productService.findAll(); + + //then + assertThat(products).hasSize(2); + + } + + private Product 상품_생성(String name) { + return 상품_생성(name, BigDecimal.valueOf(1_000L)); + } + + private Product 상품_생성(long price) { + return 상품_생성(BigDecimal.valueOf(price)); + } + + private Product 상품_생성(BigDecimal price) { + return 상품_생성("상품", price); + } + + private Product 상품_생성(String name, BigDecimal price) { + Product product = new Product(); + product.setName(name); + product.setPrice(price); + return product; + } +} diff --git a/src/test/java/kitchenpos/ui/FakeMenuGroupService.java b/src/test/java/kitchenpos/ui/FakeMenuGroupService.java new file mode 100644 index 000000000..141a16e58 --- /dev/null +++ b/src/test/java/kitchenpos/ui/FakeMenuGroupService.java @@ -0,0 +1,24 @@ +package kitchenpos.ui; + +import static kitchenpos.application.MenuGroupFixture.세트메뉴; +import static kitchenpos.application.MenuGroupFixture.추천메뉴; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import kitchenpos.application.MenuGroupService; +import kitchenpos.domain.MenuGroup; + +public class FakeMenuGroupService implements MenuGroupService { + + @Override + public MenuGroup create(MenuGroup request) { + request.setId(UUID.randomUUID()); + return request; + } + + @Override + public List findAll() { + return Arrays.asList(세트메뉴, 추천메뉴); + } +} diff --git a/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java b/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java new file mode 100644 index 000000000..38f59ebdd --- /dev/null +++ b/src/test/java/kitchenpos/ui/MenuGroupRestControllerTest.java @@ -0,0 +1,75 @@ +package kitchenpos.ui; + +import static kitchenpos.application.MenuGroupFixture.세트메뉴; +import static kitchenpos.application.MenuGroupFixture.추천메뉴; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@DisplayName("메뉴 그룹 관리") +@WebMvcTest(MenuGroupRestController.class) +@Import(MenuGroupTestConfig.class) +class MenuGroupRestControllerTest { + + private static final String MENU_GROUPS_URI = "/api/menu-groups"; + + @Autowired + private MockMvc webMvc; + + @Autowired + private ObjectMapper objectMapper; + + @DisplayName("메뉴 그룹을 생성할 수 있다.") + @Test + void create() throws Exception { + //given + String body = objectMapper.writeValueAsString(세트메뉴); + + //when + ResultActions resultActions = 그룹_생성_요청(body); + + //then + resultActions + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").isNotEmpty()) + .andExpect(jsonPath("$.name").value(세트메뉴.getName())); + } + + @DisplayName("등록된 모든 메뉴 그룹을 조회할 수 있다.") + @Test + void findAll() throws Exception { + //when + ResultActions resultActions = 그룹_조회_요청(); + + //then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].name").value(세트메뉴.getName())) + .andExpect(jsonPath("$[1].name").value(추천메뉴.getName())); + } + + private ResultActions 그룹_생성_요청(String body) throws Exception { + return webMvc.perform(post(MENU_GROUPS_URI) + .contentType(MediaType.APPLICATION_JSON) + .content(body)) + .andDo(print()); + } + + private ResultActions 그룹_조회_요청() throws Exception { + return webMvc.perform(get(MENU_GROUPS_URI) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()); + } +} diff --git a/src/test/java/kitchenpos/ui/MenuGroupTestConfig.java b/src/test/java/kitchenpos/ui/MenuGroupTestConfig.java new file mode 100644 index 000000000..02865011f --- /dev/null +++ b/src/test/java/kitchenpos/ui/MenuGroupTestConfig.java @@ -0,0 +1,14 @@ +package kitchenpos.ui; + +import kitchenpos.application.MenuGroupService; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class MenuGroupTestConfig { + + @Bean + public MenuGroupService menuGroupService() { + return new FakeMenuGroupService(); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 000000000..259681215 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,4 @@ +server.servlet.encoding.force-response=true +spring.datasource.url=jdbc:h2:~/test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.username=sa +spring.flyway.enabled=true