diff --git a/src/main/java/com/somemore/domains/review/controller/ReviewCommandApiController.java b/src/main/java/com/somemore/domains/review/controller/ReviewCommandApiController.java index 344ff1b4e..c8571f087 100644 --- a/src/main/java/com/somemore/domains/review/controller/ReviewCommandApiController.java +++ b/src/main/java/com/somemore/domains/review/controller/ReviewCommandApiController.java @@ -1,25 +1,26 @@ package com.somemore.domains.review.controller; -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; - +import com.somemore.domains.review.dto.request.ReviewCreateRequestDto; +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; import com.somemore.domains.review.usecase.CreateReviewUseCase; +import com.somemore.domains.review.usecase.DeleteReviewUseCase; +import com.somemore.domains.review.usecase.UpdateReviewUseCase; import com.somemore.global.auth.annotation.CurrentUser; import com.somemore.global.common.response.ApiResponse; import com.somemore.global.imageupload.dto.ImageUploadRequestDto; import com.somemore.global.imageupload.usecase.ImageUploadUseCase; -import com.somemore.domains.review.dto.request.ReviewCreateRequestDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.UUID; + +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + @Tag(name = "Review Command API", description = "리뷰 생성 수정 삭제 API") @RequiredArgsConstructor @RequestMapping("/api") @@ -27,6 +28,8 @@ public class ReviewCommandApiController { private final CreateReviewUseCase createReviewUseCase; + private final UpdateReviewUseCase updateReviewUseCase; + private final DeleteReviewUseCase deleteReviewUseCase; private final ImageUploadUseCase imageUploadUseCase; @Secured("ROLE_VOLUNTEER") @@ -45,4 +48,47 @@ public ApiResponse createReview( ); } + @Secured("ROLE_VOLUNTEER") + @Operation(summary = "리뷰 수정", description = "리뷰를 수정합니다.") + @PutMapping(value = "/review/{id}") + public ApiResponse updateReview( + @CurrentUser UUID userId, + @PathVariable Long id, + @Valid @RequestBody ReviewUpdateRequestDto requestDto) { + + updateReviewUseCase.updateReview(id, userId, requestDto); + + return ApiResponse.ok( + 200, + "리뷰 수정 성공" + ); + } + + @Secured("ROLE_VOLUNTEER") + @Operation(summary = "리뷰 이미지 수정", description = "리뷰 이미지를 수정합니다.") + @PutMapping(value = "/review/{id}", consumes = MULTIPART_FORM_DATA_VALUE) + public ApiResponse updateReviewImage( + @CurrentUser UUID userId, + @PathVariable Long id, + @RequestPart(value = "img_file", required = false) MultipartFile image) { + + String newImgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); + updateReviewUseCase.updateReviewImageUrl(id, userId, newImgUrl); + return ApiResponse.ok( + 200, + "리뷰 이미지 수정 성공" + ); + } + + @Secured("ROLE_VOLUNTEER") + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제합니다.") + @DeleteMapping(value = "/review/{id}") + public ApiResponse createReview( + @CurrentUser UUID userId, + @PathVariable Long id + ) { + deleteReviewUseCase.deleteReview(userId, id); + return ApiResponse.ok("리뷰 삭제 성공"); + } + } diff --git a/src/main/java/com/somemore/domains/review/controller/ReviewQueryApiController.java b/src/main/java/com/somemore/domains/review/controller/ReviewQueryApiController.java index 37ae076e3..5b35003b6 100644 --- a/src/main/java/com/somemore/domains/review/controller/ReviewQueryApiController.java +++ b/src/main/java/com/somemore/domains/review/controller/ReviewQueryApiController.java @@ -5,8 +5,8 @@ import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.global.common.response.ApiResponse; import com.somemore.domains.review.dto.condition.ReviewSearchCondition; -import com.somemore.domains.review.dto.response.ReviewResponseDto; -import com.somemore.domains.review.dto.response.ReviewWithNicknameResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailWithNicknameResponseDto; import com.somemore.domains.review.usecase.ReviewQueryUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -31,18 +31,18 @@ public class ReviewQueryApiController { @Operation(summary = "리뷰 단건 조회", description = "리뷰 ID를 사용하여 단건 리뷰 조회") @GetMapping("/review/{id}") - public ApiResponse getById(@PathVariable Long id) { + public ApiResponse getById(@PathVariable Long id) { return ApiResponse.ok( 200, - reviewQueryUseCase.getReviewById(id), + reviewQueryUseCase.getDetailById(id), "리뷰 단건 조회 성공" ); } @Operation(summary = "기관별 리뷰 조회", description = "기관 ID를 사용하여 리뷰 조회") @GetMapping("/reviews/center/{centerId}") - public ApiResponse> getReviewsByCenterId( + public ApiResponse> getReviewsByCenterId( @PathVariable UUID centerId, @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, @RequestParam(required = false) VolunteerCategory category @@ -54,14 +54,14 @@ public ApiResponse> getReviewsByCenterId( return ApiResponse.ok( 200, - reviewQueryUseCase.getReviewsByCenterId(centerId, condition), + reviewQueryUseCase.getDetailsWithNicknameByCenterId(centerId, condition), "기관 리뷰 리스트 조회 성공" ); } @Operation(summary = "봉사자 리뷰 조회", description = "봉사자 ID를 사용하여 리뷰 조회") @GetMapping("/reviews/volunteer/{volunteerId}") - public ApiResponse> getReviewsByVolunteerId( + public ApiResponse> getReviewsByVolunteerId( @PathVariable UUID volunteerId, @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, @RequestParam(required = false) VolunteerCategory category @@ -73,7 +73,7 @@ public ApiResponse> getReviewsByVolunteerId( return ApiResponse.ok( 200, - reviewQueryUseCase.getReviewsByVolunteerId(volunteerId, condition), + reviewQueryUseCase.getDetailsWithNicknameByVolunteerId(volunteerId, condition), "유저 리뷰 리스트 조회 성공" ); } diff --git a/src/main/java/com/somemore/domains/review/domain/Review.java b/src/main/java/com/somemore/domains/review/domain/Review.java index 2d9c68630..82506e1f5 100644 --- a/src/main/java/com/somemore/domains/review/domain/Review.java +++ b/src/main/java/com/somemore/domains/review/domain/Review.java @@ -3,6 +3,7 @@ import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; import com.somemore.global.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -50,4 +51,17 @@ public Review(Long volunteerApplyId, UUID volunteerId, String title, this.content = content; this.imgUrl = imgUrl; } + + public boolean isAuthor(UUID volunteerId) { + return this.volunteerId.equals(volunteerId); + } + + public void updateWith(ReviewUpdateRequestDto dto) { + this.title = dto.title(); + this.content = dto.content(); + } + + public void updateWith(String imgUrl) { + this.imgUrl = imgUrl; + } } diff --git a/src/main/java/com/somemore/domains/review/dto/request/ReviewCreateRequestDto.java b/src/main/java/com/somemore/domains/review/dto/request/ReviewCreateRequestDto.java index b2b857520..ca29da619 100644 --- a/src/main/java/com/somemore/domains/review/dto/request/ReviewCreateRequestDto.java +++ b/src/main/java/com/somemore/domains/review/dto/request/ReviewCreateRequestDto.java @@ -1,21 +1,21 @@ package com.somemore.domains.review.dto.request; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.review.domain.Review; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import java.util.UUID; import lombok.Builder; -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +import java.util.UUID; + +@JsonNaming(SnakeCaseStrategy.class) @Builder public record ReviewCreateRequestDto( - @Schema(description = "봉사 모집글 아이디", example = "1") - @NotNull(message = "봉사 모집글 아이디는 필수 값입니다.") - Long recruitBoardId, + @Schema(description = "봉사 지원 아이디", example = "1") + @NotNull(message = "봉사 지원 아이디는 필수 값입니다.") + Long volunteerApplyId, @Schema(description = "리뷰 제목", example = "내 인생 최고의 봉사 활동") @NotBlank(message = "리뷰 제목은 필수 값입니다.") String title, @@ -24,9 +24,9 @@ public record ReviewCreateRequestDto( String content ) { - public Review toEntity(VolunteerApply apply, UUID volunteerId, String imgUrl) { + public Review toEntity(UUID volunteerId, String imgUrl) { return Review.builder() - .volunteerApplyId(apply.getId()) + .volunteerApplyId(volunteerApplyId) .volunteerId(volunteerId) .title(title) .content(content) diff --git a/src/main/java/com/somemore/domains/review/dto/request/ReviewUpdateRequestDto.java b/src/main/java/com/somemore/domains/review/dto/request/ReviewUpdateRequestDto.java new file mode 100644 index 000000000..5ab6af57e --- /dev/null +++ b/src/main/java/com/somemore/domains/review/dto/request/ReviewUpdateRequestDto.java @@ -0,0 +1,19 @@ +package com.somemore.domains.review.dto.request; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +@JsonNaming(SnakeCaseStrategy.class) +@Builder +public record ReviewUpdateRequestDto( + @Schema(description = "리뷰 제목", example = "내 인생 최고의 봉사 활동") + @NotBlank(message = "리뷰 제목은 필수 값입니다.") + String title, + @Schema(description = "리뷰 내용", example = "담당자님도 정말 친절하였고 정말 보람찬 봉사였어요
") + @NotBlank(message = "리뷰 내용은 필수 값입니다.") + String content +) { +} diff --git a/src/main/java/com/somemore/domains/review/dto/response/ReviewResponseDto.java b/src/main/java/com/somemore/domains/review/dto/response/ReviewDetailResponseDto.java similarity index 88% rename from src/main/java/com/somemore/domains/review/dto/response/ReviewResponseDto.java rename to src/main/java/com/somemore/domains/review/dto/response/ReviewDetailResponseDto.java index 0198ec1d9..a94b6bc7e 100644 --- a/src/main/java/com/somemore/domains/review/dto/response/ReviewResponseDto.java +++ b/src/main/java/com/somemore/domains/review/dto/response/ReviewDetailResponseDto.java @@ -10,8 +10,8 @@ @Builder @JsonNaming(SnakeCaseStrategy.class) -@Schema(description = "리뷰 응답 DTO") -public record ReviewResponseDto( +@Schema(description = "리뷰 상세 조회 응답 DTO") +public record ReviewDetailResponseDto( @Schema(description = "리뷰 ID", example = "123") Long id, @Schema(description = "봉사자(작성자) ID", example = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d") @@ -28,8 +28,8 @@ public record ReviewResponseDto( LocalDateTime updatedAt ) { - public static ReviewResponseDto from(Review review) { - return ReviewResponseDto.builder() + public static ReviewDetailResponseDto from(Review review) { + return ReviewDetailResponseDto.builder() .id(review.getId()) .volunteerId(review.getVolunteerId()) .title(review.getTitle()) diff --git a/src/main/java/com/somemore/domains/review/dto/response/ReviewWithNicknameResponseDto.java b/src/main/java/com/somemore/domains/review/dto/response/ReviewDetailWithNicknameResponseDto.java similarity index 89% rename from src/main/java/com/somemore/domains/review/dto/response/ReviewWithNicknameResponseDto.java rename to src/main/java/com/somemore/domains/review/dto/response/ReviewDetailWithNicknameResponseDto.java index 1aac9c8dc..155eb4194 100644 --- a/src/main/java/com/somemore/domains/review/dto/response/ReviewWithNicknameResponseDto.java +++ b/src/main/java/com/somemore/domains/review/dto/response/ReviewDetailWithNicknameResponseDto.java @@ -11,7 +11,7 @@ @Builder @JsonNaming(SnakeCaseStrategy.class) @Schema(description = "작성자 닉네임이 포함된 리뷰 응답 DTO") -public record ReviewWithNicknameResponseDto( +public record ReviewDetailWithNicknameResponseDto( @Schema(description = "리뷰 ID", example = "123") Long id, @Schema(description = "봉사자(작성자) ID", example = "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d") @@ -30,8 +30,8 @@ public record ReviewWithNicknameResponseDto( LocalDateTime updateAt ) { - public static ReviewWithNicknameResponseDto from(Review review, String volunteerNickname) { - return ReviewWithNicknameResponseDto.builder() + public static ReviewDetailWithNicknameResponseDto from(Review review, String volunteerNickname) { + return ReviewDetailWithNicknameResponseDto.builder() .id(review.getId()) .volunteerId(review.getVolunteerId()) .volunteerNickname(volunteerNickname) diff --git a/src/main/java/com/somemore/domains/review/service/CreateReviewService.java b/src/main/java/com/somemore/domains/review/service/CreateReviewService.java index 139f22785..5f8ae85ed 100644 --- a/src/main/java/com/somemore/domains/review/service/CreateReviewService.java +++ b/src/main/java/com/somemore/domains/review/service/CreateReviewService.java @@ -4,6 +4,7 @@ import static com.somemore.global.exception.ExceptionMessage.REVIEW_RESTRICTED_TO_ATTENDED; import com.somemore.domains.review.repository.ReviewRepository; +import com.somemore.domains.review.usecase.ReviewQueryUseCase; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase; import com.somemore.global.exception.BadRequestException; @@ -23,24 +24,24 @@ public class CreateReviewService implements CreateReviewUseCase { private final ReviewRepository reviewRepository; + private final ReviewQueryUseCase reviewQueryUseCase; private final VolunteerApplyQueryUseCase volunteerApplyQueryUseCase; @Override public Long createReview(ReviewCreateRequestDto requestDto, UUID volunteerId, String imgUrl) { - VolunteerApply apply = getVolunteerApply(requestDto.recruitBoardId(), volunteerId); - validateDuplicateReview(apply); + validateDuplicateReview(requestDto.volunteerApplyId()); + + VolunteerApply apply = volunteerApplyQueryUseCase.getById(requestDto.volunteerApplyId()); validateActivityCompletion(apply); - Review review = requestDto.toEntity(apply, volunteerId, imgUrl); - return reviewRepository.save(review).getId(); - } + Review review = requestDto.toEntity(volunteerId, imgUrl); + reviewRepository.save(review); - private VolunteerApply getVolunteerApply(Long recruitBoardId, UUID volunteerId) { - return volunteerApplyQueryUseCase.getByRecruitIdAndVolunteerId(recruitBoardId, volunteerId); + return review.getId(); } - private void validateDuplicateReview(VolunteerApply apply) { - if (reviewRepository.existsByVolunteerApplyId(apply.getId())) { + private void validateDuplicateReview(Long volunteerApplyId) { + if (reviewQueryUseCase.existsByVolunteerApplyId(volunteerApplyId)) { throw new DuplicateException(REVIEW_ALREADY_EXISTS); } } diff --git a/src/main/java/com/somemore/domains/review/service/DeleteReviewService.java b/src/main/java/com/somemore/domains/review/service/DeleteReviewService.java new file mode 100644 index 000000000..708f55612 --- /dev/null +++ b/src/main/java/com/somemore/domains/review/service/DeleteReviewService.java @@ -0,0 +1,29 @@ +package com.somemore.domains.review.service; + +import com.somemore.domains.review.domain.Review; +import com.somemore.domains.review.service.validator.ReviewValidator; +import com.somemore.domains.review.usecase.DeleteReviewUseCase; +import com.somemore.domains.review.usecase.ReviewQueryUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@RequiredArgsConstructor +@Transactional +@Service +public class DeleteReviewService implements DeleteReviewUseCase { + + private final ReviewQueryUseCase reviewQueryUseCase; + private final ReviewValidator reviewValidator; + + @Override + public void deleteReview(UUID volunteerId, Long id) { + Review review = reviewQueryUseCase.getById(id); + + reviewValidator.validateAuthor(review, volunteerId); + review.markAsDeleted(); + } + +} diff --git a/src/main/java/com/somemore/domains/review/service/ReviewQueryService.java b/src/main/java/com/somemore/domains/review/service/ReviewQueryService.java index f70d11a0f..e3c108a25 100644 --- a/src/main/java/com/somemore/domains/review/service/ReviewQueryService.java +++ b/src/main/java/com/somemore/domains/review/service/ReviewQueryService.java @@ -1,26 +1,26 @@ package com.somemore.domains.review.service; -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_REVIEW; - import com.somemore.domains.center.usecase.query.CenterQueryUseCase; import com.somemore.domains.review.domain.Review; import com.somemore.domains.review.dto.condition.ReviewSearchCondition; -import com.somemore.domains.review.dto.response.ReviewResponseDto; -import com.somemore.domains.review.dto.response.ReviewWithNicknameResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailWithNicknameResponseDto; import com.somemore.domains.review.repository.ReviewRepository; import com.somemore.domains.review.usecase.ReviewQueryUseCase; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.usecase.VolunteerQueryUseCase; -import com.somemore.global.exception.BadRequestException; +import com.somemore.global.exception.NoSuchElementException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_REVIEW; @RequiredArgsConstructor @Transactional(readOnly = true) @@ -31,35 +31,38 @@ public class ReviewQueryService implements ReviewQueryUseCase { private final VolunteerQueryUseCase volunteerQueryUseCase; private final CenterQueryUseCase centerQueryUseCase; + @Override + public boolean existsByVolunteerApplyId(Long volunteerApplyId) { + return reviewRepository.existsByVolunteerApplyId(volunteerApplyId); + } + @Override public Review getById(Long id) { return reviewRepository.findById(id).orElseThrow( - () -> new BadRequestException(NOT_EXISTS_REVIEW) - ); + () -> new NoSuchElementException(NOT_EXISTS_REVIEW)); } @Override - public ReviewResponseDto getReviewById(Long id) { + public ReviewDetailResponseDto getDetailById(Long id) { Review review = getById(id); - return ReviewResponseDto.from(review); + return ReviewDetailResponseDto.from(review); } @Override - public Page getReviewsByVolunteerId( + public Page getDetailsWithNicknameByVolunteerId( UUID volunteerId, ReviewSearchCondition condition ) { String nickname = volunteerQueryUseCase.getNicknameById(volunteerId); - Page reviews = reviewRepository.findAllByVolunteerIdAndSearch(volunteerId, - condition); + Page reviews = reviewRepository.findAllByVolunteerIdAndSearch(volunteerId, condition); return reviews.map( - review -> ReviewWithNicknameResponseDto.from(review, nickname) + review -> ReviewDetailWithNicknameResponseDto.from(review, nickname) ); } @Override - public Page getReviewsByCenterId( + public Page getDetailsWithNicknameByCenterId( UUID centerId, ReviewSearchCondition condition ) { @@ -73,7 +76,7 @@ public Page getReviewsByCenterId( review -> { String nickname = volunteerNicknames.getOrDefault(review.getVolunteerId(), "삭제된 아이디"); - return ReviewWithNicknameResponseDto.from(review, nickname); + return ReviewDetailWithNicknameResponseDto.from(review, nickname); }); } diff --git a/src/main/java/com/somemore/domains/review/service/UpdateReviewService.java b/src/main/java/com/somemore/domains/review/service/UpdateReviewService.java new file mode 100644 index 000000000..c020527b8 --- /dev/null +++ b/src/main/java/com/somemore/domains/review/service/UpdateReviewService.java @@ -0,0 +1,36 @@ +package com.somemore.domains.review.service; + +import com.somemore.domains.review.domain.Review; +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; +import com.somemore.domains.review.service.validator.ReviewValidator; +import com.somemore.domains.review.usecase.ReviewQueryUseCase; +import com.somemore.domains.review.usecase.UpdateReviewUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@RequiredArgsConstructor +@Transactional +@Service +public class UpdateReviewService implements UpdateReviewUseCase { + + private final ReviewQueryUseCase reviewQueryUseCase; + private final ReviewValidator reviewValidator; + + @Override + public void updateReview(Long id, UUID volunteerId, ReviewUpdateRequestDto requestDto) { + Review review = reviewQueryUseCase.getById(id); + reviewValidator.validateAuthor(review, volunteerId); + review.updateWith(requestDto); + } + + @Override + public void updateReviewImageUrl(Long id, UUID volunteerId, String imgUrl) { + Review review = reviewQueryUseCase.getById(id); + reviewValidator.validateAuthor(review, volunteerId); + review.updateWith(imgUrl); + } + +} diff --git a/src/main/java/com/somemore/domains/review/service/validator/ReviewValidator.java b/src/main/java/com/somemore/domains/review/service/validator/ReviewValidator.java new file mode 100644 index 000000000..ebd6f5d51 --- /dev/null +++ b/src/main/java/com/somemore/domains/review/service/validator/ReviewValidator.java @@ -0,0 +1,21 @@ +package com.somemore.domains.review.service.validator; + +import com.somemore.domains.review.domain.Review; +import com.somemore.global.exception.BadRequestException; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_REVIEW; + +@Component +public class ReviewValidator { + + public void validateAuthor(Review review, UUID volunteerId) { + if (review.isAuthor(volunteerId)) { + return; + } + + throw new BadRequestException(UNAUTHORIZED_REVIEW); + } +} diff --git a/src/main/java/com/somemore/domains/review/usecase/DeleteReviewUseCase.java b/src/main/java/com/somemore/domains/review/usecase/DeleteReviewUseCase.java new file mode 100644 index 000000000..37e3c6d2d --- /dev/null +++ b/src/main/java/com/somemore/domains/review/usecase/DeleteReviewUseCase.java @@ -0,0 +1,8 @@ +package com.somemore.domains.review.usecase; + +import java.util.UUID; + +public interface DeleteReviewUseCase { + + void deleteReview(UUID volunteerId, Long id); +} diff --git a/src/main/java/com/somemore/domains/review/usecase/ReviewQueryUseCase.java b/src/main/java/com/somemore/domains/review/usecase/ReviewQueryUseCase.java index ebbb36a9d..862571e9a 100644 --- a/src/main/java/com/somemore/domains/review/usecase/ReviewQueryUseCase.java +++ b/src/main/java/com/somemore/domains/review/usecase/ReviewQueryUseCase.java @@ -2,21 +2,22 @@ import com.somemore.domains.review.domain.Review; import com.somemore.domains.review.dto.condition.ReviewSearchCondition; -import com.somemore.domains.review.dto.response.ReviewResponseDto; -import com.somemore.domains.review.dto.response.ReviewWithNicknameResponseDto; -import java.util.UUID; +import com.somemore.domains.review.dto.response.ReviewDetailResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailWithNicknameResponseDto; import org.springframework.data.domain.Page; +import java.util.UUID; + public interface ReviewQueryUseCase { + boolean existsByVolunteerApplyId(Long volunteerApplyId); + Review getById(Long id); - ReviewResponseDto getReviewById(Long id); + ReviewDetailResponseDto getDetailById(Long id); - Page getReviewsByVolunteerId(UUID volunteerId, - ReviewSearchCondition condition); + Page getDetailsWithNicknameByVolunteerId(UUID volunteerId, ReviewSearchCondition condition); - Page getReviewsByCenterId(UUID centerId, - ReviewSearchCondition condition); + Page getDetailsWithNicknameByCenterId(UUID centerId, ReviewSearchCondition condition); } diff --git a/src/main/java/com/somemore/domains/review/usecase/UpdateReviewUseCase.java b/src/main/java/com/somemore/domains/review/usecase/UpdateReviewUseCase.java new file mode 100644 index 000000000..945e8791e --- /dev/null +++ b/src/main/java/com/somemore/domains/review/usecase/UpdateReviewUseCase.java @@ -0,0 +1,12 @@ +package com.somemore.domains.review.usecase; + +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; + +import java.util.UUID; + +public interface UpdateReviewUseCase { + + void updateReview(Long id, UUID volunteerId, ReviewUpdateRequestDto requestDto); + + void updateReviewImageUrl(Long id, UUID volunteerId, String imgUrl); +} diff --git a/src/main/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryService.java b/src/main/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryService.java index 1b08198b0..457517a9b 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryService.java +++ b/src/main/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryService.java @@ -1,7 +1,5 @@ package com.somemore.domains.volunteerapply.service; -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY; - import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.condition.VolunteerApplySearchCondition; import com.somemore.domains.volunteerapply.dto.response.VolunteerApplyResponseDto; @@ -9,14 +7,17 @@ import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository; import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase; import com.somemore.global.exception.NoSuchElementException; -import java.util.List; -import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY; + @Slf4j @Service @RequiredArgsConstructor @@ -26,46 +27,47 @@ public class VolunteerApplyQueryService implements VolunteerApplyQueryUseCase { private final VolunteerApplyRepository volunteerApplyRepository; @Override - public List getVolunteerIdsByRecruitIds(List recruitIds) { - return volunteerApplyRepository.findVolunteerIdsByRecruitIds(recruitIds); + public VolunteerApply getById(Long id) { + return volunteerApplyRepository.findById(id).orElseThrow( + () -> new NoSuchElementException(NOT_EXISTS_VOLUNTEER_APPLY)); } @Override public VolunteerApply getByRecruitIdAndVolunteerId(Long recruitId, UUID volunteerId) { - return volunteerApplyRepository.findByRecruitIdAndVolunteerId(recruitId, volunteerId) - .orElseThrow( - () -> new NoSuchElementException(NOT_EXISTS_VOLUNTEER_APPLY)); + return volunteerApplyRepository.findByRecruitIdAndVolunteerId(recruitId, volunteerId).orElseThrow( + () -> new NoSuchElementException(NOT_EXISTS_VOLUNTEER_APPLY)); } @Override public VolunteerApplySummaryResponseDto getSummaryByRecruitId(Long recruitId) { - List applies = volunteerApplyRepository.findAllByRecruitId( - recruitId); + List applies = volunteerApplyRepository.findAllByRecruitId(recruitId); return VolunteerApplySummaryResponseDto.from(applies); } @Override - public VolunteerApplyResponseDto getVolunteerApplyByRecruitIdAndVolunteerId(Long recruitId, - UUID volunteerId) { + public VolunteerApplyResponseDto getVolunteerApplyByRecruitIdAndVolunteerId(Long recruitId, UUID volunteerId) { VolunteerApply apply = getByRecruitIdAndVolunteerId(recruitId, volunteerId); return VolunteerApplyResponseDto.from(apply); } @Override - public Page getAllByRecruitId(Long recruitId, - VolunteerApplySearchCondition condition) { + public Page getAllByRecruitId(Long recruitId, VolunteerApplySearchCondition condition) { return volunteerApplyRepository.findAllByRecruitId(recruitId, condition); } @Override - public Page getAllByVolunteerId(UUID volunteerId, - VolunteerApplySearchCondition condition) { + public Page getAllByVolunteerId(UUID volunteerId, VolunteerApplySearchCondition condition) { return volunteerApplyRepository.findAllByVolunteerId(volunteerId, condition); } + @Override + public List getVolunteerIdsByRecruitIds(List recruitIds) { + return volunteerApplyRepository.findVolunteerIdsByRecruitIds(recruitIds); + } + @Override public List getAllByIds(List ids) { return volunteerApplyRepository.findAllByIds(ids); diff --git a/src/main/java/com/somemore/domains/volunteerapply/usecase/VolunteerApplyQueryUseCase.java b/src/main/java/com/somemore/domains/volunteerapply/usecase/VolunteerApplyQueryUseCase.java index 451d680bc..f1c490a60 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/usecase/VolunteerApplyQueryUseCase.java +++ b/src/main/java/com/somemore/domains/volunteerapply/usecase/VolunteerApplyQueryUseCase.java @@ -1,30 +1,30 @@ package com.somemore.domains.volunteerapply.usecase; +import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.condition.VolunteerApplySearchCondition; import com.somemore.domains.volunteerapply.dto.response.VolunteerApplyResponseDto; import com.somemore.domains.volunteerapply.dto.response.VolunteerApplySummaryResponseDto; -import com.somemore.domains.volunteerapply.domain.VolunteerApply; +import org.springframework.data.domain.Page; import java.util.List; import java.util.UUID; -import org.springframework.data.domain.Page; public interface VolunteerApplyQueryUseCase { - List getVolunteerIdsByRecruitIds(List recruitIds); + VolunteerApply getById(Long id); VolunteerApply getByRecruitIdAndVolunteerId(Long recruitId, UUID volunteerId); VolunteerApplySummaryResponseDto getSummaryByRecruitId(Long recruitId); - VolunteerApplyResponseDto getVolunteerApplyByRecruitIdAndVolunteerId(Long recruitId, - UUID volunteerId); + VolunteerApplyResponseDto getVolunteerApplyByRecruitIdAndVolunteerId(Long recruitId, UUID volunteerId); - Page getAllByRecruitId(Long recruitId, VolunteerApplySearchCondition condition); - - Page getAllByVolunteerId(UUID volunteerId, - VolunteerApplySearchCondition condition); + List getVolunteerIdsByRecruitIds(List recruitIds); List getAllByIds(List ids); + Page getAllByRecruitId(Long recruitId, VolunteerApplySearchCondition condition); + + Page getAllByVolunteerId(UUID volunteerId, VolunteerApplySearchCondition condition); + } diff --git a/src/main/java/com/somemore/global/exception/ExceptionMessage.java b/src/main/java/com/somemore/global/exception/ExceptionMessage.java index ccaf5566c..6f476a891 100644 --- a/src/main/java/com/somemore/global/exception/ExceptionMessage.java +++ b/src/main/java/com/somemore/global/exception/ExceptionMessage.java @@ -53,6 +53,7 @@ public enum ExceptionMessage { REVIEW_ALREADY_EXISTS("이미 작성한 리뷰가 존재합니다."), REVIEW_RESTRICTED_TO_ATTENDED("리뷰는 참석한 봉사에 한해서만 작성할 수 있습니다."), NOT_EXISTS_REVIEW("존재하지 않는 리뷰입니다."), + UNAUTHORIZED_REVIEW("해당 리뷰에 권한이 없습니다."), // NOTIFICATION NOT_EXISTS_NOTIFICATION("존재하지 않는 알림입니다."), diff --git a/src/test/java/com/somemore/domains/review/controller/ReviewCommandApiControllerTest.java b/src/test/java/com/somemore/domains/review/controller/ReviewCommandApiControllerTest.java index dae38a879..86a408787 100644 --- a/src/test/java/com/somemore/domains/review/controller/ReviewCommandApiControllerTest.java +++ b/src/test/java/com/somemore/domains/review/controller/ReviewCommandApiControllerTest.java @@ -1,25 +1,32 @@ package com.somemore.domains.review.controller; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.fasterxml.jackson.core.JsonProcessingException; import com.somemore.domains.review.dto.request.ReviewCreateRequestDto; +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; import com.somemore.domains.review.usecase.CreateReviewUseCase; +import com.somemore.domains.review.usecase.DeleteReviewUseCase; +import com.somemore.domains.review.usecase.UpdateReviewUseCase; import com.somemore.global.imageupload.usecase.ImageUploadUseCase; import com.somemore.support.ControllerTestSupport; import com.somemore.support.annotation.WithMockCustomUser; -import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class ReviewCommandApiControllerTest extends ControllerTestSupport { @@ -29,13 +36,19 @@ class ReviewCommandApiControllerTest extends ControllerTestSupport { @MockBean private CreateReviewUseCase createReviewUseCase; + @MockBean + private UpdateReviewUseCase updateReviewUseCase; + + @MockBean + private DeleteReviewUseCase deleteReviewUseCase; + @DisplayName("리뷰 생성 성공") @Test @WithMockCustomUser() void createReview() throws Exception { // given ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(1L) + .volunteerApplyId(1L) .title("리뷰 제목") .content("리뷰 내용") .build(); @@ -92,7 +105,7 @@ void createReviewValidateTestRecruitBoardId() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.status").value(400)) .andExpect(jsonPath("$.title").value("유효성 예외")) - .andExpect(jsonPath("$.detail").value("봉사 모집글 아이디는 필수 값입니다.")); + .andExpect(jsonPath("$.detail").value("봉사 지원 아이디는 필수 값입니다.")); } @DisplayName("리뷰 생성 유효성 테스트 - 제목") @@ -101,7 +114,7 @@ void createReviewValidateTestRecruitBoardId() throws Exception { void createReviewValidateTestTitle() throws Exception { // given ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(1L) + .volunteerApplyId(1L) .content("리뷰 내용") .build(); @@ -127,7 +140,7 @@ void createReviewValidateTestTitle() throws Exception { void createReviewValidateTestContent() throws Exception { // given ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(1L) + .volunteerApplyId(1L) .title("리뷰 제목") .build(); @@ -147,6 +160,130 @@ void createReviewValidateTestContent() throws Exception { .andExpect(jsonPath("$.detail").value("리뷰 내용은 필수 값입니다.")); } + @DisplayName("리뷰 수정 성공") + @Test + @WithMockCustomUser() + void updateReview() throws Exception { + // given + ReviewUpdateRequestDto requestDto = ReviewUpdateRequestDto.builder() + .title("리뷰 수정 제목") + .content("리뷰 수정 내용") + .build(); + + Long reviewId = 1L; + + willDoNothing().given(updateReviewUseCase).updateReview(any(), any(UUID.class), any()); + String requestBody = objectMapper.writeValueAsString(requestDto); + + // when + mockMvc.perform(put("/api/review/{id}", reviewId) + .content(requestBody) + .contentType(APPLICATION_JSON) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("리뷰 수정 성공")); + } + + @DisplayName("리뷰 수정 유효성 테스트 - 제목") + @Test + @WithMockCustomUser() + void updateReviewValidateTitle() throws Exception { + // given + ReviewUpdateRequestDto requestDto = ReviewUpdateRequestDto.builder() + .content("업데이트 내용") + .build(); + + Long reviewId = 1L; + String requestBody = objectMapper.writeValueAsString(requestDto); + + // when + mockMvc.perform(put("/api/review/{id}", reviewId) + .content(requestBody) + .contentType(APPLICATION_JSON) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.title").value("유효성 예외")) + .andExpect(jsonPath("$.detail").value("리뷰 제목은 필수 값입니다.")); + } + + @DisplayName("리뷰 수정 유효성 테스트 - 내용") + @Test + @WithMockCustomUser() + void updateReviewValidateContent() throws Exception { + // given + ReviewUpdateRequestDto requestDto = ReviewUpdateRequestDto.builder() + .title("업데이트 제목") + .build(); + + Long reviewId = 1L; + String requestBody = objectMapper.writeValueAsString(requestDto); + + // when + mockMvc.perform(put("/api/review/{id}", reviewId) + .content(requestBody) + .contentType(APPLICATION_JSON) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.status").value(400)) + .andExpect(jsonPath("$.title").value("유효성 예외")) + .andExpect(jsonPath("$.detail").value("리뷰 내용은 필수 값입니다.")); + } + + @DisplayName("리뷰 이미지 수정 성공") + @Test + @WithMockCustomUser() + void updateReviewImage() throws Exception { + // given + Long reviewId = 1L; + + MockMultipartFile imageFile = new MockMultipartFile( + "img_file", + "test-image.jpg", + MediaType.IMAGE_JPEG_VALUE, + "test image content".getBytes() + ); + + String imgUrl = "https://example.com/image/test-image.jpg"; + + given(imageUploadUseCase.uploadImage(any())).willReturn(imgUrl); + + willDoNothing().given(updateReviewUseCase).updateReviewImageUrl(any(), any(UUID.class), anyString()); + + // when + mockMvc.perform(createMultipartPutRequest("/api/review/{id}", reviewId) + .file(imageFile) + .contentType(MULTIPART_FORM_DATA) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("리뷰 이미지 수정 성공")); + } + + @DisplayName("리뷰 삭제 성공 테스트") + @Test + @WithMockCustomUser() + void deleteReview() throws Exception { + // given + Long id = 1L; + UUID volunteerId = UUID.randomUUID(); + + willDoNothing().given(deleteReviewUseCase).deleteReview(volunteerId, id); + + // when + mockMvc.perform(delete("/api/review/{id}", id) + .header("Authorization", "Bearer access-token")) + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("리뷰 삭제 성공")); + } + private MockMultipartFile getRequestData(ReviewCreateRequestDto requestDto) throws JsonProcessingException { return new MockMultipartFile( @@ -156,4 +293,14 @@ private MockMultipartFile getRequestData(ReviewCreateRequestDto requestDto) objectMapper.writeValueAsBytes(requestDto) ); } + + private MockMultipartHttpServletRequestBuilder createMultipartPutRequest(String url, Long id) { + MockMultipartHttpServletRequestBuilder builder = multipart(url, id); + builder.with(request -> { + request.setMethod("PUT"); + return request; + }); + return builder; + } + } diff --git a/src/test/java/com/somemore/domains/review/domain/ReviewTest.java b/src/test/java/com/somemore/domains/review/domain/ReviewTest.java new file mode 100644 index 000000000..31567d693 --- /dev/null +++ b/src/test/java/com/somemore/domains/review/domain/ReviewTest.java @@ -0,0 +1,75 @@ +package com.somemore.domains.review.domain; + +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + + +class ReviewTest { + + private UUID volunteerId; + private Review review; + + @BeforeEach + void setUp() { + volunteerId = UUID.randomUUID(); + review = createReview(1L, volunteerId); + } + + @DisplayName("작성자 인지 확인할 수 있다.") + @Test + void isAuthor() { + // given + + // when + boolean result = review.isAuthor(volunteerId); + + // then + assertThat(result).isTrue(); + } + + @DisplayName("리뷰 제목 내용을 업데이트 할 수 있다.") + @Test + void updateWith() { + // given + ReviewUpdateRequestDto dto = ReviewUpdateRequestDto.builder() + .title("업데이트 제목") + .content("업데이트 내용") + .build(); + + // when + review.updateWith(dto); + + // then + assertThat(review.getTitle()).isEqualTo(dto.title()); + assertThat(review.getContent()).isEqualTo(dto.content()); + } + + @DisplayName("이미지 링크를 업데이트 할 수 있다.") + @Test + void updateWithImgUrl() { + // given + String newImgUrl = "newLink"; + + // when + review.updateWith(newImgUrl); + + // then + assertThat(review.getImgUrl()).isEqualTo(newImgUrl); + } + + private Review createReview(Long applyId, UUID volunteerId) { + return Review.builder() + .volunteerApplyId(applyId) + .volunteerId(volunteerId) + .title("제목제목") + .content("내용내용") + .imgUrl("이미지링크") + .build(); + } +} diff --git a/src/test/java/com/somemore/domains/review/service/CreateReviewServiceTest.java b/src/test/java/com/somemore/domains/review/service/CreateReviewServiceTest.java index 35636fa54..74fbf397a 100644 --- a/src/test/java/com/somemore/domains/review/service/CreateReviewServiceTest.java +++ b/src/test/java/com/somemore/domains/review/service/CreateReviewServiceTest.java @@ -39,17 +39,17 @@ class CreateReviewServiceTest extends IntegrationTestSupport { void createReview() { // given UUID volunteerId = UUID.randomUUID(); - Long recruitId = 200L; + Long recruitBoardId = 200L; VolunteerApply apply = VolunteerApply.builder() .volunteerId(volunteerId) - .recruitBoardId(200L) + .recruitBoardId(recruitBoardId) .status(APPROVED) .attended(true) .build(); volunteerApplyRepository.save(apply); ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(recruitId) + .volunteerApplyId(apply.getId()) .title("리뷰 제목") .content("리뷰 내용") .build(); @@ -63,22 +63,31 @@ void createReview() { assertThat(findReview.get().getId()).isEqualTo(reviewId); } - @DisplayName("참석하지 않은 봉사 활동에 대해 리뷰를 생성하면 에러가 발생한다") + @DisplayName("이미 작성한 봉사 활동에 대해 리뷰를 생성하면 에러가 발생한다") @Test - void createReviewWhenNotCompleted() { + void createReviewWhenExistsReview() { // given UUID volunteerId = UUID.randomUUID(); - Long recruitId = 200L; + Long recruitBoardId = 200L; VolunteerApply apply = VolunteerApply.builder() .volunteerId(volunteerId) - .recruitBoardId(200L) + .recruitBoardId(recruitBoardId) .status(APPROVED) .attended(false) .build(); volunteerApplyRepository.save(apply); + Review review = Review.builder() + .volunteerApplyId(apply.getId()) + .volunteerId(volunteerId) + .title("리뷰 제목") + .content("리뷰 내용") + .imgUrl("") + .build(); + reviewRepository.save(review); + ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(recruitId) + .volunteerApplyId(apply.getId()) .title("리뷰 제목") .content("리뷰 내용") .build(); @@ -86,37 +95,28 @@ void createReviewWhenNotCompleted() { // when // then assertThatThrownBy( - () -> createReviewService.createReview(requestDto, volunteerId, "") - ).isInstanceOf(BadRequestException.class) - .hasMessage(REVIEW_RESTRICTED_TO_ATTENDED.getMessage()); + () -> createReviewService.createReview(requestDto, volunteerId, "")) + .isInstanceOf(DuplicateException.class) + .hasMessage(REVIEW_ALREADY_EXISTS.getMessage()); } - @DisplayName("이미 작성한 봉사 활동에 대해 리뷰를 생성하면 에러가 발생한다") + + @DisplayName("참석하지 않은 봉사 활동에 대해 리뷰를 생성하면 에러가 발생한다") @Test - void createReviewWhenExistsReview() { + void createReviewWhenNotCompleted() { // given UUID volunteerId = UUID.randomUUID(); - Long recruitId = 200L; + Long recruitBoardId = 200L; VolunteerApply apply = VolunteerApply.builder() .volunteerId(volunteerId) - .recruitBoardId(200L) + .recruitBoardId(recruitBoardId) .status(APPROVED) .attended(false) .build(); volunteerApplyRepository.save(apply); - Review review = Review.builder() - .volunteerApplyId(apply.getId()) - .volunteerId(volunteerId) - .title("리뷰 제목") - .content("리뷰 내용") - .imgUrl("") - .build(); - - reviewRepository.save(review); - ReviewCreateRequestDto requestDto = ReviewCreateRequestDto.builder() - .recruitBoardId(recruitId) + .volunteerApplyId(apply.getId()) .title("리뷰 제목") .content("리뷰 내용") .build(); @@ -124,9 +124,8 @@ void createReviewWhenExistsReview() { // when // then assertThatThrownBy( - () -> createReviewService.createReview(requestDto, volunteerId, "") - ).isInstanceOf(DuplicateException.class) - .hasMessage(REVIEW_ALREADY_EXISTS.getMessage()); + () -> createReviewService.createReview(requestDto, volunteerId, "")) + .isInstanceOf(BadRequestException.class) + .hasMessage(REVIEW_RESTRICTED_TO_ATTENDED.getMessage()); } - } diff --git a/src/test/java/com/somemore/domains/review/service/DeleteReviewServiceTest.java b/src/test/java/com/somemore/domains/review/service/DeleteReviewServiceTest.java new file mode 100644 index 000000000..646054690 --- /dev/null +++ b/src/test/java/com/somemore/domains/review/service/DeleteReviewServiceTest.java @@ -0,0 +1,53 @@ +package com.somemore.domains.review.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.somemore.domains.review.domain.Review; +import com.somemore.domains.review.repository.ReviewRepository; +import com.somemore.support.IntegrationTestSupport; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +class DeleteReviewServiceTest extends IntegrationTestSupport { + + @Autowired + private DeleteReviewService deleteReviewService; + + @Autowired + private ReviewRepository reviewRepository; + + @DisplayName("리뷰 아이디와 작성자 아이디로 리뷰를 삭제할 수 있다.") + @Test + void deleteReview() { + // given + UUID volunteerId = UUID.randomUUID(); + String title = "리뷰 제목"; + String content = "리뷰 내용"; + String imgUrl = "리뷰 이미지"; + + Review review = createReview(volunteerId, title, content, imgUrl); + reviewRepository.save(review); + + // when + deleteReviewService.deleteReview(volunteerId, review.getId()); + + // then + Optional findReview = reviewRepository.findById(review.getId()); + assertThat(findReview).isEmpty(); + } + + private Review createReview(UUID volunteerId, String title, String content, String imgUrl) { + return Review.builder() + .volunteerApplyId(1L) + .volunteerId(volunteerId) + .title(title) + .content(content) + .imgUrl(imgUrl) + .build(); + } +} diff --git a/src/test/java/com/somemore/domains/review/service/ReviewQueryServiceTest.java b/src/test/java/com/somemore/domains/review/service/ReviewQueryServiceTest.java index 1a1eb5272..a0c4e39ab 100644 --- a/src/test/java/com/somemore/domains/review/service/ReviewQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/review/service/ReviewQueryServiceTest.java @@ -7,15 +7,16 @@ import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.domains.review.domain.Review; import com.somemore.domains.review.dto.condition.ReviewSearchCondition; -import com.somemore.domains.review.dto.response.ReviewResponseDto; -import com.somemore.domains.review.dto.response.ReviewWithNicknameResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailResponseDto; +import com.somemore.domains.review.dto.response.ReviewDetailWithNicknameResponseDto; import com.somemore.domains.review.repository.ReviewRepository; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.repository.VolunteerRepository; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository; -import com.somemore.global.exception.BadRequestException; +import com.somemore.global.exception.NoSuchElementException; import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,38 +58,73 @@ class ReviewQueryServiceTest extends IntegrationTestSupport { @Autowired private CenterRepository centerRepository; + private Long volunteerApplyId; + private Review review; + + @BeforeEach + void setUp() { + volunteerApplyId = 100L; + review = createReview(volunteerApplyId, UUID.randomUUID()); + reviewRepository.save(review); + } + + @DisplayName("봉사 지원 아이디로 리뷰 존재 유무를 조회할 수 있다.") + @Test + void existsByVolunteerApplyId() { + // given + // when + boolean result = reviewQueryService.existsByVolunteerApplyId(volunteerApplyId); + + // then + assertThat(result).isTrue(); + } + @DisplayName("아이디로 리뷰를 조회할 수 있다.") @Test - void getReviewById() { + void getById() { // given - Review review = createReview(1L, UUID.randomUUID()); - reviewRepository.save(review); + Long id = review.getId(); // when - ReviewResponseDto findOne = reviewQueryService.getReviewById(review.getId()); + Review findOne = reviewQueryService.getById(id); // then - assertThat(findOne).extracting("id").isEqualTo(review.getId()); - assertThat(findOne).extracting("volunteerId").isEqualTo(review.getVolunteerId()); - assertThat(findOne).extracting("title").isEqualTo(review.getTitle()); - assertThat(findOne).extracting("content").isEqualTo(review.getContent()); - assertThat(findOne).extracting("imgUrl").isEqualTo(review.getImgUrl()); + assertThat(findOne.getId()).isEqualTo(id); } @DisplayName("존재하지 않는 아이디로 리뷰를 조회하면 에러가 발생한다.") @Test - void getReviewByIdWhenWrongId() { + void getByIdWhenDoesNotExist() { // given Long wrongId = 10000000L; // when // then assertThatThrownBy( - () -> reviewQueryService.getReviewById(wrongId) - ).isInstanceOf(BadRequestException.class) + () -> reviewQueryService.getById(wrongId)) + .isInstanceOf(NoSuchElementException.class) .hasMessage(NOT_EXISTS_REVIEW.getMessage()); } + + @DisplayName("아이디로 리뷰를 상세 조회할 수 있다.") + @Test + void getDetailById() { + // given + Long id = review.getId(); + + // when + ReviewDetailResponseDto findOne = reviewQueryService.getDetailById(id); + + // then + assertThat(findOne).extracting("id").isEqualTo(review.getId()); + assertThat(findOne).extracting("volunteerId").isEqualTo(review.getVolunteerId()); + assertThat(findOne).extracting("title").isEqualTo(review.getTitle()); + assertThat(findOne).extracting("content").isEqualTo(review.getContent()); + assertThat(findOne).extracting("imgUrl").isEqualTo(review.getImgUrl()); + } + + @DisplayName("봉사자 ID로 리뷰 리스트를 조회할 수 있다.") @Test void getReviewsByVolunteerId() { @@ -122,7 +158,7 @@ void getReviewsByVolunteerId() { .build(); // when - Page result = reviewQueryService.getReviewsByVolunteerId( + Page result = reviewQueryService.getDetailsWithNicknameByVolunteerId( volunteerId, conditionWithoutCategory); @@ -172,7 +208,7 @@ void getReviewsByCenterId() { .build(); // when - Page result = reviewQueryService.getReviewsByCenterId( + Page result = reviewQueryService.getDetailsWithNicknameByCenterId( center.getId(), condition); @@ -199,7 +235,7 @@ private Review createReview(Long applyId, UUID volunteerId) { } private Review createReview(Long applyId, UUID volunteerId, String title, String content, - String imgUrl) { + String imgUrl) { return Review.builder() .volunteerApplyId(applyId) .volunteerId(volunteerId) diff --git a/src/test/java/com/somemore/domains/review/service/UpdateReviewServiceTest.java b/src/test/java/com/somemore/domains/review/service/UpdateReviewServiceTest.java new file mode 100644 index 000000000..946fff38c --- /dev/null +++ b/src/test/java/com/somemore/domains/review/service/UpdateReviewServiceTest.java @@ -0,0 +1,78 @@ +package com.somemore.domains.review.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.somemore.domains.review.domain.Review; +import com.somemore.domains.review.dto.request.ReviewUpdateRequestDto; +import com.somemore.domains.review.repository.ReviewRepository; +import com.somemore.support.IntegrationTestSupport; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +class UpdateReviewServiceTest extends IntegrationTestSupport { + + @Autowired + private UpdateReviewService updateReviewService; + + @Autowired + private ReviewRepository reviewRepository; + + private UUID volunteerId; + private Review review; + + @BeforeEach + void setUp() { + volunteerId = UUID.randomUUID(); + review = createReview(1L, volunteerId); + reviewRepository.save(review); + } + + @DisplayName("리뷰 정보를 업데이트 할 수 있다.") + @Test + void updateReview() { + // given + Long id = review.getId(); + ReviewUpdateRequestDto dto = ReviewUpdateRequestDto.builder() + .title("업데이트 제목") + .content("업데이트 내용") + .build(); + + // when + updateReviewService.updateReview(id, volunteerId, dto); + + // then + Review updateReview = reviewRepository.findById(id).orElseThrow(); + assertThat(updateReview.getTitle()).isEqualTo(dto.title()); + assertThat(updateReview.getContent()).isEqualTo(dto.content()); + } + + @DisplayName("리뷰 이미지 링크를 업데이트 할 수 있다.") + @Test + void updateReviewImageUrl() { + // given + Long id = review.getId(); + String newImgUrl = "newLink.co.kr"; + + // when + updateReviewService.updateReviewImageUrl(id, volunteerId, newImgUrl); + + // then + Review updateReview = reviewRepository.findById(id).orElseThrow(); + assertThat(updateReview.getImgUrl()).isEqualTo(newImgUrl); + } + + private Review createReview(Long applyId, UUID volunteerId) { + return Review.builder() + .volunteerApplyId(applyId) + .volunteerId(volunteerId) + .title("제목제목") + .content("내용내용") + .imgUrl("이미지링크") + .build(); + } +} diff --git a/src/test/java/com/somemore/domains/review/service/validator/ReviewValidatorTest.java b/src/test/java/com/somemore/domains/review/service/validator/ReviewValidatorTest.java new file mode 100644 index 000000000..9ac139e89 --- /dev/null +++ b/src/test/java/com/somemore/domains/review/service/validator/ReviewValidatorTest.java @@ -0,0 +1,56 @@ +package com.somemore.domains.review.service.validator; + +import com.somemore.domains.review.domain.Review; +import com.somemore.global.exception.BadRequestException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_REVIEW; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ReviewValidatorTest { + + private final ReviewValidator reviewValidator = new ReviewValidator(); + + @DisplayName("리뷰 작성자인지 검증할 수 있다") + @Test + void validateAuthor() { + // given + UUID volunteerId = UUID.randomUUID(); + Review review = createReview(volunteerId, "리뷰 제목", "내용내용", "이미지링크"); + + // when + // then + assertThatCode( + () -> reviewValidator.validateAuthor(review, volunteerId)) + .doesNotThrowAnyException(); + } + + @DisplayName("리뷰 작성자와 일치 하지 않는 경우 에러가 발생한다.") + @Test + void validateAuthorWhenMissMatch() { + // given + UUID wrongVolunteerId = UUID.randomUUID(); + Review review = createReview(UUID.randomUUID(), "다른 제목", "다른 내용", "다른 링크"); + + // when + // then + assertThatThrownBy( + () -> reviewValidator.validateAuthor(review, wrongVolunteerId)) + .isInstanceOf(BadRequestException.class) + .hasMessage(UNAUTHORIZED_REVIEW.getMessage()); + } + + private Review createReview(UUID volunteerId, String title, String content, String imgUrl) { + return Review.builder() + .volunteerApplyId(1L) + .volunteerId(volunteerId) + .title(title) + .content(content) + .imgUrl(imgUrl) + .build(); + } +} diff --git a/src/test/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryServiceTest.java b/src/test/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryServiceTest.java index 4b4387e41..32db1413d 100644 --- a/src/test/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/volunteerapply/service/VolunteerApplyQueryServiceTest.java @@ -1,12 +1,5 @@ package com.somemore.domains.volunteerapply.service; -import static com.somemore.domains.volunteerapply.domain.ApplyStatus.APPROVED; -import static com.somemore.domains.volunteerapply.domain.ApplyStatus.REJECTED; -import static com.somemore.domains.volunteerapply.domain.ApplyStatus.WAITING; -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - import com.somemore.domains.volunteerapply.domain.ApplyStatus; import com.somemore.domains.volunteerapply.domain.VolunteerApply; import com.somemore.domains.volunteerapply.dto.condition.VolunteerApplySearchCondition; @@ -15,8 +8,7 @@ import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository; import com.somemore.global.exception.NoSuchElementException; import com.somemore.support.IntegrationTestSupport; -import java.util.List; -import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +17,14 @@ import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.UUID; + +import static com.somemore.domains.volunteerapply.domain.ApplyStatus.*; +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_VOLUNTEER_APPLY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + @Transactional class VolunteerApplyQueryServiceTest extends IntegrationTestSupport { @@ -34,48 +34,73 @@ class VolunteerApplyQueryServiceTest extends IntegrationTestSupport { @Autowired private VolunteerApplyRepository volunteerApplyRepository; - @DisplayName("recruitIds로 봉사자 ID 리스트를 조회할 수 있다") + private Long recruitBoardId; + private UUID volunteerId; + private VolunteerApply apply; + + @BeforeEach + void setUp() { + recruitBoardId = 1234L; + volunteerId = UUID.randomUUID(); + apply = createApply(volunteerId, recruitBoardId); + volunteerApplyRepository.save(apply); + } + + @DisplayName("봉사 지원 아이디로 조회할 수 있다.") @Test - void getVolunteerIdsByRecruitIds() { - // Given - Long recruitId1 = 10L; - Long recruitId2 = 20L; - UUID volunteerId1 = UUID.randomUUID(); - UUID volunteerId2 = UUID.randomUUID(); + void getById() { + // given + Long id = apply.getId(); - VolunteerApply apply1 = createApply(volunteerId1, recruitId1); - VolunteerApply apply2 = createApply(volunteerId2, recruitId2); + // when + VolunteerApply findApply = volunteerApplyQueryService.getById(id); - volunteerApplyRepository.save(apply1); - volunteerApplyRepository.save(apply2); + // then + assertThat(findApply.getId()).isEqualTo(id); + assertThat(findApply.getVolunteerId()).isEqualTo(volunteerId); + assertThat(findApply.getRecruitBoardId()).isEqualTo(recruitBoardId); + } - // When - List volunteerIds = volunteerApplyQueryService.getVolunteerIdsByRecruitIds( - List.of(recruitId1, recruitId2)); + @DisplayName("존재하지 않는 봉사 지원 아이디로 조회할 경우 에러가 발생한다.") + @Test + void getByIdWhenDoesNotExist() { + // given + Long wrongId = 999L; - // Then - assertThat(volunteerIds) - .hasSize(2) - .containsExactlyInAnyOrder(volunteerId1, volunteerId2); + // when + // then + assertThatThrownBy( + () -> volunteerApplyQueryService.getById(wrongId)) + .isInstanceOf(NoSuchElementException.class) + .hasMessage(NOT_EXISTS_VOLUNTEER_APPLY.getMessage()); } @DisplayName("모집글 아이디와 봉사자 아이디로 조회할 수 있다") @Test void getByRecruitIdAndVolunteerId() { // given - Long recruitId = 1234L; - UUID volunteerId = UUID.randomUUID(); + // when + VolunteerApply findApply = volunteerApplyQueryService.getByRecruitIdAndVolunteerId(recruitBoardId, volunteerId); - VolunteerApply newApply = createApply(volunteerId, recruitId); - volunteerApplyRepository.save(newApply); + // then + assertThat(findApply.getRecruitBoardId()).isEqualTo(recruitBoardId); + assertThat(findApply.getVolunteerId()).isEqualTo(volunteerId); + } - // when - VolunteerApply apply = volunteerApplyQueryService.getByRecruitIdAndVolunteerId( - recruitId, volunteerId); + @DisplayName("존재하지 않는 모집글 아이디와 봉사자 아이디로 조회할 수 있다") + @Test + void getByRecruitIdAndVolunteerIdWhenDoesNotExist() { + // given + Long wrongRecruitBoardId = 999L; + UUID wrongVolunteerId = UUID.randomUUID(); + // when // then - assertThat(apply.getRecruitBoardId()).isEqualTo(recruitId); - assertThat(apply.getVolunteerId()).isEqualTo(volunteerId); + assertThatThrownBy( + () -> volunteerApplyQueryService.getByRecruitIdAndVolunteerId(wrongRecruitBoardId, wrongVolunteerId)) + .isInstanceOf(NoSuchElementException.class) + .hasMessage(NOT_EXISTS_VOLUNTEER_APPLY.getMessage()); + } @DisplayName("모집글 아이디로 지원 현황을 조회할 수 있다.") @@ -85,21 +110,21 @@ void getSummaryByRecruitBoardId() { long approveCount = 10; long rejectCount = 2; long waitingCount = 5; - long recruitBoardId = 100L; + long recruitId = 100L; for (int i = 0; i < waitingCount; i++) { - volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitBoardId, WAITING)); + volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId, WAITING)); } for (int i = 0; i < approveCount; i++) { - volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitBoardId, APPROVED)); + volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId, APPROVED)); } for (int i = 0; i < rejectCount; i++) { - volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitBoardId, REJECTED)); + volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId, REJECTED)); } // when - VolunteerApplySummaryResponseDto dto = volunteerApplyQueryService.getSummaryByRecruitId( - recruitBoardId); + VolunteerApplySummaryResponseDto dto = volunteerApplyQueryService.getSummaryByRecruitId(recruitId); + // then assertThat(dto.waiting()).isEqualTo(waitingCount); assertThat(dto.approve()).isEqualTo(approveCount); @@ -110,18 +135,12 @@ void getSummaryByRecruitBoardId() { @Test void getVolunteerApplyByRecruitIdAndVolunteerId() { // given - Long recruitId = 1234L; - UUID volunteerId = UUID.randomUUID(); - - VolunteerApply newApply = createApply(volunteerId, recruitId); - volunteerApplyRepository.save(newApply); - // when VolunteerApplyResponseDto dto = volunteerApplyQueryService.getVolunteerApplyByRecruitIdAndVolunteerId( - recruitId, volunteerId); + recruitBoardId, volunteerId); // then - assertThat(dto.recruitBoardId()).isEqualTo(recruitId); + assertThat(dto.recruitBoardId()).isEqualTo(recruitBoardId); assertThat(dto.volunteerId()).isEqualTo(volunteerId); } @@ -139,8 +158,7 @@ void getAllByRecruitId() { .build(); // when - Page applies = volunteerApplyQueryService.getAllByRecruitId(boardId, - condition); + Page applies = volunteerApplyQueryService.getAllByRecruitId(boardId, condition); // then assertThat(applies).hasSize(2); @@ -150,11 +168,11 @@ void getAllByRecruitId() { @Test void getVolunteerAppliesByVolunteerId() { // given - UUID volunteerId = UUID.randomUUID(); + UUID volunteerOneId = UUID.randomUUID(); - VolunteerApply apply1 = createApply(volunteerId, 200L); - VolunteerApply apply2 = createApply(volunteerId, 201L); - VolunteerApply apply3 = createApply(volunteerId, 202L); + VolunteerApply apply1 = createApply(volunteerOneId, 200L); + VolunteerApply apply2 = createApply(volunteerOneId, 201L); + VolunteerApply apply3 = createApply(volunteerOneId, 202L); VolunteerApply apply4 = createApply(UUID.randomUUID(), 203L); volunteerApplyRepository.saveAll(List.of(apply1, apply2, apply3, apply4)); @@ -164,7 +182,7 @@ void getVolunteerAppliesByVolunteerId() { // when Page applies = volunteerApplyQueryService.getAllByVolunteerId( - volunteerId, condition); + volunteerOneId, condition); // then assertThat(applies).hasSize(3); @@ -174,16 +192,12 @@ void getVolunteerAppliesByVolunteerId() { @Test void getAllByIds() { // given - Long recruitBoardId = 1L; + Long recruitId = 1L; - VolunteerApply apply1 = volunteerApplyRepository.save( - createApply(UUID.randomUUID(), recruitBoardId)); - VolunteerApply apply2 = volunteerApplyRepository.save( - createApply(UUID.randomUUID(), recruitBoardId)); - VolunteerApply apply3 = volunteerApplyRepository.save( - createApply(UUID.randomUUID(), recruitBoardId)); - VolunteerApply apply4 = volunteerApplyRepository.save( - createApply(UUID.randomUUID(), recruitBoardId)); + VolunteerApply apply1 = volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId)); + VolunteerApply apply2 = volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId)); + VolunteerApply apply3 = volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId)); + VolunteerApply apply4 = volunteerApplyRepository.save(createApply(UUID.randomUUID(), recruitId)); List ids = List.of(apply1.getId(), apply2.getId(), apply3.getId(), apply4.getId()); @@ -197,6 +211,31 @@ void getAllByIds() { .containsExactlyInAnyOrderElementsOf(ids); } + @DisplayName("recruitIds로 봉사자 ID 리스트를 조회할 수 있다") + @Test + void getVolunteerIdsByRecruitIds() { + // Given + Long recruitId1 = 10L; + Long recruitId2 = 20L; + UUID volunteerId1 = UUID.randomUUID(); + UUID volunteerId2 = UUID.randomUUID(); + + VolunteerApply apply1 = createApply(volunteerId1, recruitId1); + VolunteerApply apply2 = createApply(volunteerId2, recruitId2); + + volunteerApplyRepository.save(apply1); + volunteerApplyRepository.save(apply2); + + // When + List volunteerIds = volunteerApplyQueryService.getVolunteerIdsByRecruitIds( + List.of(recruitId1, recruitId2)); + + // Then + assertThat(volunteerIds) + .hasSize(2) + .containsExactlyInAnyOrder(volunteerId1, volunteerId2); + } + @DisplayName("존재하지 않는 봉사 지원을 조회할 경우 에러가 발생한다.") @Test void getByRecruitIdAndVolunteerIdWhenNotExist() { @@ -208,8 +247,8 @@ void getByRecruitIdAndVolunteerIdWhenNotExist() { // then assertThatThrownBy( () -> volunteerApplyQueryService.getByRecruitIdAndVolunteerId(wrongBoardId, - wrongVolunteerId) - ).isInstanceOf(NoSuchElementException.class) + wrongVolunteerId)) + .isInstanceOf(NoSuchElementException.class) .hasMessage(NOT_EXISTS_VOLUNTEER_APPLY.getMessage()); }