-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#4] Controller 및 테스트 코드 작성 #6
Changes from all commits
a96e668
f3d0722
bccdae6
e6789eb
999f389
b42f273
c59e8fb
cf0f57f
d7a13d3
31a5a06
9cc2054
c6f027a
7c2329e
39173fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
.idea | ||
.gradle | ||
build | ||
out | ||
|
||
### Java ### | ||
# Compiled class file | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,40 @@ | ||
plugins { | ||
id 'org.springframework.boot' version '3.3.5' | ||
id 'io.spring.dependency-management' version '1.1.6' | ||
id 'io.freefair.lombok' version '8.11' | ||
} | ||
|
||
dependencies { | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
|
||
implementation project(':bp-domain-rdb') | ||
implementation project(':bp-kafka-event-publisher') | ||
implementation project(':bp-dto') | ||
implementation project(':bp-utils') | ||
implementation project(':bp-core-web') | ||
} | ||
|
||
implementation 'org.apache.commons:commons-lang3:3.14.0' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
|
||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.junit.jupiter:junit-jupiter-api' | ||
|
||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' | ||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
|
||
test { | ||
useJUnitPlatform() | ||
doFirst { | ||
// build 시에는 integration-test 를 제외하고 test 를 수행한다. | ||
// 개별 테스트 수행 시에는 integration-test 도 포함하여 수행한다. | ||
if (filter.commandLineIncludePatterns.empty) { | ||
useJUnitPlatform { | ||
excludeTags("integration-test") | ||
} | ||
} | ||
} | ||
|
||
testLogging { | ||
events 'PASSED', 'FAILED', 'SKIPPED' // 테스트 이벤트 로그 | ||
showStandardStreams = false // 표준 출력 및 표준 에러 표시 | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.beautify_project.bp_app_api.controller; | ||
|
||
import com.beautify_project.bp_app_api.dto.common.ResponseMessage; | ||
import com.beautify_project.bp_app_api.dto.review.FindReviewListRequestParameters; | ||
import com.beautify_project.bp_app_api.enumeration.OrderType; | ||
import com.beautify_project.bp_app_api.enumeration.ReviewSortBy; | ||
import com.beautify_project.bp_app_api.service.ReviewService; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.NotNull; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class ReviewController { | ||
|
||
private final ReviewService reviewService; | ||
|
||
/** | ||
* 리뷰 상세 조회 | ||
* | ||
* @param reviewId 조회 대상 리뷰 아이디 | ||
* @return ResponseMessage 형태의 ResponseBody | ||
*/ | ||
@GetMapping("/v1/reviews/{id}") | ||
@ResponseStatus(code = HttpStatus.OK) | ||
public ResponseMessage findReview( | ||
@PathVariable(value = "id") @NotBlank @NotNull final String reviewId) | ||
throws RuntimeException { | ||
return reviewService.findReview(reviewId); | ||
} | ||
|
||
/** | ||
* 샵에 속한 리뷰 목록 조회 | ||
* | ||
* @param shopId 조회 대상 샵 아이디 | ||
* @param page 페이징 시 페이지 수 | ||
* @param count 페이징 시 한 페이지에 출력할 결과 개수 | ||
* @param order 리뷰 등록일자 기준 오름 차순 / 내림 차순 | ||
* @return ResponseMessage 형태의 ResponseBody | ||
*/ | ||
@GetMapping("/v1/reviews/shops/{id}") | ||
@ResponseStatus(code = HttpStatus.OK) | ||
public ResponseMessage findReviewList(@PathVariable(value = "id") @NotBlank final String shopId, | ||
@RequestParam(name = "sort", required = false, defaultValue = "registeredDate") final String sort, | ||
@RequestParam(name = "page", required = false, defaultValue = "0") final int page, | ||
@RequestParam(name = "count", required = false, defaultValue = "10") final int count, | ||
@RequestParam(name = "order", required = false, defaultValue = "asc") final String order) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. asc, desc 말고, lastest, oldest 같은 것들이 좀 더 의미를 잘 전달하지 않을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 항상 registeredDate(등록일자) 기준으로 정렬하는건 아닙니다. rate(평점) 순으로 정렬할 수도 있고, 추후에 정렬이 될 수 있는 기준이 추가될 수 있기 때문에 asc, desc 를 사용하였습니다. |
||
throws RuntimeException { | ||
return reviewService.findReviewList( | ||
FindReviewListRequestParameters.builder().shopId(shopId).sortBy(ReviewSortBy.from(sort)) | ||
.page(page).count(count).orderType(OrderType.from(order)).build()); | ||
} | ||
|
||
/** | ||
* 리뷰 삭제 | ||
* | ||
* @param reviewId 삭제 대상 리뷰 아이디 | ||
*/ | ||
@DeleteMapping("/v1/reviews/{id}") | ||
@ResponseStatus(code = HttpStatus.NO_CONTENT) | ||
public void deleteReview(@PathVariable(value = "id") @NotBlank final String reviewId) | ||
throws RuntimeException { | ||
reviewService.deleteReview(reviewId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.beautify_project.bp_app_api.controller; | ||
|
||
import com.beautify_project.bp_app_api.dto.common.ResponseMessage; | ||
import com.beautify_project.bp_app_api.dto.shop.ImageFiles; | ||
import com.beautify_project.bp_app_api.dto.shop.ShopFindListRequestParameters; | ||
import com.beautify_project.bp_app_api.dto.shop.ShopRegistrationRequest; | ||
import com.beautify_project.bp_app_api.enumeration.OrderType; | ||
import com.beautify_project.bp_app_api.enumeration.ShopSearchType; | ||
import com.beautify_project.bp_app_api.service.ShopService; | ||
import jakarta.validation.Valid; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RequestPart; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class ShopController { | ||
|
||
private final ShopService shopService; | ||
|
||
@PostMapping("/v1/shops") | ||
@ResponseStatus(code = HttpStatus.OK) | ||
ResponseMessage registerShop( | ||
@RequestPart(value = "images", required = false) final List<MultipartFile> imageFiles, | ||
@Valid @RequestPart(value = "shopRegistrationInfo") final ShopRegistrationRequest shopRegistrationRequest) { | ||
|
||
return shopService.registerShop(new ImageFiles(imageFiles), shopRegistrationRequest); | ||
} | ||
|
||
@GetMapping("/v1/shops") | ||
@ResponseStatus(code = HttpStatus.OK) | ||
ResponseMessage findShopList(@RequestParam(name = "type") final String searchType, | ||
@RequestParam(name = "page", required = false, defaultValue = "0") final Integer page, | ||
@RequestParam(name = "count", required = false, defaultValue = "10") final Integer count, | ||
@RequestParam(name = "order", required = false, defaultValue = "asc") final String order) | ||
throws RuntimeException { | ||
|
||
return shopService.findShopList( | ||
new ShopFindListRequestParameters(ShopSearchType.from(searchType), page, count, | ||
OrderType.from(order))); | ||
} | ||
|
||
// TODO: 샵 상세 조회 구현 | ||
// TODO: 샵 수정 구현 | ||
// TODO: 샵 삭제 구현 | ||
// TODO: 샵 좋아요 구현 | ||
// TODO: 샵 좋아요 취소 구현 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.beautify_project.bp_app_api.dto.common; | ||
|
||
import lombok.Getter; | ||
import org.springframework.http.HttpStatus; | ||
|
||
@Getter | ||
public enum ErrorCode { | ||
|
||
BR001(HttpStatus.BAD_REQUEST, "BR001", "요청 파라미터가 잘못되었습니다."), | ||
BR002(HttpStatus.BAD_REQUEST, "BR002", "본문 형식이 맞지 않습니다."), | ||
|
||
UA001(HttpStatus.UNAUTHORIZED, "UA001", "접근 토큰이 존재하지 않습니다."), | ||
UA002(HttpStatus.UNAUTHORIZED, "UA002", "접근 토큰이 만료되었습니다. "), | ||
|
||
FB001(HttpStatus.FORBIDDEN, "FB001", "해당 API 사용 권한이 없습니다."), | ||
|
||
NF001(HttpStatus.NOT_FOUND, "NF001", "요청 URL이 잘못되었습니다."), | ||
|
||
SH001(HttpStatus.NOT_FOUND, "SH001", "등록되지 않은 미용 시술소입니다."), | ||
|
||
IS001(HttpStatus.INTERNAL_SERVER_ERROR, "IS001", "시스템 에러가 발생하였습니다. 관리자에게 문읜해주세요.") | ||
; | ||
|
||
private final HttpStatus httpStatus; | ||
private final String errorCode; | ||
private final String errorMessage; | ||
|
||
ErrorCode(final HttpStatus httpStatus, final String errorCode, final String errorMessage) { | ||
this.httpStatus = httpStatus; | ||
this.errorCode = errorCode; | ||
this.errorMessage = errorMessage; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.beautify_project.bp_app_api.dto.common; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.fasterxml.jackson.annotation.JsonInclude.Include; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
import org.springframework.http.HttpStatus; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
@ToString | ||
public class ErrorResponseMessage { | ||
|
||
@JsonIgnore | ||
private HttpStatus httpStatus; | ||
@JsonInclude(Include.NON_NULL) | ||
private String errorCode; | ||
@JsonInclude(Include.NON_NULL) | ||
private String errorMessage; | ||
|
||
private ErrorResponseMessage(final HttpStatus httpStatus, final String errorCode, | ||
final String errorMessage) { | ||
this.httpStatus = httpStatus; | ||
this.errorCode = errorCode; | ||
this.errorMessage = errorMessage; | ||
} | ||
|
||
public static ErrorResponseMessage createCustomErrorMessage(final ErrorCode errorCode, final String message) { | ||
return new ErrorResponseMessage(errorCode.getHttpStatus(), errorCode.getErrorCode(), message); | ||
} | ||
|
||
public static ErrorResponseMessage createErrorMessage(final ErrorCode errorCode) { | ||
return new ErrorResponseMessage(errorCode.getHttpStatus(), errorCode.getErrorCode(), | ||
errorCode.getErrorMessage()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.beautify_project.bp_app_api.dto.common; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
@ToString | ||
public class ResponseMessage { | ||
|
||
private Object returnValue; | ||
|
||
private ResponseMessage(final Object returnValue) { | ||
this.returnValue = returnValue; | ||
} | ||
|
||
public static ResponseMessage createResponseMessage(final Object responseBody) { | ||
return new ResponseMessage(responseBody); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.beautify_project.bp_app_api.dto.review; | ||
|
||
import com.beautify_project.bp_app_api.enumeration.OrderType; | ||
import com.beautify_project.bp_app_api.enumeration.ReviewSortBy; | ||
import com.beautify_project.bp_app_api.exception.ParameterOutOfRangeException; | ||
import lombok.Builder; | ||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||
|
||
@Builder | ||
public record FindReviewListRequestParameters( | ||
String shopId, | ||
ReviewSortBy sortBy, | ||
Integer page, | ||
Integer count, | ||
OrderType orderType) { | ||
|
||
public FindReviewListRequestParameters { | ||
validateCount(count); | ||
} | ||
|
||
private void validateCount(Integer count) { | ||
if (count > 100 || count < 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약에 validation로직이 복잡해 진다면? |
||
throw new ParameterOutOfRangeException("count", String.valueOf(count)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.beautify_project.bp_app_api.dto.review; | ||
|
||
import lombok.Builder; | ||
|
||
@Builder | ||
public record FindReviewResult( | ||
String id, | ||
String rate, | ||
Long registeredDate, | ||
Member member, | ||
Operation operation | ||
){ | ||
|
||
public record Member (String id, String name) {} | ||
|
||
public record Operation (String id, String name, Long date) {} | ||
|
||
@Override | ||
public String toString() { | ||
return "FindReviewResponse{" + | ||
"id='" + id + '\'' + | ||
", rate='" + rate + '\'' + | ||
", registeredDate=" + registeredDate + | ||
", member=" + member + | ||
", operation=" + operation + | ||
'}'; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.beautify_project.bp_app_api.dto.shop; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public record ImageFiles(List<MultipartFile> files) { | ||
|
||
public ImageFiles { | ||
createEmptyListIfNull(files); | ||
} | ||
|
||
private void createEmptyListIfNull(List<MultipartFile> files) { | ||
if (files == null || files.isEmpty()) { | ||
files = new ArrayList<>(); | ||
} | ||
} | ||
|
||
public boolean isEmpty() { | ||
return files == null || files.isEmpty(); | ||
} | ||
|
||
public int size() { | ||
return files.size(); | ||
} | ||
|
||
public MultipartFile get(final int index) { | ||
return files().get(index); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.beautify_project.bp_app_api.dto.shop; | ||
|
||
|
||
import com.beautify_project.bp_app_api.enumeration.OrderType; | ||
import com.beautify_project.bp_app_api.enumeration.ShopSearchType; | ||
import jakarta.validation.constraints.Size; | ||
|
||
public record ShopFindListRequestParameters( | ||
ShopSearchType searchType, | ||
@Size(min = 0) | ||
Integer page, | ||
@Size(max = 100) | ||
Integer count, | ||
OrderType orderType) { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요건 뭐 .. 특별히 안된다고 보기는 어렵지만, 스프링에도 비슷한 기능을 하는 클래스들이 있지 않을까요? ㅎㅎ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spring-core 에도 비슷한 역할을 클래스가 있기는 한데, apache의 라이브러리를 계속 사용해왔던지라 사용하게 되었습니다.
혹시 관련해서 비추천 하는 이유가 있으실까요?