diff --git a/src/docs/asciidoc/api-doc.adoc b/src/docs/asciidoc/api-doc.adoc index 42dbc188..3996856f 100644 --- a/src/docs/asciidoc/api-doc.adoc +++ b/src/docs/asciidoc/api-doc.adoc @@ -425,6 +425,22 @@ include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_re | `404 NOT FOUND` | `NOT_FOUND_REVIEW` | 해당하는 리뷰가 없는 경우 | `404 NOT FOUND` | `NOT_FOUND_REVIEW_LIKE` | 해당하는 리뷰 좋아요가 없는 경우 |=== + +=== 4-7. 리뷰 삭제 +==== Path Parameters +include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_review/path-parameters.adoc[] +==== Sample Request +include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_review/http-request.adoc[] +==== Sample Response +include::{snippets}/review-controller-test/respond_200_when_succeed_to_delete_review/http-response.adoc[] +==== Error Response +|=== +| HTTP Status | Error Code | Detail + +| `403 FORBIDDEN` | `FORBIDDEN_REVIEW` | 해당 리뷰에 대한 삭제 권한이 없는 경우 +| `404 NOT FOUND` | `NOT_FOUND_REVIEW` | 해당하는 리뷰가 없는 경우 +|=== + == 5. 이미지 === 5-1. 이미지 업로드 ==== Path Parameters diff --git a/src/main/java/com/cvsgo/controller/ReviewController.java b/src/main/java/com/cvsgo/controller/ReviewController.java index a255eda2..855917a9 100644 --- a/src/main/java/com/cvsgo/controller/ReviewController.java +++ b/src/main/java/com/cvsgo/controller/ReviewController.java @@ -62,6 +62,12 @@ public SuccessResponse updateReview(@LoginUser User user, @PathVariable Lo return SuccessResponse.create(); } + @DeleteMapping("/reviews/{reviewId}") + public SuccessResponse deleteReview(@LoginUser User user, @PathVariable Long reviewId) { + reviewService.deleteReview(user, reviewId); + return SuccessResponse.create(); + } + @PostMapping("/reviews/{reviewId}/likes") @ResponseStatus(HttpStatus.CREATED) public SuccessResponse createReviewLike(@LoginUser User user, diff --git a/src/main/java/com/cvsgo/service/ReviewService.java b/src/main/java/com/cvsgo/service/ReviewService.java index 65302096..3207da42 100644 --- a/src/main/java/com/cvsgo/service/ReviewService.java +++ b/src/main/java/com/cvsgo/service/ReviewService.java @@ -119,6 +119,21 @@ public void updateReview(User user, Long reviewId, UpdateReviewRequestDto reques review.updateRating(request.getRating()); } + /** + * 리뷰를 삭제합니다. + * + * @param user 현재 로그인한 사용자 + * @param reviewId 삭제하려는 리뷰 ID + */ + @Transactional + public void deleteReview(User user, Long reviewId) { + Review review = reviewRepository.findById(reviewId).orElseThrow(() -> NOT_FOUND_REVIEW); + if (!review.getUser().equals(user)) { + throw FORBIDDEN_REVIEW; + } + reviewRepository.delete(review); + } + /** * 필터를 적용하여 리뷰를 조회합니다. * diff --git a/src/test/java/com/cvsgo/controller/ReviewControllerTest.java b/src/test/java/com/cvsgo/controller/ReviewControllerTest.java index 8d570bdd..a6debf80 100644 --- a/src/test/java/com/cvsgo/controller/ReviewControllerTest.java +++ b/src/test/java/com/cvsgo/controller/ReviewControllerTest.java @@ -92,7 +92,7 @@ class ReviewControllerTest { private static final String PRODUCT_REVIEW_API_PATH = "/api/products/{productId}/reviews"; - private static final String UPDATE_REVIEW_API_PATH = "/api/reviews/{reviewId}"; + private static final String REVIEW_API_PATH = "/api/reviews/{reviewId}"; private static final String SEARCH_REVIEW_API_PATH = "/api/reviews"; @@ -267,7 +267,7 @@ void respond_200_when_succeed_to_update_review() throws Exception { UpdateReviewRequestDto requestDto = new UpdateReviewRequestDto(5, "맛있어요", List.of("리뷰 이미지 URL 1", "리뷰 이미지 URL 2")); - mockMvc.perform(put(UPDATE_REVIEW_API_PATH, 1) + mockMvc.perform(put(REVIEW_API_PATH, 1) .content(objectMapper.writeValueAsString(requestDto)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andDo(print()) @@ -450,6 +450,22 @@ void respond_500_when_delete_review_like_but_concurrency_issue_occurs() throws E .andDo(print()); } + @Test + @DisplayName("리뷰 삭제에 성공하면 HTTP 200을 응답한다") + void respond_200_when_succeed_to_delete_review() throws Exception { + mockMvc.perform(delete(REVIEW_API_PATH, 1) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(document(documentIdentifier, + getDocumentRequest(), + getDocumentResponse(), + pathParameters( + parameterWithName("reviewId").description("리뷰 ID") + ) + )); + } + ReviewDto responseDto1 = ReviewDto.builder() .productId(13L) .productName("불닭볶음면큰컵") diff --git a/src/test/java/com/cvsgo/service/ReviewServiceTest.java b/src/test/java/com/cvsgo/service/ReviewServiceTest.java index e2282e12..ded34953 100644 --- a/src/test/java/com/cvsgo/service/ReviewServiceTest.java +++ b/src/test/java/com/cvsgo/service/ReviewServiceTest.java @@ -432,6 +432,36 @@ void succeed_to_read_user_review() { assertThat(reviews.size()).isEqualTo(1); } + @Test + @DisplayName("특정 리뷰를 정상적으로 삭제한다.") + void succeed_to_delete_review() { + given(reviewRepository.findById(any())).willReturn(Optional.of(review)); + + reviewService.deleteReview(user2, 1L); + + then(reviewRepository).should(times(1)).findById(anyLong()); + then(reviewRepository).should(times(1)).delete(any()); + } + + @Test + @DisplayName("리뷰 작성자가 아닌 사용자가 리뷰 삭제를 시도하면 ForbiddenException이 발생한다.") + void should_throw_ForbiddenException_when_delete_review_but_review_does_not_exist() { + given(reviewRepository.findById(any())).willReturn(Optional.of(review)); + + assertThrows(ForbiddenException.class, + () -> reviewService.deleteReview(user1, 1L)); + } + + @Test + @DisplayName("존재하지 않는 리뷰를 삭제하면 NotFoundException이 발생한다.") + void should_throw_NotFoundException_when_delete_review_but_review_does_not_exist() { + given(reviewRepository.findById(any())).willReturn(Optional.empty()); + + assertThrows(NotFoundException.class, + () -> reviewService.deleteReview(user1, 1000L)); + } + + User user1 = User.builder() .id(1L) .userId("abc@naver.com")