diff --git a/perfume-api/src/main/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapter.java b/perfume-api/src/main/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapter.java index fe1c5129..74f7ff0e 100644 --- a/perfume-api/src/main/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapter.java +++ b/perfume-api/src/main/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapter.java @@ -61,8 +61,8 @@ public List findByPage(long page, long size) { } @Override - public ReviewFeatureCount getReviewFeatureCount(Long perfumeId) { - Long totalReviews = + public ReviewFeatureCount getReviewFeatureCount(final long perfumeId) { + final Long totalReviews = jpaQueryFactory .select(reviewEntity.id.count()) .from(reviewEntity) @@ -70,53 +70,43 @@ public ReviewFeatureCount getReviewFeatureCount(Long perfumeId) { .fetchOne(); if (totalReviews == null || totalReviews == 0) { - return new ReviewFeatureCount( - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyMap(), - Collections.emptyMap(), - totalReviews); + return ReviewFeatureCount.EMPTY; } Map strengthMap = jpaQueryFactory .from(reviewEntity) - .where(reviewEntity.perfumeId.eq(perfumeId)) - .leftJoin(userEntity) - .on(reviewEntity.userId.eq(userEntity.id)) - .fetchJoin() + .where(reviewEntity.perfumeId.eq(perfumeId), reviewEntity.deletedAt.isNull()) .groupBy(reviewEntity.strength) .transform(groupBy(reviewEntity.strength).as(reviewEntity.id.count())); Map durationMap = jpaQueryFactory .from(reviewEntity) - .where(reviewEntity.perfumeId.eq(perfumeId)) + .where(reviewEntity.perfumeId.eq(perfumeId), reviewEntity.deletedAt.isNull()) .groupBy(reviewEntity.duration) .transform(groupBy(reviewEntity.duration).as(reviewEntity.id.count())); Map seasonMap = jpaQueryFactory .from(reviewEntity) - .where(reviewEntity.perfumeId.eq(perfumeId)) + .where(reviewEntity.perfumeId.eq(perfumeId), reviewEntity.deletedAt.isNull()) .groupBy(reviewEntity.season) .transform(groupBy(reviewEntity.season).as(reviewEntity.id.count())); Map dayTypeMap = jpaQueryFactory .from(reviewEntity) - .where(reviewEntity.perfumeId.eq(perfumeId)) + .where(reviewEntity.perfumeId.eq(perfumeId), reviewEntity.deletedAt.isNull()) .groupBy(reviewEntity.dayType) .transform(groupBy(reviewEntity.dayType).as(reviewEntity.id.count())); Map sexMap = jpaQueryFactory .from(reviewEntity) - .where(reviewEntity.perfumeId.eq(perfumeId)) .leftJoin(userEntity) - .on(reviewEntity.userId.eq(userEntity.id)) - .fetchJoin() + .on(reviewEntity.userId.eq(userEntity.id), userEntity.deletedAt.isNull()) + .where(reviewEntity.perfumeId.eq(perfumeId), reviewEntity.deletedAt.isNull()) .groupBy(userEntity.sex) .transform(groupBy(userEntity.sex).as(reviewEntity.id.count())); diff --git a/perfume-api/src/main/java/io/perfume/api/review/application/out/review/ReviewQueryRepository.java b/perfume-api/src/main/java/io/perfume/api/review/application/out/review/ReviewQueryRepository.java index fb7d8682..0e72aa9b 100644 --- a/perfume-api/src/main/java/io/perfume/api/review/application/out/review/ReviewQueryRepository.java +++ b/perfume-api/src/main/java/io/perfume/api/review/application/out/review/ReviewQueryRepository.java @@ -13,11 +13,11 @@ public interface ReviewQueryRepository { List findByPage(long page, long size); - ReviewFeatureCount getReviewFeatureCount(Long perfumeId); + ReviewFeatureCount getReviewFeatureCount(long perfumeId); Long findReviewCountByUserId(Long userId); boolean existsReviewById(Long reviewId); - CustomPage findByPerfumeId(final long perfumeId, final Pageable pageable); + CustomPage findByPerfumeId(long perfumeId, Pageable pageable); } diff --git a/perfume-api/src/main/java/io/perfume/api/review/domain/ReviewFeatureCount.java b/perfume-api/src/main/java/io/perfume/api/review/domain/ReviewFeatureCount.java index 4f9edcbd..74b47c38 100644 --- a/perfume-api/src/main/java/io/perfume/api/review/domain/ReviewFeatureCount.java +++ b/perfume-api/src/main/java/io/perfume/api/review/domain/ReviewFeatureCount.java @@ -13,4 +13,8 @@ public record ReviewFeatureCount( Map seasonMap, Map dayTypeMap, Map sexMap, - Long totalReviews) {} + Long totalReviews) { + + public static ReviewFeatureCount EMPTY = + new ReviewFeatureCount(Map.of(), Map.of(), Map.of(), Map.of(), Map.of(), 0L); +} diff --git a/perfume-api/src/test/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapterTest.java b/perfume-api/src/test/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapterTest.java index e23ec9d7..98ec5267 100644 --- a/perfume-api/src/test/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapterTest.java +++ b/perfume-api/src/test/java/io/perfume/api/review/adapter/out/persistence/repository/review/ReviewQueryPersistenceAdapterTest.java @@ -6,7 +6,6 @@ import io.perfume.api.configuration.TestQueryDSLConfiguration; import io.perfume.api.perfume.adapter.out.persistence.perfume.PerfumeJpaEntity; import io.perfume.api.perfume.domain.Concentration; -import io.perfume.api.review.adapter.out.persistence.repository.tag.TagMapper; import io.perfume.api.review.application.out.review.ReviewQueryRepository; import io.perfume.api.review.application.out.review.ReviewRepository; import io.perfume.api.review.domain.Review; @@ -17,7 +16,9 @@ import io.perfume.api.review.domain.type.Strength; import io.perfume.api.user.adapter.out.persistence.user.Sex; import io.perfume.api.user.adapter.out.persistence.user.UserEntity; +import io.perfume.api.user.adapter.out.persistence.user.UserMapper; import io.perfume.api.user.domain.Role; +import io.perfume.api.user.domain.User; import jakarta.persistence.EntityManager; import java.time.LocalDateTime; import java.util.Optional; @@ -36,7 +37,7 @@ @Import({ ReviewQueryPersistenceAdapter.class, ReviewMapper.class, - TagMapper.class, + UserMapper.class, TestQueryDSLConfiguration.class, ReviewPersistenceAdapter.class }) @@ -45,11 +46,12 @@ class ReviewQueryPersistenceAdapterTest { @Autowired private ReviewQueryRepository queryRepository; + @Autowired private ReviewRepository reviewRepository; @Autowired private ReviewMapper reviewMapper; - @Autowired private TagMapper tagMapper; + @Autowired private UserMapper userMapper; @Autowired private EntityManager entityManager; @@ -329,4 +331,41 @@ void testFindByPerfumeIdWithLastPage() { assertThat(result.getContent().size()).isEqualTo(5); assertThat(result.isLast()).isTrue(); } + + @Test + @DisplayName("향수에 대한 리뷰 통계를 조회한다.") + void testGetStatisticByPerfume() { + // given + final LocalDateTime now = LocalDateTime.now(); + final long perfumeId = 1L; + final User user = User.createSocialUser("test", "test@mail.com", "123qwe", now); + final UserEntity userEntity = userMapper.toUserJpaEntity(user); + entityManager.persist(userEntity); + final Review review = + Review.create( + "test", + "test description", + Strength.LIGHT, + Duration.LONG, + DayType.DAILY, + perfumeId, + userEntity.getId(), + Season.SPRING, + now); + final ReviewEntity createdReview = reviewMapper.toEntity(review); + entityManager.persist(createdReview); + entityManager.flush(); + entityManager.clear(); + + // when + final var result = queryRepository.getReviewFeatureCount(perfumeId); + + // then + assertThat(result.totalReviews()).isEqualTo(1); + assertThat(result.strengthMap()).containsEntry(Strength.LIGHT, 1L); + assertThat(result.durationMap()).containsEntry(Duration.LONG, 1L); + assertThat(result.dayTypeMap()).containsEntry(DayType.DAILY, 1L); + assertThat(result.seasonMap()).containsEntry(Season.SPRING, 1L); + assertThat(result.sexMap()).containsEntry(Sex.OTHER, 1L); + } } diff --git a/perfume-api/src/test/java/io/perfume/api/review/application/service/ReviewServiceTest.java b/perfume-api/src/test/java/io/perfume/api/review/application/service/ReviewServiceTest.java new file mode 100644 index 00000000..123629f2 --- /dev/null +++ b/perfume-api/src/test/java/io/perfume/api/review/application/service/ReviewServiceTest.java @@ -0,0 +1,135 @@ +package io.perfume.api.review.application.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.perfume.api.review.adapter.out.persistence.repository.review.ReviewEntity; +import io.perfume.api.review.application.in.dto.ReviewStatisticResult; +import io.perfume.api.review.domain.type.DayType; +import io.perfume.api.review.domain.type.Duration; +import io.perfume.api.review.domain.type.Season; +import io.perfume.api.review.domain.type.Strength; +import io.perfume.api.user.adapter.out.persistence.user.Sex; +import io.perfume.api.user.adapter.out.persistence.user.UserEntity; +import io.perfume.api.user.domain.Role; +import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@SuppressWarnings("NonAsciiCharacters") +class ReviewServiceTest { + + @Autowired private ReviewService service; + + @Autowired private EntityManager entityManager; + + @Test + void 리뷰가_존재하지_않는_경우_빈_통계_정보를_반환한다() { + // given + + // when + final ReviewStatisticResult result = service.getStatisticByPerfume(1L); + + // then + assertThat(result.strengthMap()) + .contains( + Map.entry(Strength.HEAVY, 0L), + Map.entry(Strength.LIGHT, 0L), + Map.entry(Strength.MODERATE, 0L)); + assertThat(result.durationMap()) + .contains( + Map.entry(Duration.LONG, 0L), + Map.entry(Duration.MEDIUM, 0L), + Map.entry(Duration.SHORT, 0L)); + assertThat(result.dayTypeMap()) + .contains( + Map.entry(DayType.DAILY, 0L), + Map.entry(DayType.SPECIAL, 0L), + Map.entry(DayType.REST, 0L), + Map.entry(DayType.TRAVEL, 0L)); + assertThat(result.seasonMap()) + .contains( + Map.entry(Season.SPRING, 0L), + Map.entry(Season.SUMMER, 0L), + Map.entry(Season.FALL, 0L), + Map.entry(Season.WINTER, 0L)); + assertThat(result.sexMap()) + .contains(Map.entry(Sex.FEMALE, 0L), Map.entry(Sex.MALE, 0L), Map.entry(Sex.OTHER, 0L)); + } + + @Test + void 리뷰_통계_정보를_반환한다() { + // given + final LocalDateTime now = LocalDateTime.now(); + final long perfumeId = 1L; + final UserEntity userEntity = + new UserEntity( + null, + "test", + "test@mail.com", + "test", + Role.USER, + "test", + LocalDate.now(), + Sex.FEMALE, + false, + false, + null, + now, + now, + null); + entityManager.persist(userEntity); + final ReviewEntity reviewEntity = + new ReviewEntity( + null, + "test", + "test description", + Strength.LIGHT, + Duration.LONG, + DayType.DAILY, + perfumeId, + userEntity.getId(), + Season.SPRING, + now, + now, + null); + entityManager.persist(reviewEntity); + entityManager.flush(); + entityManager.clear(); + + // when + final ReviewStatisticResult result = service.getStatisticByPerfume(1L); + + // then + assertThat(result.strengthMap()) + .contains( + Map.entry(Strength.HEAVY, 0L), + Map.entry(Strength.LIGHT, 100L), + Map.entry(Strength.MODERATE, 0L)); + assertThat(result.durationMap()) + .contains( + Map.entry(Duration.LONG, 100L), + Map.entry(Duration.MEDIUM, 0L), + Map.entry(Duration.SHORT, 0L)); + assertThat(result.dayTypeMap()) + .contains( + Map.entry(DayType.DAILY, 100L), + Map.entry(DayType.SPECIAL, 0L), + Map.entry(DayType.REST, 0L), + Map.entry(DayType.TRAVEL, 0L)); + assertThat(result.seasonMap()) + .contains( + Map.entry(Season.SPRING, 100L), + Map.entry(Season.SUMMER, 0L), + Map.entry(Season.FALL, 0L), + Map.entry(Season.WINTER, 0L)); + assertThat(result.sexMap()) + .contains(Map.entry(Sex.FEMALE, 100L), Map.entry(Sex.MALE, 0L), Map.entry(Sex.OTHER, 0L)); + } +} diff --git a/perfume-api/src/test/java/io/perfume/api/review/stub/StubReviewRepository.java b/perfume-api/src/test/java/io/perfume/api/review/stub/StubReviewRepository.java index 6aff62f6..5ea1e2dd 100644 --- a/perfume-api/src/test/java/io/perfume/api/review/stub/StubReviewRepository.java +++ b/perfume-api/src/test/java/io/perfume/api/review/stub/StubReviewRepository.java @@ -30,11 +30,6 @@ public List findByPage(long page, long size) { return null; } - @Override - public ReviewFeatureCount getReviewFeatureCount(Long perfumeId) { - return null; - } - public Long findReviewCountByUserId(Long userId) { return (long) reviews.size(); } @@ -49,6 +44,11 @@ public CustomPage findByPerfumeId(long perfumeId, Pageable pageable) { return null; } + @Override + public ReviewFeatureCount getReviewFeatureCount(long perfumeId) { + return null; + } + public Review addReview(Review review) { reviews.add(review); return review;