diff --git a/src/main/java/in/koreatech/koin/domain/shoptoOrderable/repository/ShopToOrderableRepository.java b/src/main/java/in/koreatech/koin/domain/shoptoOrderable/repository/ShopToOrderableRepository.java index e4d4998cf..50f5e8982 100644 --- a/src/main/java/in/koreatech/koin/domain/shoptoOrderable/repository/ShopToOrderableRepository.java +++ b/src/main/java/in/koreatech/koin/domain/shoptoOrderable/repository/ShopToOrderableRepository.java @@ -1,6 +1,7 @@ package in.koreatech.koin.domain.shoptoOrderable.repository; import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderable; +import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderableRequestStatus; import org.springframework.data.repository.Repository; @@ -13,4 +14,6 @@ public interface ShopToOrderableRepository extends Repository findByShopId(Integer shopId); boolean existsByShopId(Integer shopId); + + boolean existsByShopIdAndRequestStatus(Integer shopId, ShopToOrderableRequestStatus requestStatus); } diff --git a/src/main/java/in/koreatech/koin/domain/shoptoOrderable/service/ShopToOrderableService.java b/src/main/java/in/koreatech/koin/domain/shoptoOrderable/service/ShopToOrderableService.java index 242926c8b..884f9d68c 100644 --- a/src/main/java/in/koreatech/koin/domain/shoptoOrderable/service/ShopToOrderableService.java +++ b/src/main/java/in/koreatech/koin/domain/shoptoOrderable/service/ShopToOrderableService.java @@ -7,6 +7,7 @@ import in.koreatech.koin.domain.shop.repository.shop.ShopRepository; import in.koreatech.koin.domain.shoptoOrderable.dto.ShopToOrderableRequest; import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderable; +import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderableRequestStatus; import in.koreatech.koin.domain.shoptoOrderable.repository.ShopToOrderableRepository; import in.koreatech.koin.global.code.ApiResponseCode; import lombok.RequiredArgsConstructor; @@ -27,12 +28,18 @@ public void createOrderableRequest(Integer ownerId, ShopToOrderableRequest reque // 이미 신청한 내역이 있는지 확인 if (shopToOrderableRepository.existsByShopId(shopId)) { - throw CustomException.of(ApiResponseCode.ALREADY_REQUESTED_ORDERABLE_SHOP, "shopId: " + shopId); + throw CustomException.of(ApiResponseCode.DUPLICATE_REQUESTED_ORDERABLE_SHOP, "shopId: " + shopId); } // 가게 사장님인지 확인 + if (!shop.getOwner().getId().equals(ownerId)) { + throw CustomException.of(ApiResponseCode.FORBIDDEN_SHOP_OWNER, "ownerId: " + ownerId + ", shopId: " + shopId); + } // 이미 주문가능 상점인지 확인 + if (shopToOrderableRepository.existsByShopIdAndRequestStatus(shopId, ShopToOrderableRequestStatus.APPROVED)) { + throw CustomException.of(ApiResponseCode.DUPLICATE_ORDERABLE_SHOP, "shopId: " + shopId); + } ShopToOrderable shopToOrderable = ShopToOrderable.builder() .shop(shop) diff --git a/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java b/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java index 9f2ac73ca..bb25f9ab0 100644 --- a/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java +++ b/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java @@ -85,6 +85,7 @@ public enum ApiResponseCode { FORBIDDEN_BLOCKED_USER(HttpStatus.FORBIDDEN, "차단된 사용자입니다."), PAYMENT_ACCESS_DENIED(HttpStatus.FORBIDDEN, "결제 정보 접근 권한이 없습니다."), FORBIDDEN_ORDER(HttpStatus.FORBIDDEN, "주문 정보 접근 권한이 없습니다."), + FORBIDDEN_SHOP_OWNER(HttpStatus.FORBIDDEN, "상점의 사장님이 아닙니다."), /** * 404 Not Found (리소스를 찾을 수 없음) @@ -120,7 +121,8 @@ public enum ApiResponseCode { REQUEST_TOO_FAST(HttpStatus.CONFLICT, "요청이 너무 빠릅니다. 다시 요청해주세요."), OPTIMISTIC_LOCKING_FAILURE(HttpStatus.CONFLICT, "이미 처리된 요청입니다."), DUPLICATE_CLUB_RECRUITMENT(HttpStatus.CONFLICT, "동아리 공고가 이미 존재합니다."), - ALREADY_REQUESTED_ORDERABLE_SHOP(HttpStatus.CONFLICT, "이미 전환 신청이 접수된 상점입니다."), + DUPLICATE_REQUESTED_ORDERABLE_SHOP(HttpStatus.CONFLICT, "이미 전환 신청이 접수된 상점입니다."), + DUPLICATE_ORDERABLE_SHOP(HttpStatus.CONFLICT, "이미 주문 가능한 상점입니다."), /** * 429 Too Many Requests (요청량 초과) diff --git a/src/test/java/in/koreatech/koin/unit/domain/ShopToOrderable/ShopToOrderableServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/ShopToOrderable/ShopToOrderableServiceTest.java new file mode 100644 index 000000000..337793c9a --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/domain/ShopToOrderable/ShopToOrderableServiceTest.java @@ -0,0 +1,161 @@ +package in.koreatech.koin.unit.domain.ShopToOrderable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import in.koreatech.koin.domain.shop.model.shop.Shop; +import in.koreatech.koin.domain.shop.repository.shop.ShopRepository; +import in.koreatech.koin.domain.shoptoOrderable.dto.ShopToOrderableRequest; +import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderable; +import in.koreatech.koin.domain.shoptoOrderable.model.ShopToOrderableRequestStatus; +import in.koreatech.koin.domain.shoptoOrderable.repository.ShopToOrderableRepository; +import in.koreatech.koin.domain.shoptoOrderable.service.ShopToOrderableService; +import in.koreatech.koin.domain.owner.model.Owner; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.global.code.ApiResponseCode; +import in.koreatech.koin.global.exception.CustomException; +import in.koreatech.koin.unit.fixture.OwnerFixture; +import in.koreatech.koin.unit.fixture.ShopFixture; + +import org.springframework.test.util.ReflectionTestUtils; + +@ExtendWith(MockitoExtension.class) +class ShopToOrderableServiceTest { + + @InjectMocks + private ShopToOrderableService shopToOrderableService; + + @Mock + private ShopToOrderableRepository shopToOrderableRepository; + + @Mock + private ShopRepository shopRepository; + + private Owner owner; + private User user; + private Shop shop; + private ShopToOrderableRequest request; + + @BeforeEach + void setUp() { + owner = OwnerFixture.성빈_사장님(); + ReflectionTestUtils.setField(owner, "id", 1); + shop = ShopFixture.주문전환_이전_상점(owner); + + request = new ShopToOrderableRequest( + 5000, + true, + "BOTH", + 1000, + 2000, + true, + "https://example.com/business_license.jpg", + "https://example.com/business_certificate.jpg", + "https://example.com/bank_copy.jpg", + "국민은행", + "123-456-789" + ); + } + + @Nested + @DisplayName("주문 가능 상점 신청 생성 테스트") + class CreateOrderableRequestTest { + + @Test + @DisplayName("정상적으로 주문 가능 상점 신청이 생성된다") + void createOrderableRequestSuccessfully() { + // given + when(shopRepository.findById(100)).thenReturn(Optional.of(shop)); + when(shopToOrderableRepository.existsByShopId(100)).thenReturn(false); + when(shopToOrderableRepository.existsByShopIdAndRequestStatus(100, ShopToOrderableRequestStatus.APPROVED)).thenReturn(false); + when(shopToOrderableRepository.save(any(ShopToOrderable.class))).thenAnswer( + invocation -> invocation.getArgument(0)); + + // when + shopToOrderableService.createOrderableRequest(1, request, 100); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(ShopToOrderable.class); + verify(shopToOrderableRepository).save(captor.capture()); + + ShopToOrderable saved = captor.getValue(); + assertThat(saved.getShop()).isEqualTo(shop); + assertThat(saved.getMinimumOrderAmount()).isEqualTo(5000); + assertThat(saved.getTakeout()).isTrue(); + assertThat(saved.getDeliveryOption()).isEqualTo("BOTH"); + assertThat(saved.getCampusDeliveryTip()).isEqualTo(1000); + assertThat(saved.getOutsideDeliveryTip()).isEqualTo(2000); + assertThat(saved.getIsOpen()).isTrue(); + assertThat(saved.getBank()).isEqualTo("국민은행"); + assertThat(saved.getAccountNumber()).isEqualTo("123-456-789"); + } + + @Test + @DisplayName("존재하지 않는 상점 ID로 신청하면 예외가 발생한다") + void throwExceptionWhenShopNotFound() { + // given + when(shopRepository.findById(999)).thenReturn(Optional.empty()); + + // when & then + CustomException exception = assertThrows(CustomException.class, + () -> shopToOrderableService.createOrderableRequest(1, request, 999)); + + assertThat(exception.getErrorCode()).isEqualTo(ApiResponseCode.NOT_FOUND_SHOP); + } + + @Test + @DisplayName("이미 신청한 내역이 있으면 예외가 발생한다") + void throwExceptionWhenAlreadyRequested() { + // given + when(shopRepository.findById(100)).thenReturn(Optional.of(shop)); + when(shopToOrderableRepository.existsByShopId(100)).thenReturn(true); + + // when & then + CustomException exception = assertThrows(CustomException.class, + () -> shopToOrderableService.createOrderableRequest(1, request, 100)); + + assertThat(exception.getErrorCode()).isEqualTo(ApiResponseCode.DUPLICATE_REQUESTED_ORDERABLE_SHOP); + } + + @Test + @DisplayName("상점 사장님이 아닌 경우 예외가 발생한다") + void throwExceptionWhenNotShopOwner() { + // given + when(shopRepository.findById(100)).thenReturn(Optional.of(shop)); + + // when & then + CustomException exception = assertThrows(CustomException.class, + () -> shopToOrderableService.createOrderableRequest(2, request, 100)); + assertThat(exception.getErrorCode()).isEqualTo(ApiResponseCode.FORBIDDEN_SHOP_OWNER); + } + + @Test + @DisplayName("이미 승인된 상점이 있으면 예외가 발생한다") + void throwExceptionWhenAlreadyApproved() { + // given + when(shopRepository.findById(100)).thenReturn(Optional.of(shop)); + when(shopToOrderableRepository.existsByShopId(100)).thenReturn(false); + when(shopToOrderableRepository.existsByShopIdAndRequestStatus(100, ShopToOrderableRequestStatus.APPROVED)).thenReturn(true); + + // when & then + CustomException exception = assertThrows(CustomException.class, + () -> shopToOrderableService.createOrderableRequest(1, request, 100)); + assertThat(exception.getErrorCode()).isEqualTo(ApiResponseCode.DUPLICATE_ORDERABLE_SHOP); + } + } +} diff --git a/src/test/java/in/koreatech/koin/unit/fixture/ShopFixture.java b/src/test/java/in/koreatech/koin/unit/fixture/ShopFixture.java new file mode 100644 index 000000000..73235c698 --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/fixture/ShopFixture.java @@ -0,0 +1,24 @@ +package in.koreatech.koin.unit.fixture; + +import org.springframework.test.util.ReflectionTestUtils; + +import in.koreatech.koin.domain.owner.model.Owner; +import in.koreatech.koin.domain.shop.model.shop.Shop; + +public class ShopFixture { + + private ShopFixture() {} + + public static Shop 주문전환_이전_상점(Owner owner) { + Shop shop = Shop.builder() + .name("테스트 상점") + .owner(owner) + .phone("041-123-4567") + .address("천안시 동남구") + .description("테스트 상점입니다") + .build(); + ReflectionTestUtils.setField(shop, "id", 100); + + return shop; + } +}