Skip to content

Commit

Permalink
댓글 수정 기능 구현 (#171)
Browse files Browse the repository at this point in the history
* feat: (#130) 댓글 수정 기능 구현

* feat: (#130) 댓글 수정 로직 구현

* feat: (#130) 댓글 수정 API 구현

* chore: (#130) 코드 컨벤션 정리

* refactor: (#130) 테스트명 오타 수정
  • Loading branch information
woo-chang authored Jul 31, 2023
1 parent 2175fe4 commit e69419c
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.post.dto.request.CommentRegisterRequest;
import com.votogether.domain.post.dto.request.CommentUpdateRequest;
import com.votogether.domain.post.service.PostCommentService;
import com.votogether.exception.ExceptionResponse;
import com.votogether.global.jwt.Auth;
Expand All @@ -19,6 +20,7 @@
import org.springframework.web.bind.annotation.DeleteMapping;
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;
Expand Down Expand Up @@ -50,9 +52,39 @@ public ResponseEntity<Void> createComment(
return ResponseEntity.status(HttpStatus.CREATED).build();
}

@Operation(summary = "게시글 댓글 수정", description = "게시글 댓글을 수정한다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "게시글 댓글 수정 성공"),
@ApiResponse(
responseCode = "400",
description = "게시글에 속하지 않은 댓글, 올바르지 않은 댓글 작성자",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 게시글, 존재하지 않는 댓글",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
)
})
@PutMapping("/{postId}/comments/{commentId}")
public ResponseEntity<Void> updateComment(
@PathVariable @Parameter(description = "게시글 ID") final Long postId,
@PathVariable @Parameter(description = "댓글 ID") final Long commentId,
@RequestBody @Valid final CommentUpdateRequest commentUpdateRequest,
@Auth final Member member
) {
postCommentService.updateComment(postId, commentId, commentUpdateRequest, member);
return ResponseEntity.ok().build();
}

@Operation(summary = "게시글 댓글 삭제", description = "게시글 댓글을 삭제한다.")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "게시글 댓글 삭제 성공"),
@ApiResponse(
responseCode = "400",
description = "게시글에 속하지 않은 댓글, 올바르지 않은 댓글 작성자",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 게시글, 존재하지 않는 댓글",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.votogether.domain.post.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

@Schema(description = "게시글 수정 요청")
public record CommentUpdateRequest(
@Schema(description = "댓글 수정 내용", example = "content")
@NotBlank(message = "댓글 내용은 존재해야 합니다.")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,12 @@ public void validateBelong(final Post post) {
}
}

public void updateContent(final String content) {
this.content = new Content(content);
}

public String getContent() {
return content.getValue();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.votogether.domain.member.entity.Member;
import com.votogether.domain.post.dto.request.CommentRegisterRequest;
import com.votogether.domain.post.dto.request.CommentUpdateRequest;
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.comment.Comment;
import com.votogether.domain.post.exception.CommentExceptionType;
Expand Down Expand Up @@ -37,6 +38,24 @@ public void createComment(
post.addComment(comment);
}

@Transactional
public void updateComment(
final Long postId,
final Long commentId,
final CommentUpdateRequest commentUpdateRequest,
final Member member
) {
final Post post = postRepository.findById(postId)
.orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND));
final Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new NotFoundException(CommentExceptionType.COMMENT_NOT_FOUND));

comment.validateBelong(post);
comment.validateWriter(member);

comment.updateContent(commentUpdateRequest.content());
}

@Transactional
public void deleteComment(final Long postId, final Long commentId, final Member member) {
final Post post = postRepository.findById(postId)
Expand All @@ -49,5 +68,4 @@ public void deleteComment(final Long postId, final Long commentId, final Member

commentRepository.delete(comment);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.service.MemberService;
import com.votogether.domain.post.dto.request.CommentRegisterRequest;
import com.votogether.domain.post.dto.request.CommentUpdateRequest;
import com.votogether.domain.post.service.PostCommentService;
import com.votogether.fixtures.MemberFixtures;
import com.votogether.global.jwt.TokenPayload;
Expand Down Expand Up @@ -121,6 +122,81 @@ void createComment() throws Exception {

}

@Nested
@DisplayName("게시글 댓글 수정")
class UpdateComment {

@ParameterizedTest
@ValueSource(strings = {"@", "a", "가"})
@DisplayName("게시글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.")
void invalidPostIDType(String postId) throws Exception {
// given
TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L);
given(tokenProcessor.resolveToken(anyString())).willReturn("token");
given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload);
given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get());
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
RestAssuredMockMvc.given().log().all()
.headers(HttpHeaders.AUTHORIZATION, "Bearer token")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.when().put("/posts/{postId}/comments/{commentId}", postId, 1L)
.then().log().all()
.status(HttpStatus.BAD_REQUEST)
.body("code", equalTo(-9998))
.body("message", containsString("postId는 Long 타입이 필요합니다."));
}

@ParameterizedTest
@ValueSource(strings = {"@", "a", "가"})
@DisplayName("댓글 ID가 Long 타입으로 변환할 수 없는 값이라면 400을 응답한다.")
void invalidCommentIDType(String commentId) throws Exception {
// given
TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L);
given(tokenProcessor.resolveToken(anyString())).willReturn("token");
given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload);
given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get());
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
RestAssuredMockMvc.given().log().all()
.headers(HttpHeaders.AUTHORIZATION, "Bearer token")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.when().put("/posts/{postId}/comments/{commentId}", 1L, commentId)
.then().log().all()
.status(HttpStatus.BAD_REQUEST)
.body("code", equalTo(-9998))
.body("message", containsString("commentId는 Long 타입이 필요합니다."));
}

@ParameterizedTest
@NullAndEmptySource
@DisplayName("수정할 댓글 내용이 비어있거나 존재하지 않으면 400을 응답한다.")
void nullAndEmptyCommentContent(String content) throws Exception {
// given
TokenPayload tokenPayload = new TokenPayload(1L, 1L, 1L);
given(tokenProcessor.resolveToken(anyString())).willReturn("token");
given(tokenProcessor.parseToken(anyString())).willReturn(tokenPayload);
given(memberService.findById(anyLong())).willReturn(MemberFixtures.MALE_20.get());
CommentUpdateRequest request = new CommentUpdateRequest(content);

// when, then
RestAssuredMockMvc.given().log().all()
.headers(HttpHeaders.AUTHORIZATION, "Bearer token")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.when().put("/posts/{postId}/comments/{commentId}", 1L, 1L)
.then().log().all()
.status(HttpStatus.BAD_REQUEST)
.body("code", equalTo(-9997))
.body("message", containsString("댓글 내용은 존재해야 합니다."));
}

}

@Nested
@DisplayName("게시글 댓글 삭제")
class DeleteComment {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,29 @@ void invalidPost() {
.hasMessage("댓글의 게시글 정보와 일치하지 않습니다.");
}

@Test
@DisplayName("댓글 수정 시 최대 글자를 초과하면 예외를 던진다.")
void updateContentWithInvalidContentLength() {
// given
PostBody body = PostBody.builder()
.title("title")
.content("content")
.build();
Post post = Post.builder()
.writer(MemberFixtures.FEMALE_20.get())
.postBody(body)
.deadline(LocalDateTime.now())
.build();
Comment comment = Comment.builder()
.member(MemberFixtures.MALE_20.get())
.post(post)
.content("hello")
.build();

// when, then
assertThatThrownBy(() -> comment.updateContent("a".repeat(501)))
.isInstanceOf(BadRequestException.class)
.hasMessage("유효하지 않은 댓글 길이입니다.");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.votogether.domain.member.entity.Member;
import com.votogether.domain.member.repository.MemberRepository;
import com.votogether.domain.post.dto.request.CommentRegisterRequest;
import com.votogether.domain.post.dto.request.CommentUpdateRequest;
import com.votogether.domain.post.entity.Post;
import com.votogether.domain.post.entity.PostBody;
import com.votogether.domain.post.entity.comment.Comment;
Expand Down Expand Up @@ -76,6 +77,135 @@ void createComment() {

}

@Nested
@DisplayName("게시글 댓글 수정")
class UpdateComment {

@Test
@DisplayName("존재하지 않는 게시글이라면 예외를 던진다.")
void emptyPost() {
// given
Member member = memberRepository.save(MemberFixtures.MALE_20.get());
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
assertThatThrownBy(() -> postCommentService.updateComment(-1L, 1L, request, member))
.isInstanceOf(NotFoundException.class)
.hasMessage("해당 게시글이 존재하지 않습니다.");
}

@Test
@DisplayName("존재하지 않는 댓글이라면 예외를 던진다.")
void emptyComment() {
// given
Member member = memberRepository.save(MemberFixtures.MALE_20.get());
Post post = postRepository.save(
Post.builder()
.writer(member)
.postBody(PostBody.builder().title("title").content("content").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
assertThatThrownBy(() -> postCommentService.updateComment(post.getId(), -1L, request, member))
.isInstanceOf(NotFoundException.class)
.hasMessage("해당 댓글이 존재하지 않습니다.");
}

@Test
@DisplayName("댓글의 게시글과 일치하지 않으면 예외를 던진다.")
void invalidBelongPost() {
// given
Member member = memberRepository.save(MemberFixtures.MALE_20.get());
Post postA = postRepository.save(
Post.builder()
.writer(member)
.postBody(PostBody.builder().title("titleA").content("contentA").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
Post postB = postRepository.save(
Post.builder()
.writer(member)
.postBody(PostBody.builder().title("titleB").content("contentB").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
Comment comment = commentRepository.save(
Comment.builder()
.member(member)
.post(postA)
.content("comment")
.build()
);
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
assertThatThrownBy(() -> postCommentService.updateComment(postB.getId(), comment.getId(), request, member))
.isInstanceOf(BadRequestException.class)
.hasMessage("댓글의 게시글 정보와 일치하지 않습니다.");
}

@Test
@DisplayName("댓글의 작성자가 아니라면 예외를 던진다.")
void invalidWriter() {
// given
Member memberA = memberRepository.save(MemberFixtures.MALE_20.get());
Member memberB = memberRepository.save(MemberFixtures.FEMALE_20.get());
Post post = postRepository.save(
Post.builder()
.writer(memberA)
.postBody(PostBody.builder().title("titleA").content("contentA").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
Comment comment = commentRepository.save(
Comment.builder()
.member(memberB)
.post(post)
.content("comment")
.build()
);
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when, then
assertThatThrownBy(() -> postCommentService.updateComment(post.getId(), comment.getId(), request, memberA))
.isInstanceOf(BadRequestException.class)
.hasMessage("댓글 작성자가 아닙니다.");
}

@Test
@DisplayName("게시글의 댓글을 수정한다.")
void deleteComment() {
// given
Member member = memberRepository.save(MemberFixtures.MALE_20.get());
Post post = postRepository.save(
Post.builder()
.writer(member)
.postBody(PostBody.builder().title("titleA").content("contentA").build())
.deadline(LocalDateTime.of(2100, 7, 12, 0, 0))
.build()
);
Comment comment = commentRepository.save(
Comment.builder()
.member(member)
.post(post)
.content("comment")
.build()
);
CommentUpdateRequest request = new CommentUpdateRequest("hello");

// when
postCommentService.updateComment(post.getId(), comment.getId(), request, member);

// then
assertThat(comment.getContent()).isEqualTo("hello");
}

}

@Nested
@DisplayName("게시글 댓글 삭제")
class DeleteComment {
Expand Down Expand Up @@ -145,7 +275,7 @@ void invalidBelongPost() {
}

@Test
@DisplayName("댓글의 작성자가 아니라면 예외르 던진다.")
@DisplayName("댓글의 작성자가 아니라면 예외를 던진다.")
void invalidWriter() {
// given
Member memberA = memberRepository.save(MemberFixtures.MALE_20.get());
Expand Down

0 comments on commit e69419c

Please sign in to comment.