From a432f49a0852600d32e26c85c5942d6e45e7d488 Mon Sep 17 00:00:00 2001 From: csct3434 Date: Fri, 29 Mar 2024 19:58:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=A1=9D=EC=9E=A5=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B6=8C=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/service/ArticleLikeService.java | 27 ++++------------ .../service/ValidateArticleLikePolicy.java | 6 ++++ .../ValidateArticleLikePolicyImpl.java | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicy.java create mode 100644 src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicyImpl.java diff --git a/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java b/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java index 5d6a626..3cf82a6 100644 --- a/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java +++ b/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java @@ -11,7 +11,6 @@ import today.seasoning.seasoning.article.domain.ArticleRepository; import today.seasoning.seasoning.article.event.ArticleLikedEvent; import today.seasoning.seasoning.common.exception.CustomException; -import today.seasoning.seasoning.friendship.service.CheckFriendshipService; import today.seasoning.seasoning.user.domain.User; import today.seasoning.seasoning.user.domain.UserRepository; @@ -23,14 +22,16 @@ public class ArticleLikeService { private final UserRepository userRepository; private final ArticleRepository articleRepository; private final ArticleLikeRepository articleLikeRepository; - private final CheckFriendshipService checkFriendshipService; + private final ValidateArticleLikePolicy validateArticleLikePolicy; private final ApplicationEventPublisher applicationEventPublisher; public void doLike(Long userId, Long articleId) { Article article = articleRepository.findByIdOrElseThrow(articleId); User user = userRepository.findByIdOrElseThrow(userId); - validatePermission(userId, article); + if (!validateArticleLikePolicy.validate(userId, articleId)) { + throw new CustomException(HttpStatus.FORBIDDEN, "접근 권한 없음"); + } if (articleLikeRepository.findByArticleAndUser(articleId, userId).isEmpty()) { User author = article.getUser(); @@ -44,24 +45,10 @@ public void doLike(Long userId, Long articleId) { } public void cancelLike(Long userId, Long articleId) { - Article article = articleRepository.findByIdOrElseThrow(articleId); - validatePermission(userId, article); - articleLikeRepository.findByArticleAndUser(articleId, userId) - .ifPresent(articleLike -> articleLikeRepository.deleteById(articleLike.getId())); - } - - private void validatePermission(Long userId, Article article) { - Long authorId = article.getUser().getId(); - - // 자신의 글 - if (authorId.equals(userId)) { - return; - } - // 공개된 친구의 글 - if (article.isPublished() && checkFriendshipService.doCheck(userId, authorId)) { - return; + if (!validateArticleLikePolicy.validate(userId, articleId)) { + throw new CustomException(HttpStatus.FORBIDDEN, "접근 권한 없음"); } - throw new CustomException(HttpStatus.FORBIDDEN, "접근 권한 없음"); + articleLikeRepository.findByArticleAndUser(articleId, userId).ifPresent(articleLikeRepository::delete); } } diff --git a/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicy.java b/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicy.java new file mode 100644 index 0000000..733503e --- /dev/null +++ b/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicy.java @@ -0,0 +1,6 @@ +package today.seasoning.seasoning.article.service; + +public interface ValidateArticleLikePolicy { + + boolean validate(Long userId, Long articleId); +} diff --git a/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicyImpl.java b/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicyImpl.java new file mode 100644 index 0000000..a9821dd --- /dev/null +++ b/src/main/java/today/seasoning/seasoning/article/service/ValidateArticleLikePolicyImpl.java @@ -0,0 +1,32 @@ +package today.seasoning.seasoning.article.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import today.seasoning.seasoning.article.domain.Article; +import today.seasoning.seasoning.article.domain.ArticleRepository; +import today.seasoning.seasoning.friendship.domain.FriendshipRepository; + +@Component +@RequiredArgsConstructor +public class ValidateArticleLikePolicyImpl implements ValidateArticleLikePolicy { + + private final ArticleRepository articleRepository; + private final FriendshipRepository friendshipRepository; + + @Override + public boolean validate(Long userId, Long articleId) { + Article article = articleRepository.findByIdOrElseThrow(articleId); + Long authorId = article.getUser().getId(); + + // 자신의 글 + if (authorId.equals(userId)) { + return true; + } + // 공개된 친구의 글 + if (article.isPublished() && friendshipRepository.existsByUserIdAndFriendId(userId, authorId)) { + return true; + } + + return false; + } +} From d1acd4777d24659ef3f08da9bc0d782e2e5d4258 Mon Sep 17 00:00:00 2001 From: csct3434 Date: Fri, 29 Mar 2024 20:12:11 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=A1=9D=EC=9E=A5=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=93=B1=EB=A1=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EA=B3=BC=20=EC=B7=A8=EC=86=8C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 단일 책임 원칙 준수 및 단위 테스트에서의 모킹을 위해 별도의 클래스로 분리 --- .../article/controller/ArticleController.java | 10 ++-- .../article/service/ArticleLikeService.java | 54 ------------------- .../service/CancelArticleLikeService.java | 24 +++++++++ .../service/RegisterArticleLikeService.java | 52 ++++++++++++++++++ 4 files changed, 82 insertions(+), 58 deletions(-) delete mode 100644 src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java create mode 100644 src/main/java/today/seasoning/seasoning/article/service/CancelArticleLikeService.java create mode 100644 src/main/java/today/seasoning/seasoning/article/service/RegisterArticleLikeService.java diff --git a/src/main/java/today/seasoning/seasoning/article/controller/ArticleController.java b/src/main/java/today/seasoning/seasoning/article/controller/ArticleController.java index eeccd5a..86416b3 100644 --- a/src/main/java/today/seasoning/seasoning/article/controller/ArticleController.java +++ b/src/main/java/today/seasoning/seasoning/article/controller/ArticleController.java @@ -24,13 +24,14 @@ import today.seasoning.seasoning.article.dto.FindMyArticlesByYearResponse; import today.seasoning.seasoning.article.dto.RegisterArticleRequest; import today.seasoning.seasoning.article.dto.UpdateArticleRequest; -import today.seasoning.seasoning.article.service.ArticleLikeService; +import today.seasoning.seasoning.article.service.CancelArticleLikeService; import today.seasoning.seasoning.article.service.DeleteArticleService; import today.seasoning.seasoning.article.service.FindArticleService; import today.seasoning.seasoning.article.service.FindCollageService; import today.seasoning.seasoning.article.service.FindFriendArticlesService; import today.seasoning.seasoning.article.service.FindMyArticlesByTermService; import today.seasoning.seasoning.article.service.FindMyArticlesByYearService; +import today.seasoning.seasoning.article.service.RegisterArticleLikeService; import today.seasoning.seasoning.article.service.RegisterArticleService; import today.seasoning.seasoning.article.service.UpdateArticleService; import today.seasoning.seasoning.common.UserPrincipal; @@ -47,7 +48,8 @@ public class ArticleController { private final DeleteArticleService deleteArticleService; private final FindMyArticlesByYearService findMyArticlesByYearService; private final FindMyArticlesByTermService findMyArticlesByTermService; - private final ArticleLikeService articleLikeService; + private final RegisterArticleLikeService registerArticleLikeService; + private final CancelArticleLikeService cancelArticleLikeService; private final FindCollageService findCollageService; private final FindFriendArticlesService findFriendArticlesService; @@ -115,7 +117,7 @@ public ResponseEntity likeArticle( @AuthenticationPrincipal UserPrincipal principal, @PathVariable String articleId ) { - articleLikeService.doLike(principal.getId(), TsidUtil.toLong(articleId)); + registerArticleLikeService.doService(principal.getId(), TsidUtil.toLong(articleId)); return ResponseEntity.ok().build(); } @@ -124,7 +126,7 @@ public ResponseEntity cancelLikeArticle( @AuthenticationPrincipal UserPrincipal principal, @PathVariable String articleId ) { - articleLikeService.cancelLike(principal.getId(), TsidUtil.toLong(articleId)); + cancelArticleLikeService.doService(principal.getId(), TsidUtil.toLong(articleId)); return ResponseEntity.ok().build(); } diff --git a/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java b/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java deleted file mode 100644 index 3cf82a6..0000000 --- a/src/main/java/today/seasoning/seasoning/article/service/ArticleLikeService.java +++ /dev/null @@ -1,54 +0,0 @@ -package today.seasoning.seasoning.article.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import today.seasoning.seasoning.article.domain.Article; -import today.seasoning.seasoning.article.domain.ArticleLike; -import today.seasoning.seasoning.article.domain.ArticleLikeRepository; -import today.seasoning.seasoning.article.domain.ArticleRepository; -import today.seasoning.seasoning.article.event.ArticleLikedEvent; -import today.seasoning.seasoning.common.exception.CustomException; -import today.seasoning.seasoning.user.domain.User; -import today.seasoning.seasoning.user.domain.UserRepository; - -@Service -@Transactional -@RequiredArgsConstructor -public class ArticleLikeService { - - private final UserRepository userRepository; - private final ArticleRepository articleRepository; - private final ArticleLikeRepository articleLikeRepository; - private final ValidateArticleLikePolicy validateArticleLikePolicy; - private final ApplicationEventPublisher applicationEventPublisher; - - public void doLike(Long userId, Long articleId) { - Article article = articleRepository.findByIdOrElseThrow(articleId); - User user = userRepository.findByIdOrElseThrow(userId); - - if (!validateArticleLikePolicy.validate(userId, articleId)) { - throw new CustomException(HttpStatus.FORBIDDEN, "접근 권한 없음"); - } - - if (articleLikeRepository.findByArticleAndUser(articleId, userId).isEmpty()) { - User author = article.getUser(); - articleLikeRepository.save(new ArticleLike(article, user)); - - if (user != author) { - ArticleLikedEvent articleLikedEvent = new ArticleLikedEvent(user.getId(), author.getId(), articleId); - applicationEventPublisher.publishEvent(articleLikedEvent); - } - } - } - - public void cancelLike(Long userId, Long articleId) { - if (!validateArticleLikePolicy.validate(userId, articleId)) { - throw new CustomException(HttpStatus.FORBIDDEN, "접근 권한 없음"); - } - - articleLikeRepository.findByArticleAndUser(articleId, userId).ifPresent(articleLikeRepository::delete); - } -} diff --git a/src/main/java/today/seasoning/seasoning/article/service/CancelArticleLikeService.java b/src/main/java/today/seasoning/seasoning/article/service/CancelArticleLikeService.java new file mode 100644 index 0000000..e12cb33 --- /dev/null +++ b/src/main/java/today/seasoning/seasoning/article/service/CancelArticleLikeService.java @@ -0,0 +1,24 @@ +package today.seasoning.seasoning.article.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import today.seasoning.seasoning.article.domain.ArticleLikeRepository; +import today.seasoning.seasoning.common.exception.CustomException; + +@Service +@RequiredArgsConstructor +public class CancelArticleLikeService { + + private final ArticleLikeRepository articleLikeRepository; + private final ValidateArticleLikePolicy validateArticleLikePolicy; + + @Transactional + public void doService(Long userId, Long articleId) { + if (!validateArticleLikePolicy.validate(userId, articleId)) { + throw new CustomException(HttpStatus.FORBIDDEN, "권한 없음"); + } + articleLikeRepository.findByArticleAndUser(articleId, userId).ifPresent(articleLikeRepository::delete); + } +} diff --git a/src/main/java/today/seasoning/seasoning/article/service/RegisterArticleLikeService.java b/src/main/java/today/seasoning/seasoning/article/service/RegisterArticleLikeService.java new file mode 100644 index 0000000..701fc49 --- /dev/null +++ b/src/main/java/today/seasoning/seasoning/article/service/RegisterArticleLikeService.java @@ -0,0 +1,52 @@ +package today.seasoning.seasoning.article.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import today.seasoning.seasoning.article.domain.Article; +import today.seasoning.seasoning.article.domain.ArticleLike; +import today.seasoning.seasoning.article.domain.ArticleLikeRepository; +import today.seasoning.seasoning.article.domain.ArticleRepository; +import today.seasoning.seasoning.article.event.ArticleLikedEvent; +import today.seasoning.seasoning.common.exception.CustomException; +import today.seasoning.seasoning.user.domain.User; +import today.seasoning.seasoning.user.domain.UserRepository; + +@Service +@RequiredArgsConstructor +public class RegisterArticleLikeService { + + private final UserRepository userRepository; + private final ArticleRepository articleRepository; + private final ArticleLikeRepository articleLikeRepository; + private final ValidateArticleLikePolicy validateArticleLikePolicy; + private final ApplicationEventPublisher applicationEventPublisher; + + @Transactional + public void doService(Long userId, Long articleId) { + Article article = articleRepository.findByIdOrElseThrow(articleId); + User user = userRepository.findByIdOrElseThrow(userId); + User author = article.getUser(); + + // 사용자 권한 검증 + if (!validateArticleLikePolicy.validate(userId, articleId)) { + throw new CustomException(HttpStatus.FORBIDDEN, "권한 없음"); + } + + // 중복 요청의 경우 무시 + if (articleLikeRepository.findByArticleAndUser(articleId, userId).isPresent()) { + return; + } + + articleLikeRepository.save(new ArticleLike(article, user)); + + // 타인의 글에 좋아요를 누른 경우, 상대방에게 관련 알림 전송 + if (user != author) { + ArticleLikedEvent articleLikedEvent = new ArticleLikedEvent(user.getId(), author.getId(), articleId); + applicationEventPublisher.publishEvent(articleLikedEvent); + } + } + +} From ac3f47869170756fadb81eab0622bfec3648df8a Mon Sep 17 00:00:00 2001 From: csct3434 Date: Fri, 29 Mar 2024 20:50:12 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20=EA=B8=B0=EB=A1=9D=EC=9E=A5=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=B7=A8=EC=86=8C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CancelArticleLikeIntegrationTest.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/test/java/today/seasoning/seasoning/article/integration/CancelArticleLikeIntegrationTest.java diff --git a/src/test/java/today/seasoning/seasoning/article/integration/CancelArticleLikeIntegrationTest.java b/src/test/java/today/seasoning/seasoning/article/integration/CancelArticleLikeIntegrationTest.java new file mode 100644 index 0000000..35ab523 --- /dev/null +++ b/src/test/java/today/seasoning/seasoning/article/integration/CancelArticleLikeIntegrationTest.java @@ -0,0 +1,170 @@ +package today.seasoning.seasoning.article.integration; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import today.seasoning.seasoning.BaseIntegrationTest; +import today.seasoning.seasoning.article.domain.Article; +import today.seasoning.seasoning.article.domain.ArticleLike; +import today.seasoning.seasoning.article.domain.ArticleLikeRepository; +import today.seasoning.seasoning.article.domain.ArticleRepository; +import today.seasoning.seasoning.common.enums.LoginType; +import today.seasoning.seasoning.common.util.TsidUtil; +import today.seasoning.seasoning.friendship.domain.Friendship; +import today.seasoning.seasoning.friendship.domain.FriendshipRepository; +import today.seasoning.seasoning.notification.domain.UserNotificationRepository; +import today.seasoning.seasoning.user.domain.User; +import today.seasoning.seasoning.user.domain.UserRepository; + +@DisplayName("기록장 좋아요 취소 통합 테스트") +public class CancelArticleLikeIntegrationTest extends BaseIntegrationTest { + + @Autowired + UserRepository userRepository; + + @Autowired + ArticleRepository articleRepository; + + @Autowired + ArticleLikeRepository articleLikeRepository; + + @Autowired + FriendshipRepository friendshipRepository; + + @Autowired + UserNotificationRepository userNotificationRepository; + + @InjectSoftAssertions + SoftAssertions softAssertions; + + private void makeFriendships(User user, User friend) { + friendshipRepository.save(new Friendship(user, friend)); + friendshipRepository.save(new Friendship(friend, user)); + } + + private void cancelFriendships(User user, User friend) { + friendshipRepository.findByUserIdAndFriendId(user.getId(), friend.getId()).ifPresent(friendshipRepository::delete); + friendshipRepository.findByUserIdAndFriendId(friend.getId(), user.getId()).ifPresent(friendshipRepository::delete); + } + + @Test + @DisplayName("성공 - 자신의 공개 기록장") + void test() { + //given + User user = userRepository.save(new User("nickname0", "https://test.org/user0.jpg", "user0@email.com", LoginType.KAKAO)); + Article article = articleRepository.save(new Article(user, true, 2024, 1, "contents")); + ArticleLike articleLike = articleLikeRepository.save(new ArticleLike(article, user)); + + //when : 자신의 공개 기록장에 좋아요 취소 요청 시 + String url = "/article/" + TsidUtil.toString(article.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 좋아요는 성공적으로 삭제되어야 한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(200); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(0); + softAssertions.assertThat(articleLikeRepository.findById(articleLike.getId())).isEmpty(); + } + + @Test + @DisplayName("성공 - 자신의 비공개 기록장") + void test2() { + //given + User user = userRepository.save(new User("nickname0", "https://test.org/user0.jpg", "user0@email.com", LoginType.KAKAO)); + Article article = articleRepository.save(new Article(user, true, 2024, 1, "contents")); + ArticleLike articleLike = articleLikeRepository.save(new ArticleLike(article, user)); + + //when : 자신의 비공개 기록장에 좋아요 취소 요청 시 + String url = "/article/" + TsidUtil.toString(article.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 좋아요는 성공적으로 삭제되어야 한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(200); + softAssertions.assertThat(articleLikeRepository.findById(articleLike.getId())).isEmpty(); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(0); + } + + @Test + @DisplayName("성공 - 친구의 공개 기록장") + void test3() { + //given + User user = userRepository.save(new User("nickname", "https://test.org/user0.jpg", "user1@email.com", LoginType.KAKAO)); + User friend = userRepository.save(new User("friend", "https://test.org/user1.jpg", "user2@email.com", LoginType.KAKAO)); + Article friendArticle = articleRepository.save(new Article(friend, true, 2024, 1, "contents")); + makeFriendships(user, friend); + ArticleLike articleLike = articleLikeRepository.save(new ArticleLike(friendArticle, user)); + + //when : 친구의 공개 기록장에 좋아요 취소 요청 시 + String url = "/article/" + TsidUtil.toString(friendArticle.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 좋아요는 성공적으로 삭제되어야 한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(200); + softAssertions.assertThat(articleLikeRepository.findById(articleLike.getId())).isEmpty(); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(0); + } + + @Test + @DisplayName("실패 - 친구의 비공개 기록장") + void test4() { + //given + User user = userRepository.save(new User("nickname1", "https://test.org/user0.jpg", "user1@email.com", LoginType.KAKAO)); + User friend = userRepository.save(new User("friend", "https://test.org/user1.jpg", "user2@email.com", LoginType.KAKAO)); + Article friendArticle = articleRepository.save(new Article(friend, false, 2024, 1, "contents")); + makeFriendships(user, friend); + ArticleLike articleLike = articleLikeRepository.save(new ArticleLike(friendArticle, user)); + + //when : 친구의 비공개 기록장에 좋아요 취소 요청 시 + String url = "/article/" + TsidUtil.toString(friendArticle.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 요청은 거절되어야 한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(403); + softAssertions.assertThat(articleLikeRepository.findById(articleLike.getId())).isPresent(); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(1); + } + + @Test + @DisplayName("실패 - 타인의 기록장") + void test5() { + //given : 상대방의 기록장에 좋아요를 눌렀었지만 현재는 친구가 아닌 경우 + User user = userRepository.save(new User("nickname1", "https://test.org/user0.jpg", "user1@email.com", LoginType.KAKAO)); + User stranger = userRepository.save( + new User("nickname2", "https://test.org/user1.jpg", "user2@email.com", LoginType.KAKAO)); + Article strangerArticle = articleRepository.save(new Article(stranger, true, 2024, 1, "contents")); + makeFriendships(user, stranger); + ArticleLike articleLike = articleLikeRepository.save(new ArticleLike(strangerArticle, user)); + cancelFriendships(user, stranger); + + //when : 해당 좋아요에 대한 취소 요청 시 + String url = "/article/" + TsidUtil.toString(strangerArticle.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 요청은 거절되어야 한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(403); + softAssertions.assertThat(articleLikeRepository.findById(articleLike.getId())).isPresent(); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(1); + } + + @Test + @DisplayName("성공 - 친구의 취소 중복 요청") + void test6() { + //given : 회원이 친구의 공개 기록장에 좋아요를 누르지 않은 상태에서 + User user = userRepository.save(new User("nickname1", "https://test.org/user0.jpg", "user1@email.com", LoginType.KAKAO)); + User friend = userRepository.save(new User("friend", "https://test.org/user1.jpg", "user2@email.com", LoginType.KAKAO)); + Article friendArticle = articleRepository.save(new Article(friend, true, 2024, 1, "contents")); + makeFriendships(user, friend); + + //when : 친구의 공개 기록장에 좋아요 취소 요청 시 + String url = "/article/" + TsidUtil.toString(friendArticle.getId()) + "/like"; + ExtractableResponse response = delete(url, user.getId(), null); + + //then : 요청은 성공한다 + softAssertions.assertThat(response.statusCode()).isEqualTo(200); + softAssertions.assertThat(articleLikeRepository.count()).isEqualTo(0); + } + +}