diff --git a/backend/src/main/java/com/woowacourse/f12/application/member/MemberService.java b/backend/src/main/java/com/woowacourse/f12/application/member/MemberService.java index 79bed39dc..712ca7581 100644 --- a/backend/src/main/java/com/woowacourse/f12/application/member/MemberService.java +++ b/backend/src/main/java/com/woowacourse/f12/application/member/MemberService.java @@ -139,20 +139,19 @@ private List extractMemberIds(final List members) { @Transactional public void follow(final Long followerId, final Long followingId) { - final Member targetMember = memberRepository.findById(followingId) - .orElseThrow(MemberNotFoundException::new); - validateFollowingMembersExist(followerId); + validateMemberExists(followerId); + validateMemberExists(followingId); validateNotFollowing(followerId, followingId); final Following following = Following.builder() .followerId(followerId) .followingId(followingId) .build(); followingRepository.save(following); - targetMember.increaseFollowerCount(); + memberRepository.increaseFollowerCount(followingId); } - private void validateFollowingMembersExist(final Long followerId) { - if (!memberRepository.existsById(followerId)) { + private void validateMemberExists(final Long memberId) { + if (!memberRepository.existsById(memberId)) { throw new MemberNotFoundException(); } } @@ -165,12 +164,11 @@ private void validateNotFollowing(final Long followerId, final Long followingId) @Transactional public void unfollow(final Long followerId, final Long followingId) { - final Member targetMember = memberRepository.findById(followingId) - .orElseThrow(MemberNotFoundException::new); - validateFollowingMembersExist(followerId); + validateMemberExists(followerId); + validateMemberExists(followingId); final Following following = findFollowingRelation(followerId, followingId); followingRepository.delete(following); - targetMember.decreaseFollowerCount(); + memberRepository.decreaseFollowerCount(followingId); } private Following findFollowingRelation(final Long followerId, final Long followingId) { diff --git a/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java b/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java index 46de62cf6..a3307577d 100644 --- a/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java +++ b/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java @@ -52,6 +52,7 @@ public Long saveReviewAndInventoryProduct(final Long productId, final Long membe .orElseThrow(ProductNotFoundException::new); final Long reviewId = saveReview(reviewRequest, member, product); saveInventoryProduct(member, product); + productRepository.updateProductStatisticsForReviewInsert(product.getId(), reviewRequest.getRating()); return reviewId; } @@ -64,7 +65,6 @@ private void validateRegisterCompleted(final Member member) { private Long saveReview(final ReviewRequest reviewRequest, final Member member, final Product product) { validateNotWritten(member, product); final Review review = reviewRequest.toReview(product, member); - review.reflectToProductWhenWritten(); return reviewRepository.save(review) .getId(); } @@ -111,18 +111,20 @@ public ReviewWithAuthorAndProductPageResponse findPage(final Pageable pageable) public void update(final Long reviewId, final Long memberId, final ReviewRequest updateRequest) { final Review target = findTarget(reviewId, memberId); final Review updateReview = updateRequest.toReview(target.getProduct(), target.getMember()); + int ratingGap = updateReview.getRating() - target.getRating(); target.update(updateReview); + productRepository.updateProductStatisticsForReviewUpdate(target.getProduct().getId(), ratingGap); } @Transactional public void delete(final Long reviewId, final Long memberId) { final Review review = findTarget(reviewId, memberId); - review.reflectToProductBeforeDelete(); - reviewRepository.delete(review); final InventoryProduct inventoryProduct = inventoryProductRepository.findWithProductByMemberAndProduct( review.getMember(), review.getProduct()) .orElseThrow(InventoryProductNotFoundException::new); inventoryProductRepository.delete(inventoryProduct); + reviewRepository.delete(review); + productRepository.updateProductStatisticsForReviewDelete(review.getProduct().getId(), review.getRating()); } private Review findTarget(final Long reviewId, final Long memberId) { diff --git a/backend/src/main/java/com/woowacourse/f12/domain/member/Member.java b/backend/src/main/java/com/woowacourse/f12/domain/member/Member.java index 16bb434e5..e581aeafa 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/member/Member.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/member/Member.java @@ -2,7 +2,6 @@ import com.woowacourse.f12.domain.inventoryproduct.InventoryProduct; import com.woowacourse.f12.domain.inventoryproduct.InventoryProducts; -import com.woowacourse.f12.exception.badrequest.InvalidFollowerCountException; import java.util.List; import java.util.Objects; import javax.persistence.Column; @@ -114,17 +113,6 @@ private void updateJobType(final JobType jobType) { } } - public void increaseFollowerCount() { - this.followerCount += 1; - } - - public void decreaseFollowerCount() { - if (this.followerCount == 0) { - throw new InvalidFollowerCountException(); - } - this.followerCount -= 1; - } - private void updateRegistered(final boolean registered) { if (this.registered) { return; diff --git a/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java b/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java index 1bc2584df..37c663846 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java @@ -2,8 +2,19 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; public interface MemberRepository extends JpaRepository, MemberRepositoryCustom { Optional findByGitHubId(String gitHubId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Member m set m.followerCount = m.followerCount + 1 where m.id = :followingMemberId") + void increaseFollowerCount(Long followingMemberId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Member m set m.followerCount = case m.followerCount when 0 then 0 " + + "else (m.followerCount - 1) end where m.id = :followingMemberId") + void decreaseFollowerCount(Long followingMemberId); } diff --git a/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java b/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java index ce9dc8e03..9217606d7 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java @@ -2,8 +2,34 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; public interface ProductRepository extends JpaRepository, ProductRepositoryCustom { List findByReviewCountGreaterThanEqualAndRatingGreaterThanEqual(int reviewCount, double rating); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Product p " + + "set p.rating = (p.totalRating + :reviewRating) / cast((p.reviewCount + 1) as double), " + + "p.reviewCount = p.reviewCount + 1, " + + "p.totalRating = p.totalRating + :reviewRating " + + "where p.id = :productId") + void updateProductStatisticsForReviewInsert(Long productId, int reviewRating); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Product p " + + "set p.rating = case p.reviewCount when 1 then 0 " + + "else ((p.totalRating - :reviewRating) / cast((p.reviewCount - 1) as double)) end , " + + "p.reviewCount = p.reviewCount - 1, " + + "p.totalRating = p.totalRating - :reviewRating " + + "where p.id = :productId") + void updateProductStatisticsForReviewDelete(Long productId, int reviewRating); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Product p " + + "set p.rating = (p.totalRating + :ratingGap) / cast(p.reviewCount as double), " + + "p.totalRating = p.totalRating + :ratingGap " + + "where p.id = :productId") + void updateProductStatisticsForReviewUpdate(Long productId, int ratingGap); } diff --git a/backend/src/main/java/com/woowacourse/f12/domain/review/Review.java b/backend/src/main/java/com/woowacourse/f12/domain/review/Review.java index cf12e7a9e..bd2209266 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/review/Review.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/review/Review.java @@ -88,25 +88,13 @@ private void validateRating(final int rating) { } } - public void reflectToProductWhenWritten() { - product.increaseReviewCount(); - product.increaseRating(rating); - } - - public void reflectToProductBeforeDelete() { - product.decreaseReviewCount(); - product.decreaseRating(rating); - } - public boolean isWrittenBy(final Member member) { return this.member.equals(member); } public void update(final Review updateReview) { - product.decreaseRating(rating); content = updateReview.getContent(); rating = updateReview.getRating(); - product.increaseRating(rating); } @Override diff --git a/backend/src/main/java/com/woowacourse/f12/exception/ErrorCode.java b/backend/src/main/java/com/woowacourse/f12/exception/ErrorCode.java index f2c7cf990..0b6cf3ab3 100644 --- a/backend/src/main/java/com/woowacourse/f12/exception/ErrorCode.java +++ b/backend/src/main/java/com/woowacourse/f12/exception/ErrorCode.java @@ -25,7 +25,6 @@ public enum ErrorCode { SELF_FOLLOW("40050"), ALREADY_FOLLOWING("40051"), NOT_FOLLOWING("40052"), - INVALID_FOLLOWER_COUNT("40053"), REQUEST_DUPLICATED("40060"), diff --git a/backend/src/main/java/com/woowacourse/f12/exception/badrequest/InvalidFollowerCountException.java b/backend/src/main/java/com/woowacourse/f12/exception/badrequest/InvalidFollowerCountException.java deleted file mode 100644 index c504b6510..000000000 --- a/backend/src/main/java/com/woowacourse/f12/exception/badrequest/InvalidFollowerCountException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.woowacourse.f12.exception.badrequest; - -import static com.woowacourse.f12.exception.ErrorCode.INVALID_FOLLOWER_COUNT; -import static com.woowacourse.f12.exception.ErrorCode.INVALID_LOGIN_CODE; - -public class InvalidFollowerCountException extends InvalidValueException { - - public InvalidFollowerCountException() { - super(INVALID_FOLLOWER_COUNT, "팔로워 카운트는 0보다 작을 수 없습니다."); - } -} diff --git a/backend/src/test/java/com/woowacourse/f12/application/member/MemberServiceTest.java b/backend/src/test/java/com/woowacourse/f12/application/member/MemberServiceTest.java index 78476f5d5..676c5815d 100644 --- a/backend/src/test/java/com/woowacourse/f12/application/member/MemberServiceTest.java +++ b/backend/src/test/java/com/woowacourse/f12/application/member/MemberServiceTest.java @@ -35,7 +35,6 @@ import com.woowacourse.f12.dto.response.member.MemberResponse; import com.woowacourse.f12.dto.response.member.MemberWithProfileProductResponse; import com.woowacourse.f12.exception.badrequest.AlreadyFollowingException; -import com.woowacourse.f12.exception.badrequest.InvalidFollowerCountException; import com.woowacourse.f12.exception.badrequest.NotFollowingException; import com.woowacourse.f12.exception.notfound.MemberNotFoundException; import java.util.Collections; @@ -298,7 +297,7 @@ class MemberServiceTest { } @Test - void 다른_회원을_팔로우한다_팔로우에_성공하면_팔로잉_멤버의_팔로워_카운트가_증가한다() { + void 다른_회원을_팔로우한다_팔로우에_성공하면_팔로잉_멤버의_팔로워_카운트가_증가하는_쿼리를_실행한다() { // given Long followerId = 1L; Long followingId = 2L; @@ -306,12 +305,11 @@ class MemberServiceTest { .followerId(followerId) .followingId(followingId) .build(); - Member followingMember = CORINNE.생성(); - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(followingMember)); given(memberRepository.existsById(followerId)) .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(true); given(followingRepository.save(following)).willReturn(following); // when @@ -319,11 +317,11 @@ class MemberServiceTest { // then assertAll( - () -> assertThat(followingMember.getFollowerCount()).isEqualTo(1), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository).existsByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository).save(following) + () -> verify(followingRepository).save(following), + () -> verify(memberRepository).increaseFollowerCount(followingId) ); } @@ -332,8 +330,6 @@ class MemberServiceTest { // given Long followerId = 1L; Long followingId = 2L; - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(CORINNE.생성())); given(memberRepository.existsById(followerId)) .willReturn(false); @@ -341,11 +337,12 @@ class MemberServiceTest { assertAll( () -> assertThatThrownBy(() -> memberService.follow(followerId, followingId)) .isExactlyInstanceOf(MemberNotFoundException.class), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository, times(0)).existsById(followingId), () -> verify(followingRepository, times(0)) .existsByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository, times(0)).save(any(Following.class)) + () -> verify(followingRepository, times(0)).save(any(Following.class)), + () -> verify(memberRepository, times(0)).increaseFollowerCount(anyLong()) ); } @@ -355,18 +352,21 @@ class MemberServiceTest { Long followerId = 1L; Long followingId = 2L; - given(memberRepository.findById(followingId)) - .willReturn(Optional.empty()); + given(memberRepository.existsById(followerId)) + .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(false); // when, then assertAll( () -> assertThatThrownBy(() -> memberService.follow(followerId, followingId)) .isExactlyInstanceOf(MemberNotFoundException.class), - () -> verify(memberRepository).findById(followingId), - () -> verify(memberRepository, times(0)).existsById(followerId), + () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository, times(0)) .existsByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository, times(0)).save(any(Following.class)) + () -> verify(followingRepository, times(0)).save(any(Following.class)), + () -> verify(memberRepository, times(0)).increaseFollowerCount(anyLong()) ); } @@ -375,10 +375,10 @@ class MemberServiceTest { // given Long followerId = 1L; Long followingId = 2L; - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(CORINNE.생성(followingId))); given(memberRepository.existsById(followerId)) .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(true); given(followingRepository.existsByFollowerIdAndFollowingId(followerId, followingId)) .willReturn(true); @@ -386,15 +386,16 @@ class MemberServiceTest { assertAll( () -> assertThatThrownBy(() -> memberService.follow(followerId, followingId)) .isExactlyInstanceOf(AlreadyFollowingException.class), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository).existsByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository, times(0)).save(any(Following.class)) + () -> verify(followingRepository, times(0)).save(any(Following.class)), + () -> verify(memberRepository, times(0)).increaseFollowerCount(anyLong()) ); } @Test - void 다른_회원을_언팔로우한다_성공하면_팔로잉_멤버의_팔로워_카운트를_감소시킨다() { + void 다른_회원을_언팔로우한다_성공하면_팔로잉_멤버의_팔로워_카운트를_감소시키는_쿼리를_호출한다() { // given Long followerId = 1L; Long followingId = 2L; @@ -402,12 +403,11 @@ class MemberServiceTest { .followerId(followerId) .followingId(followingId) .build(); - Member followingMember = CORINNE.팔로워_카운트를_입력하여_생성(5); - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(followingMember)); given(memberRepository.existsById(followerId)) .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(true); given(followingRepository.findByFollowerIdAndFollowingId(followerId, followingId)) .willReturn(Optional.of(following)); willDoNothing().given(followingRepository) @@ -418,11 +418,11 @@ class MemberServiceTest { // then assertAll( - () -> assertThat(followingMember.getFollowerCount()).isEqualTo(4), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository).findByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository).delete(following) + () -> verify(followingRepository).delete(following), + () -> verify(memberRepository).decreaseFollowerCount(anyLong()) ); } @@ -436,8 +436,6 @@ class MemberServiceTest { .followingId(followingId) .build(); - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(CORINNE.생성(followingId))); given(memberRepository.existsById(followerId)) .willReturn(false); @@ -445,36 +443,36 @@ class MemberServiceTest { assertAll( () -> assertThatThrownBy(() -> memberService.unfollow(followerId, followingId)) .isExactlyInstanceOf(MemberNotFoundException.class), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository, times(0)).existsById(followingId), () -> verify(followingRepository, times(0)) .findByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository, times(0)).delete(following) + () -> verify(followingRepository, times(0)).delete(following), + () -> verify(memberRepository, times(0)).decreaseFollowerCount(anyLong()) ); } @Test - void 다른_회원을_언팔로우할_때_팔로이가_존재하지_않으면_예외를_반환한다() { + void 다른_회원을_언팔로우할_때_팔로우_대상이_존재하지_않으면_예외를_반환한다() { // given Long followerId = 1L; Long followingId = 2L; - Following following = Following.builder() - .followerId(followerId) - .followingId(followingId) - .build(); - given(memberRepository.findById(followingId)) - .willReturn(Optional.empty()); + given(memberRepository.existsById(followerId)) + .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(false); // when, then assertAll( () -> assertThatThrownBy(() -> memberService.unfollow(followerId, followingId)) .isExactlyInstanceOf(MemberNotFoundException.class), - () -> verify(memberRepository).findById(followingId), - () -> verify(memberRepository, times(0)).existsById(followerId), + () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository, times(0)) .findByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository, times(0)).delete(following) + () -> verify(followingRepository, times(0)).delete(any(Following.class)), + () -> verify(memberRepository, times(0)).decreaseFollowerCount(anyLong()) ); } @@ -488,10 +486,10 @@ class MemberServiceTest { .followingId(followingId) .build(); - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(CORINNE.생성(followingId))); given(memberRepository.existsById(followerId)) .willReturn(true); + given(memberRepository.existsById(followingId)) + .willReturn(true); given(followingRepository.findByFollowerIdAndFollowingId(followerId, followingId)) .willReturn(Optional.empty()); @@ -499,44 +497,13 @@ class MemberServiceTest { assertAll( () -> assertThatThrownBy(() -> memberService.unfollow(followerId, followingId)) .isExactlyInstanceOf(NotFollowingException.class), - () -> verify(memberRepository).findById(followingId), () -> verify(memberRepository).existsById(followerId), + () -> verify(memberRepository).existsById(followingId), () -> verify(followingRepository).findByFollowerIdAndFollowingId(followerId, followingId), () -> verify(followingRepository, times(0)).delete(following) ); } - @Test - void 다른_회원을_언팔로우할때_팔로잉_멤버의_팔로우_카운트가_0이면_예외가_발생한다() { - // given - Long followerId = 1L; - Long followingId = 2L; - Following following = Following.builder() - .followerId(followerId) - .followingId(followingId) - .build(); - Member followingMember = CORINNE.팔로워_카운트를_입력하여_생성(0); - - given(memberRepository.findById(followingId)) - .willReturn(Optional.of(followingMember)); - given(memberRepository.existsById(followerId)) - .willReturn(true); - given(followingRepository.findByFollowerIdAndFollowingId(followerId, followingId)) - .willReturn(Optional.of(following)); - willDoNothing().given(followingRepository) - .delete(following); - - // when, then - assertAll( - () -> assertThatThrownBy(() -> memberService.unfollow(followerId, followingId)) - .isExactlyInstanceOf(InvalidFollowerCountException.class), - () -> verify(memberRepository).findById(followingId), - () -> verify(memberRepository).existsById(followerId), - () -> verify(followingRepository).findByFollowerIdAndFollowingId(followerId, followingId), - () -> verify(followingRepository).delete(following) - ); - } - @Test void 팔로잉하는_회원을_키워드와_옵션_없이_조회한다() { // given diff --git a/backend/src/test/java/com/woowacourse/f12/application/review/ReviewServiceTest.java b/backend/src/test/java/com/woowacourse/f12/application/review/ReviewServiceTest.java index dcbc38c59..4f9bff90e 100644 --- a/backend/src/test/java/com/woowacourse/f12/application/review/ReviewServiceTest.java +++ b/backend/src/test/java/com/woowacourse/f12/application/review/ReviewServiceTest.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.BDDMockito.any; @@ -93,6 +94,8 @@ class ReviewServiceTest { .willReturn(inventoryProduct); given(reviewRepository.save(any(Review.class))) .willReturn(REVIEW_RATING_5.작성(1L, product, member)); + willDoNothing().given(productRepository) + .updateProductStatisticsForReviewInsert(productId, reviewRequest.getRating()); // when Long reviewId = reviewService.saveReviewAndInventoryProduct(productId, memberId, reviewRequest); @@ -100,12 +103,13 @@ class ReviewServiceTest { // then assertAll( () -> assertThat(reviewId).isEqualTo(1L), -// () -> assertThat(product.getReviewCount()).isOne(), () -> verify(productRepository).findById(productId), () -> verify(memberRepository).findById(memberId), () -> verify(reviewRepository).save(any(Review.class)), () -> verify(inventoryProductRepository).existsByMemberAndProduct(member, product), - () -> verify(inventoryProductRepository).save(inventoryProduct) + () -> verify(inventoryProductRepository).save(inventoryProduct), + () -> verify(productRepository).updateProductStatisticsForReviewInsert(productId, + reviewRequest.getRating()) ); } @@ -207,7 +211,6 @@ class ReviewServiceTest { Member member = CORINNE.생성(1L); Pageable pageable = PageRequest.of(0, 1, Sort.by(Order.desc("createdAt"))); Review review = REVIEW_RATING_5.작성(1L, product, member); - review.reflectToProductWhenWritten(); Slice slice = new SliceImpl<>(List.of(review), pageable, true); given(productRepository.existsById(productId)) @@ -237,7 +240,6 @@ class ReviewServiceTest { Member member = CORINNE.생성(1L); Pageable pageable = PageRequest.of(0, 1, Sort.by(Order.desc("createdAt"))); Review review = REVIEW_RATING_5.작성(1L, product, member); - review.reflectToProductWhenWritten(); Slice slice = new SliceImpl<>(List.of(review), pageable, true); given(productRepository.existsById(productId)) @@ -269,7 +271,6 @@ class ReviewServiceTest { Member member = CORINNE.생성(1L); Pageable pageable = PageRequest.of(0, 1, Sort.by(Order.desc("createdAt"))); Review review = REVIEW_RATING_5.작성(1L, product, member); - review.reflectToProductWhenWritten(); Slice slice = new SliceImpl<>(List.of(review), pageable, true); given(productRepository.existsById(productId)) @@ -313,9 +314,7 @@ class ReviewServiceTest { Pageable pageable = PageRequest.of(0, 2, Sort.by(Order.desc("createdAt"))); Member member = CORINNE.생성(1L); Review review1 = REVIEW_RATING_5.작성(3L, KEYBOARD_1.생성(), member); - review1.reflectToProductWhenWritten(); Review review2 = REVIEW_RATING_5.작성(2L, KEYBOARD_2.생성(), member); - review2.reflectToProductWhenWritten(); Slice slice = new SliceImpl<>(List.of(review1, review2), pageable, true); given(reviewRepository.findPageBy(pageable)) @@ -342,15 +341,17 @@ class ReviewServiceTest { Long reviewId = 1L; Long memberId = 1L; Member member = CORINNE.생성(memberId); - Product product = KEYBOARD_1.생성(); + Product product = KEYBOARD_1.생성(1L); Review review = REVIEW_RATING_5.작성(reviewId, product, member); - review.reflectToProductWhenWritten(); ReviewRequest updateRequest = new ReviewRequest("수정할 내용", 4); + int ratingGap = updateRequest.getRating() - review.getRating(); given(memberRepository.findById(memberId)) .willReturn(Optional.of(member)); given(reviewRepository.findById(reviewId)) .willReturn(Optional.of(review)); + willDoNothing().given(productRepository) + .updateProductStatisticsForReviewUpdate(product.getId(), ratingGap); // when reviewService.update(reviewId, memberId, updateRequest); @@ -360,9 +361,9 @@ class ReviewServiceTest { () -> assertThat(review).usingRecursiveComparison() .comparingOnlyFields("content", "rating") .isEqualTo(updateRequest.toReview(product, member)), - () -> assertThat(product.getRating()).isEqualTo(4.0), () -> verify(memberRepository).findById(memberId), - () -> verify(reviewRepository).findById(reviewId) + () -> verify(reviewRepository).findById(reviewId), + () -> verify(productRepository).updateProductStatisticsForReviewUpdate(product.getId(), ratingGap) ); } @@ -428,7 +429,8 @@ class ReviewServiceTest { () -> assertThatThrownBy(() -> reviewService.update(reviewId, notAuthorId, updateRequest)) .isExactlyInstanceOf(NotAuthorException.class), () -> verify(memberRepository).findById(notAuthorId), - () -> verify(reviewRepository).findById(reviewId) + () -> verify(reviewRepository).findById(reviewId), + () -> verify(productRepository, times(0)).updateProductStatisticsForReviewUpdate(anyLong(), anyInt()) ); } @@ -441,7 +443,6 @@ class ReviewServiceTest { Member member = CORINNE.생성(memberId); Product product = KEYBOARD_1.생성(productId); Review review = REVIEW_RATING_5.작성(reviewId, product, member); - review.reflectToProductWhenWritten(); InventoryProduct inventoryProduct = SELECTED_INVENTORY_PRODUCT.생성(1L, member, product); given(memberRepository.findById(memberId)) @@ -454,16 +455,18 @@ class ReviewServiceTest { .willReturn(Optional.of(inventoryProduct)); willDoNothing().given(inventoryProductRepository) .delete(any(InventoryProduct.class)); + willDoNothing().given(productRepository) + .updateProductStatisticsForReviewDelete(productId, review.getRating()); // when, then assertAll( () -> assertDoesNotThrow(() -> reviewService.delete(reviewId, memberId)), - () -> assertThat(product.getReviewCount()).isZero(), () -> verify(memberRepository).findById(memberId), () -> verify(reviewRepository).findById(reviewId), - () -> verify(reviewRepository).delete(review), () -> verify(inventoryProductRepository).findWithProductByMemberAndProduct(member, product), - () -> verify(inventoryProductRepository).delete(inventoryProduct) + () -> verify(inventoryProductRepository).delete(inventoryProduct), + () -> verify(reviewRepository).delete(review), + () -> verify(productRepository).updateProductStatisticsForReviewDelete(productId, review.getRating()) ); } @@ -544,8 +547,6 @@ class ReviewServiceTest { .willReturn(Optional.of(member)); given(reviewRepository.findById(reviewId)) .willReturn(Optional.of(review)); - willDoNothing().given(reviewRepository) - .delete(any(Review.class)); given(inventoryProductRepository.findWithProductByMemberAndProduct(member, product)) .willReturn(Optional.empty()); @@ -555,9 +556,9 @@ class ReviewServiceTest { .isExactlyInstanceOf(InventoryProductNotFoundException.class), () -> verify(memberRepository).findById(memberId), () -> verify(reviewRepository).findById(reviewId), - () -> verify(reviewRepository).delete(any(Review.class)), () -> verify(inventoryProductRepository).findWithProductByMemberAndProduct(member, product), - () -> verify(inventoryProductRepository, times(0)).delete(any(InventoryProduct.class)) + () -> verify(inventoryProductRepository, times(0)).delete(any(InventoryProduct.class)), + () -> verify(reviewRepository, times(0)).delete(any(Review.class)) ); } diff --git a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java index bc07a1fa1..844ba68d1 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java @@ -48,9 +48,7 @@ class MemberRepositoryTest { .followerId(corinne.getId()) .followingId(mincho.getId()) .build()); - mincho.increaseFollowerCount(); - entityManager.flush(); - entityManager.clear(); + memberRepository.increaseFollowerCount(mincho.getId()); // when Member savedMincho = memberRepository.findById(mincho.getId()) @@ -174,9 +172,7 @@ class MemberRepositoryTest { .followerId(corinne.getId()) .followingId(mincho.getId()) .build()); - mincho.increaseFollowerCount(); - entityManager.flush(); - entityManager.clear(); + memberRepository.increaseFollowerCount(mincho.getId()); Member expected = Member.builder() .id(mincho.getId()) @@ -212,9 +208,7 @@ class MemberRepositoryTest { .followerId(corinne.getId()) .followingId(mincho.getId()) .build()); - mincho.increaseFollowerCount(); - entityManager.flush(); - entityManager.clear(); + memberRepository.increaseFollowerCount(mincho.getId()); Member expected = Member.builder() .id(mincho.getId()) @@ -250,9 +244,7 @@ class MemberRepositoryTest { .followerId(corinne.getId()) .followingId(mincho.getId()) .build()); - mincho.increaseFollowerCount(); - entityManager.flush(); - entityManager.clear(); + memberRepository.increaseFollowerCount(mincho.getId()); Member expected = Member.builder() .id(mincho.getId()) @@ -315,4 +307,37 @@ class MemberRepositoryTest { assertThatThrownBy(() -> memberRepository.save(member2)) .isInstanceOf(DataIntegrityViolationException.class); } + + @Test + void 팔로워_수를_증가시킨다() { + // given + Member member = CORINNE.생성(); + memberRepository.save(member); + + // when + memberRepository.increaseFollowerCount(member.getId()); + + // then + Member actual = memberRepository.findById(member.getId()) + .orElseThrow(); + + assertThat(actual.getFollowerCount()).isOne(); + } + + @Test + void 팔로워_수를_감소시킨다() { + // given + Member member = CORINNE.생성(); + memberRepository.save(member); + memberRepository.increaseFollowerCount(member.getId()); + + // when + memberRepository.decreaseFollowerCount(member.getId()); + + // then + Member actual = memberRepository.findById(member.getId()) + .orElseThrow(); + + assertThat(actual.getFollowerCount()).isZero(); + } } diff --git a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberTest.java b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberTest.java index d8f9faaa2..00cf609d7 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberTest.java @@ -1,10 +1,8 @@ package com.woowacourse.f12.domain.member; -import com.woowacourse.f12.exception.badrequest.InvalidFollowerCountException; -import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; class MemberTest { @@ -57,74 +55,4 @@ class MemberTest { assertThat(member).usingRecursiveComparison() .isEqualTo(expected); } - - @Test - void 팔로워_수를_증가한다() { - // given - Member member = Member.builder() - .id(1L) - .name("유현지") - .imageUrl("imageUrl") - .careerLevel(CareerLevel.SENIOR) - .jobType(JobType.BACKEND) - .build(); - Member expected = Member.builder() - .id(1L) - .name("유현지") - .imageUrl("imageUrl") - .careerLevel(CareerLevel.SENIOR) - .jobType(JobType.BACKEND) - .followerCount(1) - .build(); - - // when - member.increaseFollowerCount(); - - // then - assertThat(member).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 팔로워_수를_감소한다() { - // given - Member member = Member.builder() - .id(1L) - .name("유현지") - .imageUrl("imageUrl") - .careerLevel(CareerLevel.SENIOR) - .jobType(JobType.BACKEND) - .followerCount(1) - .build(); - Member expected = Member.builder() - .id(1L) - .name("유현지") - .imageUrl("imageUrl") - .careerLevel(CareerLevel.SENIOR) - .jobType(JobType.BACKEND) - .build(); - - // when - member.decreaseFollowerCount(); - - // then - assertThat(member).usingRecursiveComparison() - .isEqualTo(expected); - } - - @Test - void 팔로워_수가_0인_상태에서_팔로워_수를_감소하면_예외가_발생한다() { - // given - Member member = Member.builder() - .id(1L) - .name("유현지") - .imageUrl("imageUrl") - .careerLevel(CareerLevel.SENIOR) - .jobType(JobType.BACKEND) - .build(); - - // when, then - assertThatThrownBy(member::decreaseFollowerCount) - .isInstanceOf(InvalidFollowerCountException.class); - } } diff --git a/backend/src/test/java/com/woowacourse/f12/domain/product/ProductRepositoryTest.java b/backend/src/test/java/com/woowacourse/f12/domain/product/ProductRepositoryTest.java index 729308e5e..5c97c8f29 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/product/ProductRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/product/ProductRepositoryTest.java @@ -212,12 +212,93 @@ class ProductRepositoryTest { .containsOnly(keyboard1); } + @Test + void 리뷰_작성에_맞게_제품_정합성을_맞춘다() { + // given + Long productId = 제품_저장(KEYBOARD_1.생성()).getId(); + + // when + productRepository.updateProductStatisticsForReviewInsert(productId, 1); + productRepository.updateProductStatisticsForReviewInsert(productId, 2); + + // then + Product actual = productRepository.findById(productId) + .orElseThrow(); + + assertAll( + () -> assertThat(actual.getReviewCount()).isEqualTo(2), + () -> assertThat(actual.getRating()).isEqualTo(1.5), + () -> assertThat(actual.getTotalRating()).isEqualTo(3) + ); + } + + @Test + void 리뷰_삭제에_맞게_제품_정합성을_맞춘다() { + // given + Long productId = 제품_저장(KEYBOARD_1.생성()).getId(); + productRepository.updateProductStatisticsForReviewInsert(productId, 1); + productRepository.updateProductStatisticsForReviewInsert(productId, 2); + + // when + productRepository.updateProductStatisticsForReviewDelete(productId, 1); + + // then + Product actual = productRepository.findById(productId) + .orElseThrow(); + + assertAll( + () -> assertThat(actual.getReviewCount()).isOne(), + () -> assertThat(actual.getRating()).isEqualTo(2.0), + () -> assertThat(actual.getTotalRating()).isEqualTo(2) + ); + } + + @Test + void 리뷰_삭제로_리뷰_개수가_0개일_때_제품_정합성을_맞춘다() { + // given + Long productId = 제품_저장(KEYBOARD_1.생성()).getId(); + productRepository.updateProductStatisticsForReviewInsert(productId, 1); + + // when + productRepository.updateProductStatisticsForReviewDelete(productId, 1); + + // then + Product actual = productRepository.findById(productId) + .orElseThrow(); + + assertAll( + () -> assertThat(actual.getReviewCount()).isZero(), + () -> assertThat(actual.getRating()).isZero(), + () -> assertThat(actual.getTotalRating()).isZero() + ); + } + + @Test + void 리뷰_수정에_맞게_제품_정합성을_맞춘다() { + // given + Long productId = 제품_저장(KEYBOARD_1.생성()).getId(); + productRepository.updateProductStatisticsForReviewInsert(productId, 5); + + // when + productRepository.updateProductStatisticsForReviewUpdate(productId, -2); + + // then + Product actual = productRepository.findById(productId) + .orElseThrow(); + + assertAll( + () -> assertThat(actual.getReviewCount()).isOne(), + () -> assertThat(actual.getRating()).isEqualTo(3.0), + () -> assertThat(actual.getTotalRating()).isEqualTo(3) + ); + } + private Product 제품_저장(Product product) { return productRepository.save(product); } private Review 리뷰_저장(Review review) { - review.reflectToProductWhenWritten(); + productRepository.updateProductStatisticsForReviewInsert(review.getProduct().getId(), review.getRating()); return reviewRepository.save(review); } } diff --git a/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewRepositoryTest.java b/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewRepositoryTest.java index ccccba489..631ad5ff7 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewRepositoryTest.java @@ -330,7 +330,6 @@ class ReviewRepositoryTest { } private Review 리뷰_저장(Review review) { - review.reflectToProductWhenWritten(); return reviewRepository.save(review); } } diff --git a/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewTest.java b/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewTest.java index 0a21472d3..b35423ea4 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/review/ReviewTest.java @@ -126,7 +126,6 @@ class ReviewTest { .rating(5) .product(product) .build(); - review.reflectToProductWhenWritten(); Review updateReview = Review.builder() .id(2L) @@ -143,57 +142,7 @@ class ReviewTest { () -> assertThat(review.getId()).isEqualTo(1L), () -> assertThat(review).usingRecursiveComparison() .ignoringFields("id", "product") - .isEqualTo(updateReview), - () -> assertThat(review.getProduct().getReviewCount()).isOne(), - () -> assertThat(review.getProduct().getTotalRating()).isEqualTo(4), - () -> assertThat(review.getProduct().getRating()).isEqualTo(4.0) - ); - } - - @Test - void 리뷰_작성_시_리뷰_대상_제품의_리뷰_개수와_평점을_증가시킨다() { - // given - Product product = Product.builder() - .build(); - - Review review = Review.builder() - .content("내용") - .rating(5) - .product(product) - .build(); - - // when - review.reflectToProductWhenWritten(); - - // then - assertAll( - () -> assertThat(product.getReviewCount()).isOne(), - () -> assertThat(product.getTotalRating()).isEqualTo(5), - () -> assertThat(product.getRating()).isEqualTo(5.0) - ); - } - - @Test - void 리뷰_삭제_전에_리뷰_대상_제품의_리뷰_개수와_평점을_감소시킨다() { - // given - Product product = Product.builder() - .build(); - - Review review = Review.builder() - .content("내용") - .rating(5) - .product(product) - .build(); - review.reflectToProductWhenWritten(); - - // when - review.reflectToProductBeforeDelete(); - - // then - assertAll( - () -> assertThat(product.getReviewCount()).isZero(), - () -> assertThat(product.getTotalRating()).isEqualTo(0), - () -> assertThat(product.getRating()).isEqualTo(0) + .isEqualTo(updateReview) ); } } diff --git a/backend/src/test/java/com/woowacourse/f12/presentation/member/MemberControllerTest.java b/backend/src/test/java/com/woowacourse/f12/presentation/member/MemberControllerTest.java index 4ce81d8ab..05e55fded 100644 --- a/backend/src/test/java/com/woowacourse/f12/presentation/member/MemberControllerTest.java +++ b/backend/src/test/java/com/woowacourse/f12/presentation/member/MemberControllerTest.java @@ -3,7 +3,6 @@ import static com.woowacourse.f12.exception.ErrorCode.ALREADY_FOLLOWING; import static com.woowacourse.f12.exception.ErrorCode.EMPTY_MEMBER_INFO_VALUE; import static com.woowacourse.f12.exception.ErrorCode.EXPIRED_ACCESS_TOKEN; -import static com.woowacourse.f12.exception.ErrorCode.INVALID_FOLLOWER_COUNT; import static com.woowacourse.f12.exception.ErrorCode.INVALID_MEMBER_INFO_VALUE; import static com.woowacourse.f12.exception.ErrorCode.INVALID_PAGING_PARAM; import static com.woowacourse.f12.exception.ErrorCode.INVALID_SEARCH_PARAM; @@ -52,7 +51,6 @@ import com.woowacourse.f12.dto.response.member.MemberPageResponse; import com.woowacourse.f12.dto.response.member.MemberResponse; import com.woowacourse.f12.exception.badrequest.AlreadyFollowingException; -import com.woowacourse.f12.exception.badrequest.InvalidFollowerCountException; import com.woowacourse.f12.exception.badrequest.NotFollowingException; import com.woowacourse.f12.exception.badrequest.SelfFollowException; import com.woowacourse.f12.exception.notfound.MemberNotFoundException; @@ -594,7 +592,7 @@ class MemberControllerTest extends PresentationTest { // then resultActions.andExpect(status().isNoContent()) .andDo(document("unfollow", - new ErrorCodeSnippet(NOT_FOLLOWING, INVALID_FOLLOWER_COUNT, MEMBER_NOT_FOUND))) + new ErrorCodeSnippet(NOT_FOLLOWING, MEMBER_NOT_FOUND))) .andDo(print()); verify(memberService).unfollow(followerId, followingId); @@ -656,34 +654,6 @@ class MemberControllerTest extends PresentationTest { verify(memberService).unfollow(followerId, followingId); } - @Test - void 언팔로우_실패_대상의_팔로워_수가_0_이하인_경우() throws Exception { - // given - Long followerId = 1L; - Long followingId = 2L; - - String authorizationHeader = "Bearer Token"; - given(jwtProvider.isValidToken(authorizationHeader)) - .willReturn(true); - given(jwtProvider.getPayload(authorizationHeader)) - .willReturn(new MemberPayload(followerId, Role.USER)); - willThrow(new InvalidFollowerCountException()) - .given(memberService) - .unfollow(followerId, followingId); - - // when - ResultActions resultActions = mockMvc.perform( - delete("/api/v1/members/" + followingId + "/following") - .header(HttpHeaders.AUTHORIZATION, authorizationHeader) - ); - - // then - resultActions.andExpect(status().isBadRequest()) - .andDo(print()); - - verify(memberService).unfollow(followerId, followingId); - } - @Test void 팔로잉하는_회원_목록_조회_성공() throws Exception { // given