From 3c0c7094d041eee46aa1f28ccf21f15113234596 Mon Sep 17 00:00:00 2001 From: king_0417 <73704053+gywns0417@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:58:47 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 댓글 CRUD 예외 처리 구현 * style: TODO-Comment-BalanceOption 연관 필요 * refactor: 댓글 컨트롤러 가독성 개선 * feat: PR 리뷰에 따른 코드 수정 * test: 댓글 예외 처리 단위 테스트 작성 및 통과 완료 --- .../global/exception/ErrorCode.java | 5 ++ .../comment/application/CommentService.java | 51 ++++++++--- .../module/comment/dto/CommentResponse.java | 1 + .../presentation/CommentController.java | 53 ++++-------- .../application/CommentServiceTest.java | 85 ++++++++++++++++++- 5 files changed, 143 insertions(+), 52 deletions(-) diff --git a/src/main/java/balancetalk/global/exception/ErrorCode.java b/src/main/java/balancetalk/global/exception/ErrorCode.java index 677537499..3fde1b40d 100644 --- a/src/main/java/balancetalk/global/exception/ErrorCode.java +++ b/src/main/java/balancetalk/global/exception/ErrorCode.java @@ -14,11 +14,16 @@ public enum ErrorCode { EXPIRED_POST_DEADLINE(BAD_REQUEST, "투표가 이미 종료된 게시글입니다."), UNMODIFIABLE_VOTE(BAD_REQUEST, "투표 수정이 불가능한 게시글입니다."), + // 403 + FORBIDDEN_COMMENT_MODIFY(FORBIDDEN, "댓글 수정 권한이 없습니다."), // TODO : Spring Security 적용 후 적용 필요 + FORBIDDEN_COMMENT_DELETE(FORBIDDEN, "댓글 삭제 권한이 없습니다."), // TODO : SecurityContextHolder 사용 예정 + // 404 NOT_FOUND_POST(NOT_FOUND, "존재하지 않는 게시글입니다."), NOT_FOUND_BALANCE_OPTION(NOT_FOUND, "존재하지 않는 선택지입니다."), NOT_FOUND_MEMBER(NOT_FOUND, "존재하지 않는 회원입니다."), NOT_FOUND_VOTE(NOT_FOUND, "해당 게시글에서 투표한 기록이 존재하지 않습니다."), + NOT_FOUND_COMMENT(NOT_FOUND, "존재하지 않는 댓글입니다."), // 409 ALREADY_VOTE(CONFLICT, "투표는 한 번만 가능합니다."), diff --git a/src/main/java/balancetalk/module/comment/application/CommentService.java b/src/main/java/balancetalk/module/comment/application/CommentService.java index 51cf95b48..74795914d 100644 --- a/src/main/java/balancetalk/module/comment/application/CommentService.java +++ b/src/main/java/balancetalk/module/comment/application/CommentService.java @@ -1,5 +1,9 @@ package balancetalk.module.comment.application; +import static balancetalk.global.exception.ErrorCode.NOT_FOUND_BALANCE_OPTION; +import static balancetalk.global.exception.ErrorCode.NOT_FOUND_COMMENT; +import static balancetalk.global.exception.ErrorCode.NOT_FOUND_MEMBER; +import static balancetalk.global.exception.ErrorCode.NOT_FOUND_POST; import balancetalk.global.exception.BalanceTalkException; import balancetalk.global.exception.ErrorCode; import balancetalk.module.comment.domain.Comment; @@ -10,6 +14,7 @@ import balancetalk.module.comment.dto.CommentResponse; import balancetalk.module.member.domain.Member; import balancetalk.module.member.domain.MemberRepository; +import balancetalk.module.post.domain.BalanceOption; import balancetalk.module.post.domain.BalanceOptionRepository; import balancetalk.module.post.domain.Post; import balancetalk.module.post.domain.PostRepository; @@ -31,23 +36,19 @@ public class CommentService { private final CommentLikeRepository commentLikeRepository; public CommentResponse createComment(CommentCreateRequest request, Long postId) { - Member member = memberRepository.findById(request.getMemberId()).orElseThrow(() -> new RuntimeException("Member not found")); - Post post = postRepository.findById(postId).orElseThrow(() -> new RuntimeException("Post not found")); - /* BalanceOption balanceOption = post.getOptions().stream() - .filter(option -> option.getId().equals(request.getBalanceOptionId())) - .findFirst() - .orElseThrow(() -> new RuntimeException("BalanceOption not found")); - //TODO : 추후 밸런스 옵션 구현 시 사용할 기능 - */ + Member member = validateMemberId(request); + Post post = validatePostId(postId); + validateBalanceOptionId(request, post); Comment comment = request.toEntity(member, post); comment = commentRepository.save(comment); - return CommentResponse.fromEntity(comment); } @Transactional(readOnly = true) - public List readCommentsByPostId(Long postId) { + public List findAll(Long postId) { // TODO: 탈퇴한 회원의 정보는 어떻게 표시되는가? + validatePostId(postId); + List comments = commentRepository.findByPostId(postId); return comments.stream() .map(CommentResponse::fromEntity) @@ -55,17 +56,39 @@ public List readCommentsByPostId(Long postId) { } public Comment updateComment(Long commentId, String content) { - Comment comment = commentRepository.findById(commentId) - .orElseThrow(() -> new RuntimeException("Comment not found")); - comment.updateContent(content); + Comment comment = validateCommentId(commentId); - return commentRepository.save(comment); + comment.updateContent(content); + return comment; } public void deleteComment(Long commentId) { + validateCommentId(commentId); commentRepository.deleteById(commentId); } + + private Member validateMemberId(CommentCreateRequest request) { // TODO: validate 메서드 분리 재고 + return memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_MEMBER)); + } + + private Post validatePostId(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_POST)); + } + + private BalanceOption validateBalanceOptionId(CommentCreateRequest request, Post post) { + return post.getOptions().stream() + .filter(option -> option.getId().equals(request.getBalanceOptionId())) + .findFirst() + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_BALANCE_OPTION)); + } + + private Comment validateCommentId(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new BalanceTalkException(NOT_FOUND_COMMENT)); + public Long likeComment(Long postId, Long commentId, Long memberId) { Comment comment = commentRepository.findById(commentId) .orElseThrow(); // TODO 예외 처리 diff --git a/src/main/java/balancetalk/module/comment/dto/CommentResponse.java b/src/main/java/balancetalk/module/comment/dto/CommentResponse.java index 734f89b82..714ccf1fa 100644 --- a/src/main/java/balancetalk/module/comment/dto/CommentResponse.java +++ b/src/main/java/balancetalk/module/comment/dto/CommentResponse.java @@ -14,6 +14,7 @@ public class CommentResponse { private String content; private String memberName; private Long postId; + // TODO : selectedOptionId 추가, 엔티티 연관 필요 private int likeCount; private LocalDateTime createdAt; private LocalDateTime lastModifiedAt; diff --git a/src/main/java/balancetalk/module/comment/presentation/CommentController.java b/src/main/java/balancetalk/module/comment/presentation/CommentController.java index 2ded6fea0..da7166a53 100644 --- a/src/main/java/balancetalk/module/comment/presentation/CommentController.java +++ b/src/main/java/balancetalk/module/comment/presentation/CommentController.java @@ -8,14 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; 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.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/posts/{postId}/comments") @@ -24,45 +17,31 @@ public class CommentController { private final CommentService commentService; + @ResponseStatus(HttpStatus.CREATED) @PostMapping - public ResponseEntity createComment(@PathVariable Long postId, @RequestBody CommentCreateRequest request) { - try { - CommentResponse createdCommentResponse = commentService.createComment(request, postId); - return new ResponseEntity<>(createdCommentResponse, HttpStatus.CREATED); - } catch (Exception e) { - return ResponseEntity.badRequest().body(e.getMessage()); //TODO : NULL 처리? - } + public String createComment(@PathVariable Long postId, @RequestBody CommentCreateRequest request) { + commentService.createComment(request, postId); + return "댓글이 정상적으로 작성되었습니다."; } + @ResponseStatus(HttpStatus.OK) @GetMapping() - public ResponseEntity getCommentsByPostId(@PathVariable Long postId) { - try { - List comments = commentService.readCommentsByPostId(postId); - return ResponseEntity.ok(comments); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); - } + public List findAllCommentsByPostId(@PathVariable Long postId) { + return commentService.findAll(postId); } + @ResponseStatus(HttpStatus.OK) @PutMapping("/{commentId}") - public ResponseEntity updateComment(@PathVariable Long commentId, @RequestBody CommentCreateRequest request) { - try { - Comment updatedComment = commentService.updateComment(commentId, request.getContent()); - CommentResponse response = CommentResponse.fromEntity(updatedComment); - return ResponseEntity.ok(response); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); - } + public String updateComment(@PathVariable Long commentId, @RequestBody CommentCreateRequest request) { + commentService.updateComment(commentId, request.getContent()); + return "댓글이 정상적으로 수정되었습니다."; } + @ResponseStatus(HttpStatus.NO_CONTENT) @DeleteMapping("/{commentId}") - public ResponseEntity deleteComment(@PathVariable Long commentId) { - try { - commentService.deleteComment(commentId); - return ResponseEntity.noContent().build(); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); - } + public String deleteComment(@PathVariable Long commentId) { + commentService.deleteComment(commentId); + return "댓글이 정상적으로 삭제되었습니다."; } @PostMapping("/{commentId}/likes") diff --git a/src/test/java/balancetalk/module/comment/application/CommentServiceTest.java b/src/test/java/balancetalk/module/comment/application/CommentServiceTest.java index 5026d0719..ba70c1c0e 100644 --- a/src/test/java/balancetalk/module/comment/application/CommentServiceTest.java +++ b/src/test/java/balancetalk/module/comment/application/CommentServiceTest.java @@ -84,7 +84,7 @@ void readCommentsByPostId_Success() { when(commentRepository.findByPostId(postId)).thenReturn(comments); // when - List responses = commentService.readCommentsByPostId(postId); + List responses = commentService.findAll(postId); // then assertThat(responses).hasSize(comments.size()); @@ -131,6 +131,89 @@ void deleteComment_Success() { } @Test + @DisplayName("댓글 생성 실패 - 회원을 찾을 수 없음") + void createComment_Fail_MemberNotFound() { + // given + Long memberId = 1L; + Long postId = 1L; + CommentCreateRequest request = new CommentCreateRequest("댓글 내용입니다.", memberId, null); + + when(memberRepository.findById(memberId)).thenReturn(Optional.empty()); + + // when + + // then + assertThatThrownBy(() -> commentService.createComment(request, postId)) + .isInstanceOf(BalanceTalkException.class) + .hasMessageContaining("존재하지 않는 회원입니다."); + } + + @Test + @DisplayName("댓글 생성 실패 - 게시글을 찾을 수 없음") + void createComment_Fail_PostNotFound() { + // given + Long memberId = 1L; + Long postId = 1L; + CommentCreateRequest request = new CommentCreateRequest("댓글 내용입니다.", memberId, null); + + when(memberRepository.findById(memberId)).thenReturn(Optional.of(Member.builder().id(memberId).build())); + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // when + + // then + assertThatThrownBy(() -> commentService.createComment(request, postId)) + .isInstanceOf(BalanceTalkException.class) + .hasMessageContaining("존재하지 않는 게시글입니다."); + } + + @Test + @DisplayName("댓글 수정 실패 - 댓글을 찾을 수 없음") + void updateComment_Fail_CommentNotFound() { + // given + Long commentId = 1L; + String updatedContent = "업데이트된 댓글 내용"; + + when(commentRepository.findById(commentId)).thenReturn(Optional.empty()); + + // when + + // then + assertThatThrownBy(() -> commentService.updateComment(commentId, updatedContent)) + .isInstanceOf(BalanceTalkException.class) + .hasMessageContaining("존재하지 않는 댓글입니다."); + } + + @Test + @DisplayName("댓글 삭제 실패 - 댓글을 찾을 수 없음") + void deleteComment_Fail_CommentNotFound() { + // given + Long commentId = 1L; + + when(commentRepository.findById(commentId)).thenReturn(Optional.empty()); + + // when + + // then + assertThatThrownBy(() -> commentService.deleteComment(commentId)) + .isInstanceOf(BalanceTalkException.class) + .hasMessageContaining("존재하지 않는 댓글입니다."); + } + + @Test + @DisplayName("게시글에 대한 댓글 조회 실패 - 게시글을 찾을 수 없음") +void readCommentsByPostId_Fail_PostNotFound() { + // given + Long postId = 1L; + + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // when + + // then + assertThatThrownBy(() -> commentService.findAll(postId)) + .isInstanceOf(BalanceTalkException.class) + .hasMessageContaining("존재하지 않는 게시글입니다."); @DisplayName("사용자가 특정 댓글에 추천을 누르면 해당 댓글 id가 반환된다.") void createCommentLike_Success() { // given