From 30844a477bcb875f2fadc203709da920670729a1 Mon Sep 17 00:00:00 2001 From: Dabeen Jeong Date: Tue, 4 Jun 2024 12:34:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20=EA=BF=80?= =?UTF-8?q?=EC=A1=B0=ED=95=A9=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#70)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 컨트롤러 메서드와 Swagger 문서 추가 * test: 저장한 레시피 조회 인수테스트 추가 * feat: 저장한 레시피 조회 API 요청에 필요한 기능 추가 인수 테스트만 정상적으로 실행될 수 있도록만 추가 서비스 메서드는 이후에 작성 * test: 인수 테스트에 부족한 조건 추가 * feat: 저장된 꿀조합 목록 조회 서비스 로직 및 테스트 추가 * test: 북마크한 레시피 목록 조회 리포지토리 테스트 추가 * style: 코드 포맷 수정 --- .../member/dto/MemberBookmarkRecipeDto.java | 41 ++++++ .../dto/MemberBookmarkRecipeProductDto.java | 17 +++ .../dto/MemberBookmarkRecipeProductsDto.java | 18 +++ .../dto/MemberBookmarkRecipesResponse.java | 16 +++ .../member/dto/MemberRecipeProductDto.java | 26 ---- .../presentation/MemberApiController.java | 9 ++ .../member/presentation/MemberController.java | 10 ++ .../recipe/application/RecipeService.java | 19 +++ .../recipe/persistence/RecipeRepository.java | 3 + .../member/MemberAcceptanceTest.java | 129 ++++++++++++++++++ .../funeat/acceptance/member/MemberSteps.java | 10 ++ .../com/funeat/common/RepositoryTest.java | 11 ++ .../com/funeat/fixture/RecipeFixture.java | 7 + .../recipe/application/RecipeServiceTest.java | 93 +++++++++++++ .../persistence/RecipeRepositoryTest.java | 55 ++++++++ 15 files changed, 438 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/funeat/member/dto/MemberBookmarkRecipeDto.java create mode 100644 src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductDto.java create mode 100644 src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductsDto.java create mode 100644 src/main/java/com/funeat/member/dto/MemberBookmarkRecipesResponse.java delete mode 100644 src/main/java/com/funeat/member/dto/MemberRecipeProductDto.java diff --git a/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeDto.java b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeDto.java new file mode 100644 index 00000000..b19b3095 --- /dev/null +++ b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeDto.java @@ -0,0 +1,41 @@ +package com.funeat.member.dto; + +import com.funeat.product.domain.Product; +import com.funeat.recipe.domain.Recipe; +import com.funeat.recipe.domain.RecipeImage; + +import java.time.LocalDateTime; +import java.util.List; + +public record MemberBookmarkRecipeDto( + Long id, + String title, + String image, + String content, + Boolean favorite, + MemberResponse author, + MemberBookmarkRecipeProductsDto products, + LocalDateTime createdAt +) { + + private MemberBookmarkRecipeDto(final Long id, final String title, final String content, final Boolean favorite, + final MemberResponse author, final MemberBookmarkRecipeProductsDto products, + final LocalDateTime createdAt) { + this(id, title, null, content, favorite, author, products, createdAt); + } + + public static MemberBookmarkRecipeDto toDto(final Recipe recipe, final List findRecipeImages, + final List recipeInProducts, final Boolean isFavorite) { + final MemberResponse author = MemberResponse.toResponse(recipe.getMember()); + final MemberBookmarkRecipeProductsDto products = MemberBookmarkRecipeProductsDto.toDto(recipeInProducts); + + if (findRecipeImages.isEmpty()) { + return new MemberBookmarkRecipeDto(recipe.getId(), recipe.getTitle(), recipe.getContent(), isFavorite, + author, products, recipe.getCreatedAt() + ); + } + return new MemberBookmarkRecipeDto(recipe.getId(), recipe.getTitle(), findRecipeImages.get(0).getImage(), + recipe.getContent(), isFavorite, author, products, recipe.getCreatedAt() + ); + } +} diff --git a/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductDto.java b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductDto.java new file mode 100644 index 00000000..1ccd79cf --- /dev/null +++ b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductDto.java @@ -0,0 +1,17 @@ +package com.funeat.member.dto; + +import com.funeat.product.domain.Product; + +public record MemberBookmarkRecipeProductDto( + Long id, + String name, + Long price, + String image, + Double averageRating +) { + + public static MemberBookmarkRecipeProductDto toDto(final Product product) { + return new MemberBookmarkRecipeProductDto(product.getId(), product.getName(), product.getPrice(), + product.getImage(), product.getAverageRating()); + } +} diff --git a/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductsDto.java b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductsDto.java new file mode 100644 index 00000000..f4766efc --- /dev/null +++ b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipeProductsDto.java @@ -0,0 +1,18 @@ +package com.funeat.member.dto; + +import com.funeat.product.domain.Product; + +import java.util.List; + +public record MemberBookmarkRecipeProductsDto( + List products +) { + + public static MemberBookmarkRecipeProductsDto toDto(final List recipeInProducts) { + final List products = recipeInProducts.stream() + .map(MemberBookmarkRecipeProductDto::toDto) + .toList(); + + return new MemberBookmarkRecipeProductsDto(products); + } +} diff --git a/src/main/java/com/funeat/member/dto/MemberBookmarkRecipesResponse.java b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipesResponse.java new file mode 100644 index 00000000..c5101d54 --- /dev/null +++ b/src/main/java/com/funeat/member/dto/MemberBookmarkRecipesResponse.java @@ -0,0 +1,16 @@ +package com.funeat.member.dto; + +import com.funeat.common.dto.PageDto; + +import java.util.List; + +public record MemberBookmarkRecipesResponse( + PageDto page, + List recipes +) { + + public static MemberBookmarkRecipesResponse toResponse(final PageDto page, + final List recipes) { + return new MemberBookmarkRecipesResponse(page, recipes); + } +} diff --git a/src/main/java/com/funeat/member/dto/MemberRecipeProductDto.java b/src/main/java/com/funeat/member/dto/MemberRecipeProductDto.java deleted file mode 100644 index eec5ef79..00000000 --- a/src/main/java/com/funeat/member/dto/MemberRecipeProductDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.funeat.member.dto; - -import com.funeat.product.domain.Product; - -public class MemberRecipeProductDto { - - private final Long id; - private final String name; - - private MemberRecipeProductDto(final Long id, final String name) { - this.id = id; - this.name = name; - } - - public static MemberRecipeProductDto toDto(final Product product) { - return new MemberRecipeProductDto(product.getId(), product.getName()); - } - - public Long getId() { - return id; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/com/funeat/member/presentation/MemberApiController.java b/src/main/java/com/funeat/member/presentation/MemberApiController.java index ea0db940..ae300eef 100644 --- a/src/main/java/com/funeat/member/presentation/MemberApiController.java +++ b/src/main/java/com/funeat/member/presentation/MemberApiController.java @@ -4,6 +4,7 @@ import com.funeat.auth.util.AuthenticationPrincipal; import com.funeat.common.logging.Logging; import com.funeat.member.application.MemberService; +import com.funeat.member.dto.MemberBookmarkRecipesResponse; import com.funeat.member.dto.MemberProfileResponse; import com.funeat.member.dto.MemberRecipesResponse; import com.funeat.member.dto.MemberRequest; @@ -80,4 +81,12 @@ public ResponseEntity deleteReview(@PathVariable final Long reviewId, return ResponseEntity.noContent().build(); } + + @GetMapping("/recipes/bookmark") + public ResponseEntity getMemberBookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo, + @PageableDefault final Pageable pageable) { + final MemberBookmarkRecipesResponse response = recipeService.findBookmarkRecipeByMember(loginInfo.getId(), pageable); + + return ResponseEntity.ok().body(response); + } } diff --git a/src/main/java/com/funeat/member/presentation/MemberController.java b/src/main/java/com/funeat/member/presentation/MemberController.java index 9c5e6076..a33176b3 100644 --- a/src/main/java/com/funeat/member/presentation/MemberController.java +++ b/src/main/java/com/funeat/member/presentation/MemberController.java @@ -2,6 +2,7 @@ import com.funeat.auth.dto.LoginInfo; import com.funeat.auth.util.AuthenticationPrincipal; +import com.funeat.member.dto.MemberBookmarkRecipesResponse; import com.funeat.member.dto.MemberProfileResponse; import com.funeat.member.dto.MemberRecipesResponse; import com.funeat.member.dto.MemberRequest; @@ -66,4 +67,13 @@ ResponseEntity getMemberRecipe(@AuthenticationPrincipal f @DeleteMapping ResponseEntity deleteReview(@PathVariable final Long reviewId, @AuthenticationPrincipal final LoginInfo loginInfo); + + @Operation(summary = "사용자가 저장한 꿀조합 조회 (북마크)", description = "자신이 저장한 꿀조합을 조회한다.") + @ApiResponse( + responseCode = "200", + description = "저장한 꿀조합 조회 성공." + ) + @GetMapping + ResponseEntity getMemberBookmarkRecipe(@AuthenticationPrincipal final LoginInfo loginInfo, + @PageableDefault final Pageable pageable); } diff --git a/src/main/java/com/funeat/recipe/application/RecipeService.java b/src/main/java/com/funeat/recipe/application/RecipeService.java index 17b321c2..15f087d4 100644 --- a/src/main/java/com/funeat/recipe/application/RecipeService.java +++ b/src/main/java/com/funeat/recipe/application/RecipeService.java @@ -14,6 +14,8 @@ import com.funeat.member.domain.Member; import com.funeat.member.domain.bookmark.RecipeBookmark; import com.funeat.member.domain.favorite.RecipeFavorite; +import com.funeat.member.dto.MemberBookmarkRecipeDto; +import com.funeat.member.dto.MemberBookmarkRecipesResponse; import com.funeat.member.dto.MemberRecipeDto; import com.funeat.member.dto.MemberRecipesResponse; import com.funeat.member.exception.MemberException.MemberDuplicateBookmarkException; @@ -227,6 +229,23 @@ private RecipeBookmark createAndSaveRecipeBookmark(final Member member, final Re } } + public MemberBookmarkRecipesResponse findBookmarkRecipeByMember(final Long memberId, final Pageable pageable) { + final Member findMember = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberNotFoundException(MEMBER_NOT_FOUND, memberId)); + + final Page sortedBookmarkRecipePages = recipeRepository.findBookmarkedRecipesByMember(findMember, pageable); + + final PageDto page = PageDto.toDto(sortedBookmarkRecipePages); + final List dtos = sortedBookmarkRecipePages.stream() + .map(recipe -> MemberBookmarkRecipeDto.toDto(recipe, + recipeImageRepository.findByRecipe(recipe), + productRecipeRepository.findProductByRecipe(recipe), + recipeFavoriteRepository.existsByMemberAndRecipeAndFavoriteTrue(findMember, recipe))) + .toList(); + + return MemberBookmarkRecipesResponse.toResponse(page, dtos); + } + public SearchRecipeResultsResponse getSearchResults(final String query, final Long lastRecipeId) { final List findRecipes = findAllByProductNameContaining(query, lastRecipeId); final int resultSize = getResultSize(findRecipes); diff --git a/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java b/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java index 9114272c..3ad0ae95 100644 --- a/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java +++ b/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java @@ -49,4 +49,7 @@ List findAllByProductNameContaining(@Param("name") final String name, fi List findRecipesByFavoriteCountGreaterThanEqual(final Long favoriteCount); Long countByMember(final Member member); + + @Query("SELECT r FROM Recipe r JOIN RecipeBookmark rb ON r.id = rb.recipe.id WHERE rb.member = :member AND rb.bookmark = true") + Page findBookmarkedRecipesByMember(@Param("member") final Member member, final Pageable pageable); } diff --git a/src/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java b/src/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java index f1cc24f2..c7faa475 100644 --- a/src/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java +++ b/src/test/java/com/funeat/acceptance/member/MemberAcceptanceTest.java @@ -13,8 +13,10 @@ import static com.funeat.acceptance.member.MemberSteps.리뷰_삭제_요청; import static com.funeat.acceptance.member.MemberSteps.사용자_꿀조합_조회_요청; import static com.funeat.acceptance.member.MemberSteps.사용자_리뷰_조회_요청; +import static com.funeat.acceptance.member.MemberSteps.사용자_북마크한_꿀조합_조회_요청; import static com.funeat.acceptance.member.MemberSteps.사용자_정보_수정_요청; import static com.funeat.acceptance.member.MemberSteps.사용자_정보_조회_요청; +import static com.funeat.acceptance.recipe.RecipeSteps.레시피_북마크_요청; import static com.funeat.acceptance.recipe.RecipeSteps.레시피_작성_요청; import static com.funeat.acceptance.review.ReviewSteps.리뷰_작성_요청; import static com.funeat.auth.exception.AuthErrorCode.LOGIN_MEMBER_NOT_FOUND; @@ -40,7 +42,11 @@ import static com.funeat.fixture.RecipeFixture.레시피; import static com.funeat.fixture.RecipeFixture.레시피1; import static com.funeat.fixture.RecipeFixture.레시피2; +import static com.funeat.fixture.RecipeFixture.레시피3; +import static com.funeat.fixture.RecipeFixture.레시피북마크요청_생성; import static com.funeat.fixture.RecipeFixture.레시피추가요청_생성; +import static com.funeat.fixture.RecipeFixture.북마크O; +import static com.funeat.fixture.RecipeFixture.북마크X; import static com.funeat.fixture.ReviewFixture.리뷰1; import static com.funeat.fixture.ReviewFixture.리뷰2; import static com.funeat.fixture.ReviewFixture.리뷰추가요청_재구매O_생성; @@ -56,6 +62,7 @@ import com.funeat.acceptance.common.AcceptanceTest; import com.funeat.member.domain.Member; +import com.funeat.member.dto.MemberBookmarkRecipeDto; import com.funeat.member.dto.MemberProfileResponse; import com.funeat.member.dto.MemberRecipeDto; import com.funeat.member.dto.MemberReviewDto; @@ -394,6 +401,119 @@ class deleteReview_실패_테스트 { } } + @Nested + class getMemberBookmarkRecipe_성공_테스트 { + + @Test + void 사용자가_저장한_꿀조합이_없을때_꿀조합은_빈상태로_조회된다() { + // given + final var 카테고리 = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(카테고리); + final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리)); + + 레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지1), 레시피추가요청_생성(상품)); + + final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(0L), 총_페이지(0L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); + + // when + final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE); + + // then + STATUS_CODE를_검증한다(응답, 정상_처리); + 페이지를_검증한다(응답, 예상_응답_페이지); + 사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, Collections.emptyList()); + } + + @Test + void 사용자가_저장한_꿀조합을_조회하다() { + // given + final var 카테고리 = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(카테고리); + final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리)); + + 레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지1), 레시피추가요청_생성(상품)); + 레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지2), 레시피추가요청_생성(상품)); + 레시피_작성_요청(로그인_쿠키_획득(멤버2), 여러개_사진_명세_요청(이미지3), 레시피추가요청_생성(상품)); + + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O)); + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피3, 레시피북마크요청_생성(북마크O)); + + final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(2L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); + + // when + final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE); + + // then + STATUS_CODE를_검증한다(응답, 정상_처리); + 페이지를_검증한다(응답, 예상_응답_페이지); + 사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피3, 레시피1)); + } + + @Test + void 사용자가_꿀조합_저장을_취소했으면_해당_꿀조합은_보이지_않아야_한다() { + // given + final var 카테고리 = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(카테고리); + final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리)); + + 레시피_작성_요청(로그인_쿠키_획득(멤버1), 여러개_사진_명세_요청(이미지1), 레시피추가요청_생성(상품)); + 레시피_작성_요청(로그인_쿠키_획득(멤버2), 여러개_사진_명세_요청(이미지2), 레시피추가요청_생성(상품)); + + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O)); + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피2, 레시피북마크요청_생성(북마크O)); + + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크X)); + + final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(1L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); + + // when + final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE); + + // then + STATUS_CODE를_검증한다(응답, 정상_처리); + 페이지를_검증한다(응답, 예상_응답_페이지); + 사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피2)); + } + + @Test + void 사용자가_저장한_꿀조합에_이미지가_없을때_저장된_꿀조합은_이미지없이_조회된다() { + // given + final var 카테고리 = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(카테고리); + final var 상품 = 단일_상품_저장(상품_삼각김밥_가격1000원_평점5점_생성(카테고리)); + + 레시피_작성_요청(로그인_쿠키_획득(멤버1), null, 레시피추가요청_생성(상품)); + 레시피_북마크_요청(로그인_쿠키_획득(멤버1), 레시피1, 레시피북마크요청_생성(북마크O)); + + final var 예상_응답_페이지 = 응답_페이지_생성(총_데이터_개수(1L), 총_페이지(1L), 첫페이지O, 마지막페이지O, FIRST_PAGE, PAGE_SIZE); + + // when + final var 응답 = 사용자_북마크한_꿀조합_조회_요청(로그인_쿠키_획득(멤버1), FIRST_PAGE); + + // then + STATUS_CODE를_검증한다(응답, 정상_처리); + 페이지를_검증한다(응답, 예상_응답_페이지); + 사용자_북마크한_꿀조합_조회_결과를_검증한다(응답, List.of(레시피)); + 조회한_꿀조합의_이미지가_없는지_확인한다(응답); + } + } + + @Nested + class getMemberBookmarkRecipe_실패_테스트 { + + @ParameterizedTest + @NullAndEmptySource + void 로그인하지_않은_사용자가_저장한_꿀조합을_조회할때_예외가_발생한다(final String cookie) { + // given & when + final var 응답 = 사용자_북마크한_꿀조합_조회_요청(cookie, FIRST_PAGE); + + // then + STATUS_CODE를_검증한다(응답, 인증되지_않음); + RESPONSE_CODE와_MESSAGE를_검증한다(응답, LOGIN_MEMBER_NOT_FOUND.getCode(), + LOGIN_MEMBER_NOT_FOUND.getMessage()); + } + } + private void 사용자_리뷰_조회_결과를_검증한다(final ExtractableResponse response, final int expectedReviewSize) { final var actual = response.jsonPath().getList("reviews", MemberReviewDto.class); @@ -449,4 +569,13 @@ class deleteReview_실패_테스트 { assertThat(actual).isNull(); } + + private void 사용자_북마크한_꿀조합_조회_결과를_검증한다(final ExtractableResponse response, + final List recipeIds) { + final var actual = response.jsonPath() + .getList("recipes", MemberBookmarkRecipeDto.class); + + assertThat(actual).extracting(MemberBookmarkRecipeDto::id) + .containsExactlyElementsOf(recipeIds); + } } diff --git a/src/test/java/com/funeat/acceptance/member/MemberSteps.java b/src/test/java/com/funeat/acceptance/member/MemberSteps.java index 98cf4d6f..2b3524fe 100644 --- a/src/test/java/com/funeat/acceptance/member/MemberSteps.java +++ b/src/test/java/com/funeat/acceptance/member/MemberSteps.java @@ -71,4 +71,14 @@ public class MemberSteps { .then() .extract(); } + + public static ExtractableResponse 사용자_북마크한_꿀조합_조회_요청(final String loginCookie, final Long page) { + return given() + .when() + .cookie("SESSION", loginCookie) + .queryParam("page", page) + .get("/api/members/recipes/bookmark") + .then() + .extract(); + } } diff --git a/src/test/java/com/funeat/common/RepositoryTest.java b/src/test/java/com/funeat/common/RepositoryTest.java index 1ae1fff6..f73a758b 100644 --- a/src/test/java/com/funeat/common/RepositoryTest.java +++ b/src/test/java/com/funeat/common/RepositoryTest.java @@ -3,9 +3,11 @@ import com.funeat.banner.domain.Banner; import com.funeat.banner.persistence.BannerRepository; import com.funeat.member.domain.Member; +import com.funeat.member.domain.bookmark.RecipeBookmark; import com.funeat.member.domain.favorite.RecipeFavorite; import com.funeat.member.domain.favorite.ReviewFavorite; import com.funeat.member.persistence.MemberRepository; +import com.funeat.member.persistence.RecipeBookmarkRepository; import com.funeat.member.persistence.RecipeFavoriteRepository; import com.funeat.member.persistence.ReviewFavoriteRepository; import com.funeat.product.domain.Category; @@ -45,6 +47,9 @@ public abstract class RepositoryTest { @Autowired protected RecipeFavoriteRepository recipeFavoriteRepository; + @Autowired + protected RecipeBookmarkRepository recipeBookmarkRepository; + @Autowired protected ReviewFavoriteRepository reviewFavoriteRepository; @@ -193,6 +198,12 @@ public abstract class RepositoryTest { recipeFavoriteRepository.save(recipeFavorite); } + protected void 복수_레시피_북마크_저장(final RecipeBookmark... recipeBookmarksToSave) { + final var recipeBookmarks = List.of(recipeBookmarksToSave); + + recipeBookmarkRepository.saveAll(recipeBookmarks); + } + protected void 복수_배너_저장(final Banner... bannerToSave) { final List banners = List.of(bannerToSave); diff --git a/src/test/java/com/funeat/fixture/RecipeFixture.java b/src/test/java/com/funeat/fixture/RecipeFixture.java index 0db57b72..c6d25371 100644 --- a/src/test/java/com/funeat/fixture/RecipeFixture.java +++ b/src/test/java/com/funeat/fixture/RecipeFixture.java @@ -1,6 +1,7 @@ package com.funeat.fixture; import com.funeat.member.domain.Member; +import com.funeat.member.domain.bookmark.RecipeBookmark; import com.funeat.member.domain.favorite.RecipeFavorite; import com.funeat.recipe.domain.Recipe; import com.funeat.recipe.domain.RecipeImage; @@ -29,6 +30,8 @@ public class RecipeFixture { public static final boolean 좋아요O = true; public static final boolean 좋아요X = false; + public static final boolean 북마크O = true; + public static final boolean 북마크X = false; public static final String 레시피_제목 = "The most delicious recipes"; public static final String 레시피_본문 = "More rice, more rice, more rice.. Done!!"; @@ -51,6 +54,10 @@ public class RecipeFixture { return new RecipeFavorite(member, recipe, favorite); } + public static RecipeBookmark 레시피_북마크_생성(final Member member, final Recipe recipe, final Boolean bookmark) { + return new RecipeBookmark(member, recipe, bookmark); + } + public static RecipeCreateRequest 레시피추가요청_생성(final String title, final List productIds, final String content) { return new RecipeCreateRequest(title, productIds, content); } diff --git a/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java b/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java index e3b476a2..22199a72 100644 --- a/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java +++ b/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java @@ -22,6 +22,7 @@ import static com.funeat.fixture.RecipeFixture.레시피이미지_생성; import static com.funeat.fixture.RecipeFixture.레시피좋아요요청_생성; import static com.funeat.fixture.RecipeFixture.레시피추가요청_생성; +import static com.funeat.fixture.RecipeFixture.북마크O; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -30,6 +31,8 @@ import com.funeat.common.ServiceTest; import com.funeat.common.dto.PageDto; import com.funeat.member.domain.Member; +import com.funeat.member.dto.MemberBookmarkRecipeDto; +import com.funeat.member.dto.MemberBookmarkRecipesResponse; import com.funeat.member.dto.MemberRecipeDto; import com.funeat.member.dto.MemberRecipesResponse; import com.funeat.member.exception.MemberException.MemberNotFoundException; @@ -788,6 +791,86 @@ class bookmarkRecipe_실패_테스트 { } } + @Nested + class findBookmarkRecipeByMember_성공_테스트 { + + @Test + void 사용자가_저장한_꿀조합을_조회한다() { + // given + final var member1 = 멤버_멤버1_생성(); + 단일_멤버_저장(member1); + + final var category = 카테고리_즉석조리_생성(); + 단일_카테고리_저장(category); + + final var product1 = 상품_삼각김밥_가격1000원_평점5점_생성(category); + final var product2 = 상품_삼각김밥_가격2000원_평점3점_생성(category); + 복수_상품_저장(product1, product2); + + final var recipe1 = 레시피_생성(member1); + final var recipe2 = 레시피_생성(member1); + 복수_꿀조합_저장(recipe1, recipe2); + + final var productRecipe1 = 레시피_안에_들어가는_상품_생성(product1, recipe1); + final var productRecipe2 = 레시피_안에_들어가는_상품_생성(product2, recipe2); + 복수_꿀조합_상품_저장(productRecipe1, productRecipe2); + + final var request = 레시피북마크요청_생성(북마크O); + recipeService.bookmarkRecipe(member1.getId(), recipe2.getId(), request); + + final var page = 페이지요청_생성(0, 10, 최신순); + + // when + final var actual = recipeService.findBookmarkRecipeByMember(member1.getId(), page); + + // then + final var expectedRecipes = List.of(recipe2); + final var expectedRecipesDtos = expectedRecipes.stream() + .map(recipe -> MemberBookmarkRecipeDto.toDto( + recipe, + Collections.emptyList(), + List.of(product2), + false)) + .toList(); + final var expectedPage = new PageDto(1L, 1L, true, true, 0L, 10L); + + 해당멤버가_저장한_꿀조합과_페이징_결과를_검증한다(actual, expectedRecipesDtos, expectedPage); + } + + @Test + void 사용자가_저장한_꿀조합이_없을때_꿀조합은_빈상태로_조회된다() { + // given + final var member1 = 멤버_멤버1_생성(); + 단일_멤버_저장(member1); + + final var page = 페이지요청_생성(0, 10, 최신순); + + // when + final var actual = recipeService.findBookmarkRecipeByMember(member1.getId(), page); + + // then + final var expectedRecipes = Collections.emptyList(); + final var expectedPage = new PageDto(0L, 0L, true, true, 0L, 10L); + + 해당멤버가_저장한_꿀조합과_페이징_결과를_검증한다(actual, expectedRecipes, expectedPage); + } + } + + @Nested + class findBookmarkRecipeByMember_실패_테스트 { + + @Test + void 존재하지_않는_멤버가_해당_멤버의_저장한_레시피를_조회하면_예외가_발생한다() { + // given + final var notExistMemberId = 99999L; + final var page = 페이지요청_생성(0, 10, 최신순); + + // when & then + assertThatThrownBy(() -> recipeService.findBookmarkRecipeByMember(notExistMemberId, page)) + .isInstanceOf(MemberNotFoundException.class); + } + } + @Nested class getTop4Recipes_성공_테스트 { @@ -1235,6 +1318,16 @@ class getCommentsOfRecipe_성공_테스트 { }); } + private void 해당멤버가_저장한_꿀조합과_페이징_결과를_검증한다(final MemberBookmarkRecipesResponse actual, + final List expectedBookmarkRecipesDtos, final PageDto expectedPage) { + assertSoftly(soft -> { + assertThat(actual.recipes()).usingRecursiveComparison() + .isEqualTo(expectedBookmarkRecipesDtos); + assertThat(actual.page()).usingRecursiveComparison() + .isEqualTo(expectedPage); + }); + } + private Category 카테고리_추가_요청(final Category category) { return categoryRepository.save(category); } diff --git a/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java b/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java index adb5a7a5..70aabf27 100644 --- a/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java +++ b/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java @@ -15,8 +15,11 @@ import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점1점_생성; import static com.funeat.fixture.ProductFixture.상품_삼각김밥_가격2000원_평점3점_생성; import static com.funeat.fixture.ProductFixture.상품_애플망고_가격3000원_평점5점_생성; +import static com.funeat.fixture.RecipeFixture.레시피_북마크_생성; import static com.funeat.fixture.RecipeFixture.레시피_생성; import static com.funeat.fixture.RecipeFixture.레시피이미지_생성; +import static com.funeat.fixture.RecipeFixture.북마크O; +import static com.funeat.fixture.RecipeFixture.북마크X; import static org.assertj.core.api.Assertions.assertThat; import com.funeat.common.RepositoryTest; @@ -347,4 +350,56 @@ class findRecipesByFavoriteCountGreaterThanEqual_성공_테스트 { .isEqualTo(expected); } } + + @Nested + class findBookmarkedRecipesByMember_성공_테스트 { + + @Test + void 특정_멤버가_저장한_모든_꿀조합을_조회한다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var recipe1 = 레시피_생성(member, 1L); + final var recipe2 = 레시피_생성(member, 10L); + final var recipe3 = 레시피_생성(member, 100L); + 복수_꿀조합_저장(recipe1, recipe2, recipe3); + + final var bookmarkRecipe1 = 레시피_북마크_생성(member, recipe1, 북마크X); + final var bookmarkRecipe2 = 레시피_북마크_생성(member, recipe2, 북마크O); + final var bookmarkRecipe3 = 레시피_북마크_생성(member, recipe3, 북마크O); + 복수_레시피_북마크_저장(bookmarkRecipe1, bookmarkRecipe2, bookmarkRecipe3); + + final var expected = List.of(recipe3, recipe2); + final var page = 페이지요청_생성(0, 10, 최신순); + + // when + final var actual = recipeRepository.findBookmarkedRecipesByMember(member, page).getContent(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + + @Test + void 특정_멤버가_저장한_꿀조합이_없으면_빈_리스트를_반환한다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var recipe1 = 레시피_생성(member, 0L); + final var recipe2 = 레시피_생성(member, 0L); + 복수_꿀조합_저장(recipe1, recipe2); + + final var expected = Collections.emptyList(); + final var page = 페이지요청_생성(0, 10, 최신순); + + // when + final var actual = recipeRepository.findBookmarkedRecipesByMember(member, page).getContent(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + } }