diff --git a/src/main/java/com/semosan/api/domain/community/comment/entity/Comment.java b/src/main/java/com/semosan/api/domain/community/comment/entity/Comment.java index 2203dd4f..cdf1e1d6 100644 --- a/src/main/java/com/semosan/api/domain/community/comment/entity/Comment.java +++ b/src/main/java/com/semosan/api/domain/community/comment/entity/Comment.java @@ -72,4 +72,8 @@ public void softDelete() { public boolean isReply() { return this.parent != null; } + + public boolean isOwnedBy(Long userId) { + return author != null && userId != null && userId.equals(author.getId()); + } } diff --git a/src/main/java/com/semosan/api/domain/community/comment/service/CommentService.java b/src/main/java/com/semosan/api/domain/community/comment/service/CommentService.java index 931388c7..18a37d80 100644 --- a/src/main/java/com/semosan/api/domain/community/comment/service/CommentService.java +++ b/src/main/java/com/semosan/api/domain/community/comment/service/CommentService.java @@ -87,7 +87,7 @@ public List getReplies(Long parentId, Long viewerId) { @Transactional public void delete(Long commentId, Long requesterId) { Comment comment = findActiveCommentOrThrow(commentId); - if (!comment.getAuthor().getId().equals(requesterId)) { + if (!comment.isOwnedBy(requesterId)) { throw new GeneralException(ErrorStatus.COMMENT_FORBIDDEN); } comment.softDelete(); diff --git a/src/main/java/com/semosan/api/domain/community/post/entity/Post.java b/src/main/java/com/semosan/api/domain/community/post/entity/Post.java index 34b1684e..fa4821fc 100644 --- a/src/main/java/com/semosan/api/domain/community/post/entity/Post.java +++ b/src/main/java/com/semosan/api/domain/community/post/entity/Post.java @@ -44,4 +44,8 @@ public void increaseViewCount() { public void softDelete() { this.deleted = true; } + + public boolean isOwnedBy(Long userId) { + return author != null && userId != null && userId.equals(author.getId()); + } } diff --git a/src/main/java/com/semosan/api/domain/community/post/service/FreePostReportService.java b/src/main/java/com/semosan/api/domain/community/post/service/FreePostReportService.java index 85e39ed2..756a7890 100644 --- a/src/main/java/com/semosan/api/domain/community/post/service/FreePostReportService.java +++ b/src/main/java/com/semosan/api/domain/community/post/service/FreePostReportService.java @@ -42,7 +42,7 @@ public FreePostReport report(Long reporterId, Long postId, FreePostReportReason User reporter = findReporterOrThrow(reporterId); FreePost post = findPostOrThrow(postId); - if (post.getAuthor().getId().equals(reporterId)) { + if (post.isOwnedBy(reporterId)) { throw new GeneralException(ErrorStatus.FREE_POST_REPORT_SELF_NOT_ALLOWED); } if (freePostReportRepository.existsByReporter_IdAndPost_Id(reporterId, postId)) { diff --git a/src/main/java/com/semosan/api/domain/community/post/service/FreePostService.java b/src/main/java/com/semosan/api/domain/community/post/service/FreePostService.java index bb192c8c..075067e0 100644 --- a/src/main/java/com/semosan/api/domain/community/post/service/FreePostService.java +++ b/src/main/java/com/semosan/api/domain/community/post/service/FreePostService.java @@ -93,7 +93,7 @@ public FreePostDetailResponse getDetail(Long viewerId, Long postId) { @Transactional public void delete(Long postId, Long requesterId) { FreePost post = findActivePostOrThrow(postId); - if (!post.getAuthor().getId().equals(requesterId)) { + if (!post.isOwnedBy(requesterId)) { throw new GeneralException(ErrorStatus.POST_FORBIDDEN); } post.softDelete(); diff --git a/src/main/java/com/semosan/api/domain/community/post/service/RecordPostService.java b/src/main/java/com/semosan/api/domain/community/post/service/RecordPostService.java index 83218cc5..6b826bd3 100644 --- a/src/main/java/com/semosan/api/domain/community/post/service/RecordPostService.java +++ b/src/main/java/com/semosan/api/domain/community/post/service/RecordPostService.java @@ -58,7 +58,7 @@ public RecordPost getDetail(Long postId) { @Transactional public void delete(Long postId, Long requesterId) { RecordPost post = findActivePostOrThrow(postId); - if (!post.getAuthor().getId().equals(requesterId)) { + if (!post.isOwnedBy(requesterId)) { throw new GeneralException(ErrorStatus.POST_FORBIDDEN); } post.softDelete(); diff --git a/src/test/java/com/semosan/api/domain/community/comment/service/CommentServiceTest.java b/src/test/java/com/semosan/api/domain/community/comment/service/CommentServiceTest.java index 2b291f4e..4c9657f4 100644 --- a/src/test/java/com/semosan/api/domain/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/semosan/api/domain/community/comment/service/CommentServiceTest.java @@ -1,5 +1,7 @@ package com.semosan.api.domain.community.comment.service; +import com.semosan.api.common.exception.GeneralException; +import com.semosan.api.common.status.ErrorStatus; import com.semosan.api.domain.community.comment.entity.Comment; import com.semosan.api.domain.community.comment.repository.CommentRepository; import com.semosan.api.domain.community.notification.service.CommunityNotificationService; @@ -20,6 +22,7 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -91,6 +94,36 @@ void replySavesReplyAndDelegatesNotification() throws Exception { .sendReplyNotification(post, replyAuthor, parent, mentionedUser, result); } + @Test + void deleteSoftDeletesWhenRequesterOwnsComment() throws Exception { + User postAuthor = user(1L, "post-author"); + User commentAuthor = user(2L, "comment-author"); + FreePost post = freePost(10L, postAuthor, "제목", "본문"); + Comment comment = comment(100L, post, commentAuthor, "댓글"); + + when(commentRepository.findByIdAndDeletedFalse(100L)).thenReturn(Optional.of(comment)); + + commentService.delete(100L, 2L); + + assertThat(comment.isDeleted()).isTrue(); + } + + @Test + void deleteThrowsWhenRequesterDoesNotOwnComment() throws Exception { + User postAuthor = user(1L, "post-author"); + User commentAuthor = user(2L, "comment-author"); + FreePost post = freePost(10L, postAuthor, "제목", "본문"); + Comment comment = comment(100L, post, commentAuthor, "댓글"); + + when(commentRepository.findByIdAndDeletedFalse(100L)).thenReturn(Optional.of(comment)); + + assertThatThrownBy(() -> commentService.delete(100L, 3L)) + .isInstanceOf(GeneralException.class) + .extracting("errorStatus") + .isEqualTo(ErrorStatus.COMMENT_FORBIDDEN); + assertThat(comment.isDeleted()).isFalse(); + } + private User user(Long id, String nickname) { User user = User.createTestUser(nickname, DeviceType.IOS); ReflectionTestUtils.setField(user, "id", id); diff --git a/src/test/java/com/semosan/api/domain/community/post/service/FreePostServiceTest.java b/src/test/java/com/semosan/api/domain/community/post/service/FreePostServiceTest.java index c26e6dc3..2616e8c6 100644 --- a/src/test/java/com/semosan/api/domain/community/post/service/FreePostServiceTest.java +++ b/src/test/java/com/semosan/api/domain/community/post/service/FreePostServiceTest.java @@ -112,6 +112,32 @@ void getDetailThrowsWhenViewerBlockedAuthor() throws Exception { verify(postImageRepository, never()).findByPostOrderBySortOrderAsc(post); } + @Test + void deleteSoftDeletesWhenRequesterOwnsPost() throws Exception { + User author = user(2L, "author"); + FreePost post = freePost(10L, author, "제목", "본문"); + + when(freePostRepository.findById(10L)).thenReturn(Optional.of(post)); + + freePostService.delete(10L, 2L); + + assertThat(post.isDeleted()).isTrue(); + } + + @Test + void deleteThrowsWhenRequesterDoesNotOwnPost() throws Exception { + User author = user(2L, "author"); + FreePost post = freePost(10L, author, "제목", "본문"); + + when(freePostRepository.findById(10L)).thenReturn(Optional.of(post)); + + assertThatThrownBy(() -> freePostService.delete(10L, 3L)) + .isInstanceOf(GeneralException.class) + .extracting("errorStatus") + .isEqualTo(ErrorStatus.POST_FORBIDDEN); + assertThat(post.isDeleted()).isFalse(); + } + private User user(Long id, String oauthId) { User user = User.createTestUser(oauthId, DeviceType.IOS); ReflectionTestUtils.setField(user, "id", id); diff --git a/src/test/java/com/semosan/api/domain/community/post/service/RecordPostServiceTest.java b/src/test/java/com/semosan/api/domain/community/post/service/RecordPostServiceTest.java new file mode 100644 index 00000000..c7623d72 --- /dev/null +++ b/src/test/java/com/semosan/api/domain/community/post/service/RecordPostServiceTest.java @@ -0,0 +1,79 @@ +package com.semosan.api.domain.community.post.service; + +import com.semosan.api.common.exception.GeneralException; +import com.semosan.api.common.status.ErrorStatus; +import com.semosan.api.domain.community.post.entity.RecordPost; +import com.semosan.api.domain.community.post.repository.RecordPostRepository; +import com.semosan.api.domain.hiking.repository.HikingMemberRepository; +import com.semosan.api.domain.hiking.repository.HikingRecordRepository; +import com.semosan.api.domain.user.entity.User; +import com.semosan.api.domain.user.enums.user.DeviceType; +import com.semosan.api.domain.user.service.UserReader; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RecordPostServiceTest { + + @Mock + private RecordPostRepository recordPostRepository; + + @Mock + private HikingRecordRepository hikingRecordRepository; + + @Mock + private HikingMemberRepository hikingMemberRepository; + + @Mock + private UserReader userReader; + + @InjectMocks + private RecordPostService recordPostService; + + @Test + void deleteSoftDeletesWhenRequesterOwnsPost() { + RecordPost post = recordPost(10L, user(2L, "author")); + + when(recordPostRepository.findById(10L)).thenReturn(Optional.of(post)); + + recordPostService.delete(10L, 2L); + + assertThat(post.isDeleted()).isTrue(); + } + + @Test + void deleteThrowsWhenRequesterDoesNotOwnPost() { + RecordPost post = recordPost(10L, user(2L, "author")); + + when(recordPostRepository.findById(10L)).thenReturn(Optional.of(post)); + + assertThatThrownBy(() -> recordPostService.delete(10L, 3L)) + .isInstanceOf(GeneralException.class) + .extracting("errorStatus") + .isEqualTo(ErrorStatus.POST_FORBIDDEN); + assertThat(post.isDeleted()).isFalse(); + } + + private User user(Long id, String nickname) { + User user = User.createTestUser(nickname, DeviceType.IOS); + ReflectionTestUtils.setField(user, "id", id); + ReflectionTestUtils.setField(user, "nickname", nickname); + return user; + } + + private RecordPost recordPost(Long id, User author) { + RecordPost post = RecordPost.create(author, "본문", null); + ReflectionTestUtils.setField(post, "id", id); + return post; + } +}