diff --git a/src/main/java/com/back/domain/board/controller/CommentController.java b/src/main/java/com/back/domain/board/controller/CommentController.java index 9933aa2c..cb47bcc6 100644 --- a/src/main/java/com/back/domain/board/controller/CommentController.java +++ b/src/main/java/com/back/domain/board/controller/CommentController.java @@ -33,4 +33,37 @@ public ResponseEntity> createComment( response )); } + + // 댓글 수정 + @PutMapping("/{commentId}") + public ResponseEntity> updateComment( + @PathVariable Long postId, + @PathVariable Long commentId, + @RequestBody @Valid CommentRequest request, + @AuthenticationPrincipal CustomUserDetails user + ) { + CommentResponse response = commentService.updateComment(postId, commentId, request, user.getUserId()); + return ResponseEntity + .status(HttpStatus.OK) + .body(RsData.success( + "댓글이 수정되었습니다.", + response + )); + } + + // 댓글 삭제 + @DeleteMapping("/{commentId}") + public ResponseEntity> deleteComment( + @PathVariable Long postId, + @PathVariable Long commentId, + @AuthenticationPrincipal CustomUserDetails user + ) { + commentService.deleteComment(postId, commentId, user.getUserId()); + return ResponseEntity + .status(HttpStatus.OK) + .body(RsData.success( + "댓글이 삭제되었습니다.", + null + )); + } } diff --git a/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java index a249d222..abfaebb9 100644 --- a/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java +++ b/src/main/java/com/back/domain/board/controller/CommentControllerDocs.java @@ -141,4 +141,260 @@ ResponseEntity> createComment( @RequestBody CommentRequest request, @AuthenticationPrincipal CustomUserDetails user ); + + @Operation( + summary = "댓글 수정", + description = "로그인한 사용자가 자신이 작성한 댓글을 수정합니다." + ) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "댓글 수정 성공", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": true, + "code": "SUCCESS_200", + "message": "댓글이 수정되었습니다.", + "data": { + "commentId": 25, + "postId": 101, + "author": { + "id": 5, + "nickname": "홍길동" + }, + "content": "수정된 댓글 내용입니다.", + "createdAt": "2025-09-22T11:30:00", + "updatedAt": "2025-09-22T13:00:00" + } + } + """) + ) + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청 (필드 누락 등)", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMON_400", + "message": "잘못된 요청입니다.", + "data": null + } + """) + ) + ), + @ApiResponse( + responseCode = "401", + description = "인증 실패 (토큰 없음/잘못됨/만료)", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "토큰 없음", value = """ + { + "success": false, + "code": "AUTH_001", + "message": "인증이 필요합니다.", + "data": null + } + """), + @ExampleObject(name = "잘못된 토큰", value = """ + { + "success": false, + "code": "AUTH_002", + "message": "유효하지 않은 액세스 토큰입니다.", + "data": null + } + """), + @ExampleObject(name = "만료된 토큰", value = """ + { + "success": false, + "code": "AUTH_004", + "message": "만료된 액세스 토큰입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "403", + description = "권한 없음 (작성자 아님)", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMENT_002", + "message": "댓글 작성자만 수정/삭제할 수 있습니다.", + "data": null + } + """) + ) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글 또는 댓글", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "존재하지 않는 게시글", value = """ + { + "success": false, + "code": "POST_001", + "message": "존재하지 않는 게시글입니다.", + "data": null + } + """), + @ExampleObject(name = "존재하지 않는 댓글", value = """ + { + "success": false, + "code": "COMMENT_001", + "message": "존재하지 않는 댓글입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMON_500", + "message": "서버 오류가 발생했습니다.", + "data": null + } + """) + ) + ) + }) + ResponseEntity> updateComment( + @PathVariable Long postId, + @PathVariable Long commentId, + @RequestBody CommentRequest request, + @AuthenticationPrincipal CustomUserDetails user + ); + + @Operation( + summary = "댓글 삭제", + description = "로그인한 사용자가 자신이 작성한 댓글을 삭제합니다." + ) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "댓글 삭제 성공", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": true, + "code": "SUCCESS_200", + "message": "댓글이 삭제되었습니다.", + "data": null + } + """) + ) + ), + @ApiResponse( + responseCode = "401", + description = "인증 실패 (토큰 없음/잘못됨/만료)", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "토큰 없음", value = """ + { + "success": false, + "code": "AUTH_001", + "message": "인증이 필요합니다.", + "data": null + } + """), + @ExampleObject(name = "잘못된 토큰", value = """ + { + "success": false, + "code": "AUTH_002", + "message": "유효하지 않은 액세스 토큰입니다.", + "data": null + } + """), + @ExampleObject(name = "만료된 토큰", value = """ + { + "success": false, + "code": "AUTH_004", + "message": "만료된 액세스 토큰입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "403", + description = "권한 없음 (작성자 아님)", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMENT_002", + "message": "댓글 작성자만 수정/삭제할 수 있습니다.", + "data": null + } + """) + ) + ), + @ApiResponse( + responseCode = "404", + description = "존재하지 않는 게시글 또는 댓글", + content = @Content( + mediaType = "application/json", + examples = { + @ExampleObject(name = "존재하지 않는 게시글", value = """ + { + "success": false, + "code": "POST_001", + "message": "존재하지 않는 게시글입니다.", + "data": null + } + """), + @ExampleObject(name = "존재하지 않는 댓글", value = """ + { + "success": false, + "code": "COMMENT_001", + "message": "존재하지 않는 댓글입니다.", + "data": null + } + """) + } + ) + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류", + content = @Content( + mediaType = "application/json", + examples = @ExampleObject(value = """ + { + "success": false, + "code": "COMMON_500", + "message": "서버 오류가 발생했습니다.", + "data": null + } + """) + ) + ) + }) + ResponseEntity> deleteComment( + @PathVariable Long postId, + @PathVariable Long commentId, + @AuthenticationPrincipal CustomUserDetails user + ); } \ No newline at end of file diff --git a/src/main/java/com/back/domain/board/entity/Comment.java b/src/main/java/com/back/domain/board/entity/Comment.java index 90f16d34..2ea6d859 100644 --- a/src/main/java/com/back/domain/board/entity/Comment.java +++ b/src/main/java/com/back/domain/board/entity/Comment.java @@ -41,4 +41,10 @@ public Comment(Post post, User user, String content) { this.user = user; this.content = content; } + + // -------------------- 비즈니스 메서드 -------------------- + // 댓글 업데이트 + public void update(String content) { + this.content = content; + } } diff --git a/src/main/java/com/back/domain/board/service/CommentService.java b/src/main/java/com/back/domain/board/service/CommentService.java index 0dbccf6d..9dbc3d67 100644 --- a/src/main/java/com/back/domain/board/service/CommentService.java +++ b/src/main/java/com/back/domain/board/service/CommentService.java @@ -45,4 +45,57 @@ public CommentResponse createComment(Long postId, CommentRequest request, Long u commentRepository.save(comment); return CommentResponse.from(comment); } + + /** + * 댓글 수정 서비스 + * 1. Post 조회 + * 2. Comment 조회 + * 3. 작성자 검증 + * 4. Comment 업데이트 (내용) + * 5. CommentResponse 반환 + */ + public CommentResponse updateComment(Long postId, Long commentId, CommentRequest request, Long userId) { + // Post 조회 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + + // Comment 조회 + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_FOUND)); + + // 작성자 검증 + if (!comment.getUser().getId().equals(userId)) { + throw new CustomException(ErrorCode.COMMENT_NO_PERMISSION); + } + + // Comment 업데이트 + comment.update(request.content()); + + // 응답 반환 + return CommentResponse.from(comment); + } + + /** + * 댓글 삭제 서비스 + * 1. Post 조회 + * 2. Comment 조회 + * 3. 작성자 검증 + * 4. Comment 삭제 + */ + public void deleteComment(Long postId, Long commentId, Long userId) { + // Post 조회 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + + // Comment 조회 + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_FOUND)); + + // 작성자 검증 + if (!comment.getUser().getId().equals(userId)) { + throw new CustomException(ErrorCode.COMMENT_NO_PERMISSION); + } + + commentRepository.delete(comment); + } } diff --git a/src/main/java/com/back/global/exception/ErrorCode.java b/src/main/java/com/back/global/exception/ErrorCode.java index 03b96782..c7e2dc65 100644 --- a/src/main/java/com/back/global/exception/ErrorCode.java +++ b/src/main/java/com/back/global/exception/ErrorCode.java @@ -85,6 +85,8 @@ public enum ErrorCode { POST_NOT_FOUND(HttpStatus.NOT_FOUND, "POST_001", "존재하지 않는 게시글입니다."), POST_NO_PERMISSION(HttpStatus.FORBIDDEN, "POST_002", "게시글 작성자만 수정/삭제할 수 있습니다."), CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "POST_003", "존재하지 않는 카테고리입니다."), + COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMENT_001", "존재하지 않는 댓글입니다."), + COMMENT_NO_PERMISSION(HttpStatus.FORBIDDEN, "COMMENT_002", "댓글 작성자만 수정/삭제할 수 있습니다."), // ======================== 공통 에러 ======================== BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON_400", "잘못된 요청입니다."), diff --git a/src/test/java/com/back/domain/board/controller/CommentControllerTest.java b/src/test/java/com/back/domain/board/controller/CommentControllerTest.java index 33022ded..19efc375 100644 --- a/src/test/java/com/back/domain/board/controller/CommentControllerTest.java +++ b/src/test/java/com/back/domain/board/controller/CommentControllerTest.java @@ -1,7 +1,9 @@ package com.back.domain.board.controller; import com.back.domain.board.dto.CommentRequest; +import com.back.domain.board.entity.Comment; import com.back.domain.board.entity.Post; +import com.back.domain.board.repository.CommentRepository; import com.back.domain.board.repository.PostRepository; import com.back.domain.user.entity.User; import com.back.domain.user.entity.UserProfile; @@ -23,7 +25,7 @@ import java.time.LocalDate; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -42,6 +44,9 @@ class CommentControllerTest { @Autowired private PostRepository postRepository; + @Autowired + private CommentRepository commentRepository; + @Autowired private TestJwtTokenProvider testJwtTokenProvider; @@ -201,4 +206,319 @@ void createComment_noToken() throws Exception { .andExpect(jsonPath("$.code").value("AUTH_001")) .andExpect(jsonPath("$.message").value("인증이 필요합니다.")); } + + // ====================== 댓글 수정 테스트 ====================== + + @Test + @DisplayName("댓글 수정 성공 → 200 OK") + void updateComment_success() throws Exception { + // given: 유저 + 게시글 + 댓글 + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, "소개글", LocalDate.of(2000,1,1), 1000)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "원래 댓글 내용"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(user); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글 내용입니다."); + + // when + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.data.commentId").value(comment.getId())) + .andExpect(jsonPath("$.data.content").value("수정된 댓글 내용입니다.")) + .andExpect(jsonPath("$.data.author.nickname").value("홍길동")); + } + + @Test + @DisplayName("댓글 수정 실패 - 존재하지 않는 게시글 → 404 Not Found") + void updateComment_postNotFound() throws Exception { + // given + User user = User.createUser("writer2", "writer2@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(user); + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", 999L, comment.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("POST_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 게시글입니다.")); + } + + @Test + @DisplayName("댓글 수정 실패 - 존재하지 않는 댓글 → 404 Not Found") + void updateComment_commentNotFound() throws Exception { + // given + User user = User.createUser("writer3", "writer3@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + String accessToken = generateAccessToken(user); + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", post.getId(), 999L) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("COMMENT_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 댓글입니다.")); + } + + @Test + @DisplayName("댓글 수정 실패 - 작성자가 아님 → 403 Forbidden") + void updateComment_noPermission() throws Exception { + // given: 작성자와 다른 유저 + User writer = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + writer.setUserProfile(new UserProfile(writer, "작성자", null, null, null, 0)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User other = User.createUser("other", "other@example.com", passwordEncoder.encode("P@ssw0rd!")); + other.setUserProfile(new UserProfile(other, "다른사람", null, null, null, 0)); + other.setUserStatus(UserStatus.ACTIVE); + userRepository.save(other); + + Post post = new Post(writer, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, writer, "원래 댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(other); + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("COMMENT_002")) + .andExpect(jsonPath("$.message").value("댓글 작성자만 수정/삭제할 수 있습니다.")); + } + + @Test + @DisplayName("댓글 수정 실패 - 잘못된 요청(필드 누락) → 400 Bad Request") + void updateComment_badRequest() throws Exception { + // given + User user = User.createUser("writer4", "writer4@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자4", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(user); + + String invalidJson = """ + {} + """; + + // when & then + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(invalidJson)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("COMMON_400")) + .andExpect(jsonPath("$.message").value("잘못된 요청입니다.")); + } + + @Test + @DisplayName("댓글 수정 실패 - 토큰 없음 → 401 Unauthorized") + void updateComment_noToken() throws Exception { + // given + User user = User.createUser("writer5", "writer5@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자5", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + mvc.perform(put("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequest))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value("AUTH_001")) + .andExpect(jsonPath("$.message").value("인증이 필요합니다.")); + } + // ====================== 댓글 삭제 테스트 ====================== + + @Test + @DisplayName("댓글 삭제 성공 → 200 OK") + void deleteComment_success() throws Exception { + // given + User user = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "홍길동", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "삭제할 댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(user); + + // when & then + mvc.perform(delete("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .header("Authorization", "Bearer " + accessToken)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.code").value("SUCCESS_200")) + .andExpect(jsonPath("$.message").value("댓글이 삭제되었습니다.")); + } + + @Test + @DisplayName("댓글 삭제 실패 - 존재하지 않는 게시글 → 404 Not Found") + void deleteComment_postNotFound() throws Exception { + // given + User user = User.createUser("writer2", "writer2@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자2", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(user); + + // when & then + mvc.perform(delete("/api/posts/{postId}/comments/{commentId}", 999L, comment.getId()) + .header("Authorization", "Bearer " + accessToken)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("POST_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 게시글입니다.")); + } + + @Test + @DisplayName("댓글 삭제 실패 - 존재하지 않는 댓글 → 404 Not Found") + void deleteComment_commentNotFound() throws Exception { + // given + User user = User.createUser("writer3", "writer3@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자3", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + String accessToken = generateAccessToken(user); + + // when & then + mvc.perform(delete("/api/posts/{postId}/comments/{commentId}", post.getId(), 999L) + .header("Authorization", "Bearer " + accessToken)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value("COMMENT_001")) + .andExpect(jsonPath("$.message").value("존재하지 않는 댓글입니다.")); + } + + @Test + @DisplayName("댓글 삭제 실패 - 작성자가 아님 → 403 Forbidden") + void deleteComment_noPermission() throws Exception { + // given + User writer = User.createUser("writer", "writer@example.com", passwordEncoder.encode("P@ssw0rd!")); + writer.setUserProfile(new UserProfile(writer, "작성자", null, null, null, 0)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User other = User.createUser("other", "other@example.com", passwordEncoder.encode("P@ssw0rd!")); + other.setUserProfile(new UserProfile(other, "다른사람", null, null, null, 0)); + other.setUserStatus(UserStatus.ACTIVE); + userRepository.save(other); + + Post post = new Post(writer, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, writer, "원래 댓글"); + commentRepository.save(comment); + + String accessToken = generateAccessToken(other); + + // when & then + mvc.perform(delete("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId()) + .header("Authorization", "Bearer " + accessToken)) + .andDo(print()) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value("COMMENT_002")) + .andExpect(jsonPath("$.message").value("댓글 작성자만 수정/삭제할 수 있습니다.")); + } + + @Test + @DisplayName("댓글 삭제 실패 - 토큰 없음 → 401 Unauthorized") + void deleteComment_noToken() throws Exception { + // given + User user = User.createUser("writer4", "writer4@example.com", passwordEncoder.encode("P@ssw0rd!")); + user.setUserProfile(new UserProfile(user, "작성자4", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + // when & then + mvc.perform(delete("/api/posts/{postId}/comments/{commentId}", post.getId(), comment.getId())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.code").value("AUTH_001")) + .andExpect(jsonPath("$.message").value("인증이 필요합니다.")); + } } diff --git a/src/test/java/com/back/domain/board/service/CommentServiceTest.java b/src/test/java/com/back/domain/board/service/CommentServiceTest.java index 67a9fe5e..7d15dd3c 100644 --- a/src/test/java/com/back/domain/board/service/CommentServiceTest.java +++ b/src/test/java/com/back/domain/board/service/CommentServiceTest.java @@ -2,6 +2,7 @@ import com.back.domain.board.dto.CommentRequest; import com.back.domain.board.dto.CommentResponse; +import com.back.domain.board.entity.Comment; import com.back.domain.board.entity.Post; import com.back.domain.board.repository.CommentRepository; import com.back.domain.board.repository.PostRepository; @@ -98,4 +99,196 @@ void createComment_fail_postNotFound() { .isInstanceOf(CustomException.class) .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); } + + // ====================== 댓글 수정 테스트 ====================== + + @Test + @DisplayName("댓글 수정 성공") + void updateComment_success() { + // given: 유저 + 게시글 + 댓글 + User user = User.createUser("writer", "writer@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "원래 댓글"); + commentRepository.save(comment); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when + CommentResponse response = commentService.updateComment(post.getId(), comment.getId(), updateRequest, user.getId()); + + // then + assertThat(response.content()).isEqualTo("수정된 댓글"); + assertThat(response.commentId()).isEqualTo(comment.getId()); + } + + @Test + @DisplayName("댓글 수정 실패 - 존재하지 않는 게시글") + void updateComment_fail_postNotFound() { + // given: 유저 + 댓글 + User user = User.createUser("writer2", "writer2@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자2", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + assertThatThrownBy(() -> + commentService.updateComment(999L, comment.getId(), updateRequest, user.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("댓글 수정 실패 - 존재하지 않는 댓글") + void updateComment_fail_commentNotFound() { + // given: 유저 + 게시글 + User user = User.createUser("writer3", "writer3@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자3", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + assertThatThrownBy(() -> + commentService.updateComment(post.getId(), 999L, updateRequest, user.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.COMMENT_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("댓글 수정 실패 - 작성자가 아님") + void updateComment_fail_noPermission() { + // given: 유저 2명 + 게시글 + 댓글 + User writer = User.createUser("writer", "writer@example.com", "encodedPwd"); + writer.setUserProfile(new UserProfile(writer, "작성자", null, null, null, 0)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User other = User.createUser("other", "other@example.com", "encodedPwd"); + other.setUserProfile(new UserProfile(other, "다른사람", null, null, null, 0)); + other.setUserStatus(UserStatus.ACTIVE); + userRepository.save(other); + + Post post = new Post(writer, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, writer, "원래 댓글"); + commentRepository.save(comment); + + CommentRequest updateRequest = new CommentRequest("수정된 댓글"); + + // when & then + assertThatThrownBy(() -> + commentService.updateComment(post.getId(), comment.getId(), updateRequest, other.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.COMMENT_NO_PERMISSION.getMessage()); + } + // ====================== 댓글 삭제 테스트 ====================== + + @Test + @DisplayName("댓글 삭제 성공") + void deleteComment_success() { + // given + User user = User.createUser("writer", "writer@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "삭제할 댓글"); + commentRepository.save(comment); + + // when + commentService.deleteComment(post.getId(), comment.getId(), user.getId()); + + // then + assertThat(commentRepository.findById(comment.getId())).isEmpty(); + } + + @Test + @DisplayName("댓글 삭제 실패 - 존재하지 않는 게시글") + void deleteComment_fail_postNotFound() { + // given + User user = User.createUser("writer2", "writer2@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자2", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, user, "댓글"); + commentRepository.save(comment); + + // when & then + assertThatThrownBy(() -> + commentService.deleteComment(999L, comment.getId(), user.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.POST_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("댓글 삭제 실패 - 존재하지 않는 댓글") + void deleteComment_fail_commentNotFound() { + // given + User user = User.createUser("writer3", "writer3@example.com", "encodedPwd"); + user.setUserProfile(new UserProfile(user, "작성자3", null, null, null, 0)); + user.setUserStatus(UserStatus.ACTIVE); + userRepository.save(user); + + Post post = new Post(user, "제목", "내용"); + postRepository.save(post); + + // when & then + assertThatThrownBy(() -> + commentService.deleteComment(post.getId(), 999L, user.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.COMMENT_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("댓글 삭제 실패 - 작성자가 아님") + void deleteComment_fail_noPermission() { + // given: 작성자 + 다른 사용자 + User writer = User.createUser("writer", "writer@example.com", "encodedPwd"); + writer.setUserProfile(new UserProfile(writer, "작성자", null, null, null, 0)); + writer.setUserStatus(UserStatus.ACTIVE); + userRepository.save(writer); + + User other = User.createUser("other", "other@example.com", "encodedPwd"); + other.setUserProfile(new UserProfile(other, "다른사람", null, null, null, 0)); + other.setUserStatus(UserStatus.ACTIVE); + userRepository.save(other); + + Post post = new Post(writer, "제목", "내용"); + postRepository.save(post); + + Comment comment = new Comment(post, writer, "원래 댓글"); + commentRepository.save(comment); + + // when & then + assertThatThrownBy(() -> + commentService.deleteComment(post.getId(), comment.getId(), other.getId()) + ).isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.COMMENT_NO_PERMISSION.getMessage()); + } }