Skip to content

Commit

Permalink
댓글 기능 예외 처리 구현
Browse files Browse the repository at this point in the history
* feat: 댓글 CRUD 예외 처리 구현

* style: TODO-Comment-BalanceOption 연관 필요

* refactor: 댓글 컨트롤러 가독성 개선

* feat: PR 리뷰에 따른 코드 수정

* test: 댓글 예외 처리 단위 테스트 작성 및 통과 완료
  • Loading branch information
gywns0417 authored Feb 15, 2024
1 parent 23082a4 commit 3c0c709
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 52 deletions.
5 changes: 5 additions & 0 deletions src/main/java/balancetalk/global/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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, "투표는 한 번만 가능합니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -31,41 +36,59 @@ 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<CommentResponse> readCommentsByPostId(Long postId) {
public List<CommentResponse> findAll(Long postId) { // TODO: 탈퇴한 회원의 정보는 어떻게 표시되는가?
validatePostId(postId);

List<Comment> comments = commentRepository.findByPostId(postId);
return comments.stream()
.map(CommentResponse::fromEntity)
.collect(Collectors.toList());
}

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 예외 처리
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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<CommentResponse> comments = commentService.readCommentsByPostId(postId);
return ResponseEntity.ok(comments);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
public List<CommentResponse> 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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void readCommentsByPostId_Success() {
when(commentRepository.findByPostId(postId)).thenReturn(comments);

// when
List<CommentResponse> responses = commentService.readCommentsByPostId(postId);
List<CommentResponse> responses = commentService.findAll(postId);

// then
assertThat(responses).hasSize(comments.size());
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3c0c709

Please sign in to comment.