From 0587bf9cab1dcc179b9dd0e8133cc16ba601a5a0 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Fri, 13 Oct 2023 07:05:55 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EA=BF=80=EC=A1=B0=ED=95=A9=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EC=A0=90=EC=88=98=20=EA=B3=84=EC=82=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/funeat/recipe/domain/Recipe.java | 21 ++++++++++- .../com/funeat/recipe/domain/RecipeTest.java | 37 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java diff --git a/backend/src/main/java/com/funeat/recipe/domain/Recipe.java b/backend/src/main/java/com/funeat/recipe/domain/Recipe.java index dcb607148..601067e4b 100644 --- a/backend/src/main/java/com/funeat/recipe/domain/Recipe.java +++ b/backend/src/main/java/com/funeat/recipe/domain/Recipe.java @@ -1,7 +1,7 @@ package com.funeat.recipe.domain; import com.funeat.member.domain.Member; -import java.time.LocalDateTime; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -10,10 +10,14 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; @Entity public class Recipe { + private static final double RANKING_GRAVITY = 0.1; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -48,6 +52,21 @@ public Recipe(final String title, final String content, final Member member, this.favoriteCount = favoriteCount; } + public Recipe(final String title, final String content, final Member member, final Long favoriteCount, + final LocalDateTime createdAt) { + this.title = title; + this.content = content; + this.member = member; + this.favoriteCount = favoriteCount; + this.createdAt = createdAt; + } + + public Double calculateRankingScore() { + final long age = ChronoUnit.DAYS.between(createdAt, LocalDateTime.now()); + final double denominator = Math.pow(age + 1.0, RANKING_GRAVITY); + return favoriteCount / denominator; + } + public void addFavoriteCount() { this.favoriteCount++; } diff --git a/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java b/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java new file mode 100644 index 000000000..01d5a73ac --- /dev/null +++ b/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java @@ -0,0 +1,37 @@ +package com.funeat.recipe.domain; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; +import static com.funeat.fixture.RecipeFixture.레시피_생성; +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class RecipeTest { + + @Nested + class calculateRankingScore_성공_테스트 { + + @Test + void 꿀조합_좋아요_수와_꿀조합_생성_시간으로_해당_꿀조합의_랭킹_점수를_구할_수_있다() { + // given + final var member = 멤버_멤버1_생성(); + final var favoriteCount = 4L; + final var recipe = 레시피_생성(member, favoriteCount, LocalDateTime.now().minusDays(1L)); + + final var expected = favoriteCount / Math.pow(2.0, 0.1); + + // when + final var actual = recipe.calculateRankingScore(); + + // then + assertThat(actual).isEqualTo(expected); + } + } +} From 48e5cccc0fc3c8be8711a82b5b71f614db47ed84 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Fri, 13 Oct 2023 07:06:51 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=EA=BF=80=EC=A1=B0=ED=95=A9=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recipe/application/RecipeService.java | 27 ++++++++++--------- .../recipe/persistence/RecipeRepository.java | 11 +++++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java index d67fd7b79..b899c77fe 100644 --- a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java +++ b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java @@ -1,10 +1,5 @@ package com.funeat.recipe.application; -import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_FAVORITE; -import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; -import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; -import static com.funeat.recipe.exception.RecipeErrorCode.RECIPE_NOT_FOUND; - import com.funeat.common.ImageUploader; import com.funeat.common.dto.PageDto; import com.funeat.member.domain.Member; @@ -36,23 +31,29 @@ import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; import com.funeat.recipe.persistence.RecipeImageRepository; import com.funeat.recipe.persistence.RecipeRepository; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_FAVORITE; +import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; +import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; +import static com.funeat.recipe.exception.RecipeErrorCode.RECIPE_NOT_FOUND; + @Service @Transactional(readOnly = true) public class RecipeService { - private static final int THREE = 3; - private static final int TOP = 0; + private static final long RANKING_MINIMUM_FAVORITE_COUNT = 1L; + private static final int RANKING_SIZE = 3; private final MemberRepository memberRepository; private final ProductRepository productRepository; @@ -190,9 +191,11 @@ public SearchRecipeResultsResponse getSearchResults(final String query, final Pa } public RankingRecipesResponse getTop3Recipes() { - final List<Recipe> recipes = recipeRepository.findRecipesByOrderByFavoriteCountDesc(PageRequest.of(TOP, THREE)); + final List<Recipe> recipes = recipeRepository.findRecipesByFavoriteCountGreaterThanEqual(RANKING_MINIMUM_FAVORITE_COUNT); final List<RankingRecipeDto> dtos = recipes.stream() + .sorted(Comparator.comparing(Recipe::calculateRankingScore).reversed()) + .limit(RANKING_SIZE) .map(recipe -> { final List<RecipeImage> findRecipeImages = recipeImageRepository.findByRecipe(recipe); final RecipeAuthorDto author = RecipeAuthorDto.toDto(recipe.getMember()); diff --git a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java index 4d1a3a306..5af65786d 100644 --- a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java +++ b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java @@ -1,12 +1,8 @@ package com.funeat.recipe.persistence; -import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; - import com.funeat.member.domain.Member; import com.funeat.product.domain.Product; import com.funeat.recipe.domain.Recipe; -import java.util.List; -import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,6 +10,11 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; +import java.util.Optional; + +import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; + public interface RecipeRepository extends JpaRepository<Recipe, Long> { @Query("SELECT r FROM Recipe r " @@ -37,4 +38,6 @@ public interface RecipeRepository extends JpaRepository<Recipe, Long> { @Lock(PESSIMISTIC_WRITE) @Query("SELECT r FROM Recipe r WHERE r.id=:id") Optional<Recipe> findByIdForUpdate(final Long id); + + List<Recipe> findRecipesByFavoriteCountGreaterThanEqual(final Long favoriteCount); } From 0cb4e5c7fc2593c0692406ef43dc45c0f30a1580 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Fri, 13 Oct 2023 07:07:17 +0900 Subject: [PATCH 3/7] =?UTF-8?q?test:=20=EA=BF=80=EC=A1=B0=ED=95=A9=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/fixture/RecipeFixture.java | 7 + .../recipe/application/RecipeServiceTest.java | 179 +++++++++++++++--- .../persistence/RecipeRepositoryTest.java | 49 +++-- 3 files changed, 198 insertions(+), 37 deletions(-) diff --git a/backend/src/test/java/com/funeat/fixture/RecipeFixture.java b/backend/src/test/java/com/funeat/fixture/RecipeFixture.java index 2050727f3..6f905d84d 100644 --- a/backend/src/test/java/com/funeat/fixture/RecipeFixture.java +++ b/backend/src/test/java/com/funeat/fixture/RecipeFixture.java @@ -6,6 +6,8 @@ import com.funeat.recipe.domain.RecipeImage; import com.funeat.recipe.dto.RecipeCreateRequest; import com.funeat.recipe.dto.RecipeFavoriteRequest; + +import java.time.LocalDateTime; import java.util.List; @SuppressWarnings("NonAsciiCharacters") @@ -33,6 +35,11 @@ public class RecipeFixture { return new Recipe("The most delicious recipes", "More rice, more rice, more rice.. Done!!", member, favoriteCount); } + public static Recipe 레시피_생성(final Member member, final Long favoriteCount, final LocalDateTime createdAt) { + return new Recipe("The most delicious recipes", "More rice, more rice, more rice.. Done!!", + member, favoriteCount, createdAt); + } + public static RecipeFavorite 레시피_좋아요_생성(final Member member, final Recipe recipe, final Boolean favorite) { return new RecipeFavorite(member, recipe, favorite); } diff --git a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java index fcd4e2f40..19e95460c 100644 --- a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java +++ b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java @@ -1,5 +1,32 @@ package com.funeat.recipe.application; +import com.funeat.common.ServiceTest; +import com.funeat.common.dto.PageDto; +import com.funeat.member.domain.Member; +import com.funeat.member.dto.MemberRecipeDto; +import com.funeat.member.dto.MemberRecipeProductDto; +import com.funeat.member.dto.MemberRecipesResponse; +import com.funeat.member.exception.MemberException.MemberNotFoundException; +import com.funeat.product.domain.Category; +import com.funeat.product.domain.CategoryType; +import com.funeat.product.domain.Product; +import com.funeat.product.exception.ProductException.ProductNotFoundException; +import com.funeat.recipe.dto.RankingRecipeDto; +import com.funeat.recipe.dto.RankingRecipesResponse; +import com.funeat.recipe.dto.RecipeAuthorDto; +import com.funeat.recipe.dto.RecipeCreateRequest; +import com.funeat.recipe.dto.RecipeDetailResponse; +import com.funeat.recipe.dto.RecipeDto; +import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import static com.funeat.fixture.CategoryFixture.카테고리_간편식사_생성; import static com.funeat.fixture.CategoryFixture.카테고리_즉석조리_생성; import static com.funeat.fixture.ImageFixture.여러_이미지_생성; @@ -24,29 +51,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import com.funeat.common.ServiceTest; -import com.funeat.common.dto.PageDto; -import com.funeat.member.domain.Member; -import com.funeat.member.dto.MemberRecipeDto; -import com.funeat.member.dto.MemberRecipeProductDto; -import com.funeat.member.dto.MemberRecipesResponse; -import com.funeat.member.exception.MemberException.MemberNotFoundException; -import com.funeat.product.domain.Category; -import com.funeat.product.domain.CategoryType; -import com.funeat.product.domain.Product; -import com.funeat.product.exception.ProductException.ProductNotFoundException; -import com.funeat.recipe.dto.RecipeCreateRequest; -import com.funeat.recipe.dto.RecipeDetailResponse; -import com.funeat.recipe.dto.RecipeDto; -import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - @SuppressWarnings("NonAsciiCharacters") class RecipeServiceTest extends ServiceTest { @@ -545,6 +549,135 @@ class likeRecipe_실패_테스트 { } } + @Nested + class getTop3Recipes_성공_테스트 { + + @Nested + class 꿀조합_개수에_대한_테스트 { + + @Test + void 전체_꿀조합이_하나도_없어도_반환값은_있어야한다() { + // given + final var expected = RankingRecipesResponse.toResponse(Collections.emptyList()); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + + @Test + void 전체_꿀조합이_1개_이상_3개_미만이라도_꿀조합이_나와야한다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var now = LocalDateTime.now(); + final var recipe1 = 레시피_생성(member, 2L, now.minusDays(1L)); + final var recipe2 = 레시피_생성(member, 2L, now); + 복수_꿀조합_저장(recipe1, recipe2); + + final var author = RecipeAuthorDto.toDto(member); + final var rankingRecipeDto1 = RankingRecipeDto.toDto(recipe1, Collections.emptyList(), author); + final var rankingRecipeDto2 = RankingRecipeDto.toDto(recipe2, Collections.emptyList(), author); + final var rankingRecipesDtos = List.of(rankingRecipeDto2, rankingRecipeDto1); + final var expected = RankingRecipesResponse.toResponse(rankingRecipesDtos); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + + @Test + void 전체_꿀조합_중_랭킹이_높은_상위_3개_꿀조합을_구할_수_있다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var now = LocalDateTime.now(); + final var recipe1 = 레시피_생성(member, 4L, now.minusDays(10L)); + final var recipe2 = 레시피_생성(member, 6L, now.minusDays(10L)); + final var recipe3 = 레시피_생성(member, 5L, now); + final var recipe4 = 레시피_생성(member, 6L, now); + 복수_꿀조합_저장(recipe1, recipe2, recipe3, recipe4); + + final var author = RecipeAuthorDto.toDto(member); + final var rankingRecipeDto1 = RankingRecipeDto.toDto(recipe1, Collections.emptyList(), author); + final var rankingRecipeDto2 = RankingRecipeDto.toDto(recipe2, Collections.emptyList(), author); + final var rankingRecipeDto3 = RankingRecipeDto.toDto(recipe3, Collections.emptyList(), author); + final var rankingRecipeDto4 = RankingRecipeDto.toDto(recipe4, Collections.emptyList(), author); + final var rankingRecipesDtos = List.of(rankingRecipeDto4, rankingRecipeDto3, rankingRecipeDto2); + final var expected = RankingRecipesResponse.toResponse(rankingRecipesDtos); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + } + + @Nested + class 꿀조합_랭킹_점수에_대한_테스트 { + + @Test + void 꿀조합_좋아요_수가_같으면_최근_생성된_꿀조합의_랭킹을_더_높게_반환한다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var now = LocalDateTime.now(); + final var recipe1 = 레시피_생성(member, 10L, now.minusDays(9L)); + final var recipe2 = 레시피_생성(member, 10L, now.minusDays(4L)); + 복수_꿀조합_저장(recipe1, recipe2); + + final var author = RecipeAuthorDto.toDto(member); + final var rankingRecipeDto1 = RankingRecipeDto.toDto(recipe1, Collections.emptyList(), author); + final var rankingRecipeDto2 = RankingRecipeDto.toDto(recipe2, Collections.emptyList(), author); + final var rankingRecipesDtos = List.of(rankingRecipeDto2, rankingRecipeDto1); + final var expected = RankingRecipesResponse.toResponse(rankingRecipesDtos); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + + @Test + void 꿀조합_생성_일자가_같으면_좋아요_수가_많은_꿀조합의_랭킹을_더_높게_반환한다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var now = LocalDateTime.now(); + final var recipe1 = 레시피_생성(member, 2L, now.minusDays(1L)); + final var recipe2 = 레시피_생성(member, 4L, now.minusDays(1L)); + 복수_꿀조합_저장(recipe1, recipe2); + + final var author = RecipeAuthorDto.toDto(member); + final var rankingRecipeDto1 = RankingRecipeDto.toDto(recipe1, Collections.emptyList(), author); + final var rankingRecipeDto2 = RankingRecipeDto.toDto(recipe2, Collections.emptyList(), author); + final var rankingRecipesDtos = List.of(rankingRecipeDto2, rankingRecipeDto1); + final var expected = RankingRecipesResponse.toResponse(rankingRecipesDtos); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + } + } + private <T> void 해당멤버의_꿀조합과_페이징_결과를_검증한다(final MemberRecipesResponse actual, final List<T> expectedRecipesDtos, final PageDto expectedPage) { assertSoftly(soft -> { diff --git a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java index c7130d2ce..f607235f7 100644 --- a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java +++ b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java @@ -1,5 +1,12 @@ package com.funeat.recipe.persistence; +import com.funeat.common.RepositoryTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + import static com.funeat.fixture.CategoryFixture.카테고리_간편식사_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; @@ -20,11 +27,6 @@ import static com.funeat.fixture.RecipeFixture.레시피이미지_생성; import static org.assertj.core.api.Assertions.assertThat; -import com.funeat.common.RepositoryTest; -import java.util.List; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - @SuppressWarnings("NonAsciiCharacters") class RecipeRepositoryTest extends RepositoryTest { @@ -257,25 +259,44 @@ class findRecipesByProduct_성공_테스트 { } @Nested - class findRecipesByOrderByFavoriteCountDesc_성공_테스트 { + class findRecipesByFavoriteCountGreaterThanEqual_성공_테스트 { @Test - void 좋아요순으로_상위_3개의_레시피들을_조회한다() { + void 특정_좋아요_수_이상인_모든_꿀조합들을_조회한다() { // given final var member = 멤버_멤버1_생성(); 단일_멤버_저장(member); - final var recipe1 = 레시피_생성(member, 1L); - final var recipe2 = 레시피_생성(member, 2L); - final var recipe3 = 레시피_생성(member, 3L); - final var recipe4 = 레시피_생성(member, 4L); + final var recipe1 = 레시피_생성(member, 0L); + final var recipe2 = 레시피_생성(member, 1L); + final var recipe3 = 레시피_생성(member, 10L); + final var recipe4 = 레시피_생성(member, 100L); 복수_꿀조합_저장(recipe1, recipe2, recipe3, recipe4); - final var page = 페이지요청_기본_생성(0, 3); - final var expected = List.of(recipe4, recipe3, recipe2); + final var expected = List.of(recipe2, recipe3, recipe4); + + // when + final var actual = recipeRepository.findRecipesByFavoriteCountGreaterThanEqual(1L); + + // 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(); // when - final var actual = recipeRepository.findRecipesByOrderByFavoriteCountDesc(page); + final var actual = recipeRepository.findRecipesByFavoriteCountGreaterThanEqual(1L); // then assertThat(actual).usingRecursiveComparison() From 1d6de1fc7b1d479a469753e3270ad11928ce96fa Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Mon, 16 Oct 2023 13:41:26 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20import=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/product/domain/Category.java | 7 ++- .../recipe/application/RecipeService.java | 19 ++++--- .../java/com/funeat/recipe/domain/Recipe.java | 5 +- .../recipe/persistence/RecipeRepository.java | 9 ++-- .../java/com/funeat/review/domain/Review.java | 1 - .../presentation/ReviewApiController.java | 1 - .../recipe/application/RecipeServiceTest.java | 53 +++++++++---------- .../persistence/RecipeRepositoryTest.java | 13 +++-- 8 files changed, 53 insertions(+), 55 deletions(-) diff --git a/backend/src/main/java/com/funeat/product/domain/Category.java b/backend/src/main/java/com/funeat/product/domain/Category.java index 5d6c62a08..7702a087e 100644 --- a/backend/src/main/java/com/funeat/product/domain/Category.java +++ b/backend/src/main/java/com/funeat/product/domain/Category.java @@ -1,6 +1,11 @@ package com.funeat.product.domain; -import javax.persistence.*; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; @Entity public class Category { diff --git a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java index b899c77fe..1ff6875b6 100644 --- a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java +++ b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java @@ -1,5 +1,10 @@ package com.funeat.recipe.application; +import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_FAVORITE; +import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; +import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; +import static com.funeat.recipe.exception.RecipeErrorCode.RECIPE_NOT_FOUND; + import com.funeat.common.ImageUploader; import com.funeat.common.dto.PageDto; import com.funeat.member.domain.Member; @@ -31,6 +36,10 @@ import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; import com.funeat.recipe.persistence.RecipeImageRepository; import com.funeat.recipe.persistence.RecipeRepository; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -38,16 +47,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static com.funeat.member.exception.MemberErrorCode.MEMBER_DUPLICATE_FAVORITE; -import static com.funeat.member.exception.MemberErrorCode.MEMBER_NOT_FOUND; -import static com.funeat.product.exception.ProductErrorCode.PRODUCT_NOT_FOUND; -import static com.funeat.recipe.exception.RecipeErrorCode.RECIPE_NOT_FOUND; - @Service @Transactional(readOnly = true) public class RecipeService { diff --git a/backend/src/main/java/com/funeat/recipe/domain/Recipe.java b/backend/src/main/java/com/funeat/recipe/domain/Recipe.java index 601067e4b..5ffb0438b 100644 --- a/backend/src/main/java/com/funeat/recipe/domain/Recipe.java +++ b/backend/src/main/java/com/funeat/recipe/domain/Recipe.java @@ -1,7 +1,8 @@ package com.funeat.recipe.domain; import com.funeat.member.domain.Member; - +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -10,8 +11,6 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; @Entity public class Recipe { diff --git a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java index 5af65786d..bec26f87c 100644 --- a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java +++ b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java @@ -1,8 +1,12 @@ package com.funeat.recipe.persistence; +import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; + import com.funeat.member.domain.Member; import com.funeat.product.domain.Product; import com.funeat.recipe.domain.Recipe; +import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,11 +14,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; - -import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; - public interface RecipeRepository extends JpaRepository<Recipe, Long> { @Query("SELECT r FROM Recipe r " diff --git a/backend/src/main/java/com/funeat/review/domain/Review.java b/backend/src/main/java/com/funeat/review/domain/Review.java index 3545371e3..461981dd3 100644 --- a/backend/src/main/java/com/funeat/review/domain/Review.java +++ b/backend/src/main/java/com/funeat/review/domain/Review.java @@ -6,7 +6,6 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; diff --git a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java index 57bf20359..c88f38256 100644 --- a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java +++ b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java @@ -10,7 +10,6 @@ import com.funeat.review.dto.ReviewFavoriteRequest; import com.funeat.review.dto.SortingReviewsResponse; import java.net.URI; -import java.util.Objects; import java.util.Optional; import javax.validation.Valid; import org.springframework.data.domain.Pageable; diff --git a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java index 19e95460c..e078e22da 100644 --- a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java +++ b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java @@ -1,32 +1,5 @@ package com.funeat.recipe.application; -import com.funeat.common.ServiceTest; -import com.funeat.common.dto.PageDto; -import com.funeat.member.domain.Member; -import com.funeat.member.dto.MemberRecipeDto; -import com.funeat.member.dto.MemberRecipeProductDto; -import com.funeat.member.dto.MemberRecipesResponse; -import com.funeat.member.exception.MemberException.MemberNotFoundException; -import com.funeat.product.domain.Category; -import com.funeat.product.domain.CategoryType; -import com.funeat.product.domain.Product; -import com.funeat.product.exception.ProductException.ProductNotFoundException; -import com.funeat.recipe.dto.RankingRecipeDto; -import com.funeat.recipe.dto.RankingRecipesResponse; -import com.funeat.recipe.dto.RecipeAuthorDto; -import com.funeat.recipe.dto.RecipeCreateRequest; -import com.funeat.recipe.dto.RecipeDetailResponse; -import com.funeat.recipe.dto.RecipeDto; -import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import static com.funeat.fixture.CategoryFixture.카테고리_간편식사_생성; import static com.funeat.fixture.CategoryFixture.카테고리_즉석조리_생성; import static com.funeat.fixture.ImageFixture.여러_이미지_생성; @@ -51,6 +24,32 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import com.funeat.common.ServiceTest; +import com.funeat.common.dto.PageDto; +import com.funeat.member.domain.Member; +import com.funeat.member.dto.MemberRecipeDto; +import com.funeat.member.dto.MemberRecipeProductDto; +import com.funeat.member.dto.MemberRecipesResponse; +import com.funeat.member.exception.MemberException.MemberNotFoundException; +import com.funeat.product.domain.Category; +import com.funeat.product.domain.CategoryType; +import com.funeat.product.domain.Product; +import com.funeat.product.exception.ProductException.ProductNotFoundException; +import com.funeat.recipe.dto.RankingRecipeDto; +import com.funeat.recipe.dto.RankingRecipesResponse; +import com.funeat.recipe.dto.RecipeAuthorDto; +import com.funeat.recipe.dto.RecipeCreateRequest; +import com.funeat.recipe.dto.RecipeDetailResponse; +import com.funeat.recipe.dto.RecipeDto; +import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + @SuppressWarnings("NonAsciiCharacters") class RecipeServiceTest extends ServiceTest { diff --git a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java index f607235f7..78620d519 100644 --- a/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java +++ b/backend/src/test/java/com/funeat/recipe/persistence/RecipeRepositoryTest.java @@ -1,12 +1,5 @@ package com.funeat.recipe.persistence; -import com.funeat.common.RepositoryTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Collections; -import java.util.List; - import static com.funeat.fixture.CategoryFixture.카테고리_간편식사_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; import static com.funeat.fixture.MemberFixture.멤버_멤버2_생성; @@ -27,6 +20,12 @@ import static com.funeat.fixture.RecipeFixture.레시피이미지_생성; import static org.assertj.core.api.Assertions.assertThat; +import com.funeat.common.RepositoryTest; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + @SuppressWarnings("NonAsciiCharacters") class RecipeRepositoryTest extends RepositoryTest { From 4f55c2bd51ee87d8185627a2ccacf87e014f31dc Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Mon, 16 Oct 2023 13:49:23 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=EC=83=81=ED=99=A9=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EA=BF=80=EC=A1=B0=ED=95=A9=20=EB=9E=AD?= =?UTF-8?q?=ED=82=B9=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recipe/application/RecipeServiceTest.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java index e078e22da..20eb6d9d2 100644 --- a/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java +++ b/backend/src/test/java/com/funeat/recipe/application/RecipeServiceTest.java @@ -568,7 +568,30 @@ class 꿀조합_개수에_대한_테스트 { } @Test - void 전체_꿀조합이_1개_이상_3개_미만이라도_꿀조합이_나와야한다() { + void 랭킹_조건에_부합하는_꿀조합이_1개면_꿀조합이_1개_반환된다() { + // given + final var member = 멤버_멤버1_생성(); + 단일_멤버_저장(member); + + final var now = LocalDateTime.now(); + final var recipe = 레시피_생성(member, 2L, now); + 단일_꿀조합_저장(recipe); + + final var author = RecipeAuthorDto.toDto(member); + final var rankingRecipeDto = RankingRecipeDto.toDto(recipe, Collections.emptyList(), author); + final var rankingRecipesDtos = Collections.singletonList(rankingRecipeDto); + final var expected = RankingRecipesResponse.toResponse(rankingRecipesDtos); + + // when + final var actual = recipeService.getTop3Recipes(); + + // then + assertThat(actual).usingRecursiveComparison() + .isEqualTo(expected); + } + + @Test + void 랭킹_조건에_부합하는_꿀조합이_2개면_꿀조합이_2개_반환된다() { // given final var member = 멤버_멤버1_생성(); 단일_멤버_저장(member); From a641dff01ea9b3ff8e9a8c9032fdb3e13423a379 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Mon, 16 Oct 2023 14:47:06 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20Objects=20import=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/com/funeat/review/domain/Review.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/funeat/review/domain/Review.java b/backend/src/main/java/com/funeat/review/domain/Review.java index 461981dd3..1bd13e699 100644 --- a/backend/src/main/java/com/funeat/review/domain/Review.java +++ b/backend/src/main/java/com/funeat/review/domain/Review.java @@ -5,6 +5,7 @@ import com.funeat.product.domain.Product; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Objects; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; From 474d6ee48bd386751f4d764d99f5c89fd7c27f57 Mon Sep 17 00:00:00 2001 From: Go-Jaecheol <gojaech@naver.com> Date: Wed, 18 Oct 2023 13:03:18 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/funeat/recipe/application/RecipeService.java | 5 ++--- .../funeat/recipe/persistence/RecipeRepository.java | 2 -- .../main/java/com/funeat/review/domain/Review.java | 2 +- .../review/presentation/ReviewApiController.java | 3 --- .../test/java/com/funeat/fixture/RecipeFixture.java | 1 - .../java/com/funeat/recipe/domain/RecipeTest.java | 11 +++++------ 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java index 898a0601d..19545b20b 100644 --- a/backend/src/main/java/com/funeat/recipe/application/RecipeService.java +++ b/backend/src/main/java/com/funeat/recipe/application/RecipeService.java @@ -43,13 +43,14 @@ import com.funeat.recipe.exception.RecipeException.RecipeNotFoundException; import com.funeat.recipe.persistence.RecipeImageRepository; import com.funeat.recipe.persistence.RecipeRepository; -import java.util.Comparator; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; @@ -63,8 +64,6 @@ public class RecipeService { private static final long RANKING_MINIMUM_FAVORITE_COUNT = 1L; private static final int RANKING_SIZE = 3; - private static final int THREE = 3; - private static final int TOP = 0; private static final int RECIPE_COMMENT_PAGE_SIZE = 10; private static final int DEFAULT_CURSOR_PAGINATION_SIZE = 11; diff --git a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java index bec26f87c..ce5ef3c31 100644 --- a/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java +++ b/backend/src/main/java/com/funeat/recipe/persistence/RecipeRepository.java @@ -32,8 +32,6 @@ public interface RecipeRepository extends JpaRepository<Recipe, Long> { @Query("SELECT r FROM Recipe r LEFT JOIN ProductRecipe pr ON pr.product = :product WHERE pr.recipe.id = r.id") Page<Recipe> findRecipesByProduct(final Product product, final Pageable pageable); - List<Recipe> findRecipesByOrderByFavoriteCountDesc(final Pageable pageable); - @Lock(PESSIMISTIC_WRITE) @Query("SELECT r FROM Recipe r WHERE r.id=:id") Optional<Recipe> findByIdForUpdate(final Long id); diff --git a/backend/src/main/java/com/funeat/review/domain/Review.java b/backend/src/main/java/com/funeat/review/domain/Review.java index 3b4bf7095..9b1a458d3 100644 --- a/backend/src/main/java/com/funeat/review/domain/Review.java +++ b/backend/src/main/java/com/funeat/review/domain/Review.java @@ -6,8 +6,8 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Objects; import java.util.List; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; diff --git a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java index 5641b6f42..0249b6967 100644 --- a/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java +++ b/backend/src/main/java/com/funeat/review/presentation/ReviewApiController.java @@ -13,11 +13,8 @@ import java.net.URI; import java.util.Optional; import javax.validation.Valid; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; diff --git a/backend/src/test/java/com/funeat/fixture/RecipeFixture.java b/backend/src/test/java/com/funeat/fixture/RecipeFixture.java index 6f905d84d..2d3bb3deb 100644 --- a/backend/src/test/java/com/funeat/fixture/RecipeFixture.java +++ b/backend/src/test/java/com/funeat/fixture/RecipeFixture.java @@ -6,7 +6,6 @@ import com.funeat.recipe.domain.RecipeImage; import com.funeat.recipe.dto.RecipeCreateRequest; import com.funeat.recipe.dto.RecipeFavoriteRequest; - import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java b/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java index 01d5a73ac..7a0d28030 100644 --- a/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java +++ b/backend/src/test/java/com/funeat/recipe/domain/RecipeTest.java @@ -1,16 +1,15 @@ package com.funeat.recipe.domain; +import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; +import static com.funeat.fixture.RecipeFixture.레시피_생성; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; - -import static com.funeat.fixture.MemberFixture.멤버_멤버1_생성; -import static com.funeat.fixture.RecipeFixture.레시피_생성; -import static org.assertj.core.api.Assertions.assertThat; - @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class RecipeTest {