diff --git a/build.gradle b/build.gradle index f6282a4..b49e966 100644 --- a/build.gradle +++ b/build.gradle @@ -77,6 +77,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-oauth2-client' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' } diff --git a/src/main/java/com/kidsqueue/kidsqueue/common/Api.java b/src/main/java/com/kidsqueue/kidsqueue/common/Api.java index 1303ecc..9423f70 100644 --- a/src/main/java/com/kidsqueue/kidsqueue/common/Api.java +++ b/src/main/java/com/kidsqueue/kidsqueue/common/Api.java @@ -1,5 +1,6 @@ package com.kidsqueue.kidsqueue.common; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/kidsqueue/kidsqueue/common/BaseEntity.java b/src/main/java/com/kidsqueue/kidsqueue/common/BaseEntity.java index 8bf6a75..a2d6e9d 100644 --- a/src/main/java/com/kidsqueue/kidsqueue/common/BaseEntity.java +++ b/src/main/java/com/kidsqueue/kidsqueue/common/BaseEntity.java @@ -16,9 +16,9 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; @MappedSuperclass -@Data -@SuperBuilder -@NoArgsConstructor +@Data // TODO : data 어노테이션의 위험성 상 제거하는 게 좋을 듯함 +@SuperBuilder(builderMethodName = "superBuilder") +@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @EntityListeners(AuditingEntityListener.class) // 생성 일자, 수정 일자 자동으로 채워줌 public class BaseEntity { // 공통 속성을 묶는 엔티티 @@ -40,4 +40,8 @@ public class BaseEntity { // 공통 속성을 묶는 엔티티 public boolean isActive() { // 데이터베이스 저장용 : Integer, 실제 로직용: boolean return isActive != null && isActive == 1; } + + public void softDelete() { + this.isActive = 0; + } } diff --git a/src/main/java/com/kidsqueue/kidsqueue/config/objectMapper/ObjectMapperConfig.java b/src/main/java/com/kidsqueue/kidsqueue/config/objectMapper/ObjectMapperConfig.java new file mode 100644 index 0000000..1d9cd5f --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/config/objectMapper/ObjectMapperConfig.java @@ -0,0 +1,29 @@ +package com.kidsqueue.kidsqueue.config.objectMapper; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class ObjectMapperConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + + objectMapper.registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy()); + + return objectMapper; + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/config/swagger/SwaggerConfig.java b/src/main/java/com/kidsqueue/kidsqueue/config/swagger/SwaggerConfig.java new file mode 100644 index 0000000..91da5a3 --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/config/swagger/SwaggerConfig.java @@ -0,0 +1,14 @@ +package com.kidsqueue.kidsqueue.config.swagger; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.core.jackson.ModelResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + @Bean + public ModelResolver modelResolver(ObjectMapper objectMapper) { + return new ModelResolver(objectMapper); + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/controller/ReviewController.java b/src/main/java/com/kidsqueue/kidsqueue/review/controller/ReviewController.java index c9a3281..dc8b9cb 100644 --- a/src/main/java/com/kidsqueue/kidsqueue/review/controller/ReviewController.java +++ b/src/main/java/com/kidsqueue/kidsqueue/review/controller/ReviewController.java @@ -1,25 +1,16 @@ package com.kidsqueue.kidsqueue.review.controller; import com.kidsqueue.kidsqueue.common.Api; -import com.kidsqueue.kidsqueue.review.dtos.ReviewCreateDto; -import com.kidsqueue.kidsqueue.review.dtos.ReviewDto; -import com.kidsqueue.kidsqueue.review.dtos.ReviewUpdateDto; +import com.kidsqueue.kidsqueue.review.model.*; import com.kidsqueue.kidsqueue.review.service.ReviewService; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequestMapping("/api/review") @@ -28,15 +19,18 @@ public class ReviewController { private final ReviewService reviewService; - public Api> get( + // TODO : 반환 값을 바꿔볼까 고민 중 + + // TODO : 동작 안 함 /api/review?sortCondition=desc로 url 입력한 결과. + // 디버깅 필요함 + @GetMapping + public Api> get( @PageableDefault(page = 0, size = 10) Pageable pageable, - @RequestParam(required = false) String sortCondition, - @RequestParam(required = false) String name + @RequestParam(required = false) String sortCondition ) { System.out.println("sortCondition = " + sortCondition); - System.out.println("name = " + name); - Api> apiResponse; + Api> apiResponse; if ("desc".equals(sortCondition)) { apiResponse = reviewService.findAllDesc(pageable); @@ -50,9 +44,9 @@ public Api> get( return apiResponse; } - @GetMapping("/{id}") - public ResponseEntity> getReviewById(@PathVariable Long id) { - Api apiResponse = reviewService.findReviewById(id); + @GetMapping("/{review_id}") + public ResponseEntity> getReviewById(@PathVariable("review_id") Long id) { + Api apiResponse = reviewService.findReviewById(id); if (apiResponse.getData() != null) { apiResponse.setStatus(Api.SUCCESS_STATUS); @@ -66,8 +60,8 @@ public ResponseEntity> getReviewById(@PathVariable Long id) { } @PostMapping - public ResponseEntity> createReview(@RequestBody ReviewCreateDto reviewCreateDto) { - Api apiResponse = reviewService.createReview(reviewCreateDto); + public ResponseEntity> createReview(@RequestBody ReviewCreateRequest reviewCreateRequest) { + Api apiResponse = reviewService.createReview(reviewCreateRequest); if (apiResponse.getData() != null) { apiResponse.setStatus(Api.SUCCESS_STATUS); @@ -80,10 +74,10 @@ public ResponseEntity> createReview(@RequestBody ReviewCrea } } - @PatchMapping("/{id}") - public ResponseEntity> updateReview(@PathVariable Long id, - @RequestBody ReviewUpdateDto updateDto) { - Api apiResponse = reviewService.updateReview(id, updateDto); + @PatchMapping("/{review_id}") + public ResponseEntity> updateReview(@PathVariable("review_id") Long id, + @RequestBody ReviewUpdateRequest updateDto) { + Api apiResponse = reviewService.updateReview(id, updateDto); if (apiResponse.getData() != null) { apiResponse.setStatus(Api.SUCCESS_STATUS); @@ -96,8 +90,8 @@ public ResponseEntity> updateReview(@PathVariable Long id, } } - @DeleteMapping("/{id}") - public ResponseEntity> deleteReview(@PathVariable Long id) { + @DeleteMapping("/{review_id}") + public ResponseEntity> deleteReview(@PathVariable("review_id") Long id) { Api apiResponse = reviewService.deleteReview(id); if (apiResponse.getData() == null) { @@ -110,4 +104,5 @@ public ResponseEntity> deleteReview(@PathVariable Long id) { return new ResponseEntity<>(apiResponse, HttpStatus.BAD_REQUEST); } } + } diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/db/Review.java b/src/main/java/com/kidsqueue/kidsqueue/review/db/Review.java new file mode 100644 index 0000000..b00edde --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/db/Review.java @@ -0,0 +1,39 @@ +package com.kidsqueue.kidsqueue.review.db; + +import com.kidsqueue.kidsqueue.common.BaseEntity; +import com.kidsqueue.kidsqueue.hospital.db.Hospital; +import com.kidsqueue.kidsqueue.parent.db.Parent; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "review") +public class Review extends BaseEntity { + private String title; + private Integer score; + private String description; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hospital_id") + private Hospital hospital; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Parent parent; + + @Builder + public Review(Long id, Integer isActive, LocalDateTime createdBy, LocalDateTime updatedBy, String title, Integer score, String description, Hospital hospital, Parent parent) { + super(id, isActive, createdBy, updatedBy); + this.title = title; + this.score = score; + this.description = description; + this.hospital = hospital; + this.parent = parent; + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/db/ReviewRepository.java b/src/main/java/com/kidsqueue/kidsqueue/review/db/ReviewRepository.java index 350c28c..139907f 100644 --- a/src/main/java/com/kidsqueue/kidsqueue/review/db/ReviewRepository.java +++ b/src/main/java/com/kidsqueue/kidsqueue/review/db/ReviewRepository.java @@ -1,13 +1,11 @@ package com.kidsqueue.kidsqueue.review.db; -import com.kidsqueue.kidsqueue.hospital.db.Hospital; -import com.kidsqueue.kidsqueue.review.model.Review; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import java.util.List; +import java.util.Optional; public interface ReviewRepository extends JpaRepository { // TODO : localDateTime으로 내림차순 정렬이 안 되어서 auto increment 되는 아이디를 기준으로 정렬했음 @@ -16,6 +14,8 @@ public interface ReviewRepository extends JpaRepository { Page findAllByIsActiveOrderByIdAsc(Integer isActive, Pageable pageable); + Optional findByIdAndIsActive(Long id, Integer isActive); + @Query("select r from Review r " + " join fetch r.hospital") diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewCreateDto.java b/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewCreateDto.java deleted file mode 100644 index de5f29e..0000000 --- a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewCreateDto.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.kidsqueue.kidsqueue.review.dtos; - -import com.kidsqueue.kidsqueue.hospital.db.Hospital; -import com.kidsqueue.kidsqueue.parent.db.Parent; -import com.kidsqueue.kidsqueue.review.model.Review; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReviewCreateDto { - @NotBlank - private String title; - @NotBlank - private Integer score; - private String description; - @NotBlank - private Long hospitalId; - @NotBlank - private Long parentId; - - public Review toEntity(Hospital hospital, Parent parent) { - return Review.builder() - .title(title) - .score(score) - .description(description) - .hospital(hospital) - .parent(parent) - .build(); - } -} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewDto.java b/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewDto.java deleted file mode 100644 index 907f798..0000000 --- a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.kidsqueue.kidsqueue.review.dtos; - -import com.kidsqueue.kidsqueue.review.model.Review; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@SuperBuilder -public class ReviewDto { - private String title; - private Integer score; - private String description; - private LocalDateTime createdBy; - public Review toEntity() { - return Review.builder() - .title(title) - .score(score) - .description(description) - .createdBy(createdBy) - .build(); - } -} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewUpdateDto.java b/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewUpdateDto.java deleted file mode 100644 index c6f7413..0000000 --- a/src/main/java/com/kidsqueue/kidsqueue/review/dtos/ReviewUpdateDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.kidsqueue.kidsqueue.review.dtos; - -import com.kidsqueue.kidsqueue.review.model.Review; -import jakarta.validation.constraints.NotBlank; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReviewUpdateDto { - @NotBlank - private String title; - @NotBlank - private Integer score; - private String description; - - public Review toEntity() { - return Review.builder() - .title(title) - .score(score) - .description(description) - .build(); - } -} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewErrorCode.java b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewErrorCode.java new file mode 100644 index 0000000..1621a57 --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewErrorCode.java @@ -0,0 +1,18 @@ +package com.kidsqueue.kidsqueue.review.exception; + +import com.kidsqueue.kidsqueue.common.error.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor +@Getter +public enum ReviewErrorCode implements ErrorCode { + Review_Not_Found(HttpStatus.NOT_FOUND, "No Review"), + Hospital_Not_Found(HttpStatus.NOT_FOUND, "No Hospital"), + Parent_Not_Found(HttpStatus.NOT_FOUND, "No Parent"), + ; + + private final HttpStatus httpStatusCode; + private final String description; +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewException.java b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewException.java new file mode 100644 index 0000000..20a4114 --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewException.java @@ -0,0 +1,51 @@ +package com.kidsqueue.kidsqueue.review.exception; + +import com.kidsqueue.kidsqueue.common.error.ErrorCode; +import com.kidsqueue.kidsqueue.review.model.ReviewDto; +import lombok.Getter; + +@Getter +public class ReviewException extends RuntimeException { + private final ErrorCode errorCode; + private final String description; + private final ReviewDto reviewDto; + + public ReviewException(ErrorCode errorCode) { + super(errorCode.getDescription()); + this.errorCode = errorCode; + this.description = errorCode.getDescription(); + this.reviewDto = null; + } + + public ReviewException(ErrorCode errorCode, String description) { + super(description); + this.errorCode = errorCode; + this.description = description; + this.reviewDto = null; + } + + public ReviewException(ErrorCode errorCode, ReviewDto reviewDto) { + super(errorCode.getDescription()); + this.errorCode = errorCode; + this.description = errorCode.getDescription(); + this.reviewDto = reviewDto; + } + + public ReviewException(ErrorCode errorCode, String description, ReviewDto reviewDto) { + super(description); + this.errorCode = errorCode; + this.description = description; + this.reviewDto = reviewDto; + } + + public ReviewException(ErrorCode errorCode, Throwable throwable) { + super(throwable); + this.errorCode = errorCode; + this.description = errorCode.getDescription(); + this.reviewDto = null; + } + +// public ReviewDto getReviewDto() { +// return reviewDto.getInstance(); +// } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewExceptionHandler.java b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewExceptionHandler.java new file mode 100644 index 0000000..ac153b8 --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/exception/ReviewExceptionHandler.java @@ -0,0 +1,56 @@ +package com.kidsqueue.kidsqueue.review.exception; + +import com.kidsqueue.kidsqueue.common.Api; +import com.kidsqueue.kidsqueue.review.model.ReviewDto; +import jakarta.annotation.Priority; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@Priority(0) +@RestControllerAdvice(basePackages = "com.kidsqueue.kidsqueue.review") +public class ReviewExceptionHandler { + @ExceptionHandler(value = ReviewException.class) + public ResponseEntity> reviewExceptionHandler(ReviewException reviewException) { + log.error("", reviewException); + + HttpStatus httpStatus = reviewException.getErrorCode().getHttpStatusCode(); + Api response = Api.builder() + .data(reviewException.getReviewDto()) + .status(getFullHttpStatus(httpStatus)) + .message(reviewException.getMessage()) + .build(); + + return ResponseEntity + .status(httpStatus) + .body(response); + } + @ExceptionHandler(value = RuntimeException.class) + public ResponseEntity commonExceptionHandler(RuntimeException runtimeException) { + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(runtimeException.getCause().getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + log.error("", ex); + + String errorMessage = "Validation failed"; + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(Api.error(errorMessage)); + } + + String getFullHttpStatus(HttpStatus httpStatus) { + return httpStatus.value() + " " + httpStatus.getReasonPhrase(); + } +} + + diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/Review.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/Review.java deleted file mode 100644 index 4c97fc7..0000000 --- a/src/main/java/com/kidsqueue/kidsqueue/review/model/Review.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.kidsqueue.kidsqueue.review.model; - -import com.kidsqueue.kidsqueue.common.BaseEntity; -import com.kidsqueue.kidsqueue.hospital.db.Hospital; -import com.kidsqueue.kidsqueue.parent.db.Parent; -import jakarta.persistence.*; -import lombok.*; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDate; -import java.time.LocalDateTime; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@SuperBuilder -@Entity -@Table(name = "review") -public class Review extends BaseEntity { - private String title; - private Integer score; - private String description; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "hospital_id") - private Hospital hospital; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id") - private Parent parent; - -} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateRequest.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateRequest.java new file mode 100644 index 0000000..f00a76d --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateRequest.java @@ -0,0 +1,46 @@ +package com.kidsqueue.kidsqueue.review.model; + +import com.kidsqueue.kidsqueue.hospital.db.Hospital; +import com.kidsqueue.kidsqueue.parent.db.Parent; +import com.kidsqueue.kidsqueue.review.db.Review; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.springframework.util.Assert; + +@Getter +public class ReviewCreateRequest implements ReviewDto { + @NotBlank + private final String title; + @NotNull + private final Integer score; + private final String description; + @NotNull + private final Long hospitalId; + @NotNull + private final Long parentId; + + public Review toEntity(Hospital hospital, Parent parent) { + return Review.builder() + .title(title) + .score(score) + .description(description) + .hospital(hospital) + .parent(parent) + .build(); + } + + @Builder + public ReviewCreateRequest(String title, Integer score, String description, Long hospitalId, Long parentId) { + Assert.hasText(title, "title must not be empty"); + Assert.notNull(score, "score must not be null"); + Assert.notNull(hospitalId, "hospitalId must not be null"); + Assert.notNull(parentId, "parentId must not be null"); + this.title = title; + this.score = score; + this.description = description; + this.hospitalId = hospitalId; + this.parentId = parentId; + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateResponse.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateResponse.java new file mode 100644 index 0000000..d87534b --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewCreateResponse.java @@ -0,0 +1,41 @@ +package com.kidsqueue.kidsqueue.review.model; + +import com.kidsqueue.kidsqueue.hospital.db.Hospital; +import com.kidsqueue.kidsqueue.parent.db.Parent; +import com.kidsqueue.kidsqueue.review.db.Review; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.springframework.util.Assert; + +import java.time.LocalDateTime; + +@Getter +public class ReviewCreateResponse { + @NotBlank + private final String title; + @NotNull + private final Integer score; + private final String description; + private final LocalDateTime createdBy; + + @Builder + public ReviewCreateResponse(String title, Integer score, String description, LocalDateTime createdBy) { + Assert.hasText(title, "title must not be empty"); + Assert.notNull(score, "score must not be null"); + this.title = title; + this.score = score; + this.description = description; + this.createdBy = createdBy; + } + + public static ReviewCreateResponse fromEntity(Review review) { + return ReviewCreateResponse.builder() + .title(review.getTitle()) + .score(review.getScore()) + .description(review.getDescription()) + .createdBy(review.getCreatedBy()) + .build(); + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewDto.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewDto.java new file mode 100644 index 0000000..e534b9f --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewDto.java @@ -0,0 +1,5 @@ +package com.kidsqueue.kidsqueue.review.model; + + +public interface ReviewDto { +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewReadResponse.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewReadResponse.java new file mode 100644 index 0000000..1796eb9 --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewReadResponse.java @@ -0,0 +1,40 @@ +package com.kidsqueue.kidsqueue.review.model; + +import com.kidsqueue.kidsqueue.review.db.Review; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.springframework.util.Assert; + +import java.time.LocalDateTime; + +@Getter +public class ReviewReadResponse implements ReviewDto { + @NotBlank + private final String title; + @NotNull + private final Integer score; + private final String description; + @NotNull + private final LocalDateTime createdBy; + + @Builder + public ReviewReadResponse(String title, Integer score, String description, LocalDateTime createdBy) { + Assert.hasText(title, "title must not be empty"); + Assert.notNull(score, "score must not be null"); + this.title = title; + this.score = score; + this.description = description; + this.createdBy = createdBy; + } + + public static ReviewReadResponse fromEntity(Review review) { + return ReviewReadResponse.builder() + .title(review.getTitle()) + .score(review.getScore()) + .description(review.getDescription()) + .createdBy(review.getCreatedBy()) + .build(); + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateRequest.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateRequest.java new file mode 100644 index 0000000..fed8a6c --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateRequest.java @@ -0,0 +1,36 @@ +package com.kidsqueue.kidsqueue.review.model; + +import com.kidsqueue.kidsqueue.review.db.Review; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.springframework.util.Assert; + +@Getter +public class ReviewUpdateRequest implements ReviewDto { + @NotBlank + private String title; + @NotNull + private Integer score; + private String description; + + @Builder + public ReviewUpdateRequest(String title, Integer score, String description) { + Assert.hasText(title, "title must not be empty"); + Assert.notNull(score, "score must not be null"); + this.title = title; + this.score = score; + this.description = description; + } + + public Review toEntity(Long id) { + return Review.builder() + .id(id) + .title(title) + .score(score) + .description(description) + .build(); + } + +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateResponse.java b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateResponse.java new file mode 100644 index 0000000..ef14b8e --- /dev/null +++ b/src/main/java/com/kidsqueue/kidsqueue/review/model/ReviewUpdateResponse.java @@ -0,0 +1,33 @@ +package com.kidsqueue.kidsqueue.review.model; + +import com.kidsqueue.kidsqueue.review.db.Review; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import org.springframework.util.Assert; + +@Getter +public class ReviewUpdateResponse { + @NotBlank + private final String title; + @NotNull + private final Integer score; + private final String description; + @Builder + public ReviewUpdateResponse(String title, Integer score, String description) { + Assert.hasText(title, "title must not be empty"); + Assert.notNull(score, "score must not be null"); + this.title = title; + this.score = score; + this.description = description; + } + + public static ReviewUpdateResponse fromEntity(Review review) { + return ReviewUpdateResponse.builder() + .title(review.getTitle()) + .score(review.getScore()) + .description(review.getDescription()) + .build(); + } +} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewConverter.java b/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewConverter.java deleted file mode 100644 index 83d32a0..0000000 --- a/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewConverter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.kidsqueue.kidsqueue.review.service; - -import com.kidsqueue.kidsqueue.review.model.Review; -import com.kidsqueue.kidsqueue.review.dtos.ReviewDto; -import org.springframework.stereotype.Service; - -@Service -public class ReviewConverter { - public ReviewDto toDto(Review review) { - return ReviewDto.builder() - .title(review.getTitle()) - .score(review.getScore()) - .description(review.getDescription()) - .createdBy(review.getCreatedBy()) - .build(); - } - - public Review toEntity(ReviewDto reviewDto) { - return Review.builder() - .title(reviewDto.getTitle()) - .description(reviewDto.getDescription()) - .score(reviewDto.getScore()) - .build(); - } -} diff --git a/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewService.java b/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewService.java index 9a364d0..6f1bf62 100644 --- a/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewService.java +++ b/src/main/java/com/kidsqueue/kidsqueue/review/service/ReviewService.java @@ -6,152 +6,111 @@ import com.kidsqueue.kidsqueue.hospital.db.HospitalRepository; import com.kidsqueue.kidsqueue.parent.db.Parent; import com.kidsqueue.kidsqueue.parent.db.ParentRepository; -import com.kidsqueue.kidsqueue.review.model.Review; +import com.kidsqueue.kidsqueue.review.exception.ReviewException; +import com.kidsqueue.kidsqueue.review.db.Review; import com.kidsqueue.kidsqueue.review.db.ReviewRepository; -import com.kidsqueue.kidsqueue.review.dtos.ReviewDto; +import com.kidsqueue.kidsqueue.review.model.*; + import java.util.List; -import java.util.Optional; -import com.kidsqueue.kidsqueue.review.dtos.ReviewCreateDto; -import com.kidsqueue.kidsqueue.review.dtos.ReviewUpdateDto; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import static com.kidsqueue.kidsqueue.review.exception.ReviewErrorCode.*; + @Service @RequiredArgsConstructor public class ReviewService { private final ReviewRepository reviewRepository; private final HospitalRepository hospitalRepository; private final ParentRepository parentRepository; - private final ReviewConverter reviewConverter; - public Api> findAllAsc(Pageable pageable) { + public Api> findAllAsc(Pageable pageable) { // Page 를 Page 로 변환 - Page reviewDtoPage = reviewRepository.findAllByIsActiveOrderByIdAsc(1, pageable) - .map(reviewConverter::toDto); - return getApiResponse(reviewDtoPage); + Page page = reviewRepository.findAllByIsActiveOrderByIdAsc(1, pageable) + .map(ReviewReadResponse::fromEntity); + return getApiResponse(page); } - public Api> findAllDesc(Pageable pageable) { - Page reviewDtoPage = reviewRepository.findAllByIsActiveOrderByIdDesc(1, pageable) - .map(reviewConverter::toDto); - return getApiResponse(reviewDtoPage); + public Api> findAllDesc(Pageable pageable) { + Page page = reviewRepository.findAllByIsActiveOrderByIdDesc(1, pageable) + .map(ReviewReadResponse::fromEntity); + return getApiResponse(page); } - private static Api> getApiResponse(Page reviewDtoPage) { - List reviewDtoList = reviewDtoPage.toList(); + private static Api> getApiResponse(Page response) { + List list = response.toList(); // Page 로 pagination 생성 Pagination pagination = Pagination.builder() - .page(reviewDtoPage.getNumber()) - .size(reviewDtoPage.getSize()) - .currentElements(reviewDtoPage.getNumberOfElements()) - .totalElements(reviewDtoPage.getTotalElements()) - .totalPage(reviewDtoPage.getTotalPages()) + .page(response.getNumber()) + .size(response.getSize()) + .currentElements(response.getNumberOfElements()) + .totalElements(response.getTotalElements()) + .totalPage(response.getTotalPages()) .build(); // ApiResponse> 만들어서 리턴 - return Api.>builder() - .data(reviewDtoList) + return Api.>builder() + .data(list) .pagination(pagination) .build(); } - public Api findReviewById(Long id) { - Optional reviewOptional = reviewRepository.findById(id); + public Api findReviewById(Long id) { + Review review = reviewRepository.findByIdAndIsActive(id, 1) + .orElseThrow(() -> new ReviewException(Review_Not_Found)); - if (reviewOptional.isPresent()) { - ReviewDto reviewDto = reviewConverter.toDto(reviewOptional.get()); - return Api.builder() - .data(reviewDto) + ReviewReadResponse reviewReadResponse = ReviewReadResponse.fromEntity(review); + return Api.builder() + .data(reviewReadResponse) .status(Api.SUCCESS_STATUS) .message("리뷰 정보 조회 성공") .build(); - } else { - return Api.builder() - .status(Api.ERROR_STATUS) - .message("해당 리뷰를 찾을 수 없습니다.") - .build(); - } } - public Api createReview(ReviewCreateDto createDto) { - Optional optionalHospital = hospitalRepository.findById(createDto.getHospitalId()); - Optional optionalParent = parentRepository.findById(createDto.getParentId()); - if (optionalHospital.isEmpty() || optionalParent.isEmpty()) { - StringBuilder messageBuilder = new StringBuilder(); - if (optionalHospital.isEmpty()) { - messageBuilder.append("유효하지 않은 hospital_id : ").append(createDto.getHospitalId()).append("\n"); - } - if (optionalParent.isEmpty()) { - messageBuilder.append("유효하지 않은 parent_id : ").append(createDto.getParentId()); - } - - return Api.builder() - .status(Api.FAIL_STATUS) - .message(messageBuilder.toString()) - .build(); - } - - Hospital hospital = optionalHospital.get(); - Parent parent = optionalParent.get(); + public Api createReview(ReviewCreateRequest createDto) { + Hospital hospital = hospitalRepository.findById(createDto.getHospitalId()) + .orElseThrow(() -> new ReviewException(Hospital_Not_Found, createDto)); + Parent parent = parentRepository.findById(createDto.getParentId()) + .orElseThrow(() -> new ReviewException(Parent_Not_Found, createDto)); Review review = createDto.toEntity(hospital, parent); reviewRepository.save(review); - - return Api.builder() - .data(createDto) + return Api.builder() + .data(ReviewCreateResponse.fromEntity(review)) .status(Api.SUCCESS_STATUS) .message("리뷰 정보 생성 성공") .build(); } - public Api updateReview(Long id, ReviewUpdateDto updateDto) { - Optional reviewOptional = reviewRepository.findById(id); - if (reviewOptional.isPresent()) { - // 엔티티 수정 - Review existingReview = reviewOptional.get(); - existingReview.setTitle(updateDto.getTitle()); - existingReview.setScore(updateDto.getScore()); - existingReview.setDescription(updateDto.getDescription()); + public Api updateReview(Long id, ReviewUpdateRequest updateDto) { + Review review = reviewRepository.findById(id) + .orElseThrow(() -> new ReviewException(Review_Not_Found, updateDto)); - Review updatedReview = reviewRepository.save(existingReview); + Review saved = updateDto.toEntity(review.getId()); + reviewRepository.save(saved); - return Api.builder() - .data(updateDto) + return Api.builder() + .data(ReviewUpdateResponse.fromEntity(saved)) .status(Api.SUCCESS_STATUS) .message("리뷰 정보 수정 성공") .build(); - } else { - return Api.builder() - .status(Api.ERROR_STATUS) - .message("해당 리뷰를 찾을 수 없습니다.") - .build(); - } } public Api deleteReview(Long id) { - Optional reviewOptional = reviewRepository.findById(id); + Review review = reviewRepository.findById(id) + .orElseThrow(() -> new ReviewException(Review_Not_Found)); - if (reviewOptional.isPresent()) { - Review existingReview = reviewOptional.get(); - existingReview.setIsActive(0); + review.softDelete(); - return Api.builder() + return Api.builder() .status(Api.SUCCESS_STATUS) .message("리뷰 정보 삭제 성공") - .data("리뷰 정보가 성공적으로 삭제되었습니다.") - .build(); - } else { - return Api.builder() - .status(Api.ERROR_STATUS) - .message("해당 리뷰를 찾을 수 없습니다.") + .data("리뷰 정보 삭제에 성공했습니다.") .build(); - } } - - -} +} \ No newline at end of file diff --git a/src/test/java/com/kidsqueue/kidsqueue/medium/review/service/ReviewServiceTest.java b/src/test/java/com/kidsqueue/kidsqueue/medium/review/service/ReviewServiceTest.java new file mode 100644 index 0000000..c91a344 --- /dev/null +++ b/src/test/java/com/kidsqueue/kidsqueue/medium/review/service/ReviewServiceTest.java @@ -0,0 +1,206 @@ +package com.kidsqueue.kidsqueue.medium.review.service; + +import com.kidsqueue.kidsqueue.common.Api; +import com.kidsqueue.kidsqueue.hospital.db.Hospital; +import com.kidsqueue.kidsqueue.hospital.db.HospitalRepository; +import com.kidsqueue.kidsqueue.hospital.db.enums.Status; +import com.kidsqueue.kidsqueue.parent.db.Parent; +import com.kidsqueue.kidsqueue.parent.db.ParentRepository; +import com.kidsqueue.kidsqueue.review.db.Review; +import com.kidsqueue.kidsqueue.review.db.ReviewRepository; +import com.kidsqueue.kidsqueue.review.exception.ReviewErrorCode; +import com.kidsqueue.kidsqueue.review.exception.ReviewException; +import com.kidsqueue.kidsqueue.review.model.*; +import com.kidsqueue.kidsqueue.review.service.ReviewService; +import jakarta.transaction.Transactional; +import org.assertj.core.api.Assertions; +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.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; + +import java.util.List; +import java.util.Optional; + +import static com.kidsqueue.kidsqueue.parent.db.enums.Gender.남; +import static com.kidsqueue.kidsqueue.parent.db.enums.RoleType.USER; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@Transactional +class ReviewServiceTest { + @Autowired + ReviewService reviewService; + @Autowired + HospitalRepository hospitalRepository; + @Autowired + ParentRepository parentRepository; + @Autowired + ReviewRepository reviewRepository; + private Hospital hospital; + private Parent parent; + + @BeforeEach + void setUp() { + hospital = createHospital("병원1", "000-0000-0001"); + parent = createParent("testSignUp1", "010-0000-0001", "0000000000000", "testSignUp@naver.com"); + } + + // 메서드 시그너쳐 상 동일성 비교가 불가능하므로 repository 테스트로 이를 대체하고 반환값만 검증하였음 + // 테스트 DB가 계속 비어있다는 가정이면, 전체 조회를 2회 시행해서 전후를 비교하고 괜찮은 보호 수준을 확보해도 문제 없을 것. + @Test + @DisplayName("리뷰 생성에 성공한다.") + void createReview_positiveCase() { + ReviewCreateRequest request = createReviewCreateRequest("리뷰 1", hospital, parent); + + Api apiResponse = reviewService.createReview(request); + + assertThat(apiResponse.getData().getTitle()).isEqualTo(request.getTitle()); + } + + @Test + @DisplayName("리뷰 생성에 실패한다. - hospital id 조회 실패") + void createReview_negativeCase_notFound_hospitalId() { + //given + hospitalRepository.delete(hospital); + ReviewCreateRequest request = createReviewCreateRequest("리뷰 1", hospital, parent); + + //when + //then + assertThatThrownBy(() -> { + Api response = reviewService.createReview(request); + }).isInstanceOf(ReviewException.class) + .hasMessageContaining(ReviewErrorCode.Hospital_Not_Found.getDescription()); + } + + @Test + @DisplayName("리뷰 생성에 실패한다. - parent id 조회 실패") + void createReview_negativeCase_notFound_parentId() { + //given + parentRepository.delete(parent); + ReviewCreateRequest request = createReviewCreateRequest("리뷰 1", hospital, parent); + + //when + //then + assertThatThrownBy(() -> { + Api response = reviewService.createReview(request); + }).isInstanceOf(ReviewException.class) + .hasMessageContaining(ReviewErrorCode.Parent_Not_Found.getDescription()); + } + + @Test + @DisplayName("리뷰를 오름차순 정렬한다.") + void findAllAsc() { + Review review1 = createReview("리뷰 1", hospital, parent); + Review review2 = createReview("리뷰 2", hospital, parent); + reviewRepository.save(review1); + reviewRepository.save(review2); + PageRequest pageRequest = PageRequest.of(0, 10); + + List list = reviewService.findAllAsc(pageRequest).getData(); + + assertTrue((list.get(list.size() - 2).getCreatedBy().isBefore(list.get(list.size() - 1).getCreatedBy()))); + } + + @Test + @DisplayName("리뷰를 내림차순 정렬한다.") + void findAllDesc() { + Review review1 = createReview("리뷰 1", hospital, parent); + Review review2 = createReview("리뷰 2", hospital, parent); + reviewRepository.save(review1); + reviewRepository.save(review2); + PageRequest pageRequest = PageRequest.of(0, 10); + + List list = reviewService.findAllDesc(pageRequest).getData(); + + assertTrue((list.get(list.size() - 2).getCreatedBy().isAfter(list.get(list.size() - 1).getCreatedBy()))); + } + + @Test + @DisplayName("리뷰를 업데이트한다.") + void updateReview() { + Review review = createReview("리뷰1", hospital, parent); + reviewRepository.save(review); + ReviewUpdateRequest request = ReviewUpdateRequest.builder() + .title("리뷰 수정1") + .description("설명 수정1") + .score(1) + .build(); + + Api response = reviewService.updateReview(review.getId(), request); + + Review found = reviewRepository.findById(review.getId()).orElseThrow(()-> new RuntimeException("not found")); + assertThat(found.getTitle()).isEqualTo(request.getTitle()); + } + + @Test + @DisplayName("리뷰를 삭제한다.") + void deleteReview() { + Review review = createReview("리뷰1", hospital, parent); + reviewRepository.save(review); + + reviewService.deleteReview(review.getId()); + + Optional reviewOptional = reviewRepository.findById(review.getId()); + if (reviewOptional.isPresent()) assertEquals(0, reviewOptional.get().getIsActive()); + else fail("deleteReview 메서드는 삭제되는 대신 isActive 가 변경돼야 합니다."); + } + + Hospital createHospital(String name, String phoneNumber) { + Hospital hospital = Hospital.builder() + .name(name) + .address(name + "주소") + .description(name + "설명") + .phoneNumber(phoneNumber) + .maxNumOfPeople(5) + .status(Status.OPEN) + .build(); + hospitalRepository.save(hospital); + + return hospital; + } + + Parent createParent(String loginId, String phoneNumber, String residentRegistrationNumber, String email) { + Parent parent = Parent.builder() + .loginId(loginId) + .password("비밀번호") + .nickname(loginId + "별명") + .name("이름") + .age("20") + .gender(남) + .phoneNumber(phoneNumber) + .residentRegistrationNumber(residentRegistrationNumber) + .email(email) + .role(USER) + .build(); + parentRepository.save(parent); + return parent; + } + + Review createReview(String title, Hospital hospital, Parent parent) { + return Review.builder() + .title(title) + .score(5) + .description(title + "설명") + .hospital(hospital) + .parent(parent) + .isActive(1) + .build(); + } + + ReviewCreateRequest createReviewCreateRequest(String title, Hospital hospital, Parent parent) { + return ReviewCreateRequest.builder() + .title(title) + .score(5) + .description(title + " 설명") + .parentId(parent.getId()) + .hospitalId(hospital.getId()) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/kidsqueue/kidsqueue/review/db/ReviewRepositoryTest.java b/src/test/java/com/kidsqueue/kidsqueue/review/db/ReviewRepositoryTest.java new file mode 100644 index 0000000..ca4abf7 --- /dev/null +++ b/src/test/java/com/kidsqueue/kidsqueue/review/db/ReviewRepositoryTest.java @@ -0,0 +1,90 @@ +package com.kidsqueue.kidsqueue.review.db; + +import com.kidsqueue.kidsqueue.hospital.db.Hospital; +import com.kidsqueue.kidsqueue.hospital.db.HospitalRepository; +import com.kidsqueue.kidsqueue.hospital.db.enums.Status; +import com.kidsqueue.kidsqueue.parent.db.Parent; +import com.kidsqueue.kidsqueue.parent.db.ParentRepository; +import com.kidsqueue.kidsqueue.review.model.ReviewCreateRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static com.kidsqueue.kidsqueue.parent.db.enums.Gender.남; +import static com.kidsqueue.kidsqueue.parent.db.enums.RoleType.USER; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +class ReviewRepositoryTest { + @Autowired private ReviewRepository reviewRepository; + @Autowired private HospitalRepository hospitalRepository; + @Autowired private ParentRepository parentRepository; + private Hospital hospital; + private Parent parent; + @BeforeEach + void setUp() { + hospital = createHospital("병원1", "000-0000-0001"); + parent = createParent("testSignUp1", "010-0000-0001", "0000000000000", "testSignUp@naver.com"); + } + + @Test + void 리뷰를_저장한다() { + Review review = createReview("리뷰 1", hospital, parent); + + reviewRepository.save(review); + + assertThat(review.getId()).isNotNull(); + } + Hospital createHospital(String name, String phoneNumber) { + Hospital hospital = Hospital.builder() + .name(name) + .address(name + "주소") + .description(name + "설명") + .phoneNumber(phoneNumber) + .maxNumOfPeople(5) + .status(Status.OPEN) + .build(); + hospitalRepository.save(hospital); + + return hospital; + } + + Parent createParent(String loginId, String phoneNumber, String residentRegistrationNumber, String email) { + Parent parent = Parent.builder() + .loginId(loginId) + .password("비밀번호") + .nickname(loginId + "별명") + .name("이름") + .age("20") + .gender(남) + .phoneNumber(phoneNumber) + .residentRegistrationNumber(residentRegistrationNumber) + .email(email) + .role(USER) + .build(); + parentRepository.save(parent); + return parent; + } + + Review createReview(String title, Hospital hospital, Parent parent) { + return Review.builder() + .title(title) + .score(5) + .description(title + "설명") + .hospital(hospital) + .parent(parent) + .isActive(1) + .build(); + } + + ReviewCreateRequest createReviewCreateRequest(String title, Hospital hospital, Parent parent) { + return ReviewCreateRequest.builder() + .title(title) + .score(5) + .description(title + " 설명") + .parentId(parent.getId()) + .hospitalId(hospital.getId()) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/kidsqueue/kidsqueue/review/service/ReviewServiceTest.java b/src/test/java/com/kidsqueue/kidsqueue/review/service/ReviewServiceTest.java index 1dda71e..d2ea644 100644 --- a/src/test/java/com/kidsqueue/kidsqueue/review/service/ReviewServiceTest.java +++ b/src/test/java/com/kidsqueue/kidsqueue/review/service/ReviewServiceTest.java @@ -6,182 +6,135 @@ import com.kidsqueue.kidsqueue.hospital.db.enums.Status; import com.kidsqueue.kidsqueue.parent.db.Parent; import com.kidsqueue.kidsqueue.parent.db.ParentRepository; +import com.kidsqueue.kidsqueue.review.db.Review; import com.kidsqueue.kidsqueue.review.db.ReviewRepository; -import com.kidsqueue.kidsqueue.review.dtos.ReviewCreateDto; -import com.kidsqueue.kidsqueue.review.dtos.ReviewDto; -import com.kidsqueue.kidsqueue.review.dtos.ReviewUpdateDto; -import com.kidsqueue.kidsqueue.review.model.Review; -import jakarta.transaction.Transactional; +import com.kidsqueue.kidsqueue.review.model.ReviewCreateRequest; +import com.kidsqueue.kidsqueue.review.model.ReviewCreateResponse; +import com.kidsqueue.kidsqueue.review.model.ReviewUpdateRequest; +import com.kidsqueue.kidsqueue.review.model.ReviewUpdateResponse; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.PageRequest; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; import java.util.Optional; import static com.kidsqueue.kidsqueue.parent.db.enums.Gender.남; -import static com.kidsqueue.kidsqueue.parent.db.enums.Gender.여; import static com.kidsqueue.kidsqueue.parent.db.enums.RoleType.USER; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.BDDAssertions.then; +import static org.mockito.Mockito.*; -@SpringBootTest -@Transactional -class ReviewServiceTest { +@ExtendWith(MockitoExtension.class) +public class ReviewServiceTest { - @Autowired ReviewService reviewService; - @Autowired HospitalRepository hospitalRepository; - @Autowired ParentRepository parentRepository; - @Autowired - ReviewRepository reviewRepository; + @InjectMocks ReviewService reviewService; + @Mock private ReviewRepository reviewRepository; + @Mock private HospitalRepository hospitalRepository; + @Mock private ParentRepository parentRepository; + private Hospital hospital; + private Parent parent; @BeforeEach void setUp() { - // TODO : 이 코드를 축약할 방법은 없는지 생각 중 - Hospital hospital1 = Hospital.builder() - .name("병원1") - .address("주소1") - .description("설명1") - .phoneNumber("번호1") - .maxNumOfPeople(5) - .status(Status.OPEN) - .build(); - - Hospital hospital2 = Hospital.builder() - .name("병원2") - .address("주소2") - .description("설명2") - .phoneNumber("번호2") - .maxNumOfPeople(5) - .status(Status.OPEN) - .build(); - - hospitalRepository.save(hospital1); - hospitalRepository.save(hospital2); - - Parent parent1 = Parent.builder() - .loginId("아이디1") - .password("비밀번호1") - .nickname("별명1") - .name("이름1") - .age("20") - .gender(남) - .phoneNumber("번호1") - .residentRegistrationNumber("3333334444444") - .email("test1@test.com") - .role(USER) - .build(); + hospital = spy(createHospital("병원1", "000-0000-0001")); + parent = spy(createParent("testSignUp1", "010-0000-0001", "0000000000000", "testSignUp@naver.com")); + Mockito.when(reviewRepository.save(any(Review.class))).thenAnswer(invocation -> { + return invocation.getArgument(0); + }); + } - Parent parent2 = Parent.builder() - .loginId("아이디2") - .password("비밀번호2") - .nickname("별명2") - .name("이름2") - .age("20") - .gender(여) - .phoneNumber("번호2") - .residentRegistrationNumber("5555556666666") - .email("test2@test.com") - .role(USER) - .build(); - parentRepository.save(parent1); - parentRepository.save(parent2); + // ToDto, fromEntity 등 POJO 메서드들에 대한 테스트 추가해야 함 + // 메서드는 객체 간 협력을 검사하는 역할만 수행하고 있다. + @Test + void 리뷰_생성에_성공한다() { + doReturn(1L).when(hospital).getId(); + doReturn(1L).when(parent).getId(); + Mockito.when(hospitalRepository.findById(any(Long.class))).thenReturn(Optional.ofNullable(hospital)); + Mockito.when(parentRepository.findById(any(Long.class))).thenReturn(Optional.ofNullable(parent)); + ReviewCreateRequest request = createReviewCreateRequest("리뷰 1", hospital, parent); - Review review1 = Review.builder() - .title("리뷰1") - .score(5) - .description("설명1") - .hospital(hospital1) - .parent(parent1) - .build(); + Api response = reviewService.createReview(request); - Review review2 = Review.builder() - .title("리뷰2") - .score(5) - .description("설명2") - .hospital(hospital1) - .parent(parent1) - .build(); - reviewRepository.save(review1); - reviewRepository.save(review2); + then(response.getData().getTitle()).isEqualTo(request.getTitle()); } + // TODO : 외부 의존성을 끊고 나면 스스로 검사하는 항목이 없는 거 같아 고민 중 + @Disabled // 활성화하면 BeforeEach의 save stubbing 때문에 컴파일 에러가 발생함 @Test @DisplayName("리뷰를 오름차순 정렬한다.") void findAllAsc() { - PageRequest pageRequest = PageRequest.of(0, 10); - - List list = reviewService.findAllAsc(pageRequest).getData(); - - assertTrue((list.get(0).getCreatedBy().isBefore(list.get(1).getCreatedBy()))); } @Test - @DisplayName("리뷰를 내림차순 정렬한다.") - void findAllDesc() { - PageRequest pageRequest = PageRequest.of(0, 10); + void 리뷰를_업데이트_한다() { + Review review = spy(createReview("리뷰 1", hospital, parent)); + doReturn(1L).when(review).getId(); + Mockito.when(reviewRepository.findById(any(Long.class))).thenReturn(Optional.ofNullable(review)); + ReviewUpdateRequest request = createReviewUpdateRequest("수정 1"); - List list = reviewService.findAllDesc(pageRequest).getData(); + Api response = reviewService.updateReview(review.getId(), request); - assertTrue((list.get(0).getCreatedBy().isAfter(list.get(1).getCreatedBy()))); + then(response.getData().getTitle()).isEqualTo(request.getTitle()); + } + Hospital createHospital(String name, String phoneNumber) { + Hospital hospital = Hospital.builder() + .name(name) + .address(name + "주소") + .description(name + "설명") + .phoneNumber(phoneNumber) + .maxNumOfPeople(5) + .status(Status.OPEN) + .build(); + return hospital; } - @Test - void findReviewById() { - // TODO : ID 리터럴을 어떻게 구해야 맞을까? + Parent createParent(String loginId, String phoneNumber, String residentRegistrationNumber, String email) { + Parent parent = Parent.builder() + .loginId(loginId) + .password("비밀번호") + .nickname(loginId + "별명") + .name("이름") + .age("20") + .gender(남) + .phoneNumber(phoneNumber) + .residentRegistrationNumber(residentRegistrationNumber) + .email(email) + .role(USER) + .build(); + return parent; } - @Test - @DisplayName("리뷰 생성에 성공한다.") - void createReview() { - // TODO : given 이 너무 무겁지만 API 스펙을 변경하지 않는 선에서 auto increment 되는 ID를 어떻게 특정할지 모르겠음 - int prevSize = reviewRepository.findAll().size(); - Long hospitalId = hospitalRepository.findAll().get(0).getId(); - Long parentId = parentRepository.findAll().get(0).getId(); - ReviewCreateDto reviewCreateDto = ReviewCreateDto.builder() - .title("리뷰3") - .description("설명3") + Review createReview(String title, Hospital hospital, Parent parent) { + return Review.builder() + .title(title) .score(5) - .parentId(parentId) - .hospitalId(hospitalId) + .description(title + "설명") + .hospital(hospital) + .parent(parent) + .isActive(1) .build(); - - Api apiResponse = reviewService.createReview(reviewCreateDto); - - // 검증문으로 2 가지를 보고 있는데 적절성 판단할 필요 있음 - assertEquals("리뷰 정보 생성 성공", apiResponse.getMessage()); - assertEquals(prevSize + 1, reviewRepository.findAll().size()); } - @Test - @DisplayName("리뷰를 업데이트한다.") - void updateReview() { - Long id = reviewRepository.findAll().get(0).getId(); - ReviewUpdateDto reviewUpdateDto = ReviewUpdateDto.builder() - .title("리뷰 수정1") - .description("설명 수정1") - .score(1) + ReviewCreateRequest createReviewCreateRequest(String title, Hospital hospital, Parent parent) { + return ReviewCreateRequest.builder() + .title(title) + .score(5) + .description(title + " 설명") + .parentId(parent.getId()) + .hospitalId(hospital.getId()) .build(); - - Api apiResponse = reviewService.updateReview(id, reviewUpdateDto); - - assertEquals("리뷰 정보 수정 성공", apiResponse.getMessage()); - assertEquals("리뷰 수정1", reviewService.findReviewById(id).getData().getTitle()); } - @Test - @DisplayName("리뷰를 삭제한다.") - void deleteReview() { - Long id = reviewRepository.findAll().get(0).getId(); - - reviewService.deleteReview(id); - - // TODO : 더 나은 Optional 처리 방법이 없을까 고민 - Optional reviewOptional = reviewRepository.findById(id); - if (reviewOptional.isPresent()) - assertEquals(0, reviewRepository.findById(id).get().getIsActive()); - else fail("deleteReview 메서드는 삭제되는 대신 isActive 가 변경돼야 합니다."); + ReviewUpdateRequest createReviewUpdateRequest(String title) { + return ReviewUpdateRequest.builder() + .title(title) + .score(5) + .description(title + " 설명") + .build(); } -} \ No newline at end of file +}