From 747b51bf145279a81a9816d6ab898a312855a5e3 Mon Sep 17 00:00:00 2001 From: wugawuga Date: Sun, 2 Jun 2024 13:46:19 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=ED=83=9C=EA=B7=B8=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품의 태그 Top3 중 있으면 검색해서 조회 --- .../product/application/ProductService.java | 33 ++++-- .../persistence/ProductRepository.java | 19 +++- .../persistence/ProductRepositoryCustom.java | 13 +++ .../persistence/ProductRepositoryImpl.java | 73 +++++++++++++ .../presentation/ProductApiController.java | 7 ++ .../presentation/ProductController.java | 9 ++ .../product/ProductAcceptanceTest.java | 33 ++++-- .../acceptance/product/ProductSteps.java | 11 ++ .../com/funeat/fixture/ReviewFixture.java | 10 ++ .../persistence/ProductRepositoryTest.java | 100 ++++++++++++++++++ 10 files changed, 294 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/funeat/product/persistence/ProductRepositoryCustom.java create mode 100644 src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java diff --git a/src/main/java/com/funeat/product/application/ProductService.java b/src/main/java/com/funeat/product/application/ProductService.java index 7efa1781..64c710b8 100644 --- a/src/main/java/com/funeat/product/application/ProductService.java +++ b/src/main/java/com/funeat/product/application/ProductService.java @@ -34,12 +34,13 @@ import com.funeat.recipe.dto.SortingRecipesResponse; import com.funeat.recipe.persistence.RecipeImageRepository; import com.funeat.recipe.persistence.RecipeRepository; -import com.funeat.review.persistence.ReviewRepository; import com.funeat.review.persistence.ReviewTagRepository; import com.funeat.tag.domain.Tag; + import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; + import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -61,7 +62,6 @@ public class ProductService { private final CategoryRepository categoryRepository; private final ProductRepository productRepository; private final ReviewTagRepository reviewTagRepository; - private final ReviewRepository reviewRepository; private final ProductRecipeRepository productRecipeRepository; private final RecipeImageRepository recipeImageRepository; private final RecipeRepository recipeRepository; @@ -69,15 +69,13 @@ public class ProductService { private final RecipeFavoriteRepository recipeFavoriteRepository; public ProductService(final CategoryRepository categoryRepository, final ProductRepository productRepository, - final ReviewTagRepository reviewTagRepository, final ReviewRepository reviewRepository, + final ReviewTagRepository reviewTagRepository, final ProductRecipeRepository productRecipeRepository, - final RecipeImageRepository recipeImageRepository, - final RecipeRepository recipeRepository, final MemberRepository memberRepository, - final RecipeFavoriteRepository recipeFavoriteRepository) { + final RecipeImageRepository recipeImageRepository, final RecipeRepository recipeRepository, + final MemberRepository memberRepository, final RecipeFavoriteRepository recipeFavoriteRepository) { this.categoryRepository = categoryRepository; this.productRepository = productRepository; this.reviewTagRepository = reviewTagRepository; - this.reviewRepository = reviewRepository; this.productRecipeRepository = productRecipeRepository; this.recipeImageRepository = recipeImageRepository; this.recipeRepository = recipeRepository; @@ -211,4 +209,25 @@ private RecipeDto createRecipeDto(final Long memberId, final Recipe recipe) { final Boolean favorite = recipeFavoriteRepository.existsByMemberAndRecipeAndFavoriteTrue(member, recipe); return RecipeDto.toDto(recipe, images, products, favorite); } + + public SearchProductsResponse getSearchResultsByTag(final Long tagId, final Long lastProductId) { + final List findProducts = findAllByTag(tagId, lastProductId); + final int resultSize = getResultSize(findProducts); + final List products = findProducts.subList(0, resultSize); + + final boolean hasNext = hasNextPage(findProducts); + final List productDtos = products.stream() + .map(SearchProductDto::toDto) + .toList(); + + return SearchProductsResponse.toResponse(hasNext, productDtos); + } + + private List findAllByTag(Long tagId, Long lastProductId) { + final PageRequest size = PageRequest.ofSize(DEFAULT_CURSOR_PAGINATION_SIZE); + if (lastProductId == 0) { + return productRepository.searchProductsByTopTagsFirst(tagId, size); + } + return productRepository.searchProductsByTopTags(tagId, lastProductId, size); + } } diff --git a/src/main/java/com/funeat/product/persistence/ProductRepository.java b/src/main/java/com/funeat/product/persistence/ProductRepository.java index 7d4e2161..90e714d0 100644 --- a/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -4,11 +4,13 @@ import com.funeat.product.domain.Product; import com.funeat.product.dto.ProductReviewCountDto; import java.util.List; + +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ProductRepository extends BaseRepository { +public interface ProductRepository extends BaseRepository, ProductRepositoryCustom { @Query("SELECT new com.funeat.product.dto.ProductReviewCountDto(p, COUNT(r.id)) " + "FROM Product p " @@ -53,4 +55,19 @@ List findAllWithReviewCountByNameContainingFirst(@Param(" + "ORDER BY (CASE WHEN p.name LIKE CONCAT(:name, '%') THEN 1 ELSE 2 END), p.id DESC") List findAllWithReviewCountByNameContaining(@Param("name") final String name, final Long lastId, final Pageable pageable); + + @Query("SELECT DISTINCT p FROM Product p " + + "JOIN Review r on r.product.id = p.id " + + "LEFT JOIN ReviewTag rt on rt.review.id = r.id " + + "WHERE rt.tag.id = :tagId " + + "ORDER BY p.id DESC") + List findAllByTagFirst(@Param("tagId") Long tagId, Pageable pageable); + + @Query("SELECT DISTINCT p FROM Product p " + + "JOIN Review r on r.product.id = p.id " + + "LEFT JOIN ReviewTag rt on rt.review.id = r.id " + + "WHERE rt.tag.id = :tagId " + + "AND p.id < :lastId " + + "ORDER BY p.id DESC") + List findAllByTag(@Param("tagId") final Long tagId, @Param("lastId") final Long lastId, final Pageable pageable); } diff --git a/src/main/java/com/funeat/product/persistence/ProductRepositoryCustom.java b/src/main/java/com/funeat/product/persistence/ProductRepositoryCustom.java new file mode 100644 index 00000000..57824a99 --- /dev/null +++ b/src/main/java/com/funeat/product/persistence/ProductRepositoryCustom.java @@ -0,0 +1,13 @@ +package com.funeat.product.persistence; + +import com.funeat.product.domain.Product; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface ProductRepositoryCustom { + + List searchProductsByTopTagsFirst(final Long tagId, final Pageable pageable); + + List searchProductsByTopTags(final Long tagId, final Long lastProductId, final Pageable pageable); +} diff --git a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java new file mode 100644 index 00000000..0903074f --- /dev/null +++ b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java @@ -0,0 +1,73 @@ +package com.funeat.product.persistence; + +import com.funeat.product.domain.Product; +import com.funeat.tag.domain.Tag; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class ProductRepositoryImpl implements ProductRepositoryCustom { + + @PersistenceContext + private EntityManager entityManager; + + @Override + public List searchProductsByTopTagsFirst(final Long tagId, final Pageable pageable) { + String jpql = "SELECT DISTINCT p FROM Product p " + + "WHERE p.id IN ( " + + " SELECT p2.id FROM Product p2 " + + " JOIN p2.reviews r2 " + + " JOIN r2.reviewTags rt2 " + + " WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( " + + " SELECT rt3.tag.id FROM Review r3 " + + " JOIN r3.reviewTags rt3 " + + " WHERE r3.product.id = p2.id " + + " GROUP BY rt3.tag.id " + + " ORDER BY COUNT(rt3.tag.id) DESC " + + " ) " + + " GROUP BY p2.id " + + " HAVING COUNT(DISTINCT rt2.tag.id) <= 3 " + + ") " + + "ORDER BY p.id DESC"; + + TypedQuery query = entityManager.createQuery(jpql, Product.class); + query.setParameter("tagId", tagId); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + return query.getResultList(); + } + + @Override + public List searchProductsByTopTags(Long tagId, Long lastProductId, Pageable pageable) { + String jpql = "SELECT DISTINCT p FROM Product p " + + "WHERE p.id < :lastProductId AND p.id IN ( " + + " SELECT p2.id FROM Product p2 " + + " JOIN p2.reviews r2 " + + " JOIN r2.reviewTags rt2 " + + " WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( " + + " SELECT rt3.tag.id FROM Review r3 " + + " JOIN r3.reviewTags rt3 " + + " WHERE r3.product.id = p2.id " + + " GROUP BY rt3.tag.id " + + " ORDER BY COUNT(rt3.tag.id) DESC " + + " ) " + + " GROUP BY p2.id " + + " HAVING COUNT(DISTINCT rt2.tag.id) <= 3 " + + ") " + + "ORDER BY p.id DESC"; + + TypedQuery query = entityManager.createQuery(jpql, Product.class); + query.setParameter("tagId", tagId); + query.setParameter("lastProductId", lastProductId); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + return query.getResultList(); + } +} diff --git a/src/main/java/com/funeat/product/presentation/ProductApiController.java b/src/main/java/com/funeat/product/presentation/ProductApiController.java index cfdd20dc..235876d5 100644 --- a/src/main/java/com/funeat/product/presentation/ProductApiController.java +++ b/src/main/java/com/funeat/product/presentation/ProductApiController.java @@ -71,4 +71,11 @@ public ResponseEntity getProductRecipes(@AuthenticationP final SortingRecipesResponse response = productService.getProductRecipes(loginInfo.getId(), productId, pageable); return ResponseEntity.ok(response); } + + @GetMapping("/search/tags/results") + public ResponseEntity getSearchResultByTag(@RequestParam final Long tagId, + @RequestParam final Long lastProductId) { + final SearchProductsResponse response = productService.getSearchResultsByTag(tagId, lastProductId); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/funeat/product/presentation/ProductController.java b/src/main/java/com/funeat/product/presentation/ProductController.java index 87065f24..42d755e1 100644 --- a/src/main/java/com/funeat/product/presentation/ProductController.java +++ b/src/main/java/com/funeat/product/presentation/ProductController.java @@ -76,4 +76,13 @@ ResponseEntity getSearchResults(@RequestParam fina ResponseEntity getProductRecipes(@AuthenticationPrincipal final LoginInfo loginInfo, @PathVariable final Long productId, @PageableDefault final Pageable pageable); + + @Operation(summary = "해당 태그 상품 목록 조회", description = "해당 태그가 포함된 상품 목록을 조회한다.") + @ApiResponse( + responseCode = "200", + description = "해당 태그 상품 목록 조회 성공." + ) + @GetMapping("/search/tags/results") + public ResponseEntity getSearchResultByTag(@RequestParam final Long tagId, + @RequestParam final Long lastProductId); } diff --git a/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index dc68565d..f541531e 100644 --- a/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -8,12 +8,7 @@ import static com.funeat.acceptance.common.CommonSteps.정상_처리; import static com.funeat.acceptance.common.CommonSteps.찾을수_없음; import static com.funeat.acceptance.common.CommonSteps.페이지를_검증한다; -import static com.funeat.acceptance.product.ProductSteps.상품_검색_결과_조회_요청; -import static com.funeat.acceptance.product.ProductSteps.상품_랭킹_조회_요청; -import static com.funeat.acceptance.product.ProductSteps.상품_레시피_목록_요청; -import static com.funeat.acceptance.product.ProductSteps.상품_상세_조회_요청; -import static com.funeat.acceptance.product.ProductSteps.상품_자동_완성_검색_요청; -import static com.funeat.acceptance.product.ProductSteps.카테고리별_상품_목록_조회_요청; +import static com.funeat.acceptance.product.ProductSteps.*; import static com.funeat.acceptance.recipe.RecipeSteps.레시피_작성_요청; import static com.funeat.acceptance.recipe.RecipeSteps.여러명이_레시피_좋아요_요청; import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청; @@ -657,6 +652,32 @@ class getProductRecipes_성공_테스트 { } } + @Nested + class getSearchResultsByTag_성공_테스트 { + + @Test + void 상품_상세_정보를_조회한다() { + // given + final var 카테고리 = 카테고리_간편식사_생성(); + 단일_카테고리_저장(카테고리); + final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점3점_생성(카테고리)); + final var 상품2 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점3점_생성(카테고리)); + final var 태그1 = 단일_태그_저장(태그_맛있어요_TASTE_생성()); + final var 태그2 = 단일_태그_저장(태그_단짠단짠_TASTE_생성()); + + 리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품, 사진_명세_요청(이미지1), 리뷰추가요청_재구매X_생성(점수_4점, List.of(태그1, 태그2))); + 리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품, 사진_명세_요청(이미지2), 리뷰추가요청_재구매X_생성(점수_4점, List.of(태그2))); + 리뷰_작성_요청(로그인_쿠키_획득(멤버1), 상품2, 사진_명세_요청(이미지3), 리뷰추가요청_재구매X_생성(점수_1점, List.of(태그2))); + + // when + final var 응답 = 태그_상품_검색_결과_조회_요청(태그2, 0L); + + // then + STATUS_CODE를_검증한다(응답, 정상_처리); + 상품_검색_결과를_검증한다(응답, false, List.of(상품2, 상품1)); + } + } + private void 카테고리별_상품_목록_조회_결과를_검증한다(final ExtractableResponse response, final List productIds) { final var actual = response.jsonPath() .getList("products", ProductInCategoryDto.class); diff --git a/src/test/java/com/funeat/acceptance/product/ProductSteps.java b/src/test/java/com/funeat/acceptance/product/ProductSteps.java index 8c989c2a..4b001813 100644 --- a/src/test/java/com/funeat/acceptance/product/ProductSteps.java +++ b/src/test/java/com/funeat/acceptance/product/ProductSteps.java @@ -66,4 +66,15 @@ public class ProductSteps { .then() .extract(); } + + public static ExtractableResponse 태그_상품_검색_결과_조회_요청(final Long tagId, final Long lastProductId) { + return given() + .log().all() + .queryParam("tagId", tagId) + .queryParam("lastProductId", lastProductId) + .when() + .get("/api/search/tags/results") + .then().log().all() + .extract(); + } } diff --git a/src/test/java/com/funeat/fixture/ReviewFixture.java b/src/test/java/com/funeat/fixture/ReviewFixture.java index fee2b0b7..362e7d2f 100644 --- a/src/test/java/com/funeat/fixture/ReviewFixture.java +++ b/src/test/java/com/funeat/fixture/ReviewFixture.java @@ -8,9 +8,12 @@ import com.funeat.member.domain.Member; import com.funeat.product.domain.Product; import com.funeat.review.domain.Review; +import com.funeat.review.domain.ReviewTag; import com.funeat.review.dto.ReviewCreateRequest; import com.funeat.review.dto.ReviewFavoriteRequest; import com.funeat.review.dto.SortingReviewRequest; +import com.funeat.tag.domain.Tag; + import java.time.LocalDateTime; import java.util.List; @@ -121,4 +124,11 @@ public class ReviewFixture { public static SortingReviewRequest 리뷰정렬요청_존재하지않는정렬_생성() { return new SortingReviewRequest("test,test", 1L); } + + public static class ReviewTagFixture { + + public static ReviewTag 리뷰태그_생성(final Review review, final Tag tag) { + return ReviewTag.createReviewTag(review, tag); + } + } } diff --git a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index 9cdced56..0bdd4f8f 100644 --- a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -10,16 +10,20 @@ import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격3000원_평점5점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격4000원_평점2점_생성; import static com.funeat.fixture.ProductFixture.상품_애플망고_가격3000원_평점5점_생성; +import static com.funeat.fixture.ReviewFixture.ReviewTagFixture.리뷰태그_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test1_평점1점_재구매X_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test5_평점5점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test5_평점5점_재구매X_생성; +import static com.funeat.fixture.TagFixture.*; import static org.assertj.core.api.Assertions.assertThat; import com.funeat.common.RepositoryTest; import com.funeat.product.dto.ProductReviewCountDto; + import java.time.LocalDateTime; import java.util.List; + import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.data.domain.PageRequest; @@ -189,4 +193,100 @@ class findAllWithReviewCountByNameContaining_성공_테스트 { .isEqualTo(expected); } } + + @Nested + class findAllByTagFirst_성공_테스트 { + + @Test + void 특정_태그가_포함된_상품들을_조회한다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + + final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); + final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); + final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); + + final var 태그1 = 단일_태그_저장(태그_맛있어요); + final var 태그2 = 단일_태그_저장(태그_단짠단짠); + final var 태그3 = 단일_태그_저장(태그_갓성비); + + final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); + final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); + 복수_상품_저장(product1, product2, product3, product4); + + final var member1 = 멤버_멤버1_생성(); + final var member2 = 멤버_멤버2_생성(); + 복수_멤버_저장(member1, member2); + + final var review1_1 = 리뷰_이미지test1_평점1점_재구매X_생성(member1, product1, 0L); + final var review1_2 = 리뷰_이미지test5_평점5점_재구매O_생성(member2, product1, 0L); + final var review2_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product2, 0L); + 복수_리뷰_저장(review1_1, review1_2, review2_1); + + 복수_리뷰_태그_저장(리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_갓성비), 리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_2, 태그_단짠단짠), + 리뷰태그_생성(review2_1, 태그_맛있어요)); + + final var expected = List.of(product2, product1); + final var expected2 = List.of(product1); + + // when + final var actual = productRepository.searchProductsByTopTagsFirst(태그1, PageRequest.of(0, 10)); + final var actual2 = productRepository.searchProductsByTopTagsFirst(태그2, PageRequest.of(0, 10)); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + + // then + assertThat(actual2).usingRecursiveComparison() + .isEqualTo(expected2); + } + + @Test + void 특정_태그와_마지막_상품아이디_이후_상품들을_조회한다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + + final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); + final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); + final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); + + final var 태그1 = 단일_태그_저장(태그_맛있어요); + final var 태그2 = 단일_태그_저장(태그_단짠단짠); + final var 태그3 = 단일_태그_저장(태그_갓성비); + + final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); + final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); + 복수_상품_저장(product1, product2, product3, product4); + + final var member1 = 멤버_멤버1_생성(); + final var member2 = 멤버_멤버2_생성(); + 복수_멤버_저장(member1, member2); + + final var review1_1 = 리뷰_이미지test1_평점1점_재구매X_생성(member1, product1, 0L); + final var review1_2 = 리뷰_이미지test5_평점5점_재구매O_생성(member2, product1, 0L); + final var review2_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product2, 0L); + 복수_리뷰_저장(review1_1, review1_2, review2_1); + + 복수_리뷰_태그_저장(리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_갓성비), 리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_2, 태그_단짠단짠), + 리뷰태그_생성(review2_1, 태그_맛있어요)); + + final var expected = List.of(product1); + + // when + final var actual = productRepository.searchProductsByTopTags(태그1, product2.getId(), PageRequest.of(0, 10)); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + } } From 6d73823d7495da225c7443a6b7a8318640fc5f9e Mon Sep 17 00:00:00 2001 From: wugawuga Date: Mon, 3 Jun 2024 16:59:56 +0900 Subject: [PATCH 2/6] =?UTF-8?q?style:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ProductRepository.java | 16 ------------- .../persistence/ProductRepositoryImpl.java | 10 ++++---- .../acceptance/product/ProductSteps.java | 2 +- .../persistence/ProductRepositoryTest.java | 24 ++++++++++--------- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/funeat/product/persistence/ProductRepository.java b/src/main/java/com/funeat/product/persistence/ProductRepository.java index 90e714d0..b519e7d2 100644 --- a/src/main/java/com/funeat/product/persistence/ProductRepository.java +++ b/src/main/java/com/funeat/product/persistence/ProductRepository.java @@ -5,7 +5,6 @@ import com.funeat.product.dto.ProductReviewCountDto; import java.util.List; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -55,19 +54,4 @@ List findAllWithReviewCountByNameContainingFirst(@Param(" + "ORDER BY (CASE WHEN p.name LIKE CONCAT(:name, '%') THEN 1 ELSE 2 END), p.id DESC") List findAllWithReviewCountByNameContaining(@Param("name") final String name, final Long lastId, final Pageable pageable); - - @Query("SELECT DISTINCT p FROM Product p " - + "JOIN Review r on r.product.id = p.id " - + "LEFT JOIN ReviewTag rt on rt.review.id = r.id " - + "WHERE rt.tag.id = :tagId " - + "ORDER BY p.id DESC") - List findAllByTagFirst(@Param("tagId") Long tagId, Pageable pageable); - - @Query("SELECT DISTINCT p FROM Product p " - + "JOIN Review r on r.product.id = p.id " - + "LEFT JOIN ReviewTag rt on rt.review.id = r.id " - + "WHERE rt.tag.id = :tagId " - + "AND p.id < :lastId " - + "ORDER BY p.id DESC") - List findAllByTag(@Param("tagId") final Long tagId, @Param("lastId") final Long lastId, final Pageable pageable); } diff --git a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java index 0903074f..ed3f83dd 100644 --- a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java +++ b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java @@ -18,7 +18,7 @@ public class ProductRepositoryImpl implements ProductRepositoryCustom { @Override public List searchProductsByTopTagsFirst(final Long tagId, final Pageable pageable) { - String jpql = "SELECT DISTINCT p FROM Product p " + + final String jpql = "SELECT DISTINCT p FROM Product p " + "WHERE p.id IN ( " + " SELECT p2.id FROM Product p2 " + " JOIN p2.reviews r2 " + @@ -35,7 +35,7 @@ public List searchProductsByTopTagsFirst(final Long tagId, final Pageab ") " + "ORDER BY p.id DESC"; - TypedQuery query = entityManager.createQuery(jpql, Product.class); + final TypedQuery query = entityManager.createQuery(jpql, Product.class); query.setParameter("tagId", tagId); query.setFirstResult((int) pageable.getOffset()); query.setMaxResults(pageable.getPageSize()); @@ -44,8 +44,8 @@ public List searchProductsByTopTagsFirst(final Long tagId, final Pageab } @Override - public List searchProductsByTopTags(Long tagId, Long lastProductId, Pageable pageable) { - String jpql = "SELECT DISTINCT p FROM Product p " + + public List searchProductsByTopTags(final Long tagId, final Long lastProductId, final Pageable pageable) { + final String jpql = "SELECT DISTINCT p FROM Product p " + "WHERE p.id < :lastProductId AND p.id IN ( " + " SELECT p2.id FROM Product p2 " + " JOIN p2.reviews r2 " + @@ -62,7 +62,7 @@ public List searchProductsByTopTags(Long tagId, Long lastProductId, Pag ") " + "ORDER BY p.id DESC"; - TypedQuery query = entityManager.createQuery(jpql, Product.class); + final TypedQuery query = entityManager.createQuery(jpql, Product.class); query.setParameter("tagId", tagId); query.setParameter("lastProductId", lastProductId); query.setFirstResult((int) pageable.getOffset()); diff --git a/src/test/java/com/funeat/acceptance/product/ProductSteps.java b/src/test/java/com/funeat/acceptance/product/ProductSteps.java index 4b001813..cc952cc3 100644 --- a/src/test/java/com/funeat/acceptance/product/ProductSteps.java +++ b/src/test/java/com/funeat/acceptance/product/ProductSteps.java @@ -74,7 +74,7 @@ public class ProductSteps { .queryParam("lastProductId", lastProductId) .when() .get("/api/search/tags/results") - .then().log().all() + .then() .extract(); } } diff --git a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index 0bdd4f8f..b4daedd1 100644 --- a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -15,8 +15,11 @@ import static com.funeat.fixture.ReviewFixture.리뷰_이미지test3_평점3점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test5_평점5점_재구매O_생성; import static com.funeat.fixture.ReviewFixture.리뷰_이미지test5_평점5점_재구매X_생성; -import static com.funeat.fixture.TagFixture.*; +import static com.funeat.fixture.TagFixture.태그_맛있어요_TASTE_생성; +import static com.funeat.fixture.TagFixture.태그_단짠단짠_TASTE_생성; +import static com.funeat.fixture.TagFixture.태그_갓성비_PRICE_생성; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; import com.funeat.common.RepositoryTest; import com.funeat.product.dto.ProductReviewCountDto; @@ -238,12 +241,12 @@ class findAllByTagFirst_성공_테스트 { final var actual2 = productRepository.searchProductsByTopTagsFirst(태그2, PageRequest.of(0, 10)); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); - - // then - assertThat(actual2).usingRecursiveComparison() - .isEqualTo(expected2); + assertSoftly(soft -> { + soft.assertThat(actual) + .isEqualTo(expected); + soft.assertThat(actual2) + .isEqualTo(expected2); + }); } @Test @@ -253,8 +256,8 @@ class findAllByTagFirst_성공_테스트 { 단일_카테고리_저장(category); final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); - final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); - final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); + final var 태그_단짠단짠 = 태그_단짠단짠_TASTE_생성(); + final var 태그_갓성비 = 태그_갓성비_PRICE_생성(); final var 태그1 = 단일_태그_저장(태그_맛있어요); final var 태그2 = 단일_태그_저장(태그_단짠단짠); @@ -263,8 +266,7 @@ class findAllByTagFirst_성공_테스트 { final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); - final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); - 복수_상품_저장(product1, product2, product3, product4); + 복수_상품_저장(product1, product2, product3); final var member1 = 멤버_멤버1_생성(); final var member2 = 멤버_멤버2_생성(); From 92e83201ad05eeef132429ac4b2cccc75aac58b1 Mon Sep 17 00:00:00 2001 From: wugawuga Date: Mon, 3 Jun 2024 17:08:46 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20query=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ProductRepositoryImpl.java | 77 +++++++++++-------- .../persistence/ProductRepositoryTest.java | 56 ++++++++++++++ 2 files changed, 101 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java index ed3f83dd..96de4b95 100644 --- a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java +++ b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java @@ -18,22 +18,27 @@ public class ProductRepositoryImpl implements ProductRepositoryCustom { @Override public List searchProductsByTopTagsFirst(final Long tagId, final Pageable pageable) { - final String jpql = "SELECT DISTINCT p FROM Product p " + - "WHERE p.id IN ( " + - " SELECT p2.id FROM Product p2 " + - " JOIN p2.reviews r2 " + - " JOIN r2.reviewTags rt2 " + - " WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( " + - " SELECT rt3.tag.id FROM Review r3 " + - " JOIN r3.reviewTags rt3 " + - " WHERE r3.product.id = p2.id " + - " GROUP BY rt3.tag.id " + - " ORDER BY COUNT(rt3.tag.id) DESC " + - " ) " + - " GROUP BY p2.id " + - " HAVING COUNT(DISTINCT rt2.tag.id) <= 3 " + - ") " + - "ORDER BY p.id DESC"; + final String jpql = """ + SELECT DISTINCT p\s + FROM Product p + WHERE p.id IN ( + SELECT p2.id\s + FROM Product p2\s + JOIN p2.reviews r2\s + JOIN r2.reviewTags rt2\s + WHERE rt2.tag.id = :tagId\s + AND rt2.tag.id IN ( + SELECT rt3.tag.id\s + FROM Review r3\s + JOIN r3.reviewTags rt3\s + WHERE r3.product.id = p2.id\s + GROUP BY rt3.tag.id\s + ORDER BY COUNT(rt3.tag.id) DESC\s + LIMIT 3 + ) + ) + ORDER BY p.id DESC + """; final TypedQuery query = entityManager.createQuery(jpql, Product.class); query.setParameter("tagId", tagId); @@ -45,22 +50,30 @@ public List searchProductsByTopTagsFirst(final Long tagId, final Pageab @Override public List searchProductsByTopTags(final Long tagId, final Long lastProductId, final Pageable pageable) { - final String jpql = "SELECT DISTINCT p FROM Product p " + - "WHERE p.id < :lastProductId AND p.id IN ( " + - " SELECT p2.id FROM Product p2 " + - " JOIN p2.reviews r2 " + - " JOIN r2.reviewTags rt2 " + - " WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( " + - " SELECT rt3.tag.id FROM Review r3 " + - " JOIN r3.reviewTags rt3 " + - " WHERE r3.product.id = p2.id " + - " GROUP BY rt3.tag.id " + - " ORDER BY COUNT(rt3.tag.id) DESC " + - " ) " + - " GROUP BY p2.id " + - " HAVING COUNT(DISTINCT rt2.tag.id) <= 3 " + - ") " + - "ORDER BY p.id DESC"; + final String jpql = """ + SELECT DISTINCT p\s + FROM Product p\s + WHERE p.id < :lastProductId\s + AND p.id IN ( + SELECT p2.id\s + FROM Product p2\s + JOIN p2.reviews r2\s + JOIN r2.reviewTags rt2\s + WHERE rt2.tag.id = :tagId\s + AND rt2.tag.id IN ( + SELECT rt3.tag.id\s + FROM Review r3\s + JOIN r3.reviewTags rt3\s + WHERE r3.product.id = p2.id\s + GROUP BY rt3.tag.id\s + ORDER BY COUNT(rt3.tag.id) DESC\s + LIMIT 3 + ) + GROUP BY p2.id\s + HAVING COUNT(DISTINCT rt2.tag.id) <= 3\s + )\s + ORDER BY p.id DESC + """; final TypedQuery query = entityManager.createQuery(jpql, Product.class); query.setParameter("tagId", tagId); diff --git a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index b4daedd1..586f0575 100644 --- a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -18,6 +18,7 @@ import static com.funeat.fixture.TagFixture.태그_맛있어요_TASTE_생성; import static com.funeat.fixture.TagFixture.태그_단짠단짠_TASTE_생성; import static com.funeat.fixture.TagFixture.태그_갓성비_PRICE_생성; +import static com.funeat.fixture.TagFixture.태그_간식_ETC_생성; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -291,4 +292,59 @@ class findAllByTagFirst_성공_테스트 { .isEqualTo(expected); } } + + @Nested + class findAllByTagFirst_실패_테스트 { + + @Test + void 간식_태그는_4위이므로_검색되지_말아야한다() { + // given + final var category = 카테고리_간편식사_생성(); + 단일_카테고리_저장(category); + + final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); + final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); + final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); + final var 태그_간식 = 태그_간식_ETC_생성(); + + final var 태그1 = 단일_태그_저장(태그_맛있어요); + final var 태그2 = 단일_태그_저장(태그_단짠단짠); + final var 태그3 = 단일_태그_저장(태그_갓성비); + final var 태그4 = 단일_태그_저장(태그_간식); + + final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); + final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); + final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); + 복수_상품_저장(product1, product2, product3, product4); + + final var member1 = 멤버_멤버1_생성(); + final var member2 = 멤버_멤버2_생성(); + 복수_멤버_저장(member1, member2); + + final var review1_1 = 리뷰_이미지test1_평점1점_재구매X_생성(member1, product1, 0L); + final var review1_2 = 리뷰_이미지test5_평점5점_재구매O_생성(member2, product1, 0L); + final var review2_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product2, 0L); + 복수_리뷰_저장(review1_1, review1_2, review2_1); + + 복수_리뷰_태그_저장( + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_갓성비), + 리뷰태그_생성(review1_1, 태그_갓성비), + 리뷰태그_생성(review1_1, 태그_간식), + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_2, 태그_단짠단짠), + 리뷰태그_생성(review2_1, 태그_맛있어요) + ); + + // when + final var actual = productRepository.searchProductsByTopTagsFirst(태그4, PageRequest.of(0, 10)); + + // then + assertThat(actual).isEmpty(); + } + } } From 6e1701573b9d1206f5037ebca4da99d109e2d518 Mon Sep 17 00:00:00 2001 From: wugawuga Date: Thu, 6 Jun 2024 10:41:35 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=EC=BF=BC=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20"\s"=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ProductRepositoryImpl.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java index 96de4b95..eb624ade 100644 --- a/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java +++ b/src/main/java/com/funeat/product/persistence/ProductRepositoryImpl.java @@ -19,21 +19,21 @@ public class ProductRepositoryImpl implements ProductRepositoryCustom { @Override public List searchProductsByTopTagsFirst(final Long tagId, final Pageable pageable) { final String jpql = """ - SELECT DISTINCT p\s + SELECT DISTINCT p FROM Product p WHERE p.id IN ( - SELECT p2.id\s - FROM Product p2\s - JOIN p2.reviews r2\s - JOIN r2.reviewTags rt2\s - WHERE rt2.tag.id = :tagId\s + SELECT p2.id + FROM Product p2 + JOIN p2.reviews r2 + JOIN r2.reviewTags rt2 + WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( - SELECT rt3.tag.id\s - FROM Review r3\s - JOIN r3.reviewTags rt3\s - WHERE r3.product.id = p2.id\s - GROUP BY rt3.tag.id\s - ORDER BY COUNT(rt3.tag.id) DESC\s + SELECT rt3.tag.id + FROM Review r3 + JOIN r3.reviewTags rt3 + WHERE r3.product.id = p2.id + GROUP BY rt3.tag.id + ORDER BY COUNT(rt3.tag.id) DESC LIMIT 3 ) ) @@ -51,27 +51,27 @@ ORDER BY COUNT(rt3.tag.id) DESC\s @Override public List searchProductsByTopTags(final Long tagId, final Long lastProductId, final Pageable pageable) { final String jpql = """ - SELECT DISTINCT p\s - FROM Product p\s - WHERE p.id < :lastProductId\s + SELECT DISTINCT p + FROM Product p + WHERE p.id < :lastProductId AND p.id IN ( - SELECT p2.id\s - FROM Product p2\s - JOIN p2.reviews r2\s - JOIN r2.reviewTags rt2\s - WHERE rt2.tag.id = :tagId\s + SELECT p2.id + FROM Product p2 + JOIN p2.reviews r2 + JOIN r2.reviewTags rt2 + WHERE rt2.tag.id = :tagId AND rt2.tag.id IN ( - SELECT rt3.tag.id\s - FROM Review r3\s - JOIN r3.reviewTags rt3\s - WHERE r3.product.id = p2.id\s - GROUP BY rt3.tag.id\s - ORDER BY COUNT(rt3.tag.id) DESC\s + SELECT rt3.tag.id + FROM Review r3 + JOIN r3.reviewTags rt3 + WHERE r3.product.id = p2.id + GROUP BY rt3.tag.id + ORDER BY COUNT(rt3.tag.id) DESC LIMIT 3 ) - GROUP BY p2.id\s - HAVING COUNT(DISTINCT rt2.tag.id) <= 3\s - )\s + GROUP BY p2.id + HAVING COUNT(DISTINCT rt2.tag.id) <= 3 + ) ORDER BY p.id DESC """; From fcd31a95db187d8c54517d29c28c675f562e4905 Mon Sep 17 00:00:00 2001 From: wugawuga Date: Thu, 6 Jun 2024 10:44:11 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20import=20=EC=99=80=EC=9D=BC?= =?UTF-8?q?=EB=93=9C=EC=B9=B4=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../funeat/acceptance/product/ProductAcceptanceTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java b/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java index f541531e..e35b0292 100644 --- a/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java +++ b/src/test/java/com/funeat/acceptance/product/ProductAcceptanceTest.java @@ -8,7 +8,13 @@ import static com.funeat.acceptance.common.CommonSteps.정상_처리; import static com.funeat.acceptance.common.CommonSteps.찾을수_없음; import static com.funeat.acceptance.common.CommonSteps.페이지를_검증한다; -import static com.funeat.acceptance.product.ProductSteps.*; +import static com.funeat.acceptance.product.ProductSteps.상품_검색_결과_조회_요청; +import static com.funeat.acceptance.product.ProductSteps.상품_랭킹_조회_요청; +import static com.funeat.acceptance.product.ProductSteps.상품_레시피_목록_요청; +import static com.funeat.acceptance.product.ProductSteps.상품_상세_조회_요청; +import static com.funeat.acceptance.product.ProductSteps.상품_자동_완성_검색_요청; +import static com.funeat.acceptance.product.ProductSteps.카테고리별_상품_목록_조회_요청; +import static com.funeat.acceptance.product.ProductSteps.태그_상품_검색_결과_조회_요청; import static com.funeat.acceptance.recipe.RecipeSteps.레시피_작성_요청; import static com.funeat.acceptance.recipe.RecipeSteps.여러명이_레시피_좋아요_요청; import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청; @@ -94,7 +100,6 @@ import com.funeat.product.dto.SearchProductResultDto; import com.funeat.product.dto.SearchProductResultsResponse; import com.funeat.product.dto.SearchProductsResponse; -import com.funeat.product.dto.CategoryDto; import com.funeat.recipe.dto.RecipeDto; import com.funeat.tag.dto.TagDto; import io.restassured.response.ExtractableResponse; From a76f820b23f4a58437953bf7a5835417ed75eb0f Mon Sep 17 00:00:00 2001 From: wugawuga Date: Thu, 6 Jun 2024 10:48:08 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ProductRepositoryTest.java | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java index 586f0575..b38f918d 100644 --- a/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java +++ b/src/test/java/com/funeat/product/persistence/ProductRepositoryTest.java @@ -199,7 +199,7 @@ class findAllWithReviewCountByNameContaining_성공_테스트 { } @Nested - class findAllByTagFirst_성공_테스트 { + class searchProductsByTopTagsFirst_성공_테스트 { @Test void 특정_태그가_포함된_상품들을_조회한다() { @@ -208,8 +208,8 @@ class findAllByTagFirst_성공_테스트 { 단일_카테고리_저장(category); final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); - final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); - final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); + final var 태그_단짠단짠 = 태그_단짠단짠_TASTE_생성(); + final var 태그_갓성비 = 태그_갓성비_PRICE_생성(); final var 태그1 = 단일_태그_저장(태그_맛있어요); final var 태그2 = 단일_태그_저장(태그_단짠단짠); @@ -249,9 +249,13 @@ class findAllByTagFirst_성공_테스트 { .isEqualTo(expected2); }); } + } + + @Nested + class searchProductsByTopTagsFirst_실패_테스트 { @Test - void 특정_태그와_마지막_상품아이디_이후_상품들을_조회한다() { + void 간식_태그는_4위이므로_검색되지_말아야한다() { // given final var category = 카테고리_간편식사_생성(); 단일_카테고리_저장(category); @@ -259,15 +263,18 @@ class findAllByTagFirst_성공_테스트 { final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); final var 태그_단짠단짠 = 태그_단짠단짠_TASTE_생성(); final var 태그_갓성비 = 태그_갓성비_PRICE_생성(); + final var 태그_간식 = 태그_간식_ETC_생성(); final var 태그1 = 단일_태그_저장(태그_맛있어요); final var 태그2 = 단일_태그_저장(태그_단짠단짠); final var 태그3 = 단일_태그_저장(태그_갓성비); + final var 태그4 = 단일_태그_저장(태그_간식); final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); - 복수_상품_저장(product1, product2, product3); + final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); + 복수_상품_저장(product1, product2, product3, product4); final var member1 = 멤버_멤버1_생성(); final var member2 = 멤버_멤버2_생성(); @@ -278,45 +285,48 @@ class findAllByTagFirst_성공_테스트 { final var review2_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product2, 0L); 복수_리뷰_저장(review1_1, review1_2, review2_1); - 복수_리뷰_태그_저장(리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_1, 태그_단짠단짠), - 리뷰태그_생성(review1_1, 태그_갓성비), 리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_2, 태그_단짠단짠), - 리뷰태그_생성(review2_1, 태그_맛있어요)); - - final var expected = List.of(product1); + 복수_리뷰_태그_저장( + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_갓성비), + 리뷰태그_생성(review1_1, 태그_갓성비), + 리뷰태그_생성(review1_1, 태그_간식), + 리뷰태그_생성(review1_1, 태그_맛있어요), + 리뷰태그_생성(review1_2, 태그_단짠단짠), + 리뷰태그_생성(review2_1, 태그_맛있어요) + ); // when - final var actual = productRepository.searchProductsByTopTags(태그1, product2.getId(), PageRequest.of(0, 10)); + final var actual = productRepository.searchProductsByTopTagsFirst(태그4, PageRequest.of(0, 10)); // then - assertThat(actual).usingRecursiveComparison() - .isEqualTo(expected); + assertThat(actual).isEmpty(); } } @Nested - class findAllByTagFirst_실패_테스트 { + class searchProductsByTopTags_성공_테스트 { @Test - void 간식_태그는_4위이므로_검색되지_말아야한다() { + void 특정_태그와_마지막_상품아이디_이후_상품들을_조회한다() { // given final var category = 카테고리_간편식사_생성(); 단일_카테고리_저장(category); final var 태그_맛있어요 = 태그_맛있어요_TASTE_생성(); - final var 태그_단짠단짠 = 태그_맛있어요_TASTE_생성(); - final var 태그_갓성비 = 태그_맛있어요_TASTE_생성(); - final var 태그_간식 = 태그_간식_ETC_생성(); + final var 태그_단짠단짠 = 태그_단짠단짠_TASTE_생성(); + final var 태그_갓성비 = 태그_갓성비_PRICE_생성(); final var 태그1 = 단일_태그_저장(태그_맛있어요); final var 태그2 = 단일_태그_저장(태그_단짠단짠); final var 태그3 = 단일_태그_저장(태그_갓성비); - final var 태그4 = 단일_태그_저장(태그_간식); final var product1 = 상품_애플망고_가격3000원_평점5점_생성(category); final var product2 = 상품_망고빙수_가격5000원_평점4점_생성(category); final var product3 = 상품_망고빙수_가격5000원_평점4점_생성(category); - final var product4 = 상품_망고빙수_가격5000원_평점4점_생성(category); - 복수_상품_저장(product1, product2, product3, product4); + 복수_상품_저장(product1, product2, product3); final var member1 = 멤버_멤버1_생성(); final var member2 = 멤버_멤버2_생성(); @@ -327,24 +337,18 @@ class findAllByTagFirst_실패_테스트 { final var review2_1 = 리뷰_이미지test3_평점3점_재구매O_생성(member1, product2, 0L); 복수_리뷰_저장(review1_1, review1_2, review2_1); - 복수_리뷰_태그_저장( - 리뷰태그_생성(review1_1, 태그_맛있어요), - 리뷰태그_생성(review1_1, 태그_맛있어요), - 리뷰태그_생성(review1_1, 태그_단짠단짠), - 리뷰태그_생성(review1_1, 태그_단짠단짠), - 리뷰태그_생성(review1_1, 태그_갓성비), - 리뷰태그_생성(review1_1, 태그_갓성비), - 리뷰태그_생성(review1_1, 태그_간식), - 리뷰태그_생성(review1_1, 태그_맛있어요), - 리뷰태그_생성(review1_2, 태그_단짠단짠), - 리뷰태그_생성(review2_1, 태그_맛있어요) - ); + 복수_리뷰_태그_저장(리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_1, 태그_단짠단짠), + 리뷰태그_생성(review1_1, 태그_갓성비), 리뷰태그_생성(review1_1, 태그_맛있어요), 리뷰태그_생성(review1_2, 태그_단짠단짠), + 리뷰태그_생성(review2_1, 태그_맛있어요)); + + final var expected = List.of(product1); // when - final var actual = productRepository.searchProductsByTopTagsFirst(태그4, PageRequest.of(0, 10)); + final var actual = productRepository.searchProductsByTopTags(태그1, product2.getId(), PageRequest.of(0, 10)); // then - assertThat(actual).isEmpty(); + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); } } }