diff --git a/src/main/java/com/tasksprints/auction/api/auction/AuctionController.java b/src/main/java/com/tasksprints/auction/api/auction/AuctionController.java index 2136a4f8..49881281 100644 --- a/src/main/java/com/tasksprints/auction/api/auction/AuctionController.java +++ b/src/main/java/com/tasksprints/auction/api/auction/AuctionController.java @@ -7,6 +7,7 @@ import com.tasksprints.auction.domain.auction.service.AuctionService; import com.tasksprints.auction.domain.bid.dto.BidResponse; import com.tasksprints.auction.domain.bid.service.BidService; +import com.tasksprints.auction.domain.product.model.ProductCategory; import com.tasksprints.auction.domain.review.dto.request.ReviewRequest; import com.tasksprints.auction.domain.review.dto.response.ReviewResponse; import com.tasksprints.auction.domain.review.service.ReviewService; @@ -16,6 +17,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -80,8 +83,8 @@ public ResponseEntity> getAuctionStatus(@PathVariable Long auc @GetMapping @Operation(summary = "Get all auctions", description = "Retrieves all auctions.") @ApiResponse(responseCode = "200", description = "All auctions retrieved successfully") - public ResponseEntity>> getAllAuctions(AuctionRequest.SearchCondition searchCondition) { - List auctions = auctionService.getAuctionsByFilter(searchCondition); + public ResponseEntity>> getAllAuctions(Pageable pageable, AuctionRequest.SearchCondition searchCondition) { + Page auctions = auctionService.getAuctionsByFilter(pageable, searchCondition); return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.ALL_AUCTIONS_RETRIEVED, auctions)); } @@ -93,6 +96,15 @@ public ResponseEntity> getAuctionById(@PathVariable L return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.AUCTION_RETRIEVED, auction)); } + @Deprecated + @GetMapping("/category/{category}") + @Operation(summary = "Get auctions by ProductCategory", description = "Retrieve all auction by its ProductCategory.") + @ApiResponse(responseCode = "200", description = "All auctions retrieved successfully") + public ResponseEntity>> getAuctionByProductCategory(Pageable pageable, @PathVariable String category, AuctionRequest.SearchCondition searchCondition) { + Page auctions = auctionService.getAuctionsByProductCategory(pageable, searchCondition, ProductCategory.fromDisplayName(category)); + return ResponseEntity.ok(ApiResult.success(ApiResponseMessages.AUCTION_RETRIEVED, auctions)); + } + // Bid Endpoints @PostMapping("/{auctionId}/bid") @Operation(summary = "Submit a bid", description = "Submits a bid for the specified auction.") diff --git a/src/main/java/com/tasksprints/auction/domain/auction/dto/response/AuctionResponse.java b/src/main/java/com/tasksprints/auction/domain/auction/dto/response/AuctionResponse.java index ff4356db..c0ff0dd4 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/dto/response/AuctionResponse.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/dto/response/AuctionResponse.java @@ -1,13 +1,15 @@ package com.tasksprints.auction.domain.auction.dto.response; import com.tasksprints.auction.domain.auction.model.Auction; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; +import com.tasksprints.auction.domain.product.model.ProductImage; +import lombok.*; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static java.util.stream.Collectors.toList; @Data @Builder @@ -20,11 +22,50 @@ public class AuctionResponse { private String category; private String status; private BigDecimal startingBid; - + private Long viewCount; private Long sellerId; private String sellerNickName; + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Details { + private Long id; + private LocalDateTime startTime; + private LocalDateTime endTime; + private String category; + private String status; + private BigDecimal startingBid; + private Long viewCount; + private Long sellerId; + private String sellerNickName; + private Long productId; + private String productCategory; + @Setter + private List productImageUrls; + public static AuctionResponse.Details of(Auction auction) { + return AuctionResponse.Details.builder() + .id(auction.getId()) + .startTime(auction.getStartTime()) + .endTime(auction.getEndTime()) + .category(auction.getAuctionCategory().name()) + .status(auction.getAuctionStatus().name()) + .startingBid(auction.getStartingBid()) + .viewCount(auction.getViewCount()) + .sellerId(auction.getSeller() != null ? auction.getSeller().getId() : null) + .sellerNickName(auction.getSeller() != null ? auction.getSeller().getNickName() : null) + .productId(auction.getProduct() != null ? auction.getProduct().getId() : null) + .productCategory(auction.getProduct() != null && auction.getProduct().getCategory() != null ? auction.getProduct().getCategory().name() : null) + .productImageUrls(auction.getProduct() != null && auction.getProduct().getProductImageList() != null + ? auction.getProduct().getProductImageList().stream() + .map(ProductImage::getImageUrl) + .collect(toList()) + : Collections.emptyList()) + .build(); + } + } public static AuctionResponse of(Auction auction) { return AuctionResponse.builder() .id(auction.getId()) @@ -33,6 +74,7 @@ public static AuctionResponse of(Auction auction) { .category(auction.getAuctionCategory().name()) .status(auction.getAuctionStatus().name()) .startingBid(auction.getStartingBid()) + .viewCount(auction.getViewCount()) .sellerId(auction.getSeller().getId()) .sellerNickName(auction.getSeller().getNickName()) .build(); diff --git a/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java b/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java index 320b5078..c141b035 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/model/Auction.java @@ -6,6 +6,7 @@ import com.tasksprints.auction.domain.user.model.User; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.ColumnDefault; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -50,11 +51,20 @@ public class Auction extends BaseEntity { @Builder.Default private Product product = null; - @OneToMany @Builder.Default private List bids = new ArrayList<>(); + @Column(nullable = false) + private Long viewCount; + + @PrePersist + protected void onCreate() { + if (viewCount == null) { + viewCount = 0L; // 기본값 설정 + } + } + public static Auction create(LocalDateTime startTime, LocalDateTime endTime, BigDecimal startingBid, AuctionCategory auctionCategory, AuctionStatus auctionStatus, User seller) { Auction newAuction = Auction.builder() .startTime(startTime) @@ -68,7 +78,7 @@ public static Auction create(LocalDateTime startTime, LocalDateTime endTime, Big } public void addProduct(Product product) { - product.addAuction(this); + //product.addAuction(this); product에서 auction을 추가하고 있어서 중복 this.product = product; } @@ -77,4 +87,12 @@ public void addUser(User seller) { this.seller = seller; } + public void incrementViewCount() { + if (viewCount == null) { + viewCount = 0L; + } + this.viewCount += 1; + } + + } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionCategory.java b/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionCategory.java index 697f08f9..7c362815 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionCategory.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionCategory.java @@ -6,11 +6,13 @@ public enum AuctionCategory { PRIVATE_PAID, PUBLIC_PAID; + public static AuctionCategory fromDisplayName(String auctionCategory) { - try { - return AuctionCategory.valueOf(auctionCategory.toUpperCase()); // 대문자로 변환하여 비교 - } catch (IllegalArgumentException e) { - return PUBLIC_FREE; // 유효하지 않은 값일 경우 기본값으로 PUBLIC_FREE 반환 + for (AuctionCategory category : values()) { + if (category.name().equalsIgnoreCase(auctionCategory)) { + return category; + } } + throw new IllegalArgumentException("Unknown auctionCategory: " + auctionCategory); } } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionStatus.java b/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionStatus.java index 2f69867d..71355903 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionStatus.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/model/AuctionStatus.java @@ -7,10 +7,11 @@ public enum AuctionStatus { CANCELED; public static AuctionStatus fromDisplayName(String auctionStatus) { - try { - return AuctionStatus.valueOf(auctionStatus.toUpperCase()); // 대문자로 변환하여 비교 - } catch (IllegalArgumentException e) { - return AuctionStatus.ACTIVE; // 유효하지 않은 값일 경우 기본값으로 ACTIVE 반환 + for (AuctionStatus status : values() ) { + if (status.name().equalsIgnoreCase(auctionStatus)) { + return status; + } } + throw new IllegalArgumentException("Unknown auctionStatus: " + auctionStatus); } } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepository.java b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepository.java index 3999b874..47fc40ee 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepository.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepository.java @@ -1,10 +1,18 @@ package com.tasksprints.auction.domain.auction.repository.support; import com.tasksprints.auction.domain.auction.dto.request.AuctionRequest; +import com.tasksprints.auction.domain.auction.dto.response.AuctionResponse; + import com.tasksprints.auction.domain.auction.model.Auction; +import com.tasksprints.auction.domain.product.model.ProductCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; -import java.util.List; public interface AuctionCriteriaRepository { - List getAuctionsByFilters(AuctionRequest.SearchCondition searchCondition); + Page getAuctionsByFilters(Pageable pageable, AuctionRequest.SearchCondition searchCondition); + @Deprecated + Page getAuctionsByCategory(Pageable pageable, + AuctionRequest.SearchCondition searchCondition, + ProductCategory category); } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java index 9db471af..18e085be 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/repository/support/AuctionCriteriaRepositoryImpl.java @@ -5,37 +5,85 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.tasksprints.auction.domain.auction.dto.request.AuctionRequest; import com.tasksprints.auction.domain.auction.model.Auction; -import com.tasksprints.auction.domain.auction.model.QAuction; -import com.tasksprints.auction.domain.product.model.QProduct; +import com.tasksprints.auction.domain.product.model.ProductCategory; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; import java.util.List; +import static com.tasksprints.auction.domain.auction.model.QAuction.auction; +import static com.tasksprints.auction.domain.product.model.QProduct.product; +import static com.tasksprints.auction.domain.user.model.QUser.user; + @RequiredArgsConstructor @Repository public class AuctionCriteriaRepositoryImpl implements AuctionCriteriaRepository { private final JPAQueryFactory queryFactory; - public List getAuctionsByFilters(AuctionRequest.SearchCondition condition) { - QAuction auction = QAuction.auction; - QProduct product = QProduct.product; + public Page getAuctionsByFilters(Pageable pageable, AuctionRequest.SearchCondition condition) { + BooleanBuilder builder = buildSearchCondition(condition); + OrderSpecifier sortOrder = getSortOrder(condition); + List result = buildQueryWithPaginationAndSorting(builder, pageable, sortOrder); + + // int 오버플로 주의 +// int total = queryFactory +// .selectFrom(auction) +// .where(builder) +// .fetch().size(); - BooleanBuilder builder = buildSearchCondition(condition, auction, product); + long total = result.size(); + + return new PageImpl<>(result, pageable, total); + } - var query = queryFactory.selectFrom(auction) + private List buildQueryWithPaginationAndSorting(BooleanBuilder builder, Pageable pageable, OrderSpecifier sortOrder) { + var mainQuery = queryFactory + .selectFrom(auction) .leftJoin(auction.product, product) - .where(builder); + .fetchJoin() + .leftJoin(auction.seller, user) + .fetchJoin() + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()); - OrderSpecifier sortOrder = getSortOrder(condition, auction); if (sortOrder != null) { - query = query.orderBy(sortOrder); + mainQuery.orderBy(sortOrder); } - return query.fetch(); + return mainQuery.fetch(); + } + + @Deprecated + public Page getAuctionsByCategory(Pageable pageable, + AuctionRequest.SearchCondition condition, + ProductCategory category) { + + BooleanBuilder builder = buildSearchCondition(condition); + filterByCategory(category, builder); + OrderSpecifier sortOrder = getSortOrder(condition); + List result = buildQueryWithPaginationAndSorting(builder, pageable, sortOrder); + + int total = queryFactory + .selectFrom(auction) + .where(builder) + .fetch().size(); + + return new PageImpl<>(result, pageable, total); + + } + @Deprecated + private void filterByCategory(ProductCategory category, BooleanBuilder builder) { + if (category != null) { + builder.and(product.category.eq(category)); + } } - private BooleanBuilder buildSearchCondition(AuctionRequest.SearchCondition condition, QAuction auction, QProduct product) { + + private BooleanBuilder buildSearchCondition(AuctionRequest.SearchCondition condition) { BooleanBuilder builder = new BooleanBuilder(); if (condition.getAuctionCategory() != null) { @@ -57,15 +105,20 @@ private BooleanBuilder buildSearchCondition(AuctionRequest.SearchCondition condi return builder; } - private OrderSpecifier getSortOrder(AuctionRequest.SearchCondition condition, QAuction auction) { + private OrderSpecifier getSortOrder(AuctionRequest.SearchCondition condition) { if (condition.getSortBy() != null) { return switch (condition.getSortBy()) { case "bidsAsc" -> auction.bids.size().asc(); case "bidsDesc" -> auction.bids.size().desc(); - case "endingSoon" -> auction.endTime.asc(); + case "endTimeASC" -> auction.endTime.asc(); + case "startTimeASC" -> auction.startTime.asc(); + case "viewCountDESC" -> auction.viewCount.desc(); + default -> null; }; } return null; } + + } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionService.java b/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionService.java index 788d1ddb..69606124 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionService.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionService.java @@ -2,6 +2,9 @@ import com.tasksprints.auction.domain.auction.dto.request.AuctionRequest; import com.tasksprints.auction.domain.auction.dto.response.AuctionResponse; +import com.tasksprints.auction.domain.product.model.ProductCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import java.util.List; @@ -21,6 +24,8 @@ public interface AuctionService { AuctionResponse getAuctionById(Long auctionId); - List getAuctionsByFilter(AuctionRequest.SearchCondition searchCondition); + Page getAuctionsByFilter(Pageable pageable, AuctionRequest.SearchCondition searchCondition); + @Deprecated + Page getAuctionsByProductCategory(Pageable pageable, AuctionRequest.SearchCondition searchCondition, ProductCategory category); } diff --git a/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImpl.java b/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImpl.java index 8cfef088..f14522cf 100644 --- a/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImpl.java +++ b/src/main/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImpl.java @@ -8,11 +8,14 @@ import com.tasksprints.auction.domain.auction.model.Auction; import com.tasksprints.auction.domain.auction.model.AuctionStatus; import com.tasksprints.auction.domain.auction.repository.AuctionRepository; +import com.tasksprints.auction.domain.product.model.ProductCategory; import com.tasksprints.auction.domain.user.exception.UserNotFoundException; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; @@ -97,7 +100,8 @@ public List getAllAuctions() { public AuctionResponse getAuctionById(Long auctionId) { Auction foundAuction = auctionRepository.findAuctionById(auctionId) .orElseThrow(() -> new AuctionNotFoundException("Auction not found")); - + foundAuction.incrementViewCount(); + auctionRepository.save(foundAuction); return AuctionResponse.of(foundAuction); } @@ -106,13 +110,16 @@ public AuctionResponse getAuctionById(Long auctionId) { * NULL 안정성 보장을 해줬음 **/ @Override - public List getAuctionsByFilter(AuctionRequest.SearchCondition searchCondition) { - List foundAuctions = auctionRepository.getAuctionsByFilters( - searchCondition - ); + public Page getAuctionsByFilter(Pageable pageable, AuctionRequest.SearchCondition searchCondition) { + Page auctions = auctionRepository.getAuctionsByFilters(pageable, searchCondition); + return auctions.map(AuctionResponse.Details::of); + } - return foundAuctions.stream() - .map(AuctionResponse::of) - .toList(); + @Deprecated + @Override + public Page getAuctionsByProductCategory(Pageable pageable, AuctionRequest.SearchCondition searchCondition, ProductCategory category) { + Page auctions = auctionRepository.getAuctionsByFilters(pageable, searchCondition); + return auctions.map(AuctionResponse.Details::of); } + } diff --git a/src/main/java/com/tasksprints/auction/domain/product/model/Product.java b/src/main/java/com/tasksprints/auction/domain/product/model/Product.java index 2d650910..ff72c7c7 100644 --- a/src/main/java/com/tasksprints/auction/domain/product/model/Product.java +++ b/src/main/java/com/tasksprints/auction/domain/product/model/Product.java @@ -37,6 +37,7 @@ public class Product extends BaseEntity { @OneToOne //mappedby + @JoinColumn(name = "auction_id") private Auction auction; @OneToMany @@ -55,7 +56,9 @@ public void addOwner(User owner) { } public void addAuction(Auction auction) { + //양방향 매핑 this.auction = auction; + auction.addProduct(this); } public void initProductImageList(List productImageList) { diff --git a/src/test/java/com/tasksprints/auction/api/AuctionControllerTest.java b/src/test/java/com/tasksprints/auction/api/AuctionControllerTest.java index e6456531..e21acad2 100644 --- a/src/test/java/com/tasksprints/auction/api/AuctionControllerTest.java +++ b/src/test/java/com/tasksprints/auction/api/AuctionControllerTest.java @@ -19,6 +19,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -114,8 +118,10 @@ public void testGetAuctionStatus_Success() throws Exception { @DisplayName("QueryString을 통한 경매 목록 조회") public void testFindAuctionByUsingQueryString_Success() throws Exception { // Given - List auctionResponseList = new ArrayList<>(); - when(auctionService.getAuctionsByFilter(any())).thenReturn(auctionResponseList); + List auctionResponseList = new ArrayList<>(); + Pageable pageable = PageRequest.of(0, 10); + Page pageAuctionResponse = new PageImpl<>(auctionResponseList, pageable, 0); + when(auctionService.getAuctionsByFilter(any(),any())).thenReturn(pageAuctionResponse); // When & Then mockMvc.perform(get("/api/v1/auction") diff --git a/src/test/java/com/tasksprints/auction/domain/auction/repository/AuctionRepositoryTest.java b/src/test/java/com/tasksprints/auction/domain/auction/repository/AuctionRepositoryTest.java index 1455d8b5..57ce46bf 100644 --- a/src/test/java/com/tasksprints/auction/domain/auction/repository/AuctionRepositoryTest.java +++ b/src/test/java/com/tasksprints/auction/domain/auction/repository/AuctionRepositoryTest.java @@ -1,10 +1,14 @@ package com.tasksprints.auction.domain.auction.repository; import com.tasksprints.auction.common.config.QueryDslConfig; +import com.tasksprints.auction.domain.auction.dto.response.AuctionResponse; import com.tasksprints.auction.domain.auction.dto.request.AuctionRequest; import com.tasksprints.auction.domain.auction.model.Auction; import com.tasksprints.auction.domain.auction.model.AuctionCategory; import com.tasksprints.auction.domain.auction.model.AuctionStatus; +import com.tasksprints.auction.domain.product.model.Product; +import com.tasksprints.auction.domain.product.model.ProductCategory; +import com.tasksprints.auction.domain.product.repository.ProductRepository; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; import lombok.extern.slf4j.Slf4j; @@ -14,6 +18,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -34,6 +41,9 @@ public class AuctionRepositoryTest { @Autowired private UserRepository userRepository; + @Autowired + private ProductRepository productRepository; + private User seller; @BeforeEach @@ -58,6 +68,28 @@ private Auction createAuction(User seller, AuctionCategory category, AuctionStat ); } + private Auction createAuction(User seller, AuctionCategory auctionCategory) { + return Auction.create( + LocalDateTime.of(2024, 8, 1, 10, 0), + LocalDateTime.of(2024, 9, 1, 10, 0), + BigDecimal.valueOf(100.00), + auctionCategory, + AuctionStatus.ACTIVE, + seller + ); + } + + private Product createProduct(User user, Auction auction, String productCategory) { + return Product.create( + "testName", + "testDescription", + user, + auction, + productCategory, + null + ); + } + @Test @DisplayName("사용자 ID로 경매 목록 조회") public void testFindAuctionsByUserId() { @@ -97,80 +129,58 @@ public void testFindAll() { } - + @Deprecated @Test @DisplayName("경매 유형이 []인 경매 목록 조회") public void testFindAuctionsByAuctionCategory() { //given - Auction auction1 = createAuction(seller, AuctionCategory.PUBLIC_FREE, AuctionStatus.ACTIVE); - Auction auction2 = createAuction(seller, AuctionCategory.PUBLIC_PAID, AuctionStatus.PENDING); - Auction auction3 = createAuction(seller, AuctionCategory.PUBLIC_PAID, AuctionStatus.PENDING); - AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_PAID, null, null, null, null, null, null, null); - auctionRepository.save(auction1); - auctionRepository.save(auction2); - auctionRepository.save(auction3); + Auction auction1 = createAuction(seller, AuctionCategory.PUBLIC_FREE); + Auction auction2 = createAuction(seller, AuctionCategory.PUBLIC_PAID); + Auction auction3 = createAuction(seller, AuctionCategory.PUBLIC_PAID); + auctionRepository.saveAll(List.of(auction1, auction2, auction3)); + + Product product1 = createProduct(seller, auction1, "TV"); + Product product2 = createProduct(seller, auction2, "TV"); + Product product3 = createProduct(seller, auction3, "TV"); + productRepository.saveAll(List.of(product1, product2, product3)); + + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_PAID, ProductCategory.TV, null, null, null, null, null, null); + + Pageable pageable = PageRequest.of(0, 10); //when - List auctions = auctionRepository.getAuctionsByFilters(condition); + Page auctions = auctionRepository.getAuctionsByFilters(pageable, condition); //then assertThat(auctions).hasSize(2); - assertThat(auctions).allMatch(auction -> auction.getAuctionCategory() == AuctionCategory.PUBLIC_PAID); + assertThat(auctions.getContent()).allMatch(auction -> auction.getAuctionCategory().equals(AuctionCategory.PUBLIC_PAID)); + assertThat(auctions.getContent()).allMatch(auction -> auction.getProduct().getCategory().equals(ProductCategory.TV)); } - @Test @DisplayName("QueryDSL 필터를 통해서 경매 목록 조회") public void testFindAllUsingFilter() { - Auction auction1 = createAuction(seller, AuctionCategory.PUBLIC_FREE, AuctionStatus.ACTIVE); - Auction auction2 = createAuction(seller, AuctionCategory.PUBLIC_PAID, AuctionStatus.PENDING); - AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_FREE, null, null, null, null, null, null, null); - auctionRepository.save(auction1); - auctionRepository.save(auction2); + //given + Auction auction1 = createAuction(seller, AuctionCategory.PUBLIC_FREE); + Auction auction2 = createAuction(seller, AuctionCategory.PUBLIC_PAID); + auctionRepository.saveAll(List.of(auction1, auction2)); + + Product product1 = createProduct(seller, auction1, "TV"); + Product product2 = createProduct(seller, auction2, "TV"); + productRepository.saveAll(List.of(product1, product2)); - List auctions = auctionRepository.getAuctionsByFilters(condition); + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_FREE, ProductCategory.TV, null, null, null, null, null, null); + Pageable pageable = PageRequest.of(0, 10); + + //when + Page auctions = auctionRepository.getAuctionsByFilters(pageable, condition); log.info(auctions.toString()); + //then assertThat(auctions).hasSize(1); - assertThat(auctions.get(0).getAuctionCategory()).isEqualTo(AuctionCategory.PUBLIC_FREE); - assertThat(auctions.get(0).getAuctionCategory()).isNotEqualTo(AuctionCategory.PUBLIC_PAID); + assertThat(auctions.getContent().get(0).getAuctionCategory()).isEqualTo(AuctionCategory.PUBLIC_FREE); + assertThat(auctions.getContent().get(0).getProduct().getCategory()).isEqualTo(ProductCategory.TV); } -// @Test -// @DisplayName("경매 마감 시간까지 24시간 이하로 남은 경매 목록 조회") -// public void testFindAuctionsByEndTimeBetweenOrderByEndTimeAsc() { -// //given -// LocalDateTime fixedNow = LocalDateTime.of(2024, 9, 1, 10, 0); -// LocalDateTime next24Hours = fixedNow.plusHours(24); -// -// List auctions = List.of( -// createAuction(fixedNow.plusHours(23), AuctionStatus.ACTIVE), -// createAuction(fixedNow.plusHours(22), AuctionStatus.ACTIVE), -// createAuction(fixedNow.plusHours(21), AuctionStatus.ACTIVE), -// createAuction(fixedNow.plusHours(48), AuctionStatus.ACTIVE), -// createAuction(fixedNow.plusHours(20), AuctionStatus.PENDING) -// ); -// -// auctionRepository.saveAll(auctions); -// -// // when -// List result = auctionRepository.getAuctionsEndWith24Hours(fixedNow, next24Hours, AuctionStatus.ACTIVE); -// -// //then -// assertThat(result).hasSize(3); -// -// assertAll("endTime을 기준으로 오름차순 정렬이 되었는지 확인", -// () -> { -// LocalDateTime previousEndTime = null; -// for (Auction auction : result) { -// if (previousEndTime != null) { -// assertThat(auction.getEndTime()).isAfterOrEqualTo(previousEndTime); -// } -// previousEndTime = auction.getEndTime(); -// } -// } -// ); -// } - } diff --git a/src/test/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImplTest.java b/src/test/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImplTest.java index 224ac20c..966a0587 100644 --- a/src/test/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImplTest.java +++ b/src/test/java/com/tasksprints/auction/domain/auction/service/AuctionServiceImplTest.java @@ -20,6 +20,10 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -68,13 +72,13 @@ private Auction createAuction(Long auctionId, User seller, AuctionStatus status) .build(); } - private Auction createAuction(Long auctionId, LocalDateTime endTime, AuctionStatus status) { + private Auction createAuction(Long auctionId, LocalDateTime startTime) { return Auction.builder() .id(auctionId) .auctionCategory(AuctionCategory.PUBLIC_PAID) - .auctionStatus(status) - .startTime(LocalDateTime.of(2024, 8, 1, 10, 0)) - .endTime(endTime) + .auctionStatus(AuctionStatus.ACTIVE) + .startTime(startTime) + .endTime(LocalDateTime.of(2024, 9, 1, 10, 0)) .startingBid(BigDecimal.valueOf(100.00)) .seller(seller) .build(); @@ -318,94 +322,84 @@ class GetAuctionsByAuctionCategoryTests { @Test @DisplayName("경매 유형 조회 : [성공]") public void testGetAuctionsByAuctionCategory_Success() { - Auction auction1 = createAuction(1L, seller, AuctionStatus.PENDING); - Auction auction2 = createAuction(2L, seller, AuctionStatus.PENDING); - AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_PAID, null, null, null, null, null, null, null); - List expectedAuctions = List.of(auction1, auction2); + //given + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(null, null, null, null, null, null, AuctionStatus.PENDING, null); + List auctions = List.of( + createAuction(1L, seller, AuctionStatus.PENDING), + createAuction(2L, seller, AuctionStatus.PENDING) + ); + + Pageable pageable = PageRequest.of(0, 10); + Page auctionPage = new PageImpl<>(auctions, pageable, auctions.size()); + when(auctionRepository.getAuctionsByFilters(pageable, condition)).thenReturn(auctionPage); - when(auctionRepository.getAuctionsByFilters(condition)).thenReturn(expectedAuctions); - List expectedResponses = expectedAuctions.stream() - .map(AuctionResponse::of) - .toList(); + //when + Page actualPage = auctionService.getAuctionsByFilter(pageable, condition); - List actualAuctions = auctionService.getAuctionsByFilter(condition); - assertThat(actualAuctions).isEqualTo(expectedResponses); + //then + assertThat(actualPage.getContent()).hasSize(2); + assertThat(actualPage.getContent()) + .allMatch(auctionResponseDetail -> auctionResponseDetail.getStatus().equals(AuctionStatus.PENDING.name())); } @Test @DisplayName("경매 유형 조회 : [성공] -Criteria 사용") public void testGetAuctionsByAuctionCategory_Success_Criteria() { - Auction auction1 = createAuction(1L, seller, AuctionStatus.PENDING); - Auction auction2 = createAuction(2L, seller, AuctionStatus.PENDING); - AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_PAID, null, null, null, null, null, null, null); - List expectedAuctions = List.of(auction1, auction2); + //given + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(null, null, null, null, null, null, AuctionStatus.PENDING, null); + List auctions = List.of( + createAuction(1L, seller, AuctionStatus.PENDING), + createAuction(2L, seller, AuctionStatus.PENDING) + ); - when(auctionRepository.getAuctionsByFilters(condition)).thenReturn(expectedAuctions); - List expectedResponses = expectedAuctions.stream() - .map(AuctionResponse::of) - .toList(); + Pageable pageable = PageRequest.of(0, 10); + Page auctionPage = new PageImpl<>(auctions, pageable, auctions.size()); + when(auctionRepository.getAuctionsByFilters(pageable, condition)).thenReturn(auctionPage); - List actualAuctions = auctionService.getAuctionsByFilter(condition); - assertThat(actualAuctions).isEqualTo(expectedResponses); + //when + Page actualPage = auctionService.getAuctionsByFilter(pageable, condition); + + //then + assertThat(actualPage.getContent()).hasSize(2); + assertThat(actualPage.getContent()) + .allMatch(auctionResponseDetail -> auctionResponseDetail.getStatus().equals(AuctionStatus.PENDING.name())); } @Test @DisplayName("경매 유형 조회 : [결과 없음]") public void testGetAuctionsByAuctionCategory_AuctionNotFound() { + //given List emptyAuctionList = List.of(); AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_FREE, null, null, null, null, null, null, null); - when(auctionRepository.getAuctionsByFilters(condition)) - .thenReturn(emptyAuctionList); + Pageable pageable = PageRequest.of(0, 10); + Page auctionPage = new PageImpl<>(emptyAuctionList, pageable, 0); + when(auctionRepository.getAuctionsByFilters(pageable, condition)).thenReturn(auctionPage); - List actualAuctions = auctionService.getAuctionsByFilter(condition); + //when + Page actualPage = auctionService.getAuctionsByFilter(pageable, condition); - assertThat(actualAuctions).isEmpty(); + //then + assertThat(actualPage.isEmpty()).isTrue(); } @Test @DisplayName("경매 유형 조회 : [결과 없음] - Criteria 사용") public void testGetAuctionsByAuctionCategory_AuctionNotFound_Criteria() { + //given List emptyAuctionList = List.of(); - AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_PAID, null, null, null, null, null, null, null); + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition(AuctionCategory.PUBLIC_FREE, null, null, null, null, null, null, null); - when(auctionRepository.getAuctionsByFilters(condition)) - .thenReturn(emptyAuctionList); + Pageable pageable = PageRequest.of(0, 10); + Page auctionPage = new PageImpl<>(emptyAuctionList, pageable, 0); + when(auctionRepository.getAuctionsByFilters(pageable, condition)).thenReturn(auctionPage); - List actualAuctions = auctionService.getAuctionsByFilter(condition); + //when + Page actualPage = auctionService.getAuctionsByFilter(pageable, condition); - assertThat(actualAuctions).isEmpty(); + //then + assertThat(actualPage.isEmpty()).isTrue(); } } -// @Nested -// @DisplayName("경매 마감 시간이 24시간 이하인 경매 목록 조회") -// class GetAuctionsByEndTimeBetweenAndAuctionStatusOrderByEndTimeAscTests { -// @Test -// @DisplayName("경매 마감 임박 목록 조회 : 성공") -// public void testGetAuctionsByEndTimeBetweenAndAuctionStatusOrderByEndTimeAsc_Success() { -// //given -// LocalDateTime fixedNow = LocalDateTime.of(2024, 9, 1, 10, 0); -// LocalDateTime next24Hours = fixedNow.plusHours(24); -// -// List expectedAuctions = List.of(createAuction(1L, fixedNow.plusHours(21), AuctionStatus.ACTIVE), -// createAuction(2L, fixedNow.plusHours(22), AuctionStatus.ACTIVE), -// createAuction(3L, fixedNow.plusHours(23), AuctionStatus.ACTIVE) -// ); -// -// List expectedResponses = expectedAuctions.stream() -// .map(AuctionResponse::of) -// .toList(); -// -// when(auctionRepository.getAuctionsEndWith24Hours(fixedNow, next24Hours, AuctionStatus.ACTIVE)).thenReturn(expectedAuctions); -// List actualResponses = auctionService.getAuctionsEndWith24Hours(fixedNow, next24Hours); -// -// //then -// assertThat(actualResponses).isEqualTo(expectedResponses); -// -// -// } -// } - - } diff --git a/src/test/java/com/tasksprints/auction/domain/product/ProductRepositoryTest.java b/src/test/java/com/tasksprints/auction/domain/product/ProductRepositoryTest.java index 11bb7d92..2972ffef 100644 --- a/src/test/java/com/tasksprints/auction/domain/product/ProductRepositoryTest.java +++ b/src/test/java/com/tasksprints/auction/domain/product/ProductRepositoryTest.java @@ -1,20 +1,28 @@ package com.tasksprints.auction.domain.product; import com.tasksprints.auction.common.config.QueryDslConfig; +import com.tasksprints.auction.domain.auction.dto.request.AuctionRequest; +import com.tasksprints.auction.domain.auction.dto.response.AuctionResponse; import com.tasksprints.auction.domain.auction.model.Auction; import com.tasksprints.auction.domain.auction.model.AuctionCategory; import com.tasksprints.auction.domain.auction.model.AuctionStatus; import com.tasksprints.auction.domain.auction.repository.AuctionRepository; import com.tasksprints.auction.domain.product.model.Product; +import com.tasksprints.auction.domain.product.model.ProductCategory; +import com.tasksprints.auction.domain.product.model.ProductImage; import com.tasksprints.auction.domain.product.repository.ProductRepository; import com.tasksprints.auction.domain.user.model.User; import com.tasksprints.auction.domain.user.repository.UserRepository; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; 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.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -98,6 +106,63 @@ public void testDeleteById() { Optional deletedProduct = productRepository.findById(product.getId()); assertTrue(deletedProduct.isEmpty()); } + @Deprecated + @Test + @DisplayName("queryDSL 검색 필터로 조회") + public void testFindAllUsingProductCategory() { + //given + Auction auction1 = createAuction(owner, AuctionStatus.PENDING); + Auction auction2 = createAuction(owner, AuctionStatus.ACTIVE); + + Product product1 = createProduct("product1", "description1", ProductCategory.TV, owner, auction1); + Product product2 = createProduct("product2", "description2", ProductCategory.DSLR, owner, auction2); + + product1.addAuction(auction1); + product2.addAuction(auction2); + + AuctionRequest.SearchCondition condition = new AuctionRequest.SearchCondition( + null, ProductCategory.TV, null, null, null, null, AuctionStatus.PENDING, null + ); + Pageable pageable = PageRequest.of(0, 10); + + //when + Page auctionsByFilters = auctionRepository.getAuctionsByFilters(pageable, condition); + + //then + assertThat(auctionsByFilters).hasSize(1); + assertThat(auctionsByFilters.getContent().get(0).getProduct().getCategory()).isEqualTo(ProductCategory.TV); + assertThat(auctionsByFilters.getContent().get(0).getAuctionStatus()).isEqualTo(AuctionStatus.PENDING); + + + } + @Deprecated + @Test + @DisplayName("상품 카테고리로 경매를 조회 : Condition 쿼리스트링이 넘어온 경우") + public void testFindAllUsingConditionFilter() { + //given + Auction auction1 = createAuction(owner, AuctionStatus.ACTIVE); + Auction auction2 = createAuction(owner, AuctionStatus.PENDING); + + Product product1 = createProduct("product1", "description1", ProductCategory.TV, owner, auction1); + Product product2 = createProduct("product2", "description2", ProductCategory.DSLR, owner, auction2); + + product1.addAuction(auction1); + product2.addAuction(auction2); + + Pageable pageable = PageRequest.of(0, 10); + + AuctionRequest.SearchCondition searchCondition = new AuctionRequest.SearchCondition(null, null, null, + null, null, null, + AuctionStatus.PENDING, null) + ; + //when + Page auctionsByCategory = auctionRepository.getAuctionsByCategory(pageable, searchCondition, ProductCategory.DSLR); + + //then + assertThat(auctionsByCategory).hasSize(1); + assertThat(auctionsByCategory.getContent().get(0).getProduct().getCategory()).isEqualTo(ProductCategory.DSLR); + } + // Helper methods to minimize code duplication private User createUser(String name, String nickName, String email) { @@ -109,7 +174,6 @@ private User createUser(String name, String nickName, String email) { .build(); return userRepository.save(user); } - private Auction createAuction(User owner, BigDecimal startingBid, LocalDateTime startTime, LocalDateTime endTime) { Auction auction = Auction.builder() .startingBid(startingBid) @@ -122,6 +186,18 @@ private Auction createAuction(User owner, BigDecimal startingBid, LocalDateTime return auctionRepository.save(auction); } + private Auction createAuction(User owner, AuctionStatus auctionStatus) { + Auction auction = Auction.builder() + .startingBid(BigDecimal.valueOf(100.00)) + .startTime(LocalDateTime.now()) + .endTime(LocalDateTime.now().plusHours(1)) + .auctionStatus(auctionStatus) + .auctionCategory(AuctionCategory.PRIVATE_FREE) + .seller(owner) + .build(); + return auctionRepository.save(auction); + } + private Product createProduct(String name, String description, User owner, Auction auction) { Product product = Product.builder() .name(name) @@ -131,4 +207,15 @@ private Product createProduct(String name, String description, User owner, Aucti .build(); return productRepository.save(product); } + + private Product createProduct(String name, String description, ProductCategory category, User owner, Auction auction) { + Product product = Product.builder() + .name(name) + .description(description) + .owner(owner) + .auction(auction) + .category(category) + .build(); + return productRepository.save(product); + } }