Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

댓글 수정 기능 구현 #171

Merged
merged 5 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q
이 코드를 붙일때와 안붙일 때의 차이점이 궁금합니다! swagger에서 이 차이점이 무엇인지 잘 안보여서요.
(´。_。`)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소한 부분이긴 합니다 ㅎㅎ

스크린샷 2023-07-31 오후 2 35 07

붙이지 않은 경우 응답 형식을 확인할 수 없지만,

스크린샷 2023-07-31 오후 2 35 37

붙이게 된다면 응답 형식을 확인할 수 있습니다!

),
@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