diff --git a/README.md b/README.md index 76f8a12cc7..2881b0f006 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # SpringBoot Basic Weekly Mission 스프링부트 basic 위클리미션을 코드리뷰하는 Repository입니다. + +## 1주차 요구 사항 +- 바우처 관리 Command-line Application을 만든다. +- create / list 커맨드를 지원한다. + - create 커맨드를 통해 바우처를 생성할수 있다. + - list 커맨드를 통해 만들어진 바우처를 조회할 수 있다. + +## 2주차 요구사항 +- 단위 테스트를 작성한다. +- 고객을 적용한다. + - customers 테이블 정의 및 추가한다. + - CustomerRepository 추가 및 JdbcTemplate을 사용해서 구현한다. +- 바우처 정보를 DB로 관리한다. + - 바우처에 엔터티에 해당하는 vouchers 테이블을 정의한다. + - JdbcTemplate을 사용해서 바우처 레포지토리를 만든다. + - vouchers 테이블을 통해서 CRUD를 할 수 있다. + +## 3주차 요구사항 +- 커맨드로 지원했던 기능을 thymeleaf를 이용해서 다음 기능을 지원할 수 있다. + - 조회페이지 + - 상세페이지 + - 입력페이지 + - 삭제기능 +- Spring MVC를 적용해서 JSON을 지원하는 REST API를 개발한다. + - 조회페이지 + - 상세페이지 + - 입력페이지 + - 삭제기능 diff --git a/build.gradle b/build.gradle index dc5654fb65..5beedc6d3c 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,9 @@ repositories { } dependencies { + // AOP + implementation 'org.springframework.boot:spring-boot-starter-aop' + // JDBC implementation 'org.springframework.boot:spring-boot-starter-jdbc' @@ -29,6 +32,9 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/com/programmers/vouchermanagement/CommandLineController.java b/src/main/java/com/programmers/vouchermanagement/CommandLineController.java index 1d1ba3744f..5d7b3a27d4 100644 --- a/src/main/java/com/programmers/vouchermanagement/CommandLineController.java +++ b/src/main/java/com/programmers/vouchermanagement/CommandLineController.java @@ -11,14 +11,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Controller; import java.util.List; import java.util.UUID; @Profile("!test") @Slf4j -@Controller +//@Controller @RequiredArgsConstructor public class CommandLineController implements CommandLineRunner { @@ -60,8 +59,8 @@ private boolean isRunning() { UUID id = InputView.inputVoucherUpdate(vouchers); OutputView.showDiscountType(); VoucherCreationRequest updateVoucherInfo = InputView.inputVoucherInfo(); - VoucherUpdateRequest request = new VoucherUpdateRequest(id, updateVoucherInfo.type(), updateVoucherInfo.amount()); - voucherController.updateVoucher(request); + VoucherUpdateRequest request = new VoucherUpdateRequest(updateVoucherInfo.type(), updateVoucherInfo.amount()); + voucherController.updateVoucher(id, request); } case DELETE -> { List vouchers = voucherController.getVouchers(); diff --git a/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java index 6fc1cddfb2..dbd13eb21e 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.customer.domain; -import com.programmers.vouchermanagement.customer.exception.InvalidCustomerTypeException; +import com.programmers.vouchermanagement.global.exception.ErrorCode; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; public enum CustomerType { BLACK, WHITE; @@ -9,7 +10,7 @@ public static CustomerType from(String type) { try { return valueOf(type.toUpperCase()); } catch (IllegalArgumentException e) { - throw new InvalidCustomerTypeException("잘못된 고객 타입입니다."); + throw new VoucherException(ErrorCode.INVALID_CUSTOMER_TYPE); } } } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java b/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java deleted file mode 100644 index 3a57a94cb0..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.programmers.vouchermanagement.customer.exception; - -public class InvalidCustomerTypeException extends RuntimeException { - public InvalidCustomerTypeException(String message) { - super(message); - } -} diff --git a/src/main/java/com/programmers/vouchermanagement/docs/README.md b/src/main/java/com/programmers/vouchermanagement/docs/README.md deleted file mode 100644 index 8a52f73d7f..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/docs/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# 기능 명세서 ->바우처 관리 Command-line Application을 만들기 - -## Domain -### 바우처 -```markdown -- 바우처마다 할인 정책이 다르기 때문에 바우처를 인터페이스로 구현 - - 정액 할인 바우처 - - 정률 할인 바우처 -- 값을 할인한다. -- 바우처 저장소 변경에 유연한 대처를 위해 인터페이스로 구현 - - 메모리 바우처 저장소 (개발 환경에서 동작) - - 파일 바우처 저장소 -- 바우처를 저장한다. -- 바우처를 조회한다. -``` - -### 고객 -```markdown -- 고객 블랙 리스트 명단을 조회한다. -``` \ No newline at end of file diff --git a/src/main/java/com/programmers/vouchermanagement/global/aop/ControllerLogAop.java b/src/main/java/com/programmers/vouchermanagement/global/aop/ControllerLogAop.java new file mode 100644 index 0000000000..492e2ab299 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/global/aop/ControllerLogAop.java @@ -0,0 +1,29 @@ +package com.programmers.vouchermanagement.global.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +@Slf4j +@Aspect +@Component +public class ControllerLogAop { + + @Before("bean(*Controller)") + public void beforeParameterLog(JoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + log.info("### method name: {}", method.getName()); + + Object[] args = joinPoint.getArgs(); + for (Object arg : args) { + log.info("### parameter type: {}", arg.getClass().getSimpleName()); + log.info("### parameter value: {}", arg); + } + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/global/exception/BaseException.java b/src/main/java/com/programmers/vouchermanagement/global/exception/BaseException.java new file mode 100644 index 0000000000..e12a6d3770 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/global/exception/BaseException.java @@ -0,0 +1,10 @@ +package com.programmers.vouchermanagement.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class BaseException extends RuntimeException { + private final ErrorCode errorCode; +} diff --git a/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorCode.java b/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorCode.java new file mode 100644 index 0000000000..df4304f598 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorCode.java @@ -0,0 +1,26 @@ +package com.programmers.vouchermanagement.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode { + + // Voucher + INVALID_DISCOUNT_TYPE(HttpStatus.BAD_REQUEST, "V001", "유효하지 않은 할인 타입입니다."), + VOUCHER_NOT_FOUND(HttpStatus.NOT_FOUND, "V002", "바우처를 찾을 수 없습니다."), + INVALID_FIX_AMOUNT(HttpStatus.BAD_REQUEST, "V003", "고정할인금액은 최소 1원 이상이여야 합니다."), + INVALID_PERCENT(HttpStatus.BAD_REQUEST, "V004", "할인률은 1에서 100 사이여야 합니다."), + + // Customer + INVALID_CUSTOMER_TYPE(HttpStatus.BAD_REQUEST, "C001", "유효하지 않은 고객 타입입니다."), + + // Common + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S999", "Internal Server Error"); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorResponse.java b/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorResponse.java new file mode 100644 index 0000000000..b8cfcc9858 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/global/exception/ErrorResponse.java @@ -0,0 +1,25 @@ +package com.programmers.vouchermanagement.global.exception; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@Getter +@Builder +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class ErrorResponse { + + private final String error; + private final String code; + private final String message; + + public static ErrorResponse of(ErrorCode errorCode) { + return ErrorResponse.builder() + .error(errorCode.name()) + .code(errorCode.getCode()) + .message(errorCode.getMessage()) + .build(); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/global/exception/GlobalExceptionHandler.java b/src/main/java/com/programmers/vouchermanagement/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000000..0adba1cf42 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package com.programmers.vouchermanagement.global.exception; + +import com.programmers.vouchermanagement.voucher.exception.VoucherException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + log.error("Unexpected Exception", e); + ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR; + ErrorResponse response = ErrorResponse.of(errorCode); + return new ResponseEntity<>(response, errorCode.getHttpStatus()); + } + + @ExceptionHandler(VoucherException.class) + public ResponseEntity handleVoucherException(VoucherException e) { + log.error("Voucher Exception", e); + ErrorCode errorCode = e.getErrorCode(); + ErrorResponse response = ErrorResponse.of(errorCode); + return new ResponseEntity<>(response, errorCode.getHttpStatus()); + } +} \ No newline at end of file diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java b/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java index e49bc8713a..6accbf42e7 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java @@ -1,12 +1,12 @@ package com.programmers.vouchermanagement.voucher.application; -import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy; -import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.global.exception.ErrorCode; import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherRepository; import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; import com.programmers.vouchermanagement.voucher.dto.request.VoucherUpdateRequest; import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -22,12 +22,10 @@ public class VoucherService { private final VoucherRepository voucherRepository; - public Voucher createVoucher(VoucherCreationRequest request) { - DiscountType discountType = request.type(); - int amount = request.amount(); - DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount); - Voucher voucher = new Voucher(discountPolicy); - return voucherRepository.save(voucher); + public VoucherResponse createVoucher(VoucherCreationRequest request) { + Voucher voucher = request.toEntity(); + Voucher savedVoucher = voucherRepository.save(voucher); + return new VoucherResponse(savedVoucher); } public List getVouchers() { @@ -37,16 +35,26 @@ public List getVouchers() { .collect(Collectors.toList()); } - public void updateVoucher(VoucherUpdateRequest request) { - UUID id = request.id(); - DiscountType discountType = request.type(); - int amount = request.amount(); - DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount); - Voucher voucher = new Voucher(id, discountPolicy); + public VoucherResponse getVoucher(UUID id) { + Voucher voucher = voucherRepository.findById(id) + .orElseThrow(() -> new VoucherException(ErrorCode.VOUCHER_NOT_FOUND)); + return new VoucherResponse(voucher); + } + + public void updateVoucher(UUID id, VoucherUpdateRequest request) { + existsVoucher(id); + Voucher voucher = request.toEntity(id); voucherRepository.update(voucher); } public void deleteVoucher(UUID id) { + existsVoucher(id); voucherRepository.deleteById(id); } + + private void existsVoucher(UUID id) { + if (!voucherRepository.existById(id)) { + throw new VoucherException(ErrorCode.VOUCHER_NOT_FOUND); + } + } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java index fd92e1487e..297da91f1b 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountTypeException; +import com.programmers.vouchermanagement.global.exception.ErrorCode; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import lombok.Getter; import java.util.Collections; @@ -35,7 +36,7 @@ public static DiscountType from(String number) { if (DISCOUNT_TYPE_MAP.containsKey(number)) { return DISCOUNT_TYPE_MAP.get(number); } - throw new InvalidDiscountTypeException("존재하지 않는 할인 유형 입니다."); + throw new VoucherException(ErrorCode.INVALID_DISCOUNT_TYPE); } public DiscountPolicy createDiscountPolicy(int amount) { diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicy.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicy.java index 443af3a97f..27784ca60b 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicy.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicy.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import com.programmers.vouchermanagement.global.exception.ErrorCode; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; public class FixedAmountDiscountPolicy extends DiscountPolicy { @@ -13,7 +14,7 @@ public FixedAmountDiscountPolicy(int amount) { @Override void validateAmount(int amount) { if (amount < MIN_AMOUNT) { - throw new InvalidDiscountAmountException("고정할인금액은 최소 1원 이상이여야 합니다."); + throw new VoucherException(ErrorCode.INVALID_FIX_AMOUNT); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicy.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicy.java index ae162bb217..0f1653c6b3 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicy.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicy.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import com.programmers.vouchermanagement.global.exception.ErrorCode; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import java.math.BigDecimal; @@ -16,7 +17,7 @@ public PercentDiscountPolicy(int amount) { @Override void validateAmount(int amount) { if (MIN_PERCENT > amount || amount > MAX_PERCENT) { - throw new InvalidDiscountAmountException("할인률은 1에서 100 사이여야 합니다."); + throw new VoucherException(ErrorCode.INVALID_PERCENT); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java index 42b8c98733..1e2d112854 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java @@ -1,5 +1,6 @@ package com.programmers.vouchermanagement.voucher.domain; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -8,15 +9,19 @@ @Getter @EqualsAndHashCode -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) public class Voucher { - private final UUID id; + private UUID id; private DiscountPolicy discountPolicy; - public Voucher(DiscountPolicy discountPolicy) { - this.id = UUID.randomUUID(); - this.discountPolicy = discountPolicy; + public static Voucher from(DiscountPolicy discountPolicy) { + UUID id = UUID.randomUUID(); + return new Voucher(id, discountPolicy); + } + + public static Voucher of(UUID id, DiscountPolicy discountPolicy) { + return new Voucher(id, discountPolicy); } public int discount(int originalPrice) { diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java index d10a664494..83b083068f 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java @@ -15,4 +15,6 @@ public interface VoucherRepository { void update(Voucher voucher); void deleteById(UUID id); + + boolean existById(UUID id); } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherCreationRequest.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherCreationRequest.java index e5c8f4cdab..f792737548 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherCreationRequest.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherCreationRequest.java @@ -1,6 +1,13 @@ package com.programmers.vouchermanagement.voucher.dto.request; +import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy; import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import jakarta.validation.constraints.Min; -public record VoucherCreationRequest(DiscountType type, int amount) { +public record VoucherCreationRequest(DiscountType type, @Min(1) int amount) { + public Voucher toEntity() { + DiscountPolicy discountPolicy = this.type.createDiscountPolicy(this.amount); + return Voucher.from(discountPolicy); + } } \ No newline at end of file diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherUpdateRequest.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherUpdateRequest.java index 51ccd551a9..536d08c35f 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherUpdateRequest.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherUpdateRequest.java @@ -1,8 +1,15 @@ package com.programmers.vouchermanagement.voucher.dto.request; +import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy; import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import jakarta.validation.constraints.Min; import java.util.UUID; -public record VoucherUpdateRequest(UUID id, DiscountType type, int amount) { +public record VoucherUpdateRequest(DiscountType type, @Min(1) int amount) { + public Voucher toEntity(UUID id) { + DiscountPolicy discountPolicy = this.type.createDiscountPolicy(this.amount); + return Voucher.of(id, discountPolicy); + } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java b/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java deleted file mode 100644 index 35d3e101ad..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.programmers.vouchermanagement.voucher.exception; - -public class InvalidDiscountAmountException extends RuntimeException { - public InvalidDiscountAmountException(String message) { - super(message); - } -} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountTypeException.java b/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountTypeException.java deleted file mode 100644 index 4018ec998d..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountTypeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.programmers.vouchermanagement.voucher.exception; - -public class InvalidDiscountTypeException extends RuntimeException { - public InvalidDiscountTypeException(String message) { - super(message); - } -} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/exception/VoucherException.java b/src/main/java/com/programmers/vouchermanagement/voucher/exception/VoucherException.java new file mode 100644 index 0000000000..cd7ccc3bd8 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/exception/VoucherException.java @@ -0,0 +1,10 @@ +package com.programmers.vouchermanagement.voucher.exception; + +import com.programmers.vouchermanagement.global.exception.BaseException; +import com.programmers.vouchermanagement.global.exception.ErrorCode; + +public class VoucherException extends BaseException { + public VoucherException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java index 2135600fc6..f170ff4038 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java @@ -67,13 +67,18 @@ public void deleteById(UUID id) { jdbcTemplate.update(sql, id.toString()); } + public boolean existById(UUID id) { + String sql = "select exists(select 1 from voucher where id = ?)"; + return Boolean.TRUE.equals(jdbcTemplate.queryForObject(sql, Boolean.class, id.toString())); + } + private RowMapper voucherRowMapper() { return (rs, rowNum) -> { UUID id = UUID.fromString(rs.getString("id")); DiscountType type = DiscountType.valueOf(rs.getString("type")); int amount = rs.getInt("amount"); DiscountPolicy discountPolicy = type.createDiscountPolicy(amount); - return new Voucher(id, discountPolicy); + return Voucher.of(id, discountPolicy); }; } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java index eb68b483a5..cf2d393a0a 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java @@ -41,6 +41,11 @@ public void deleteById(UUID id) { storage.remove(id); } + @Override + public boolean existById(UUID id) { + return storage.containsKey(id); + } + public void clearStorage() { storage.clear(); } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherController.java b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherController.java new file mode 100644 index 0000000000..ef4aca8d5c --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherController.java @@ -0,0 +1,48 @@ +package com.programmers.vouchermanagement.voucher.presentation; + +import com.programmers.vouchermanagement.voucher.application.VoucherService; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherUpdateRequest; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@RestController +@RequestMapping("/api/v1/vouchers") +@RequiredArgsConstructor +public class RestApiVoucherController { + + private final VoucherService voucherService; + + @PostMapping + public VoucherResponse createVoucher(@RequestBody VoucherCreationRequest request) { + return voucherService.createVoucher(request); + } + + @GetMapping + public List getVouchers() { + return voucherService.getVouchers(); + } + + @GetMapping("/{voucherId}") + public VoucherResponse getVoucher(@PathVariable UUID voucherId) { + return voucherService.getVoucher(voucherId); + } + + @PostMapping("/{voucherId}") + public boolean updateVoucher(@PathVariable UUID voucherId, @RequestBody VoucherUpdateRequest request) { + voucherService.updateVoucher(voucherId, request); + return true; + } + + @DeleteMapping("/{voucherId}") + public boolean deleteVoucher(@PathVariable UUID voucherId) { + voucherService.deleteVoucher(voucherId); + return true; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/presentation/ThymeleafVoucherController.java b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/ThymeleafVoucherController.java new file mode 100644 index 0000000000..57d772fa11 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/ThymeleafVoucherController.java @@ -0,0 +1,76 @@ +package com.programmers.vouchermanagement.voucher.presentation; + +import com.programmers.vouchermanagement.voucher.application.VoucherService; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherUpdateRequest; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Controller +@RequestMapping("/api/v1/vouchers") +@RequiredArgsConstructor +public class ThymeleafVoucherController { + + private final VoucherService voucherService; + + @GetMapping + public String getVouchers(Model model) { + List vouchers = voucherService.getVouchers(); + model.addAttribute("vouchers", vouchers); + return "voucher/vouchers"; + } + + @GetMapping("/add") + public String addForm() { + return "voucher/addForm"; + } + + @PostMapping("/add") + public String createVoucher(@Valid VoucherCreationRequest request, RedirectAttributes redirectAttributes) { + VoucherResponse voucher = voucherService.createVoucher(request); + redirectAttributes.addAttribute("voucherId", voucher.getId()); + redirectAttributes.addAttribute("status", true); + return "redirect:/v2/vouchers/{voucherId}"; + } + + @GetMapping("/{voucherId}") + public String getVoucher(@PathVariable UUID voucherId, Model model) { + VoucherResponse voucher = voucherService.getVoucher(voucherId); + model.addAttribute("voucher", voucher); + return "voucher/voucher"; + } + + @GetMapping("/{voucherId}/edit") + public String editForm(@PathVariable UUID voucherId, Model model) { + VoucherResponse voucher = voucherService.getVoucher(voucherId); + model.addAttribute("voucher", voucher); + return "voucher/editForm"; + } + + @PostMapping("/{voucherId}/edit") + public String updateVoucher(@PathVariable UUID voucherId, @ModelAttribute("voucher") @Valid VoucherUpdateRequest request) { + voucherService.updateVoucher(voucherId, request); + return "redirect:/v2/vouchers/{voucherId}"; + } + + @GetMapping("/{voucherId}/delete") + public String deleteVoucher(@PathVariable UUID voucherId) { + voucherService.deleteVoucher(voucherId); + return "redirect:/v2/vouchers"; + } + + @RequestMapping("/error") + public String handleError() { + return "error"; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java index 198df1be2b..4c4f80137e 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java @@ -24,8 +24,8 @@ public List getVouchers() { return voucherService.getVouchers(); } - public void updateVoucher(VoucherUpdateRequest request) { - voucherService.updateVoucher(request); + public void updateVoucher(UUID id, VoucherUpdateRequest request) { + voucherService.updateVoucher(id, request); } public void deleteVoucher(UUID id) { diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 17d427ba1a..8f0d94763f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,6 +21,10 @@ + + + + diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000000..1414ef12bc --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,8 @@ + + + +

Something went wrong!

+

Our Engineers are on it

+Go Home + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000000..1c740acf8c --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,26 @@ + + + + + + Bootstrap demo + + + +
+
+

바우처 관리

+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/voucher/addForm.html b/src/main/resources/templates/voucher/addForm.html new file mode 100644 index 0000000000..f2d4f05d79 --- /dev/null +++ b/src/main/resources/templates/voucher/addForm.html @@ -0,0 +1,48 @@ + + + + + + Bootstrap demo + + + +
+
+

바우처 폼

+
+

바우처 정보 입력

+
+
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/voucher/editForm.html b/src/main/resources/templates/voucher/editForm.html new file mode 100644 index 0000000000..f106c95471 --- /dev/null +++ b/src/main/resources/templates/voucher/editForm.html @@ -0,0 +1,49 @@ + + + + + + Bootstrap demo + + + +
+
+

바우처 수정 폼

+
+
+
+ + +
+ + +
+ + +
+
+
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/voucher/voucher.html b/src/main/resources/templates/voucher/voucher.html new file mode 100644 index 0000000000..47cbef5e0e --- /dev/null +++ b/src/main/resources/templates/voucher/voucher.html @@ -0,0 +1,54 @@ + + + + + + Bootstrap demo + + + +
+
+

바우처 상세

+
+

+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/voucher/vouchers.html b/src/main/resources/templates/voucher/vouchers.html new file mode 100644 index 0000000000..e6868ec68f --- /dev/null +++ b/src/main/resources/templates/voucher/vouchers.html @@ -0,0 +1,43 @@ + + + + + + Bootstrap demo + + + +
+
+

바우처 목록

+
+
+
+ +
+ + + + + + + + + + + + + + + + +
바우처 IDTypeAmount
+ + \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java b/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java index d6383e7807..b6bd7ff314 100644 --- a/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java +++ b/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java @@ -1,6 +1,6 @@ package com.programmers.vouchermanagement.customer.domain; -import com.programmers.vouchermanagement.customer.exception.InvalidCustomerTypeException; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -50,7 +50,6 @@ void fromException() { // given & then assertThatThrownBy(() -> CustomerType.from(type)) - .isInstanceOf(InvalidCustomerTypeException.class) - .hasMessage("잘못된 고객 타입입니다."); + .isInstanceOf(VoucherException.class); } } \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/application/VoucherServiceTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/application/VoucherServiceTest.java index 85e7c648c6..0e4a4c659a 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/application/VoucherServiceTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/application/VoucherServiceTest.java @@ -35,13 +35,13 @@ void createVoucher() { DiscountType discountType = request.type(); int amount = request.amount(); DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount); - Voucher voucher = new Voucher(discountPolicy); + Voucher voucher = Voucher.from(discountPolicy); given(voucherRepository.save(any(Voucher.class))) .willReturn(voucher); // when - Voucher result = voucherService.createVoucher(request); + VoucherResponse result = voucherService.createVoucher(request); // then assertThat(result).isNotNull(); @@ -54,8 +54,8 @@ void createVoucher() { @DisplayName("바우처를 모두 조회한다.") void getVouchers() { // given - Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); - Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + Voucher voucher1 = Voucher.from(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = Voucher.from(new PercentDiscountPolicy(10)); given(voucherRepository.findAll()) .willReturn(List.of(voucher1, voucher2)); diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java index 8d3e3ab818..082a97df03 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java @@ -1,6 +1,6 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountTypeException; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -49,7 +49,6 @@ void from_exception() { // when & then assertThatThrownBy(() -> DiscountType.from(number)) - .isInstanceOf(InvalidDiscountTypeException.class) - .hasMessage("존재하지 않는 할인 유형 입니다."); + .isInstanceOf(VoucherException.class); } } \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicyTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicyTest.java index 5818627cf6..87085cd234 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicyTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicyTest.java @@ -1,6 +1,6 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; @@ -30,8 +30,7 @@ void isMoreThanMinAmount(int amount) { void lessThanMinAmount(int amount) { // when & then assertThatThrownBy(() -> new FixedAmountDiscountPolicy(amount)) - .isInstanceOf(InvalidDiscountAmountException.class) - .hasMessage("고정할인금액은 최소 1원 이상이여야 합니다."); + .isInstanceOf(VoucherException.class); } } diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java index e8ac5fd174..bae7ef39d1 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java @@ -1,6 +1,6 @@ package com.programmers.vouchermanagement.voucher.domain; -import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import com.programmers.vouchermanagement.voucher.exception.VoucherException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.params.ParameterizedTest; @@ -30,8 +30,7 @@ void isBetween1And100(int amount) { void isNotBetween1And100(int amount) { // when & then assertThatThrownBy(() -> new PercentDiscountPolicy(amount)) - .isInstanceOf(InvalidDiscountAmountException.class) - .hasMessage("할인률은 1에서 100 사이여야 합니다."); + .isInstanceOf(VoucherException.class); } } diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java index e2bc7090a3..677ef91b92 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java @@ -27,7 +27,7 @@ class JdbcTemplateVoucherRepositoryTest { @DisplayName("바우처를 저장한다.") void save() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); // when voucherRepository.save(voucher); @@ -41,7 +41,7 @@ void save() { @DisplayName("Id로 바우처를 조회한다.") void findById() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); voucherRepository.save(voucher); // when @@ -55,8 +55,8 @@ void findById() { @DisplayName("바우처를 모두 조회한다.") void findAll() { // given - Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); - Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + Voucher voucher1 = Voucher.from(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = Voucher.from(new PercentDiscountPolicy(10)); voucherRepository.save(voucher1); voucherRepository.save(voucher2); @@ -72,7 +72,7 @@ void findAll() { @DisplayName("바우처를 업데이트한다.") void update() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); voucherRepository.save(voucher); voucher.changeDiscountPolicy(new PercentDiscountPolicy(10)); @@ -88,7 +88,7 @@ void update() { @DisplayName("바우처를 삭제한다.") void deleteById() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); // when voucherRepository.deleteById(voucher.getId()); diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java index 6f841ef025..c7deb82a44 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java @@ -26,7 +26,7 @@ void afterEach() { @DisplayName("바우처를 저장한다.") void save() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); // when memoryVoucherRepository.save(voucher); @@ -41,7 +41,7 @@ void save() { void findById() { // given UUID id = UUID.randomUUID(); - Voucher voucher = new Voucher(id, new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.of(id, new FixedAmountDiscountPolicy(5000)); memoryVoucherRepository.save(voucher); // when @@ -55,8 +55,8 @@ void findById() { @DisplayName("바우처를 모두 조회한다.") void findAll() { // given - Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); - Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + Voucher voucher1 = Voucher.from(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = Voucher.from(new PercentDiscountPolicy(10)); memoryVoucherRepository.save(voucher1); memoryVoucherRepository.save(voucher2); @@ -72,7 +72,7 @@ void findAll() { @DisplayName("바우처를 업데이트한다.") void update() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); memoryVoucherRepository.save(voucher); voucher.changeDiscountPolicy(new PercentDiscountPolicy(10)); @@ -88,7 +88,7 @@ void update() { @DisplayName("바우처를 삭제한다.") void deleteById() { // given - Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); // when memoryVoucherRepository.deleteById(voucher.getId()); diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherControllerTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherControllerTest.java new file mode 100644 index 0000000000..32d4efe1f9 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/presentation/RestApiVoucherControllerTest.java @@ -0,0 +1,132 @@ +package com.programmers.vouchermanagement.voucher.presentation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.programmers.vouchermanagement.voucher.application.VoucherService; +import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.domain.FixedAmountDiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherUpdateRequest; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.UUID; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(RestApiVoucherController.class) +class RestApiVoucherControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private VoucherService voucherService; + + @Test + @DisplayName("바우처 생성 API") + void createVoucher() throws Exception { + // given + VoucherCreationRequest request = new VoucherCreationRequest(DiscountType.FIX, 5000); + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); + VoucherResponse response = new VoucherResponse(voucher); + + given(voucherService.createVoucher(request)) + .willReturn(response); + + String requestJson = objectMapper.writeValueAsString(request); + String responseJson = objectMapper.writeValueAsString(response); + + // when & then + this.mockMvc.perform(post("/api/v3/vouchers") + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isOk()) + .andExpect(content().string(responseJson)); + } + + @Test + @DisplayName("바우처 목록 조회 API") + void getVouchers() throws Exception { + // given + Voucher voucher = Voucher.from(new FixedAmountDiscountPolicy(5000)); + List responses = List.of(new VoucherResponse(voucher)); + given(voucherService.getVouchers()) + .willReturn(responses); + + String responsesJson = objectMapper.writeValueAsString(responses); + + // when & then + this.mockMvc.perform(get("/api/v3/vouchers")) + .andExpect(status().isOk()) + .andExpect(content().string(responsesJson)); + } + + @Test + @DisplayName("바우처 조회 API") + void getVoucher() throws Exception { + // given + UUID voucherId = UUID.randomUUID(); + Voucher voucher = Voucher.of(voucherId, new FixedAmountDiscountPolicy(5000)); + VoucherResponse response = new VoucherResponse(voucher); + + given(voucherService.getVoucher(voucherId)) + .willReturn(response); + + String responseJson = objectMapper.writeValueAsString(response); + + // when & then + this.mockMvc.perform(get("/api/v3/vouchers/{voucherId}", voucherId.toString())) + .andExpect(status().isOk()) + .andExpect(content().string(responseJson)); + } + + @Test + @DisplayName("바우처 수정 API") + void updateVoucher() throws Exception { + // given + UUID voucherId = UUID.randomUUID(); + VoucherUpdateRequest request = new VoucherUpdateRequest(DiscountType.FIX, 5000); + + String requestJson = objectMapper.writeValueAsString(request); + + // when & then + this.mockMvc.perform(post("/api/v3/vouchers/{voucherId}", voucherId.toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isOk()) + .andExpect(content().string("true")); + + // verify + verify(voucherService).updateVoucher(voucherId, request); + } + + @Test + @DisplayName("바우처 삭제 API") + void deleteVoucher() throws Exception { + // given + UUID voucherId = UUID.randomUUID(); + + // when & then + this.mockMvc.perform(delete("/api/v3/vouchers/{voucherId}", voucherId.toString())) + .andExpect(status().isOk()) + .andExpect(content().string("true")); + + // verify + verify(voucherService).deleteVoucher(voucherId); + } +} \ No newline at end of file