diff --git a/.github/workflows/pr-title-and-branch-validation.yml b/.github/workflows/pr-title-and-branch-validation.yml index 36d5c177..997a0e3e 100644 --- a/.github/workflows/pr-title-and-branch-validation.yml +++ b/.github/workflows/pr-title-and-branch-validation.yml @@ -11,7 +11,7 @@ on: jobs: validate-title-and-branch: - if: ${{ !(startsWith(github.head_ref, 'release/') && github.base_ref == 'develop') && github.actor != 'jira[bot]' }} # release/* -> develop일 때는 실행하지 않음, 또한 Jira Bot이 실행하는 경우 무시 + if: ${{ !(startsWith(github.head_ref, 'release/') && github.base_ref == 'develop')}} # release/* -> develop일 때는 실행하지 않음 runs-on: ubuntu-latest steps: - name: Check out code diff --git a/src/main/java/org/chzz/market/domain/payment/service/PaymentService.java b/src/main/java/org/chzz/market/domain/payment/service/PaymentService.java index 67ce4774..fa67d24a 100644 --- a/src/main/java/org/chzz/market/domain/payment/service/PaymentService.java +++ b/src/main/java/org/chzz/market/domain/payment/service/PaymentService.java @@ -61,7 +61,7 @@ public Auction getAuction(Long auctionId) { @Transactional(readOnly = true) public void validateOrderId(String orderId) { - if (paymentRepository.existsByOrderId(orderId) || paymentClient.isValidOrderId(orderId)) { + if (paymentRepository.existsByOrderId(orderId) || !paymentClient.isValidOrderId(orderId)) { throw new PaymentException(PaymentErrorCode.ALREADY_EXIST); } } diff --git a/src/main/java/org/chzz/market/domain/product/error/ProductErrorCode.java b/src/main/java/org/chzz/market/domain/product/error/ProductErrorCode.java index 07c9fca8..129b477e 100644 --- a/src/main/java/org/chzz/market/domain/product/error/ProductErrorCode.java +++ b/src/main/java/org/chzz/market/domain/product/error/ProductErrorCode.java @@ -10,7 +10,7 @@ public enum ProductErrorCode implements ErrorCode { PRODUCT_REGISTER_FAILED(HttpStatus.BAD_REQUEST, "상품 등록에 실패했습니다."), INVALID_PRODUCT_STATE(HttpStatus.BAD_REQUEST, "상품 상태가 유효하지 않습니다."), - ALREADY_IN_AUCTION(HttpStatus.BAD_REQUEST, "이미 경매가 진행 중인 상품입니다."), + ALREADY_IN_AUCTION(HttpStatus.BAD_REQUEST, "이미 정식경매로 등록된 상품입니다."), PRODUCT_ALREADY_AUCTIONED(HttpStatus.BAD_REQUEST, "상품이 이미 경매로 등록되어 삭제할 수 없습니다."), FORBIDDEN_PRODUCT_ACCESS(HttpStatus.FORBIDDEN, "상품에 접근할 수 없습니다."), PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "상품을 찾을 수 없습니다."), diff --git a/src/main/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImpl.java b/src/main/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImpl.java index 74c5b7b7..0ba7fe44 100644 --- a/src/main/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImpl.java +++ b/src/main/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImpl.java @@ -102,8 +102,9 @@ public Optional findProductDetailsById(Long productId, L product.category )) .from(product) + .leftJoin(auction).on(auction.product.id.eq(product.id)) .join(product.user, user) - .where(product.id.eq(productId)) + .where(auction.id.isNull().and(product.id.eq(productId))) .fetchOne()); return result.map(response -> { diff --git a/src/main/java/org/chzz/market/domain/product/service/ProductService.java b/src/main/java/org/chzz/market/domain/product/service/ProductService.java index e1f80145..abf3093b 100644 --- a/src/main/java/org/chzz/market/domain/product/service/ProductService.java +++ b/src/main/java/org/chzz/market/domain/product/service/ProductService.java @@ -61,6 +61,9 @@ public Page getProductListByCategory(Category category, Long us * 상품 상세 정보 조회 */ public ProductDetailsResponse getProductDetails(Long productId, Long userId) { + if (auctionRepository.existsByProductId(productId)) { + throw new ProductException(ALREADY_IN_AUCTION); + } return productRepository.findProductDetailsById(productId, userId) .orElseThrow(() -> new ProductException(PRODUCT_NOT_FOUND)); } diff --git a/src/main/java/org/chzz/market/domain/user/oauth2/CustomFailureHandler.java b/src/main/java/org/chzz/market/domain/user/oauth2/CustomFailureHandler.java index bd0391b3..7a080694 100644 --- a/src/main/java/org/chzz/market/domain/user/oauth2/CustomFailureHandler.java +++ b/src/main/java/org/chzz/market/domain/user/oauth2/CustomFailureHandler.java @@ -20,7 +20,7 @@ public class CustomFailureHandler extends SimpleUrlAuthenticationFailureHandler @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + log.error("Social login failure - User-Agent: {}, Error: {}", request.getHeader("User-Agent"), exception.getMessage()); response.sendRedirect(clientUrl + REDIRECT_URL_FAILURE); - log.error("social login failure"); } } diff --git a/src/test/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImplTest.java b/src/test/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImplTest.java index 53ab26d8..39c9a58c 100644 --- a/src/test/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImplTest.java +++ b/src/test/java/org/chzz/market/domain/product/repository/ProductRepositoryCustomImplTest.java @@ -15,6 +15,8 @@ import java.util.NoSuchElementException; import java.util.Optional; import org.chzz.market.common.DatabaseTest; +import org.chzz.market.domain.auction.entity.Auction; +import org.chzz.market.domain.auction.repository.AuctionRepository; import org.chzz.market.domain.image.entity.Image; import org.chzz.market.domain.image.repository.ImageRepository; import org.chzz.market.domain.like.entity.Like; @@ -55,17 +57,22 @@ class ProductRepositoryCustomImplTest { @Autowired LikeRepository likeRepository; + @Autowired + AuctionRepository auctionRepository; + @PersistenceContext EntityManager entityManager; private static User user1, user2, user3; - private static Product product1, product2, product3, product4, product5; - private static Image image1, image2, image3, image4, image5; + private static Product product1, product2, product3, product4, product5, product6; + private static Image image1, image2, image3, image4, image5, image6; private static Like like1, like2, like3; + private static Auction auction1; @BeforeAll static void setUpOnce(@Autowired UserRepository userRepository, @Autowired ProductRepository productRepository, + @Autowired AuctionRepository auctionRepository, @Autowired ImageRepository imageRepository, @Autowired LikeRepository likeRepository) { user1 = User.builder().providerId("1234").nickname("닉네임1").email("user1@test.com").build(); @@ -83,19 +90,24 @@ static void setUpOnce(@Autowired UserRepository userRepository, ReflectionTestUtils.setField(product4, "createdAt", LocalDateTime.now().minusDays(2)); product5 = Product.builder().user(user3).name("사전등록상품5").category(FASHION_AND_CLOTHING).minPrice(50000).build(); ReflectionTestUtils.setField(product5, "createdAt", LocalDateTime.now().minusDays(1)); - productRepository.saveAll(List.of(product1, product2, product3, product4, product5)); + product6 = Product.builder().user(user3).name("사전등록상품6").category(FASHION_AND_CLOTHING).minPrice(50000).build(); + productRepository.saveAll(List.of(product1, product2, product3, product4, product5, product6)); image1 = Image.builder().product(product1).cdnPath("path/to/image1.jpg").sequence(1).build(); image2 = Image.builder().product(product2).cdnPath("path/to/image2.jpg").sequence(1).build(); image3 = Image.builder().product(product3).cdnPath("path/to/image3.jpg").sequence(1).build(); image4 = Image.builder().product(product4).cdnPath("path/to/image4.jpg").sequence(1).build(); image5 = Image.builder().product(product5).cdnPath("path/to/image5.jpg").sequence(1).build(); - imageRepository.saveAll(List.of(image1, image2, image3, image4, image5)); + image6 = Image.builder().product(product6).cdnPath("path/to/image6.jpg").sequence(1).build(); + imageRepository.saveAll(List.of(image1, image2, image3, image4, image5, image6)); like1 = Like.builder().user(user2).product(product1).build(); like2 = Like.builder().user(user3).product(product1).build(); like3 = Like.builder().user(user1).product(product3).build(); likeRepository.saveAll(List.of(like1, like2, like3)); + + auction1 = Auction.toEntity(product6); + auctionRepository.save(auction1); } @AfterEach @@ -104,6 +116,7 @@ void tearDown() { imageRepository.deleteAll(); productRepository.deleteAll(); userRepository.deleteAll(); + auctionRepository.deleteAll(); } @Nested @@ -308,6 +321,14 @@ void findProductDetailsWithAnonymousUser() { }); } + @Test + @DisplayName("8. 정식 경매 상품의 사전 경매 조회 불가") + void findPreAuctionDetailsForRegularAuctionProduct() { + Optional result = productRepository.findProductDetailsById( + product6.getId(), null); + assertThat(result).isEmpty(); + } + } @Nested diff --git a/src/test/java/org/chzz/market/domain/product/service/ProductServiceTest.java b/src/test/java/org/chzz/market/domain/product/service/ProductServiceTest.java index 3b66210f..39dffb93 100644 --- a/src/test/java/org/chzz/market/domain/product/service/ProductServiceTest.java +++ b/src/test/java/org/chzz/market/domain/product/service/ProductServiceTest.java @@ -5,6 +5,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.chzz.market.domain.product.entity.Product.Category.ELECTRONICS; import static org.chzz.market.domain.product.entity.Product.Category.HOME_APPLIANCES; +import static org.chzz.market.domain.product.error.ProductErrorCode.ALREADY_IN_AUCTION; +import static org.chzz.market.domain.product.error.ProductErrorCode.FORBIDDEN_PRODUCT_ACCESS; +import static org.chzz.market.domain.product.error.ProductErrorCode.PRODUCT_ALREADY_AUCTIONED; +import static org.chzz.market.domain.product.error.ProductErrorCode.PRODUCT_NOT_FOUND; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -137,7 +141,6 @@ void setUp() { .imageSequence(Collections.emptyMap()) .build(); - System.setProperty("org.mockito.logging.verbosity", "all"); } @@ -187,7 +190,7 @@ void updateProduct_ProductNotFound() { // when & then assertThatThrownBy(() -> productService.updateProduct(user.getId(), 1L, updateRequest, null)) .isInstanceOf(ProductException.class) - .hasMessageContaining("상품을 찾을 수 없습니다."); + .hasMessageContaining(PRODUCT_NOT_FOUND.getMessage()); } @Test @@ -200,7 +203,7 @@ void updateProduct_AlreadyInAuction() { // when & then assertThatThrownBy(() -> productService.updateProduct(user.getId(), 1L, updateRequest, null)) .isInstanceOf(ProductException.class) - .hasMessageContaining("이미 경매가 진행 중인 상품입니다."); + .hasMessageContaining(ALREADY_IN_AUCTION.getMessage()); } @Test @@ -241,7 +244,7 @@ void updateProduct_InvalidUser() { // when & then assertThatThrownBy(() -> productService.updateProduct(999L, 1L, invalidUserRequest, null)) .isInstanceOf(ProductException.class) - .hasMessageContaining("상품을 찾을 수 없습니다."); + .hasMessageContaining(PRODUCT_NOT_FOUND.getMessage()); } @Test @@ -261,7 +264,7 @@ void updateProduct_InvalidOwner() { // when & then assertThatThrownBy(() -> productService.updateProduct(2L, 1L, invalidUserRequest, null)) .isInstanceOf(ProductException.class) - .hasMessageContaining("상품에 접근할 수 없습니다."); + .hasMessageContaining(FORBIDDEN_PRODUCT_ACCESS.getMessage()); } @Test @@ -314,7 +317,7 @@ void updateProduct_Unauthorized() { // when & then assertThatThrownBy(() -> productService.updateProduct(2L, 1L, updateRequest, null)) .isInstanceOf(ProductException.class) - .hasMessageContaining("상품에 접근할 수 없습니다."); + .hasMessageContaining(FORBIDDEN_PRODUCT_ACCESS.getMessage()); } @Test @@ -378,7 +381,7 @@ void deleteAlreadyAuctionedProduct() { // When & Then assertThatThrownBy(() -> productService.deleteProduct(1L, 1L)) .isInstanceOf(ProductException.class) - .hasMessage("상품이 이미 경매로 등록되어 삭제할 수 없습니다."); + .hasMessage(PRODUCT_ALREADY_AUCTIONED.getMessage()); } @Test @@ -390,7 +393,7 @@ void deleteNonExistingProduct() { // When & Then assertThatThrownBy(() -> productService.deleteProduct(1L, 1L)) .isInstanceOf(ProductException.class) - .hasMessage("상품을 찾을 수 없습니다."); + .hasMessage(PRODUCT_NOT_FOUND.getMessage()); } }