diff --git a/build.gradle b/build.gradle index 997b13c0fb..dc5654fb65 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,15 @@ repositories { } dependencies { + // JDBC + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + + // MySQL + runtimeOnly 'com.mysql:mysql-connector-j:8.0.33' + + // H2 + runtimeOnly 'com.h2database:h2' + // Lombok compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' diff --git a/src/main/java/com/programmers/vouchermanagement/CommandLineController.java b/src/main/java/com/programmers/vouchermanagement/CommandLineController.java index f9f19d2617..1d1ba3744f 100644 --- a/src/main/java/com/programmers/vouchermanagement/CommandLineController.java +++ b/src/main/java/com/programmers/vouchermanagement/CommandLineController.java @@ -1,15 +1,22 @@ package com.programmers.vouchermanagement; import com.programmers.vouchermanagement.view.Command; -import com.programmers.vouchermanagement.view.Console; -import com.programmers.vouchermanagement.voucher.domain.DiscountType; -import com.programmers.vouchermanagement.voucher.dto.VoucherDto; +import com.programmers.vouchermanagement.view.InputView; +import com.programmers.vouchermanagement.view.OutputView; +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.presentation.VoucherController; import lombok.RequiredArgsConstructor; 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 @RequiredArgsConstructor @@ -25,7 +32,6 @@ public void run(String... args) { try { running = isRunning(); } catch (RuntimeException e) { - Console.outputErrorMessage(e.getMessage()); log.error(e.getMessage()); } @@ -33,21 +39,36 @@ public void run(String... args) { } private boolean isRunning() { - Command command = Command.from(Console.selectCommand()); + OutputView.showCommand(); + Command command = InputView.inputCommand(); switch (command) { case CREATE -> { - DiscountType discountType = DiscountType.from(Console.selectDiscountType()); - int discountAmount = Console.inputDiscountAmount(); - VoucherDto.Request request = new VoucherDto.Request(discountType, discountAmount); + OutputView.showDiscountType(); + VoucherCreationRequest request = InputView.inputVoucherInfo(); voucherController.createVoucher(request); } case LIST-> { - VoucherDto.Response vouchers = voucherController.getVouchers(); - Console.outputVouchers(vouchers); + List vouchers = voucherController.getVouchers(); + OutputView.showVouchers(vouchers); } case EXIT -> { return false; } + case UPDATE -> { + List vouchers = voucherController.getVouchers(); + OutputView.showVoucherUpdate(vouchers); + UUID id = InputView.inputVoucherUpdate(vouchers); + OutputView.showDiscountType(); + VoucherCreationRequest updateVoucherInfo = InputView.inputVoucherInfo(); + VoucherUpdateRequest request = new VoucherUpdateRequest(id, updateVoucherInfo.type(), updateVoucherInfo.amount()); + voucherController.updateVoucher(request); + } + case DELETE -> { + List vouchers = voucherController.getVouchers(); + OutputView.showVoucherDelete(vouchers); + UUID id = InputView.inputVoucherDelete(vouchers); + voucherController.deleteVoucher(id); + } } return true; } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java b/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java new file mode 100644 index 0000000000..9aa0dfc029 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java @@ -0,0 +1,27 @@ +package com.programmers.vouchermanagement.customer.domain; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.UUID; + +@Getter +@EqualsAndHashCode +@AllArgsConstructor +public class Customer { + + private final UUID id; + private final String name; + private CustomerType type; + + public Customer(String name, CustomerType type) { + this.id = UUID.randomUUID(); + this.name = name; + this.type = type; + } + + public void changeType(CustomerType type) { + this.type = type; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerRepository.java new file mode 100644 index 0000000000..9e31b38435 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerRepository.java @@ -0,0 +1,18 @@ +package com.programmers.vouchermanagement.customer.domain; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CustomerRepository { + + Customer save(Customer customer); + + Optional findById(UUID id); + + List findAll(); + + void update(Customer customer); + + void deleteById(UUID id); +} diff --git a/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java new file mode 100644 index 0000000000..6fc1cddfb2 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/domain/CustomerType.java @@ -0,0 +1,15 @@ +package com.programmers.vouchermanagement.customer.domain; + +import com.programmers.vouchermanagement.customer.exception.InvalidCustomerTypeException; + +public enum CustomerType { + BLACK, WHITE; + + public static CustomerType from(String type) { + try { + return valueOf(type.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new InvalidCustomerTypeException("잘못된 고객 타입입니다."); + } + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java b/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java new file mode 100644 index 0000000000..3a57a94cb0 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/exception/InvalidCustomerTypeException.java @@ -0,0 +1,7 @@ +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/customer/infrastructure/JdbcTemplateCustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/infrastructure/JdbcTemplateCustomerRepository.java new file mode 100644 index 0000000000..d61c59eb6f --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/infrastructure/JdbcTemplateCustomerRepository.java @@ -0,0 +1,76 @@ +package com.programmers.vouchermanagement.customer.infrastructure; + +import com.programmers.vouchermanagement.customer.domain.Customer; +import com.programmers.vouchermanagement.customer.domain.CustomerRepository; +import com.programmers.vouchermanagement.customer.domain.CustomerType; +import org.springframework.context.annotation.Primary; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Primary +@Repository +public class JdbcTemplateCustomerRepository implements CustomerRepository { + + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateCustomerRepository(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Customer save(Customer customer) { + String sql = "insert into customer (id, name, type) values (?, ?, ?)"; + jdbcTemplate.update(sql, + customer.getId().toString(), + customer.getName(), + customer.getType().toString()); + return customer; + } + + @Override + public Optional findById(UUID id) { + String sql = "select * from customer where id = ?"; + try { + Customer customer = jdbcTemplate.queryForObject(sql, customerRowMapper(), id.toString()); + return Optional.of(customer); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + @Override + public List findAll() { + String sql = "select * from customer"; + return jdbcTemplate.query(sql, customerRowMapper()); + } + + @Override + public void update(Customer customer) { + String sql = "update customer set type = ? where id = ?"; + jdbcTemplate.update(sql, + customer.getType().toString(), + customer.getId().toString()); + } + + @Override + public void deleteById(UUID id) { + String sql = "delete from customer where id = ?"; + jdbcTemplate.update(sql, id.toString()); + } + + private RowMapper customerRowMapper() { + return (rs, rowNum) -> { + UUID id = UUID.fromString(rs.getString("id")); + String name = rs.getString("name"); + CustomerType type = CustomerType.valueOf(rs.getString("type")); + return new Customer(id, name, type); + }; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/view/Command.java b/src/main/java/com/programmers/vouchermanagement/view/Command.java index 6d80749e53..4290ba5cb7 100644 --- a/src/main/java/com/programmers/vouchermanagement/view/Command.java +++ b/src/main/java/com/programmers/vouchermanagement/view/Command.java @@ -1,37 +1,40 @@ package com.programmers.vouchermanagement.view; +import lombok.Getter; + import java.util.Collections; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +@Getter public enum Command { - CREATE("create"), - LIST("list"), - EXIT("exit"); + EXIT("1", "프로그램 종료"), + CREATE("2", "바우처 생성"), + LIST("3", "바우처 조회"), + UPDATE("4", "바우처 수정"), + DELETE("5", "바우처 삭제"); - private final String name; + private final String number; + private final String description; private static final Map COMMAND_MAP; static { COMMAND_MAP = Collections.unmodifiableMap(Stream.of(values()) - .collect(Collectors.toMap(Command::getName, Function.identity()))); - } - - Command(String name) { - this.name = name; + .collect(Collectors.toMap(Command::getNumber, Function.identity()))); } - public String getName() { - return name; + Command(String number, String description) { + this.number = number; + this.description = description; } - public static Command from(String name) { - if (COMMAND_MAP.containsKey(name)) { - return COMMAND_MAP.get(name); + public static Command from(String number) { + if (COMMAND_MAP.containsKey(number)) { + return COMMAND_MAP.get(number); } - throw new IllegalArgumentException("This command does not exist."); + throw new IllegalArgumentException("존재하지 않는 명령어 입니다."); } } diff --git a/src/main/java/com/programmers/vouchermanagement/view/Console.java b/src/main/java/com/programmers/vouchermanagement/view/Console.java deleted file mode 100644 index a0e1a21fb5..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/view/Console.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.programmers.vouchermanagement.view; - -import com.programmers.vouchermanagement.voucher.dto.VoucherDto; - -import java.util.Scanner; -import java.util.stream.Collectors; - -public class Console { - - private final static Scanner SCANNER = new Scanner(System.in); - - private static String inputString() { - return SCANNER.nextLine(); - } - - public static String selectCommand() { - System.out.println(""" - - === Voucher Program === - Type exit to exit the program. - Type create to create a new voucher. - Type list to list all vouchers. - """); - return inputString(); - } - - public static String selectDiscountType() { - System.out.println(""" - - Choose a discount type. - Type fix to select a fixed discount - Type percent to select a fixed discount - """); - return inputString(); - } - - public static int inputDiscountAmount() { - System.out.println("\nPlease enter the discount amount\n"); - return inputInt(); - } - - private static int inputInt() { - int inputValue = 0; - try { - inputValue = Integer.parseInt(inputString()); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Please enter only numbers."); - } - return inputValue; - } - - public static void outputVouchers(VoucherDto.Response vouchers) { - System.out.println("\nThis is a list of vouchers"); - System.out.println(vouchers.voucherName().stream() - .map(voucher -> voucher + System.lineSeparator()) - .collect(Collectors.joining())); - } - - public static void outputErrorMessage(String errorMessage) { - System.out.println(errorMessage); - } -} diff --git a/src/main/java/com/programmers/vouchermanagement/view/InputView.java b/src/main/java/com/programmers/vouchermanagement/view/InputView.java new file mode 100644 index 0000000000..26dcb44753 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/view/InputView.java @@ -0,0 +1,66 @@ +package com.programmers.vouchermanagement.view; + +import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; + +import java.util.List; +import java.util.Scanner; +import java.util.UUID; + +public class InputView { + + private static final String INPUT_COMMAND = "명령어 번호를 입력해주세요."; + private static final String INPUT_DISCOUNT_TYPE = "할인 유형 번호를 입력해주세요."; + private static final String INPUT_DISCOUNT_AMOUNT = "할인 양을 입력해주세요."; + private static final String INPUT_VOUCHER_UPDATE = "수정할 바우처 번호를 입력해주세요."; + private static final String INPUT_VOUCHER_DELETE = "삭제할 바우처 번호를 입력해주세요."; + private static final Scanner SCANNER = new Scanner(System.in); + + public static Command inputCommand() { + System.out.println(INPUT_COMMAND); + return Command.from(SCANNER.nextLine()); + } + + public static VoucherCreationRequest inputVoucherInfo() { + DiscountType type = inputDiscountType(); + int amount = inputDiscountAmount(); + return new VoucherCreationRequest(type, amount); + } + + public static DiscountType inputDiscountType() { + System.out.println(INPUT_DISCOUNT_TYPE); + return DiscountType.from(SCANNER.nextLine()); + } + + public static int inputDiscountAmount() { + System.out.println(INPUT_DISCOUNT_AMOUNT); + return inputAmount(); + } + + private static int inputAmount() { + int inputValue = 0; + try { + inputValue = Integer.parseInt(SCANNER.nextLine()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자만 입력해주세요"); + } + return inputValue; + } + + public static UUID inputVoucherUpdate(List vouchers) { + System.out.println(INPUT_VOUCHER_UPDATE); + return inputVoucherId(vouchers); + } + + private static UUID inputVoucherId(List vouchers) { + String selectedVoucherNumber = SCANNER.nextLine(); + VoucherResponse voucherResponse = vouchers.get(Integer.parseInt(selectedVoucherNumber) - 1); + return voucherResponse.getId(); + } + + public static UUID inputVoucherDelete(List vouchers) { + System.out.println(INPUT_VOUCHER_DELETE); + return inputVoucherId(vouchers); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/view/OutputView.java b/src/main/java/com/programmers/vouchermanagement/view/OutputView.java new file mode 100644 index 0000000000..eaec94513c --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/view/OutputView.java @@ -0,0 +1,59 @@ +package com.programmers.vouchermanagement.view; + +import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; + +import java.text.MessageFormat; +import java.util.List; + +public class OutputView { + + private static final String VOUCHER_PROGRAM = "============= Voucher Program ============="; + private static final String DISCOUNT_TYPE = "=============== 바우처 할인 유형 ==============="; + private static final String VOUCHER_LIST = "================= 바우처 조회 ================="; + private static final String LINE = "ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ"; + private static final String VOUCHER_TABLE_TITLE = " 번호 | 바우처 ID | 할인 유형 | 할인 양 "; + private static final String VOUCHER_UPDATE = "================= 바우처 수정 ================="; + private static final String VOUCHER_DELETE = "================= 바우처 삭제 ================="; + + public static void showCommand() { + System.out.println(VOUCHER_PROGRAM); + for (Command command : Command.values()) { + System.out.println(MessageFormat.format("({0}) {1} : {2}", command.getNumber(), command, command.getDescription())); + } + } + + public static void showDiscountType() { + System.out.println(DISCOUNT_TYPE); + for (DiscountType discountType : DiscountType.values()) { + System.out.println(MessageFormat.format("({0}) {1} : {2}", discountType.getNumber(), discountType, discountType.getName())); + } + } + + public static void showVouchers(List vouchers) { + System.out.println(VOUCHER_LIST); + showVoucher(vouchers); + } + + private static void showVoucher(List vouchers) { + System.out.println(LINE); + System.out.println(VOUCHER_TABLE_TITLE); + System.out.println(LINE); + int i = 1; + for (VoucherResponse voucher : vouchers) { + System.out.println(MessageFormat.format(" {0} | {1} | {2} | {3}", i, voucher.getId(), voucher.getType().getName(), voucher.getAmount())); + System.out.println(LINE); + i++; + } + } + + public static void showVoucherUpdate(List vouchers) { + System.out.println(VOUCHER_UPDATE); + showVoucher(vouchers); + } + + public static void showVoucherDelete(List vouchers) { + System.out.println(VOUCHER_DELETE); + showVoucher(vouchers); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherFactory.java b/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherFactory.java deleted file mode 100644 index f83bc1ac02..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.programmers.vouchermanagement.voucher.application; - -import com.programmers.vouchermanagement.voucher.domain.DiscountType; -import com.programmers.vouchermanagement.voucher.domain.FixedAmountVoucher; -import com.programmers.vouchermanagement.voucher.domain.PercentDiscountVoucher; -import com.programmers.vouchermanagement.voucher.domain.Voucher; - -public class VoucherFactory { - - public static Voucher createVoucher(DiscountType discountType, int discountAmount) { - if (discountType == DiscountType.FIX) { - return new FixedAmountVoucher(discountAmount); - } - return new PercentDiscountVoucher(discountAmount); - } -} 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 f27d10b8c0..e49bc8713a 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/application/VoucherService.java @@ -1,13 +1,19 @@ package com.programmers.vouchermanagement.voucher.application; +import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.DiscountType; import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherRepository; -import com.programmers.vouchermanagement.voucher.dto.VoucherDto; +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.stereotype.Service; import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @Slf4j @Service @@ -16,14 +22,31 @@ public class VoucherService { private final VoucherRepository voucherRepository; - public void createVoucher(VoucherDto.Request request) { - Voucher voucher = VoucherFactory.createVoucher(request.discountType(), request.discountAmount()); - voucherRepository.save(voucher); - log.info("Create Voucher! DiscountType: {}, DiscountAmount: {}", voucher.getClass().getSimpleName(), request.discountAmount()); + 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 VoucherDto.Response getVouchers() { + public List getVouchers() { List vouchers = voucherRepository.findAll(); - return VoucherDto.Response.toDto(vouchers); + return vouchers.stream() + .map(VoucherResponse::new) + .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); + voucherRepository.update(voucher); + } + + public void deleteVoucher(UUID id) { + voucherRepository.deleteById(id); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountPolicy.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountPolicy.java new file mode 100644 index 0000000000..0909826f43 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountPolicy.java @@ -0,0 +1,22 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode +public abstract class DiscountPolicy { + + private final int amount; + + DiscountPolicy(int amount) { + validateAmount(amount); + this.amount = amount; + } + + abstract void validateAmount(int amount); + + public abstract DiscountType getType(); + + public abstract int discount(int originalPrice); +} 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 43a7ed1426..fd92e1487e 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/DiscountType.java @@ -1,36 +1,44 @@ package com.programmers.vouchermanagement.voucher.domain; +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountTypeException; +import lombok.Getter; + import java.util.Collections; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +@Getter public enum DiscountType { - FIX("fix"), - PERCENT("percent"); + FIX("1", "정액 할인", FixedAmountDiscountPolicy::new), + PERCENT("2", "정률 할인", PercentDiscountPolicy::new); + private final String number; private final String name; + private final Function voucherCreationFunction; private static final Map DISCOUNT_TYPE_MAP; static { DISCOUNT_TYPE_MAP = Collections.unmodifiableMap(Stream.of(values()) - .collect(Collectors.toMap(DiscountType::getName, Function.identity()))); + .collect(Collectors.toMap(DiscountType::getNumber, Function.identity()))); } - DiscountType(String name) { + DiscountType(String number, String name, Function voucherCreationFunction) { + this.number = number; this.name = name; + this.voucherCreationFunction = voucherCreationFunction; } - public String getName() { - return name; + public static DiscountType from(String number) { + if (DISCOUNT_TYPE_MAP.containsKey(number)) { + return DISCOUNT_TYPE_MAP.get(number); + } + throw new InvalidDiscountTypeException("존재하지 않는 할인 유형 입니다."); } - public static DiscountType from(String name) { - if (DISCOUNT_TYPE_MAP.containsKey(name)) { - return DISCOUNT_TYPE_MAP.get(name); - } - throw new IllegalArgumentException("This discount type does not exist."); + public DiscountPolicy createDiscountPolicy(int amount) { + return voucherCreationFunction.apply(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 new file mode 100644 index 0000000000..443af3a97f --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicy.java @@ -0,0 +1,32 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; + +public class FixedAmountDiscountPolicy extends DiscountPolicy { + + public static final int MIN_AMOUNT = 1; + + public FixedAmountDiscountPolicy(int amount) { + super(amount); + } + + @Override + void validateAmount(int amount) { + if (amount < MIN_AMOUNT) { + throw new InvalidDiscountAmountException("고정할인금액은 최소 1원 이상이여야 합니다."); + } + } + + @Override + public DiscountType getType() { + return DiscountType.FIX; + } + + @Override + public int discount(int originalPrice) { + if (getAmount() > originalPrice) { + return 0; + } + return originalPrice - getAmount(); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountVoucher.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountVoucher.java deleted file mode 100644 index 4d5fdb52fa..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountVoucher.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.programmers.vouchermanagement.voucher.domain; - -import java.util.UUID; - -public class FixedAmountVoucher implements Voucher { - - public static final int MIN_AMOUNT = 1; - - private final UUID id; - private final int amount; - - public FixedAmountVoucher(int amount) { - validationAmount(amount); - this.id = UUID.randomUUID(); - this.amount = amount; - } - - private void validationAmount(int amount) { - if (amount <= MIN_AMOUNT) { - throw new IllegalArgumentException("The minimum discount amount is 1."); - } - } - - @Override - public UUID getId() { - return this.id; - } - - @Override - public int discount(int originalPrice) { - if (amount > originalPrice) { - return 0; - } - return originalPrice - 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 new file mode 100644 index 0000000000..ae162bb217 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicy.java @@ -0,0 +1,39 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; + +import java.math.BigDecimal; + +public class PercentDiscountPolicy extends DiscountPolicy { + + public static final int MIN_PERCENT = 1; + public static final int MAX_PERCENT = 100; + + public PercentDiscountPolicy(int amount) { + super(amount); + } + + @Override + void validateAmount(int amount) { + if (MIN_PERCENT > amount || amount > MAX_PERCENT) { + throw new InvalidDiscountAmountException("할인률은 1에서 100 사이여야 합니다."); + } + } + + @Override + public DiscountType getType() { + return DiscountType.PERCENT; + } + + @Override + public int discount(int originalPrice) { + int discountedAmount = calculateDiscountedAmount(originalPrice); + return originalPrice - discountedAmount; + } + + private int calculateDiscountedAmount(int originalPrice) { + BigDecimal percent = BigDecimal.valueOf(getAmount()).divide(BigDecimal.valueOf(MAX_PERCENT)); + BigDecimal discountedAmount = percent.multiply(BigDecimal.valueOf(originalPrice)); + return discountedAmount.intValue(); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountVoucher.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountVoucher.java deleted file mode 100644 index 8c348dc9ca..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountVoucher.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.programmers.vouchermanagement.voucher.domain; - -import java.util.UUID; - -public class PercentDiscountVoucher implements Voucher { - - public static final int MIN_PERCENT = 1; - public static final int MAX_PERCENT = 100; - - private final UUID id; - private final int amount; - - public PercentDiscountVoucher(int amount) { - validationAmount(amount); - this.id = UUID.randomUUID(); - this.amount = amount; - } - - private void validationAmount(int amount) { - if (MIN_PERCENT > amount || amount > MAX_PERCENT) { - throw new IllegalArgumentException("The discount percentage must be between 1 and 100%."); - } - } - - @Override - public UUID getId() { - return this.id; - } - - @Override - public int discount(int originalPrice) { - return originalPrice * (amount / MAX_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 fd955ae5b4..42b8c98733 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java @@ -1,10 +1,29 @@ package com.programmers.vouchermanagement.voucher.domain; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + import java.util.UUID; -public interface Voucher { +@Getter +@EqualsAndHashCode +@AllArgsConstructor +public class Voucher { + + private final UUID id; + private DiscountPolicy discountPolicy; + + public Voucher(DiscountPolicy discountPolicy) { + this.id = UUID.randomUUID(); + this.discountPolicy = discountPolicy; + } - UUID getId(); + public int discount(int originalPrice) { + return discountPolicy.discount(originalPrice); + } - int discount(int originalPrice); + public void changeDiscountPolicy(DiscountPolicy discountPolicy) { + this.discountPolicy = discountPolicy; + } } 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 d2f385ca65..d10a664494 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherRepository.java @@ -1,10 +1,18 @@ package com.programmers.vouchermanagement.voucher.domain; import java.util.List; +import java.util.Optional; +import java.util.UUID; public interface VoucherRepository { Voucher save(Voucher voucher); + Optional findById(UUID id); + List findAll(); + + void update(Voucher voucher); + + void deleteById(UUID id); } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherDto.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherDto.java deleted file mode 100644 index 0e20dd578c..0000000000 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.programmers.vouchermanagement.voucher.dto; - -import com.programmers.vouchermanagement.voucher.domain.DiscountType; -import com.programmers.vouchermanagement.voucher.domain.Voucher; - -import java.util.List; -import java.util.stream.Collectors; - -public class VoucherDto { - - public record Request(DiscountType discountType, int discountAmount) { - } - - public record Response(List voucherName) { - - @Override - public List voucherName() { - return voucherName; - } - - public static VoucherDto.Response toDto(List vouchers) { - List vourcherList = vouchers.stream() - .map(voucher -> voucher.getClass().getSimpleName()) - .collect(Collectors.toList()); - return new VoucherDto.Response(vourcherList); - } - } - -} 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 new file mode 100644 index 0000000000..e5c8f4cdab --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherCreationRequest.java @@ -0,0 +1,6 @@ +package com.programmers.vouchermanagement.voucher.dto.request; + +import com.programmers.vouchermanagement.voucher.domain.DiscountType; + +public record VoucherCreationRequest(DiscountType type, int amount) { +} \ 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 new file mode 100644 index 0000000000..51ccd551a9 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/request/VoucherUpdateRequest.java @@ -0,0 +1,8 @@ +package com.programmers.vouchermanagement.voucher.dto.request; + +import com.programmers.vouchermanagement.voucher.domain.DiscountType; + +import java.util.UUID; + +public record VoucherUpdateRequest(UUID id, DiscountType type, int amount) { +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/response/VoucherResponse.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/response/VoucherResponse.java new file mode 100644 index 0000000000..7f327d7484 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/response/VoucherResponse.java @@ -0,0 +1,21 @@ +package com.programmers.vouchermanagement.voucher.dto.response; + +import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import lombok.Getter; + +import java.util.UUID; + +@Getter +public class VoucherResponse { + + private final UUID id; + private final DiscountType type; + private final int amount; + + public VoucherResponse(Voucher voucher) { + this.id = voucher.getId(); + this.type = voucher.getDiscountPolicy().getType(); + this.amount = voucher.getDiscountPolicy().getAmount(); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java b/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java new file mode 100644 index 0000000000..35d3e101ad --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountAmountException.java @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000000..4018ec998d --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/exception/InvalidDiscountTypeException.java @@ -0,0 +1,7 @@ +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/infrastructure/JdbcTemplateVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java new file mode 100644 index 0000000000..2135600fc6 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepository.java @@ -0,0 +1,79 @@ +package com.programmers.vouchermanagement.voucher.infrastructure; + +import com.programmers.vouchermanagement.voucher.domain.DiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.DiscountType; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.domain.VoucherRepository; +import org.springframework.context.annotation.Primary; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Primary +@Repository +public class JdbcTemplateVoucherRepository implements VoucherRepository { + + private final JdbcTemplate jdbcTemplate; + + public JdbcTemplateVoucherRepository(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Voucher save(Voucher voucher) { + String sql = "insert into voucher (id, type, amount) values (?, ?, ?)"; + jdbcTemplate.update(sql, + voucher.getId().toString(), + voucher.getDiscountPolicy().getType().toString(), + voucher.getDiscountPolicy().getAmount()); + return voucher; + } + + @Override + public Optional findById(UUID id) { + String sql = "select * from voucher where id = ?"; + try { + Voucher voucher = jdbcTemplate.queryForObject(sql, voucherRowMapper(), id.toString()); + return Optional.of(voucher); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + @Override + public List findAll() { + String sql = "select * from voucher"; + return jdbcTemplate.query(sql, voucherRowMapper()); + } + + @Override + public void update(Voucher voucher) { + String sql = "update voucher set type = ?, amount = ? where id = ?"; + jdbcTemplate.update(sql, + voucher.getDiscountPolicy().getType().toString(), + voucher.getDiscountPolicy().getAmount(), + voucher.getId().toString()); + } + + @Override + public void deleteById(UUID id) { + String sql = "delete from voucher where id = ?"; + jdbcTemplate.update(sql, 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); + }; + } +} 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 41bd15da65..eb68b483a5 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepository.java @@ -2,14 +2,13 @@ import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherRepository; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Repository; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +@Profile("dev") @Repository public class MemoryVoucherRepository implements VoucherRepository { @@ -21,8 +20,28 @@ public Voucher save(Voucher voucher) { return voucher; } + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(storage.get(id)); + } + @Override public List findAll() { return new ArrayList<>(storage.values()); } + + @Override + public void update(Voucher voucher) { + deleteById(voucher.getId()); + save(voucher); + } + + @Override + public void deleteById(UUID id) { + storage.remove(id); + } + + public void clearStorage() { + storage.clear(); + } } 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 41d0733eb9..198df1be2b 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/presentation/VoucherController.java @@ -1,21 +1,34 @@ package com.programmers.vouchermanagement.voucher.presentation; import com.programmers.vouchermanagement.voucher.application.VoucherService; -import com.programmers.vouchermanagement.voucher.dto.VoucherDto; +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 org.springframework.stereotype.Controller; +import java.util.List; +import java.util.UUID; + @Controller @RequiredArgsConstructor public class VoucherController { private final VoucherService voucherService; - public void createVoucher(VoucherDto.Request request) { + public void createVoucher(VoucherCreationRequest request) { voucherService.createVoucher(request); } - public VoucherDto.Response getVouchers() { + public List getVouchers() { return voucherService.getVouchers(); } + + public void updateVoucher(VoucherUpdateRequest request) { + voucherService.updateVoucher(request); + } + + public void deleteVoucher(UUID id) { + voucherService.deleteVoucher(id); + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000000..ba07cd74d6 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,2 @@ +version: "1.0" +description: "Memory" \ No newline at end of file diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000000..d74e531b8b --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,15 @@ +version: "2.0" +description: "JDBC H2" + +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:voucher_management + username: sa + password: + h2: + console: + enabled: true + sql: + init: + mode: always diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000000..acbd34efcb --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +version: "2.0" +description: "JDBC MySQL" + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/voucher_management?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: root + password: password + + sql: + init: + platform: mysql + mode: always diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 168e7ebf0d..17d427ba1a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,11 +21,7 @@ - - - - - + \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..b9d031d28a --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS voucher; +DROP TABLE IF EXISTS customer; + +CREATE TABLE voucher ( + id VARCHAR(36), + type VARCHAR(20) NOT NULL, + amount int NOT NULL, + primary key (id) +); + +CREATE TABLE customer ( + id VARCHAR(36), + name VARCHAR(20) NOT NULL, + type VARCHAR(20) NOT NULL, + primary key (id) +); diff --git a/src/test/java/com/programmers/vouchermanagement/VoucherManagementApplicationTests.java b/src/test/java/com/programmers/vouchermanagement/VoucherManagementApplicationTests.java deleted file mode 100644 index 85414204ad..0000000000 --- a/src/test/java/com/programmers/vouchermanagement/VoucherManagementApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.programmers.vouchermanagement; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class VoucherManagementApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java b/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java new file mode 100644 index 0000000000..d6383e7807 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/customer/domain/CustomerTypeTest.java @@ -0,0 +1,56 @@ +package com.programmers.vouchermanagement.customer.domain; + +import com.programmers.vouchermanagement.customer.exception.InvalidCustomerTypeException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CustomerTypeTest { + + @Nested + @DisplayName("고객 유형에 적합한 CustomerType 을 반환한다.") + class fromSuccess { + + @Test + @DisplayName("white 가 들어온 경우") + void whenWhiteComeInto() { + // given + String type = "white"; + + // when + CustomerType result = CustomerType.from(type); + + // then + assertThat(result).isEqualTo(CustomerType.WHITE); + } + + @Test + @DisplayName("black 가 들어온 경우") + void whenBlackComeInto() { + // given + String type = "black"; + + // when + CustomerType result = CustomerType.from(type); + + // then + assertThat(result).isEqualTo(CustomerType.BLACK); + } + } + + + @Test + @DisplayName("존재하지 않는 고객 유형이 들어오면 에외가 발생한다.") + void fromException() { + // given + String type = "red"; + + // given & then + assertThatThrownBy(() -> CustomerType.from(type)) + .isInstanceOf(InvalidCustomerTypeException.class) + .hasMessage("잘못된 고객 타입입니다."); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/customer/infrastructure/JdbcTemplateCustomerRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/customer/infrastructure/JdbcTemplateCustomerRepositoryTest.java new file mode 100644 index 0000000000..b5b18dc941 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/customer/infrastructure/JdbcTemplateCustomerRepositoryTest.java @@ -0,0 +1,99 @@ +package com.programmers.vouchermanagement.customer.infrastructure; + +import com.programmers.vouchermanagement.customer.domain.Customer; +import com.programmers.vouchermanagement.customer.domain.CustomerRepository; +import com.programmers.vouchermanagement.customer.domain.CustomerType; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +@SpringBootTest +@Transactional +class JdbcTemplateCustomerRepositoryTest { + + @Autowired + CustomerRepository customerRepository; + + @Test + @DisplayName("고객을 저장한다.") + void save() { + // given + Customer customer = new Customer("고객", CustomerType.WHITE); + + // when + customerRepository.save(customer); + + // then + Customer result = customerRepository.findById(customer.getId()).get(); + assertThat(result).isEqualTo(customer); + } + + @Test + @DisplayName("Id로 고객을 조회한다.") + void findById() { + // given + Customer customer = new Customer("고객", CustomerType.WHITE); + customerRepository.save(customer); + + // when + Customer result = customerRepository.findById(customer.getId()).get(); + + // then + assertThat(result).isEqualTo(customer); + } + + @Test + @DisplayName("고객을 모두 조회한다.") + void findAll() { + // given + Customer customer1 = new Customer("고객1", CustomerType.WHITE); + Customer customer2 = new Customer("고객2", CustomerType.BLACK); + + customerRepository.save(customer1); + customerRepository.save(customer2); + + // when + List result = customerRepository.findAll(); + + // then + assertThat(result).hasSize(2); + } + + @Test + @DisplayName("고객 정보를 업데이트한다.") + void update() { + // given + Customer customer = new Customer("고객", CustomerType.WHITE); + customerRepository.save(customer); + customer.changeType(CustomerType.BLACK); + + // when + customerRepository.update(customer); + + // then + assertThat(customer.getType()).isEqualTo(CustomerType.BLACK); + } + + @Test + @DisplayName("고객을 삭제한다.") + void deleteById() { + // given + Customer customer = new Customer("고객", CustomerType.WHITE); + + // when + customerRepository.deleteById(customer.getId()); + + // then + Optional result = customerRepository.findById(customer.getId()); + assertThat(result).isEmpty(); + } +} \ 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 new file mode 100644 index 0000000000..85e7c648c6 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/application/VoucherServiceTest.java @@ -0,0 +1,72 @@ +package com.programmers.vouchermanagement.voucher.application; + +import com.programmers.vouchermanagement.voucher.domain.*; +import com.programmers.vouchermanagement.voucher.dto.request.VoucherCreationRequest; +import com.programmers.vouchermanagement.voucher.dto.response.VoucherResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class VoucherServiceTest { + + @InjectMocks + private VoucherService voucherService; + + @Mock + private VoucherRepository voucherRepository; + + @Test + @DisplayName("바우처를 생성한다.") + void createVoucher() { + // given + VoucherCreationRequest request = new VoucherCreationRequest(DiscountType.FIX, 5000); + DiscountType discountType = request.type(); + int amount = request.amount(); + DiscountPolicy discountPolicy = discountType.createDiscountPolicy(amount); + Voucher voucher = new Voucher(discountPolicy); + + given(voucherRepository.save(any(Voucher.class))) + .willReturn(voucher); + + // when + Voucher result = voucherService.createVoucher(request); + + // then + assertThat(result).isNotNull(); + + // verify + verify(voucherRepository, times(1)).save(any(Voucher.class)); + } + + @Test + @DisplayName("바우처를 모두 조회한다.") + void getVouchers() { + // given + Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + + given(voucherRepository.findAll()) + .willReturn(List.of(voucher1, voucher2)); + + // when + List result = voucherService.getVouchers(); + + // then + assertThat(result).hasSize(2); + + // verify + verify(voucherRepository, times(1)).findAll(); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java new file mode 100644 index 0000000000..8d3e3ab818 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/DiscountTypeTest.java @@ -0,0 +1,55 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountTypeException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DiscountTypeTest { + + @Nested + @DisplayName("할인 유형에 적합한 DiscountType 을 반환한다.") + class from_success { + + @Test + @DisplayName("fix 가 들어온 경우") + void WhenFixComeInto() { + // given + String number = "1"; // Fix amount + + // when + DiscountType result = DiscountType.from(number); + + // then + assertThat(result).isEqualTo(DiscountType.FIX); + } + + @Test + @DisplayName("percent 가 들어온 경우") + void WhenPercentComeInto() { + // given + String number = "2"; // Percent + + // when + DiscountType result = DiscountType.from(number); + + // then + assertThat(result).isEqualTo(DiscountType.PERCENT); + } + } + + @Test + @DisplayName("존재하지 않는 할인 유형이 들어오면 에외가 발생한다.") + void from_exception() { + // given + String number = "3"; + + // when & then + assertThatThrownBy(() -> DiscountType.from(number)) + .isInstanceOf(InvalidDiscountTypeException.class) + .hasMessage("존재하지 않는 할인 유형 입니다."); + } +} \ 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 new file mode 100644 index 0000000000..5818627cf6 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/FixedAmountDiscountPolicyTest.java @@ -0,0 +1,51 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.*; + +class FixedAmountDiscountPolicyTest { + + @Nested + @DisplayName("정액 할인 정책을 생성한다.") + class createFixedAmountDiscountPolicy { + + @ParameterizedTest + @ValueSource(ints = {1, 10}) + @DisplayName("최소 할인 금액 이상인 경우 예외가 발생하지 않는다.") + void isMoreThanMinAmount(int amount) { + // when & then + assertThatCode(() -> new FixedAmountDiscountPolicy(amount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1}) + @DisplayName("최소 할인 금액 미만인 경우 예외가 발생한다.") + void lessThanMinAmount(int amount) { + // when & then + assertThatThrownBy(() -> new FixedAmountDiscountPolicy(amount)) + .isInstanceOf(InvalidDiscountAmountException.class) + .hasMessage("고정할인금액은 최소 1원 이상이여야 합니다."); + } + } + + @ParameterizedTest + @CsvSource({"20000, 10000", "10000, 0", "5000, 0"}) + @DisplayName("정액 할인을 적용한다.") + void discount(int originalPrice, int expected) { + // given + DiscountPolicy discountPolicy = new FixedAmountDiscountPolicy(10000); + + // when + int result = discountPolicy.discount(originalPrice); + + // then + assertThat(result).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java new file mode 100644 index 0000000000..e8ac5fd174 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/PercentDiscountPolicyTest.java @@ -0,0 +1,51 @@ +package com.programmers.vouchermanagement.voucher.domain; + +import com.programmers.vouchermanagement.voucher.exception.InvalidDiscountAmountException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.*; + +class PercentDiscountPolicyTest { + + @Nested + @DisplayName("정률 할인 정책을 생성한다.") + class createPercentDiscountPolicy { + + @ParameterizedTest + @ValueSource(ints = {1, 50, 100}) + @DisplayName("할인률이 1에서 100 사이인 경우 예외가 발생하지 않는다.") + void isBetween1And100(int amount) { + // when & then + assertThatCode(() -> new PercentDiscountPolicy(amount)) + .doesNotThrowAnyException(); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 0, 101}) + @DisplayName("할인률이 1에서 100 사이가 아닌 경우 예외가 발생한다.") + void isNotBetween1And100(int amount) { + // when & then + assertThatThrownBy(() -> new PercentDiscountPolicy(amount)) + .isInstanceOf(InvalidDiscountAmountException.class) + .hasMessage("할인률은 1에서 100 사이여야 합니다."); + } + } + + @ParameterizedTest + @CsvSource({"10, 9000", "50, 5000", "100, 0", "33, 6700"}) + @DisplayName("정률 할인을 적용한다.") + void discount(int amount, int expected) { + // given + DiscountPolicy discountPolicy = new PercentDiscountPolicy(amount); + + // when + int result = discountPolicy.discount(10000); + + // then + assertThat(result).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java new file mode 100644 index 0000000000..e2bc7090a3 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/JdbcTemplateVoucherRepositoryTest.java @@ -0,0 +1,100 @@ +package com.programmers.vouchermanagement.voucher.infrastructure; + +import com.programmers.vouchermanagement.voucher.domain.FixedAmountDiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.PercentDiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +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.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +@SpringBootTest +@Transactional +class JdbcTemplateVoucherRepositoryTest { + + @Autowired + JdbcTemplateVoucherRepository voucherRepository; + + @Test + @DisplayName("바우처를 저장한다.") + void save() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + + // when + voucherRepository.save(voucher); + + // then + Voucher result = voucherRepository.findById(voucher.getId()).get(); + assertThat(result).isEqualTo(voucher); + } + + @Test + @DisplayName("Id로 바우처를 조회한다.") + void findById() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + voucherRepository.save(voucher); + + // when + Voucher result = voucherRepository.findById(voucher.getId()).get(); + + // then + assertThat(result).isEqualTo(voucher); + } + + @Test + @DisplayName("바우처를 모두 조회한다.") + void findAll() { + // given + Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + + voucherRepository.save(voucher1); + voucherRepository.save(voucher2); + + // when + List result = voucherRepository.findAll(); + + // then + assertThat(result).hasSize(2); + } + + @Test + @DisplayName("바우처를 업데이트한다.") + void update() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + voucherRepository.save(voucher); + voucher.changeDiscountPolicy(new PercentDiscountPolicy(10)); + + // when + voucherRepository.update(voucher); + + // then + assertThat(voucher.getDiscountPolicy()).isInstanceOf(PercentDiscountPolicy.class); + assertThat(voucher.getDiscountPolicy().getAmount()).isEqualTo(10); + } + + @Test + @DisplayName("바우처를 삭제한다.") + void deleteById() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + + // when + voucherRepository.deleteById(voucher.getId()); + + // then + Optional result = voucherRepository.findById(voucher.getId()); + assertThat(result).isEmpty(); + } +} \ No newline at end of file diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java new file mode 100644 index 0000000000..6f841ef025 --- /dev/null +++ b/src/test/java/com/programmers/vouchermanagement/voucher/infrastructure/MemoryVoucherRepositoryTest.java @@ -0,0 +1,100 @@ +package com.programmers.vouchermanagement.voucher.infrastructure; + +import com.programmers.vouchermanagement.voucher.domain.FixedAmountDiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.PercentDiscountPolicy; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +class MemoryVoucherRepositoryTest { + + MemoryVoucherRepository memoryVoucherRepository = new MemoryVoucherRepository(); + + @AfterEach + void afterEach() { + memoryVoucherRepository.clearStorage(); + } + + @Test + @DisplayName("바우처를 저장한다.") + void save() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + + // when + memoryVoucherRepository.save(voucher); + + // then + Voucher result = memoryVoucherRepository.findById(voucher.getId()).get(); + assertThat(result).isEqualTo(voucher); + } + + @Test + @DisplayName("Id로 바우처를 조회한다.") + void findById() { + // given + UUID id = UUID.randomUUID(); + Voucher voucher = new Voucher(id, new FixedAmountDiscountPolicy(5000)); + memoryVoucherRepository.save(voucher); + + // when + Voucher result = memoryVoucherRepository.findById(voucher.getId()).get(); + + // then + assertThat(result).isEqualTo(voucher); + } + + @Test + @DisplayName("바우처를 모두 조회한다.") + void findAll() { + // given + Voucher voucher1 = new Voucher(new FixedAmountDiscountPolicy(5000)); + Voucher voucher2 = new Voucher(new PercentDiscountPolicy(10)); + + memoryVoucherRepository.save(voucher1); + memoryVoucherRepository.save(voucher2); + + // when + List result = memoryVoucherRepository.findAll(); + + // then + assertThat(result).hasSize(2); + } + + @Test + @DisplayName("바우처를 업데이트한다.") + void update() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + memoryVoucherRepository.save(voucher); + voucher.changeDiscountPolicy(new PercentDiscountPolicy(10)); + + // when + memoryVoucherRepository.update(voucher); + + // then + assertThat(voucher.getDiscountPolicy()).isInstanceOf(PercentDiscountPolicy.class); + assertThat(voucher.getDiscountPolicy().getAmount()).isEqualTo(10); + } + + @Test + @DisplayName("바우처를 삭제한다.") + void deleteById() { + // given + Voucher voucher = new Voucher(new FixedAmountDiscountPolicy(5000)); + + // when + memoryVoucherRepository.deleteById(voucher.getId()); + + // then + Optional result = memoryVoucherRepository.findById(voucher.getId()); + assertThat(result).isEmpty(); + } +} \ No newline at end of file