diff --git a/README.md b/README.md index 2e51d59c56..f20a049ac5 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,10 @@ $ gradle test ## Run Locally -Clone the project branch `ASak1104/week2` +Clone the project branch `ASak1104/week3` ```bash -$ git clone -b ASak1104/week2 https://github.com/ASak1104/voucher-management-system.git +$ git clone -b ASak1104/week3 https://github.com/ASak1104/voucher-management-system.git ``` Go to the project directory diff --git a/build.gradle b/build.gradle index 6eec52269b..36db808c04 100644 --- a/build.gradle +++ b/build.gradle @@ -18,11 +18,16 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.15.2' implementation 'com.mysql:mysql-connector-j:8.1.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'com.github.javafaker:javafaker:1.0.2' } tasks.named('test') { diff --git a/src/main/java/team/marco/voucher_management_system/VoucherManagementSystemApplication.java b/src/main/java/team/marco/voucher_management_system/VoucherManagementSystemApplication.java index eaa9ff323b..9deeabfedf 100644 --- a/src/main/java/team/marco/voucher_management_system/VoucherManagementSystemApplication.java +++ b/src/main/java/team/marco/voucher_management_system/VoucherManagementSystemApplication.java @@ -1,28 +1,40 @@ package team.marco.voucher_management_system; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; +import java.util.function.Consumer; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; -import team.marco.voucher_management_system.application.CommandMainApplication; +import org.springframework.context.annotation.ComponentScan; +import team.marco.voucher_management_system.type_enum.ApplicationType; +import team.marco.voucher_management_system.util.Console; -@SpringBootApplication +@ComponentScan @ConfigurationPropertiesScan public class VoucherManagementSystemApplication { - private static final Logger logger = LoggerFactory.getLogger(VoucherManagementSystemApplication.class); - public static void main(String[] args) { - ApplicationContext context = SpringApplication.run(VoucherManagementSystemApplication.class, args); - Environment environment = context.getEnvironment(); + Console.print(""" + === 실행할 애플리케이션을 선택해주세요. === + 0. Console Application + 1. Web Application"""); + + selectApplication(args); + + Console.close(); + } + + private static void selectApplication(String[] args) { + Consumer mainMethod = getMainMethod(); + + mainMethod.accept(args); + } - logger.info("Program start (profile: {})", environment.getActiveProfiles()[0]); + private static Consumer getMainMethod() { + try { + int input = Console.readInt(); - CommandMainApplication application = context.getBean(CommandMainApplication.class); - application.run(); + return ApplicationType.getMainMethod(input); + } catch (IllegalArgumentException e) { + Console.print("사용할 수 없는 애플리케이션 입니다."); - logger.info("Program exit"); + return getMainMethod(); + } } } diff --git a/src/main/java/team/marco/voucher_management_system/aspect/LoggingPointCut.java b/src/main/java/team/marco/voucher_management_system/aspect/LoggingPointCut.java index e31b0c675f..ac92b0c4d9 100644 --- a/src/main/java/team/marco/voucher_management_system/aspect/LoggingPointCut.java +++ b/src/main/java/team/marco/voucher_management_system/aspect/LoggingPointCut.java @@ -6,7 +6,7 @@ public final class LoggingPointCut { private LoggingPointCut() { } - @Pointcut("execution(* team.marco.voucher_management_system.controller..*(..))") + @Pointcut("execution(* team.marco.voucher_management_system..controller..*(..))") public static void controllerMethodPointcut() { } @@ -14,7 +14,7 @@ public static void controllerMethodPointcut() { public static void serviceMethodPointcut() { } - @Pointcut("execution(* team.marco.voucher_management_system.repository..*(..))") + @Pointcut("execution(* team.marco.voucher_management_system..repository..*(..))") public static void repositorySavePointcut() { } } diff --git a/src/main/java/team/marco/voucher_management_system/console_app/ConsoleApplication.java b/src/main/java/team/marco/voucher_management_system/console_app/ConsoleApplication.java new file mode 100644 index 0000000000..aa2eb3c933 --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/console_app/ConsoleApplication.java @@ -0,0 +1,23 @@ +package team.marco.voucher_management_system.console_app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Import; +import team.marco.voucher_management_system.VoucherManagementSystemApplication; +import team.marco.voucher_management_system.console_app.application.CommandMainApplication; + +@Import(VoucherManagementSystemApplication.class) +@SpringBootApplication +public class ConsoleApplication { + public static void main(String[] args) { + SpringApplication springApplication = new SpringApplication(ConsoleApplication.class); + springApplication.setWebApplicationType(WebApplicationType.NONE); + + ConfigurableApplicationContext context = springApplication.run(args); + CommandMainApplication application = context.getBean(CommandMainApplication.class); + + application.run(); + } +} diff --git a/src/main/java/team/marco/voucher_management_system/application/CommandCustomerApplication.java b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandCustomerApplication.java similarity index 79% rename from src/main/java/team/marco/voucher_management_system/application/CommandCustomerApplication.java rename to src/main/java/team/marco/voucher_management_system/console_app/application/CommandCustomerApplication.java index 01eb9cf0b5..c67fbf0906 100644 --- a/src/main/java/team/marco/voucher_management_system/application/CommandCustomerApplication.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandCustomerApplication.java @@ -1,13 +1,13 @@ -package team.marco.voucher_management_system.application; +package team.marco.voucher_management_system.console_app.application; import java.util.NoSuchElementException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DataAccessResourceFailureException; -import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException; import org.springframework.stereotype.Component; -import team.marco.voucher_management_system.controller.ConsoleCustomerController; -import team.marco.voucher_management_system.type_enum.CustomerCommandType; +import team.marco.voucher_management_system.console_app.command_enum.CustomerCommandType; +import team.marco.voucher_management_system.console_app.controller.ConsoleCustomerController; import team.marco.voucher_management_system.util.Console; @Component @@ -48,12 +48,15 @@ private void executeCommand(int userInput) { } catch (IllegalArgumentException e) { logger.warn(e.toString()); Console.print(e.getMessage()); - } catch (EmptyResultDataAccessException | NoSuchElementException e) { - logger.error(e.toString()); - Console.print("존재하지 않는 ID 입니다."); - } catch (DataAccessResourceFailureException e) { + } catch (DataSourceLookupFailureException e) { logger.error(e.toString()); Console.print(e.getMessage()); + } catch (DuplicateKeyException e) { + logger.error(e.toString()); + Console.print("이미 존재하는 이메일 입니다."); + } catch (NoSuchElementException e) { + logger.error(e.toString()); + Console.print("존재하지 않는 ID 입니다."); } } diff --git a/src/main/java/team/marco/voucher_management_system/application/CommandMainApplication.java b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandMainApplication.java similarity index 77% rename from src/main/java/team/marco/voucher_management_system/application/CommandMainApplication.java rename to src/main/java/team/marco/voucher_management_system/console_app/application/CommandMainApplication.java index 8b7175b37f..e9d4d9f70d 100644 --- a/src/main/java/team/marco/voucher_management_system/application/CommandMainApplication.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandMainApplication.java @@ -1,11 +1,12 @@ -package team.marco.voucher_management_system.application; +package team.marco.voucher_management_system.console_app.application; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.stereotype.Component; -import team.marco.voucher_management_system.controller.ConsoleBlacklistController; -import team.marco.voucher_management_system.controller.ConsoleVoucherController; -import team.marco.voucher_management_system.type_enum.MainCommandType; +import team.marco.voucher_management_system.console_app.command_enum.MainCommandType; +import team.marco.voucher_management_system.console_app.controller.ConsoleBlacklistController; +import team.marco.voucher_management_system.console_app.controller.ConsoleVoucherController; import team.marco.voucher_management_system.util.Console; @Component @@ -31,11 +32,10 @@ public CommandMainApplication(ConsoleVoucherController voucherController, protected void start() { try { selectCommand(); + } catch (CannotGetJdbcConnectionException e) { + errorHandler(e, "데이터베이스에 연결할 수 없습니다."); } catch (Exception e) { - logger.error(e.toString()); - Console.print("프로그램에 에러가 발생했습니다."); - - runningFlag = false; + errorHandler(e, "프로그램에 에러가 발생했습니다."); } } @@ -79,6 +79,13 @@ private void switchCommand(MainCommandType commandType) { } } + private void errorHandler(Exception e, String message) { + logger.error(e.toString()); + Console.print(message); + + runningFlag = false; + } + @Override protected void close() { Console.print("프로그램이 종료되었습니다."); diff --git a/src/main/java/team/marco/voucher_management_system/application/CommandWalletApplication.java b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandWalletApplication.java similarity index 78% rename from src/main/java/team/marco/voucher_management_system/application/CommandWalletApplication.java rename to src/main/java/team/marco/voucher_management_system/console_app/application/CommandWalletApplication.java index 1ccb1ace47..bc496876bd 100644 --- a/src/main/java/team/marco/voucher_management_system/application/CommandWalletApplication.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/application/CommandWalletApplication.java @@ -1,15 +1,12 @@ -package team.marco.voucher_management_system.application; +package team.marco.voucher_management_system.console_app.application; import java.util.NoSuchElementException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DuplicateKeyException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.stereotype.Component; -import team.marco.voucher_management_system.controller.ConsoleWalletController; -import team.marco.voucher_management_system.type_enum.WalletCommandType; +import team.marco.voucher_management_system.console_app.command_enum.WalletCommandType; +import team.marco.voucher_management_system.console_app.controller.ConsoleWalletController; import team.marco.voucher_management_system.util.Console; @Component @@ -48,13 +45,10 @@ private void executeCommand(int userInput) { } catch (IllegalArgumentException e) { logger.warn(e.toString()); Console.print(e.getMessage()); - } catch (DataAccessResourceFailureException e) { - logger.error(e.toString()); - Console.print(e.getMessage()); } catch (DuplicateKeyException e) { logger.error(e.toString()); Console.print("이미 존재하는 쿠폰입니다."); - } catch (EmptyResultDataAccessException | NoSuchElementException | UncategorizedSQLException e) { + } catch (NoSuchElementException e) { logger.error(e.toString()); Console.print("존재하지 않는 ID 입니다."); } diff --git a/src/main/java/team/marco/voucher_management_system/application/RunnableCommandApplication.java b/src/main/java/team/marco/voucher_management_system/console_app/application/RunnableCommandApplication.java similarity index 81% rename from src/main/java/team/marco/voucher_management_system/application/RunnableCommandApplication.java rename to src/main/java/team/marco/voucher_management_system/console_app/application/RunnableCommandApplication.java index 212fb591e3..498d161298 100644 --- a/src/main/java/team/marco/voucher_management_system/application/RunnableCommandApplication.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/application/RunnableCommandApplication.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.application; +package team.marco.voucher_management_system.console_app.application; public abstract class RunnableCommandApplication { protected boolean runningFlag; diff --git a/src/main/java/team/marco/voucher_management_system/type_enum/CustomerCommandType.java b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/CustomerCommandType.java similarity index 86% rename from src/main/java/team/marco/voucher_management_system/type_enum/CustomerCommandType.java rename to src/main/java/team/marco/voucher_management_system/console_app/command_enum/CustomerCommandType.java index 502e56a720..fe83a27194 100644 --- a/src/main/java/team/marco/voucher_management_system/type_enum/CustomerCommandType.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/CustomerCommandType.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.type_enum; +package team.marco.voucher_management_system.console_app.command_enum; public enum CustomerCommandType { EXIT, CREATE, LIST, UPDATE, FIND_BY_ID, FIND_BY_NAME, FIND_BY_EMAIL; diff --git a/src/main/java/team/marco/voucher_management_system/type_enum/MainCommandType.java b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/MainCommandType.java similarity index 86% rename from src/main/java/team/marco/voucher_management_system/type_enum/MainCommandType.java rename to src/main/java/team/marco/voucher_management_system/console_app/command_enum/MainCommandType.java index 856b82336c..8c6bfae0f5 100644 --- a/src/main/java/team/marco/voucher_management_system/type_enum/MainCommandType.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/MainCommandType.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.type_enum; +package team.marco.voucher_management_system.console_app.command_enum; import static java.text.MessageFormat.format; diff --git a/src/main/java/team/marco/voucher_management_system/type_enum/WalletCommandType.java b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/WalletCommandType.java similarity index 85% rename from src/main/java/team/marco/voucher_management_system/type_enum/WalletCommandType.java rename to src/main/java/team/marco/voucher_management_system/console_app/command_enum/WalletCommandType.java index e9301dc999..6222ed66ed 100644 --- a/src/main/java/team/marco/voucher_management_system/type_enum/WalletCommandType.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/command_enum/WalletCommandType.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.type_enum; +package team.marco.voucher_management_system.console_app.command_enum; public enum WalletCommandType { EXIT, SUPPLY, VOUCHER_LIST, RETURN, CUSTOMER_LIST; diff --git a/src/main/java/team/marco/voucher_management_system/controller/ConsoleBlacklistController.java b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleBlacklistController.java similarity index 87% rename from src/main/java/team/marco/voucher_management_system/controller/ConsoleBlacklistController.java rename to src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleBlacklistController.java index 707aedbc41..bb55f9bd3a 100644 --- a/src/main/java/team/marco/voucher_management_system/controller/ConsoleBlacklistController.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleBlacklistController.java @@ -1,12 +1,12 @@ -package team.marco.voucher_management_system.controller; +package team.marco.voucher_management_system.console_app.controller; import java.util.List; -import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Component; import team.marco.voucher_management_system.model.BlacklistUser; import team.marco.voucher_management_system.service.BlacklistService; import team.marco.voucher_management_system.util.Console; -@Controller +@Component public class ConsoleBlacklistController { private static final String INFO_DELIMINATOR = "\n"; diff --git a/src/main/java/team/marco/voucher_management_system/controller/ConsoleCustomerController.java b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleCustomerController.java similarity index 87% rename from src/main/java/team/marco/voucher_management_system/controller/ConsoleCustomerController.java rename to src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleCustomerController.java index 037197251e..373b9ccc1a 100644 --- a/src/main/java/team/marco/voucher_management_system/controller/ConsoleCustomerController.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleCustomerController.java @@ -1,13 +1,15 @@ -package team.marco.voucher_management_system.controller; +package team.marco.voucher_management_system.console_app.controller; import java.text.MessageFormat; import java.util.List; -import org.springframework.stereotype.Controller; +import java.util.UUID; +import org.springframework.stereotype.Component; import team.marco.voucher_management_system.model.Customer; import team.marco.voucher_management_system.service.CustomerService; import team.marco.voucher_management_system.util.Console; +import team.marco.voucher_management_system.util.UUIDConverter; -@Controller +@Component public class ConsoleCustomerController { private static final String INFO_DELIMINATOR = MessageFormat.format("\n{0}\n", "-".repeat(42)); @@ -37,7 +39,8 @@ public void customerList() { public void updateCustomer() { Console.print("수정할 고객의 ID를 입력해 주세요."); - String id = Console.readString(); + String input = Console.readString(); + UUID id = UUIDConverter.convert(input); customerService.findById(id); @@ -51,7 +54,8 @@ public void updateCustomer() { public void findCustomerById() { Console.print("조회할 고객의 ID를 입력해 주세요."); - String id = Console.readString(); + String input = Console.readString(); + UUID id = UUIDConverter.convert(input); Customer customer = customerService.findById(id); diff --git a/src/main/java/team/marco/voucher_management_system/controller/ConsoleVoucherController.java b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleVoucherController.java similarity index 90% rename from src/main/java/team/marco/voucher_management_system/controller/ConsoleVoucherController.java rename to src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleVoucherController.java index 9dbc2c2641..e049a96481 100644 --- a/src/main/java/team/marco/voucher_management_system/controller/ConsoleVoucherController.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleVoucherController.java @@ -1,13 +1,13 @@ -package team.marco.voucher_management_system.controller; +package team.marco.voucher_management_system.console_app.controller; import java.text.MessageFormat; import java.util.List; -import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Component; import team.marco.voucher_management_system.model.Voucher; import team.marco.voucher_management_system.service.VoucherService; import team.marco.voucher_management_system.util.Console; -@Controller +@Component public class ConsoleVoucherController { private static final String INFO_DELIMINATOR = MessageFormat.format("\n{0}\n", "-".repeat(42)); @@ -32,7 +32,7 @@ public void selectVoucher() { } public void voucherList() { - List vouchers = voucherService.getVouchersInfo() + List vouchers = voucherService.getVouchers() .stream() .map(Voucher::getInfo) .toList(); diff --git a/src/main/java/team/marco/voucher_management_system/controller/ConsoleWalletController.java b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleWalletController.java similarity index 75% rename from src/main/java/team/marco/voucher_management_system/controller/ConsoleWalletController.java rename to src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleWalletController.java index 18a76b038d..9c38e429f4 100644 --- a/src/main/java/team/marco/voucher_management_system/controller/ConsoleWalletController.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/controller/ConsoleWalletController.java @@ -1,14 +1,16 @@ -package team.marco.voucher_management_system.controller; +package team.marco.voucher_management_system.console_app.controller; import java.text.MessageFormat; import java.util.List; -import org.springframework.stereotype.Controller; +import java.util.UUID; +import org.springframework.stereotype.Component; import team.marco.voucher_management_system.model.Customer; import team.marco.voucher_management_system.model.Voucher; import team.marco.voucher_management_system.service.WalletService; import team.marco.voucher_management_system.util.Console; +import team.marco.voucher_management_system.util.UUIDConverter; -@Controller +@Component public class ConsoleWalletController { private static final String INFO_DELIMINATOR = MessageFormat.format("\n{0}\n", "-".repeat(42)); @@ -21,12 +23,13 @@ public ConsoleWalletController(WalletService walletService) { public void supplyVoucher() { Console.print("쿠폰을 받을 고객 ID를 입력해주세요."); - String customerId = Console.readString(); + UUID customerId = UUIDConverter.convert(Console.readString()); + walletService.checkCustomerId(customerId); Console.print("지급할 쿠폰 ID를 입력해주세요."); - String voucherId = Console.readString(); + UUID voucherId = UUIDConverter.convert(Console.readString()); walletService.checkVoucherId(voucherId); if (walletService.supplyVoucher(customerId, voucherId) != 1) { @@ -41,9 +44,9 @@ public void supplyVoucher() { public void voucherList() { Console.print("보유 중인 쿠폰 목록을 확인할 고객 ID를 입력해주세요."); - String customerId = Console.readString(); + UUID customerId = UUIDConverter.convert(Console.readString()); - List voucherInfos = walletService.findVouchersByCustomerId(customerId) + List voucherInfos = walletService.findReceivedVouchers(customerId) .stream() .map(Voucher::getInfo) .toList(); @@ -55,12 +58,12 @@ public void voucherList() { public void returnVoucher() { Console.print("쿠폰을 반납할 고객 ID를 입력해주세요."); - String customerId = Console.readString(); + UUID customerId = UUIDConverter.convert(Console.readString()); walletService.checkCustomerId(customerId); Console.print("반납할 쿠폰 ID를 입력해주세요."); - String voucherId = Console.readString(); + UUID voucherId = UUIDConverter.convert(Console.readString()); walletService.checkVoucherId(voucherId); if (walletService.returnVoucher(customerId, voucherId) != 1) { @@ -75,10 +78,10 @@ public void returnVoucher() { public void customerList() { Console.print("보유 중인 고객 목록을 확인할 쿠폰 ID를 입력해주세요."); - String voucherId = Console.readString(); + UUID voucherId = UUIDConverter.convert(Console.readString()); walletService.checkVoucherId(voucherId); - List customerInfos = walletService.findCustomersByVoucherId(voucherId) + List customerInfos = walletService.findHavingCustomers(voucherId) .stream() .map(Customer::getInfo) .toList(); diff --git a/src/main/java/team/marco/voucher_management_system/properties/FilePathProperties.java b/src/main/java/team/marco/voucher_management_system/console_app/properties/FilePathProperties.java similarity index 75% rename from src/main/java/team/marco/voucher_management_system/properties/FilePathProperties.java rename to src/main/java/team/marco/voucher_management_system/console_app/properties/FilePathProperties.java index b94457dffd..9c88d6a827 100644 --- a/src/main/java/team/marco/voucher_management_system/properties/FilePathProperties.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/properties/FilePathProperties.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.properties; +package team.marco.voucher_management_system.console_app.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/team/marco/voucher_management_system/repository/BlacklistRepository.java b/src/main/java/team/marco/voucher_management_system/console_app/repository/BlacklistRepository.java similarity index 89% rename from src/main/java/team/marco/voucher_management_system/repository/BlacklistRepository.java rename to src/main/java/team/marco/voucher_management_system/console_app/repository/BlacklistRepository.java index 66c7997fbe..1d8a0b5ec7 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/BlacklistRepository.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/repository/BlacklistRepository.java @@ -1,4 +1,4 @@ -package team.marco.voucher_management_system.repository; +package team.marco.voucher_management_system.console_app.repository; import java.io.BufferedReader; import java.io.IOException; @@ -10,8 +10,8 @@ import java.util.List; import java.util.UUID; import org.springframework.stereotype.Repository; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; import team.marco.voucher_management_system.model.BlacklistUser; -import team.marco.voucher_management_system.properties.FilePathProperties; @Repository public class BlacklistRepository { diff --git a/src/main/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepository.java b/src/main/java/team/marco/voucher_management_system/console_app/repository/JSONFileVoucherRepository.java similarity index 65% rename from src/main/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepository.java rename to src/main/java/team/marco/voucher_management_system/console_app/repository/JSONFileVoucherRepository.java index e72ed8142a..b13b9ec3ca 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepository.java +++ b/src/main/java/team/marco/voucher_management_system/console_app/repository/JSONFileVoucherRepository.java @@ -1,20 +1,23 @@ -package team.marco.voucher_management_system.repository; +package team.marco.voucher_management_system.console_app.repository; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Repository; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; import team.marco.voucher_management_system.model.LoadedVoucher; import team.marco.voucher_management_system.model.Voucher; -import team.marco.voucher_management_system.properties.FilePathProperties; +import team.marco.voucher_management_system.repository.VoucherRepository; import team.marco.voucher_management_system.type_enum.VoucherType; @Profile("dev") @@ -57,6 +60,17 @@ public void save(Voucher voucher) { voucherMap.put(voucher.getId(), voucher); } + @Override + public int deleteById(UUID id) { + if (!voucherMap.containsKey(id)) { + return 0; + } + + voucherMap.remove(id); + + return 1; + } + @Override public List findAll() { return voucherMap.values() @@ -64,6 +78,30 @@ public List findAll() { .toList(); } + @Override + public Optional findById(UUID id) { + if (!voucherMap.containsKey(id)) { + return Optional.empty(); + } + + return Optional.of(voucherMap.get(id)); + } + + @Override + public List findByType(VoucherType voucherType) { + return voucherMap.values() + .stream() + .filter(voucher -> voucher.isSameType(voucherType)) + .toList(); + } + + @Override + public List findByCreateAt(LocalDateTime from, LocalDateTime to) { + return findAll().stream() + .filter(voucher -> voucher.isCreatedBetween(from, to)) + .toList(); + } + @Override public void destroy() { try { diff --git a/src/main/java/team/marco/voucher_management_system/console_app/repository/MemoryVoucherRepository.java b/src/main/java/team/marco/voucher_management_system/console_app/repository/MemoryVoucherRepository.java new file mode 100644 index 0000000000..6cea8af33a --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/console_app/repository/MemoryVoucherRepository.java @@ -0,0 +1,66 @@ +package team.marco.voucher_management_system.console_app.repository; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; +import team.marco.voucher_management_system.model.Voucher; +import team.marco.voucher_management_system.repository.VoucherRepository; +import team.marco.voucher_management_system.type_enum.VoucherType; + +@Profile({"debug", "test"}) +@Repository +public class MemoryVoucherRepository implements VoucherRepository { + private final Map voucherMap = new HashMap<>(); + + @Override + public void save(Voucher voucher) { + voucherMap.put(voucher.getId(), voucher); + } + + @Override + public int deleteById(UUID id) { + if (!voucherMap.containsKey(id)) { + return 0; + } + + voucherMap.remove(id); + + return 1; + } + + @Override + public List findAll() { + return voucherMap.values() + .stream() + .toList(); + } + + @Override + public Optional findById(UUID id) { + if (!voucherMap.containsKey(id)) { + return Optional.empty(); + } + + return Optional.of(voucherMap.get(id)); + } + + @Override + public List findByType(VoucherType voucherType) { + return voucherMap.values() + .stream() + .filter(voucher -> voucher.isSameType(voucherType)) + .toList(); + } + + @Override + public List findByCreateAt(LocalDateTime from, LocalDateTime to) { + return findAll().stream() + .filter(voucher -> voucher.isCreatedBetween(from, to)) + .toList(); + } +} diff --git a/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacade.java b/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacade.java index 5255d8de39..194b87c402 100644 --- a/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacade.java +++ b/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacade.java @@ -6,9 +6,9 @@ import team.marco.voucher_management_system.model.Voucher; public interface VoucherCustomerFacade { - boolean hasVoucher(String voucherId); + boolean hasVoucher(UUID voucherId); - boolean hasCustomer(String customerId); + boolean hasCustomer(UUID customerId); List getVouchers(List voucherIds); diff --git a/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImpl.java b/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImpl.java index 937e8c53b7..392bda37e1 100644 --- a/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImpl.java +++ b/src/main/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImpl.java @@ -1,7 +1,6 @@ package team.marco.voucher_management_system.facade; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import org.springframework.stereotype.Component; @@ -21,16 +20,14 @@ public VoucherCustomerFacadeImpl(VoucherRepository voucherRepository, CustomerRe } @Override - public boolean hasVoucher(String voucherId) { - return voucherRepository.findAll() - .stream() - .map(Voucher::getId) - .map(UUID::toString) - .anyMatch(id -> Objects.equals(id, voucherId)); + public boolean hasVoucher(UUID voucherId) { + Optional optionalVoucher = voucherRepository.findById(voucherId); + + return optionalVoucher.isPresent(); } @Override - public boolean hasCustomer(String customerId) { + public boolean hasCustomer(UUID customerId) { Optional optionalCustomer = customerRepository.findById(customerId); return optionalCustomer.isPresent(); diff --git a/src/main/java/team/marco/voucher_management_system/model/Customer.java b/src/main/java/team/marco/voucher_management_system/model/Customer.java index 3328e42a9f..80165717b7 100644 --- a/src/main/java/team/marco/voucher_management_system/model/Customer.java +++ b/src/main/java/team/marco/voucher_management_system/model/Customer.java @@ -82,7 +82,7 @@ private void validateName(String name) { private void validateEmail(String email) { if (!Pattern.matches(EMAIL_REGEX, email)) { - throw new IllegalArgumentException("이메일 형식이 올바르지 않습니다.."); + throw new IllegalArgumentException("이메일 형식이 올바르지 않습니다."); } } diff --git a/src/main/java/team/marco/voucher_management_system/model/FixedAmountVoucher.java b/src/main/java/team/marco/voucher_management_system/model/FixedAmountVoucher.java index f9c254acd3..00ca60f780 100644 --- a/src/main/java/team/marco/voucher_management_system/model/FixedAmountVoucher.java +++ b/src/main/java/team/marco/voucher_management_system/model/FixedAmountVoucher.java @@ -3,6 +3,7 @@ import static java.text.MessageFormat.format; import java.text.MessageFormat; +import java.time.LocalDateTime; import java.util.UUID; import team.marco.voucher_management_system.type_enum.VoucherType; @@ -18,8 +19,9 @@ public FixedAmountVoucher(int amount) { this.amount = amount; } - public FixedAmountVoucher(UUID id, int amount) { - super(id); + public FixedAmountVoucher(UUID id, int amount, LocalDateTime createAt) { + super(id, createAt); + this.amount = amount; } diff --git a/src/main/java/team/marco/voucher_management_system/model/LoadedVoucher.java b/src/main/java/team/marco/voucher_management_system/model/LoadedVoucher.java index d9cabad6f1..468e7a8103 100644 --- a/src/main/java/team/marco/voucher_management_system/model/LoadedVoucher.java +++ b/src/main/java/team/marco/voucher_management_system/model/LoadedVoucher.java @@ -1,5 +1,6 @@ package team.marco.voucher_management_system.model; +import java.time.LocalDateTime; import java.util.UUID; import team.marco.voucher_management_system.type_enum.VoucherType; @@ -11,8 +12,9 @@ private LoadedVoucher() { // for object mapper deserializing } - public LoadedVoucher(UUID id, VoucherType type, int data) { - super(id); + public LoadedVoucher(UUID id, VoucherType type, int data, LocalDateTime createAt) { + super(id, createAt); + this.type = type; this.data = data; } diff --git a/src/main/java/team/marco/voucher_management_system/model/PercentDiscountVoucher.java b/src/main/java/team/marco/voucher_management_system/model/PercentDiscountVoucher.java index 3e19a67c02..bcb7d066c7 100644 --- a/src/main/java/team/marco/voucher_management_system/model/PercentDiscountVoucher.java +++ b/src/main/java/team/marco/voucher_management_system/model/PercentDiscountVoucher.java @@ -3,6 +3,7 @@ import static java.text.MessageFormat.format; import java.text.MessageFormat; +import java.time.LocalDateTime; import java.util.UUID; import team.marco.voucher_management_system.type_enum.VoucherType; @@ -18,8 +19,9 @@ public PercentDiscountVoucher(int percent) { this.percent = percent; } - public PercentDiscountVoucher(UUID id, int percent) { - super(id); + public PercentDiscountVoucher(UUID id, int percent, LocalDateTime createAt) { + super(id, createAt); + this.percent = percent; } diff --git a/src/main/java/team/marco/voucher_management_system/model/Voucher.java b/src/main/java/team/marco/voucher_management_system/model/Voucher.java index 6f8b9a3716..a005774364 100644 --- a/src/main/java/team/marco/voucher_management_system/model/Voucher.java +++ b/src/main/java/team/marco/voucher_management_system/model/Voucher.java @@ -1,24 +1,50 @@ package team.marco.voucher_management_system.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import java.time.LocalDateTime; import java.util.UUID; import team.marco.voucher_management_system.type_enum.VoucherType; public abstract class Voucher { protected final UUID id; + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + protected final LocalDateTime createAt; protected Voucher() { this.id = UUID.randomUUID(); + this.createAt = LocalDateTime.now(); } - protected Voucher(UUID id) { + protected Voucher(UUID id, LocalDateTime createAt) { this.id = id; + this.createAt = createAt; + } + + public boolean isSameId(UUID id) { + return this.id.equals(id); + } + + public boolean isSameType(VoucherType voucherType) { + return getType() == voucherType; + } + + public boolean isCreatedBetween(LocalDateTime from, LocalDateTime to) { + return !(createAt.isBefore(from) | createAt.isAfter(to)); } public final UUID getId() { return id; } + public LocalDateTime getCreateAt() { + return createAt; + } + public abstract VoucherType getType(); public abstract int getData(); diff --git a/src/main/java/team/marco/voucher_management_system/repository/CustomerRepository.java b/src/main/java/team/marco/voucher_management_system/repository/CustomerRepository.java index 5de5403f15..25a8824f79 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/CustomerRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/CustomerRepository.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Optional; +import java.util.UUID; import team.marco.voucher_management_system.model.Customer; public interface CustomerRepository { @@ -11,9 +12,11 @@ public interface CustomerRepository { int update(Customer customer); - Optional findById(String id); + Optional findById(UUID id); List findByName(String name); List findByEmail(String email); + + int deleteById(UUID id); } diff --git a/src/main/java/team/marco/voucher_management_system/repository/JdbcCustomerRepository.java b/src/main/java/team/marco/voucher_management_system/repository/JdbcCustomerRepository.java index 8b6cf7b023..0669288dc1 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/JdbcCustomerRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/JdbcCustomerRepository.java @@ -12,6 +12,7 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @@ -60,27 +61,29 @@ public List findAll() { @Override public int update(Customer customer) { return jdbcTemplate.update( - "UPDATE customer SET name = :name, email = :email, last_login_at = :lastLoginAt" + "UPDATE customer SET name = :name, last_login_at = :lastLoginAt" + " WHERE id = UUID_TO_BIN(:id)", customerToMap(customer)); } @Override - public Optional findById(String id) { - Customer customer = jdbcTemplate.queryForObject( - "SELECT * FROM customer" - + " WHERE id = UUID_TO_BIN(:id)", - Collections.singletonMap("id", id.getBytes()), - customerRowMapper); - - return Optional.ofNullable(customer); + public Optional findById(UUID id) { + try { + Customer customer = jdbcTemplate.queryForObject( + "SELECT * FROM customer WHERE id = UUID_TO_BIN(:id)", + Collections.singletonMap("id", id.toString().getBytes()), + customerRowMapper); + + return Optional.ofNullable(customer); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } } @Override public List findByName(String name) { return jdbcTemplate.query( - "SELECT * FROM customer" - + " WHERE name LIKE :name", + "SELECT * FROM customer WHERE name LIKE :name", Collections.singletonMap("name", withWildCards(name)), customerRowMapper); } @@ -88,22 +91,19 @@ public List findByName(String name) { @Override public List findByEmail(String email) { return jdbcTemplate.query( - "SELECT * FROM customer" - + " WHERE email LIKE :email", + "SELECT * FROM customer WHERE email LIKE :email", Collections.singletonMap("email", withWildCards(email)), customerRowMapper); } + @Override + public int deleteById(UUID id) { + return jdbcTemplate.update("DELETE FROM customer WHERE id = UUID_TO_BIN(:id)", + Collections.singletonMap("id", id.toString().getBytes())); + } + private Map customerToMap(Customer customer) { - Map customerFields = new HashMap<>() { - { - put("id", customer.getId().toString().getBytes()); - put("name", customer.getName()); - put("email", customer.getEmail()); - put("lastLoginAt", null); - put("createdAt", Timestamp.valueOf(customer.getCreatedAt())); - } - }; + Map customerFields = getCustomerFields(customer); if (!Objects.isNull(customer.getLastLoginAt())) { customerFields.put("lastLoginAt", Timestamp.valueOf(customer.getLastLoginAt())); @@ -112,6 +112,18 @@ private Map customerToMap(Customer customer) { return customerFields; } + private Map getCustomerFields(Customer customer) { + Map customerFields = new HashMap<>(); + + customerFields.put("id", customer.getId().toString().getBytes()); + customerFields.put("name", customer.getName()); + customerFields.put("email", customer.getEmail()); + customerFields.put("lastLoginAt", null); + customerFields.put("createdAt", Timestamp.valueOf(customer.getCreatedAt())); + + return customerFields; + } + private String withWildCards(String word) { return MessageFormat.format("%{0}%", word); } diff --git a/src/main/java/team/marco/voucher_management_system/repository/JdbcVoucherRepository.java b/src/main/java/team/marco/voucher_management_system/repository/JdbcVoucherRepository.java index 0b8d663070..f546b0fb9d 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/JdbcVoucherRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/JdbcVoucherRepository.java @@ -2,11 +2,16 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.UUID; import org.springframework.context.annotation.Profile; -import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @@ -34,8 +39,9 @@ private static Voucher mapToVoucher(ResultSet resultSet, int ignored) throws SQL UUID id = UUIDConverter.convert(idBytes); VoucherType type = VoucherType.valueOf(typeString); int data = Integer.parseInt(dataString); + LocalDateTime createAt = resultSet.getTimestamp("created_at").toLocalDateTime(); - LoadedVoucher loadedVoucher = new LoadedVoucher(id, type, data); + LoadedVoucher loadedVoucher = new LoadedVoucher(id, type, data, createAt); return VoucherType.convertVoucher(loadedVoucher); } @@ -43,23 +49,73 @@ private static Voucher mapToVoucher(ResultSet resultSet, int ignored) throws SQL @Override public void save(Voucher voucher) { int updateCount = jdbcTemplate.update( - "INSERT INTO voucher(id, type, data) VALUES (UUID_TO_BIN(:id), :type, :data)", + "INSERT INTO voucher(id, type, data, created_at)" + + " VALUES (UUID_TO_BIN(:id), :type, :data, :created_at)", voucherToMap(voucher)); - if (updateCount != 1) { - throw new DataAccessResourceFailureException("Insert query not committed"); + if (updateCount == 0) { + throw new NoSuchElementException("Insert fail voucher=%s".formatted(voucher)); } } + @Override + public int deleteById(UUID id) { + return jdbcTemplate.update( + "DELETE FROM voucher WHERE id = UUID_TO_BIN(:id)", + Collections.singletonMap("id", id.toString().getBytes())); + } + @Override public List findAll() { return jdbcTemplate.query("SELECT * FROM voucher", voucherRowMapper); } + @Override + public Optional findById(UUID id) { + try { + Voucher voucher = jdbcTemplate.queryForObject( + "SELECT * FROM voucher WHERE id = UUID_TO_BIN(:id)", + Collections.singletonMap("id", id.toString().getBytes()), + voucherRowMapper); + + return Optional.ofNullable(voucher); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + @Override + public List findByType(VoucherType voucherType) { + return jdbcTemplate.query( + "SELECT * FROM voucher WHERE type = :type", + Collections.singletonMap("type", voucherType.toString()), + voucherRowMapper); + } + + @Override + public List findByCreateAt(LocalDateTime from, LocalDateTime to) { + return jdbcTemplate.query( + "SELECT * FROM voucher WHERE created_at BETWEEN :from AND :to", + mapToTimestamp(from, to), + voucherRowMapper); + } + private Map voucherToMap(Voucher voucher) { + Timestamp createAt = Timestamp.valueOf(voucher.getCreateAt()); + return Map.ofEntries( Map.entry("id", voucher.getId().toString().getBytes()), Map.entry("type", voucher.getType().toString()), - Map.entry("data", voucher.getData())); + Map.entry("data", voucher.getData()), + Map.entry("created_at", createAt)); + } + + private Map mapToTimestamp(LocalDateTime from, LocalDateTime to) { + Timestamp createdFrom = Timestamp.valueOf(from); + Timestamp createdTo = Timestamp.valueOf(to); + + return Map.ofEntries( + Map.entry("from", createdFrom), + Map.entry("to", createdTo)); } } diff --git a/src/main/java/team/marco/voucher_management_system/repository/JdbcWalletRepository.java b/src/main/java/team/marco/voucher_management_system/repository/JdbcWalletRepository.java index cdd0b67e9c..3b6f8def67 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/JdbcWalletRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/JdbcWalletRepository.java @@ -1,7 +1,6 @@ package team.marco.voucher_management_system.repository; import java.io.ByteArrayInputStream; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; @@ -18,7 +17,7 @@ public JdbcWalletRepository(NamedParameterJdbcTemplate jdbcTemplate) { } @Override - public int link(String customerId, String voucherId) { + public int link(UUID customerId, UUID voucherId) { return jdbcTemplate.update( "INSERT INTO wallet(customer_id, voucher_id)" + " VALUES (UUID_TO_BIN(:customerId), UUID_TO_BIN(:voucherId))", @@ -26,7 +25,7 @@ public int link(String customerId, String voucherId) { } @Override - public int unlink(String customerId, String voucherId) { + public int unlink(UUID customerId, UUID voucherId) { return jdbcTemplate.update( "DELETE FROM wallet" + " WHERE (customer_id, voucher_id) = (UUID_TO_BIN(:customerId), UUID_TO_BIN(:voucherId))", @@ -34,30 +33,34 @@ public int unlink(String customerId, String voucherId) { } @Override - public List getVoucherIds(String customerId) { - return jdbcTemplate.queryForList( - "SELECT voucher_id FROM wallet" - + " WHERE customer_id = UUID_TO_BIN(:customerId)", - Collections.singletonMap("customerId", customerId), ByteArrayInputStream.class) - .stream() - .map(byteArray -> UUIDConverter.convert(byteArray.readAllBytes())) - .toList(); + public List getVoucherIds(UUID customerId) { + List voucherIds = jdbcTemplate.queryForList( + "SELECT voucher_id FROM wallet" + + " WHERE customer_id = UUID_TO_BIN(:customerId)", + walletToMap(customerId, customerId), + ByteArrayInputStream.class); + + return mapToUUID(voucherIds); } @Override - public List getCustomerIds(String voucherId) { - return jdbcTemplate.queryForList( - "SELECT customer_id FROM wallet" - + " WHERE voucher_id = UUID_TO_BIN(:voucherId)", - Collections.singletonMap("voucherId", voucherId), ByteArrayInputStream.class) - .stream() - .map(byteArray -> UUIDConverter.convert(byteArray.readAllBytes())) - .toList(); + public List getCustomerIds(UUID voucherId) { + return mapToUUID(jdbcTemplate.queryForList( + "SELECT customer_id FROM wallet" + + " WHERE voucher_id = UUID_TO_BIN(:voucherId)", + walletToMap(voucherId, voucherId), + ByteArrayInputStream.class)); } - private Map walletToMap(String customerId, String voucherId) { + private Map walletToMap(UUID customerId, UUID voucherId) { return Map.ofEntries( - Map.entry("customerId", customerId), - Map.entry("voucherId", voucherId)); + Map.entry("customerId", customerId.toString().getBytes()), + Map.entry("voucherId", voucherId.toString().getBytes())); + } + + private List mapToUUID(List ids) { + return ids.stream() + .map(byteArray -> UUIDConverter.convert(byteArray.readAllBytes())) + .toList(); } } diff --git a/src/main/java/team/marco/voucher_management_system/repository/MemoryVoucherRepository.java b/src/main/java/team/marco/voucher_management_system/repository/MemoryVoucherRepository.java deleted file mode 100644 index 5e8021ab39..0000000000 --- a/src/main/java/team/marco/voucher_management_system/repository/MemoryVoucherRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package team.marco.voucher_management_system.repository; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; -import team.marco.voucher_management_system.model.Voucher; - -@Profile({"debug", "test"}) -@Repository -public class MemoryVoucherRepository implements VoucherRepository { - private final Map voucherMap = new HashMap<>(); - - @Override - public void save(Voucher voucher) { - voucherMap.put(voucher.getId(), voucher); - } - - @Override - public List findAll() { - return voucherMap.values() - .stream() - .toList(); - } -} diff --git a/src/main/java/team/marco/voucher_management_system/repository/VoucherRepository.java b/src/main/java/team/marco/voucher_management_system/repository/VoucherRepository.java index afd4feeb0c..e0efbfcdc9 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/VoucherRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/VoucherRepository.java @@ -1,10 +1,22 @@ package team.marco.voucher_management_system.repository; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import java.util.UUID; import team.marco.voucher_management_system.model.Voucher; +import team.marco.voucher_management_system.type_enum.VoucherType; public interface VoucherRepository { void save(Voucher voucher); + int deleteById(UUID id); + List findAll(); + + Optional findById(UUID id); + + List findByType(VoucherType voucherType); + + List findByCreateAt(LocalDateTime from, LocalDateTime to); } diff --git a/src/main/java/team/marco/voucher_management_system/repository/WalletRepository.java b/src/main/java/team/marco/voucher_management_system/repository/WalletRepository.java index a0a2847e69..2bb57dfbd3 100644 --- a/src/main/java/team/marco/voucher_management_system/repository/WalletRepository.java +++ b/src/main/java/team/marco/voucher_management_system/repository/WalletRepository.java @@ -4,11 +4,11 @@ import java.util.UUID; public interface WalletRepository { - int link(String customerId, String voucherId); + int link(UUID customerId, UUID voucherId); - int unlink(String customerId, String voucherId); + int unlink(UUID customerId, UUID voucherId); - List getVoucherIds(String customerId); + List getVoucherIds(UUID customerId); - List getCustomerIds(String voucherId); + List getCustomerIds(UUID voucherId); } diff --git a/src/main/java/team/marco/voucher_management_system/service/BlacklistService.java b/src/main/java/team/marco/voucher_management_system/service/BlacklistService.java index 6609b4069a..3aec6d7319 100644 --- a/src/main/java/team/marco/voucher_management_system/service/BlacklistService.java +++ b/src/main/java/team/marco/voucher_management_system/service/BlacklistService.java @@ -2,8 +2,8 @@ import java.util.List; import org.springframework.stereotype.Service; +import team.marco.voucher_management_system.console_app.repository.BlacklistRepository; import team.marco.voucher_management_system.model.BlacklistUser; -import team.marco.voucher_management_system.repository.BlacklistRepository; @Service public class BlacklistService { diff --git a/src/main/java/team/marco/voucher_management_system/service/CustomerService.java b/src/main/java/team/marco/voucher_management_system/service/CustomerService.java index c1e48984d6..38a0a5f312 100644 --- a/src/main/java/team/marco/voucher_management_system/service/CustomerService.java +++ b/src/main/java/team/marco/voucher_management_system/service/CustomerService.java @@ -2,7 +2,8 @@ import java.util.List; import java.util.Optional; -import org.springframework.dao.DataAccessResourceFailureException; +import java.util.UUID; +import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException; import org.springframework.stereotype.Service; import team.marco.voucher_management_system.model.Customer; import team.marco.voucher_management_system.repository.JdbcCustomerRepository; @@ -21,7 +22,7 @@ public void create(String name, String email) { int insert = customerRepository.create(customer); if (insert != 1) { - throw new DataAccessResourceFailureException("고객을 추가하는 과정에서 오류가 발생했습니다."); + throw new DataSourceLookupFailureException("고객을 추가하는 과정에서 오류가 발생했습니다."); } } @@ -29,7 +30,7 @@ public List getCustomers() { return customerRepository.findAll(); } - public void update(String id, String name) { + public void update(UUID id, String name) { Optional customerOptional = customerRepository.findById(id); Customer customer = customerOptional.orElseThrow(); @@ -39,16 +40,24 @@ public void update(String id, String name) { int update = customerRepository.update(customer); if (update != 1) { - throw new DataAccessResourceFailureException("고객을 추가하는 과정에서 오류가 발생했습니다."); + throw new DataSourceLookupFailureException("이름을 변경하는 과정에서 오류가 발생했습니다."); } } - public Customer findById(String id) { + public Customer findById(UUID id) { Optional customerOptional = customerRepository.findById(id); return customerOptional.orElseThrow(); } + public void deleteById(UUID id) { + int delete = customerRepository.deleteById(id); + + if (delete != 1) { + throw new DataSourceLookupFailureException("고객을 삭제하는 과정에서 오류가 발생했습니다."); + } + } + public List findByName(String name) { return customerRepository.findByName(name); } diff --git a/src/main/java/team/marco/voucher_management_system/service/VoucherService.java b/src/main/java/team/marco/voucher_management_system/service/VoucherService.java index 1116b928d2..fd5396a701 100644 --- a/src/main/java/team/marco/voucher_management_system/service/VoucherService.java +++ b/src/main/java/team/marco/voucher_management_system/service/VoucherService.java @@ -1,11 +1,17 @@ package team.marco.voucher_management_system.service; +import java.time.LocalDateTime; import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; import org.springframework.stereotype.Service; import team.marco.voucher_management_system.model.FixedAmountVoucher; import team.marco.voucher_management_system.model.PercentDiscountVoucher; import team.marco.voucher_management_system.model.Voucher; import team.marco.voucher_management_system.repository.VoucherRepository; +import team.marco.voucher_management_system.type_enum.VoucherType; +import team.marco.voucher_management_system.web_app.dto.CreateVoucherRequest; @Service public class VoucherService { @@ -15,17 +21,50 @@ public VoucherService(VoucherRepository voucherRepository) { this.voucherRepository = voucherRepository; } - public void createFixedAmountVoucher(int amount) { + public Voucher createFixedAmountVoucher(int amount) { Voucher voucher = new FixedAmountVoucher(amount); + voucherRepository.save(voucher); + + return voucher; } - public void createPercentDiscountVoucher(int percent) { + public Voucher createPercentDiscountVoucher(int percent) { Voucher voucher = new PercentDiscountVoucher(percent); + voucherRepository.save(voucher); + + return voucher; } - public List getVouchersInfo() { + public List getVouchers() { return voucherRepository.findAll(); } + + public Optional findById(UUID id) { + return voucherRepository.findById(id); + } + + public List findByCreateAt(LocalDateTime from, LocalDateTime to) { + return voucherRepository.findByCreateAt(from, to); + } + + public Voucher create(CreateVoucherRequest createVoucherRequest) { + return switch (createVoucherRequest.type()) { + case FIXED -> createFixedAmountVoucher(createVoucherRequest.data()); + case PERCENT -> createPercentDiscountVoucher(createVoucherRequest.data()); + }; + } + + public void deleteById(UUID id) { + int deleteCount = voucherRepository.deleteById(id); + + if (deleteCount == 0) { + throw new NoSuchElementException(); + } + } + + public List findByType(VoucherType voucherType) { + return voucherRepository.findByType(voucherType); + } } diff --git a/src/main/java/team/marco/voucher_management_system/service/WalletService.java b/src/main/java/team/marco/voucher_management_system/service/WalletService.java index 057f27cc6f..3a17756f93 100644 --- a/src/main/java/team/marco/voucher_management_system/service/WalletService.java +++ b/src/main/java/team/marco/voucher_management_system/service/WalletService.java @@ -19,33 +19,33 @@ public WalletService(WalletRepository walletRepository, VoucherCustomerFacade vo this.voucherCustomerFacade = voucherCustomerFacade; } - public void checkVoucherId(String voucherId) { + public void checkVoucherId(UUID voucherId) { if (!voucherCustomerFacade.hasVoucher(voucherId)) { throw new NoSuchElementException("ID가 일치하는 쿠폰이 존재하지 않습니다."); } } - public void checkCustomerId(String customerId) { + public void checkCustomerId(UUID customerId) { if (!voucherCustomerFacade.hasCustomer(customerId)) { throw new NoSuchElementException("ID가 일치하는 고객이 존재하지 않습니다."); } } - public int supplyVoucher(String customerId, String voucherId) { + public int supplyVoucher(UUID customerId, UUID voucherId) { return walletRepository.link(customerId, voucherId); } - public List findVouchersByCustomerId(String customerId) { + public List findReceivedVouchers(UUID customerId) { List voucherIds = walletRepository.getVoucherIds(customerId); return voucherCustomerFacade.getVouchers(voucherIds); } - public int returnVoucher(String customerId, String voucherId) { + public int returnVoucher(UUID customerId, UUID voucherId) { return walletRepository.unlink(customerId, voucherId); } - public List findCustomersByVoucherId(String voucherId) { + public List findHavingCustomers(UUID voucherId) { List customerIds = walletRepository.getCustomerIds(voucherId); return voucherCustomerFacade.getCustomers(customerIds); diff --git a/src/main/java/team/marco/voucher_management_system/type_enum/ApplicationType.java b/src/main/java/team/marco/voucher_management_system/type_enum/ApplicationType.java new file mode 100644 index 0000000000..aee5610088 --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/type_enum/ApplicationType.java @@ -0,0 +1,28 @@ +package team.marco.voucher_management_system.type_enum; + +import java.util.function.Consumer; +import team.marco.voucher_management_system.console_app.ConsoleApplication; +import team.marco.voucher_management_system.web_app.WebApplication; + +public enum ApplicationType { + CONSOLE(ConsoleApplication::main), + WEB(WebApplication::main); + + private final Consumer mainMethod; + + ApplicationType(Consumer consumer) { + mainMethod = consumer; + } + + public static Consumer getMainMethod(int index) { + validate(index); + + return values()[index].mainMethod; + } + + private static void validate(int index) { + if (index < 0 || index >= values().length) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/team/marco/voucher_management_system/type_enum/VoucherType.java b/src/main/java/team/marco/voucher_management_system/type_enum/VoucherType.java index a81ecdc303..6a6ace36fa 100644 --- a/src/main/java/team/marco/voucher_management_system/type_enum/VoucherType.java +++ b/src/main/java/team/marco/voucher_management_system/type_enum/VoucherType.java @@ -10,8 +10,14 @@ public enum VoucherType { public static Voucher convertVoucher(LoadedVoucher loadedVoucher) { return switch (loadedVoucher.getType()) { - case FIXED -> new FixedAmountVoucher(loadedVoucher.getId(), loadedVoucher.getData()); - case PERCENT -> new PercentDiscountVoucher(loadedVoucher.getId(), loadedVoucher.getData()); + case FIXED -> new FixedAmountVoucher( + loadedVoucher.getId(), + loadedVoucher.getData(), + loadedVoucher.getCreateAt()); + case PERCENT -> new PercentDiscountVoucher( + loadedVoucher.getId(), + loadedVoucher.getData(), + loadedVoucher.getCreateAt()); }; } } diff --git a/src/main/java/team/marco/voucher_management_system/util/Console.java b/src/main/java/team/marco/voucher_management_system/util/Console.java index 339e6a73c8..7a11a7b852 100644 --- a/src/main/java/team/marco/voucher_management_system/util/Console.java +++ b/src/main/java/team/marco/voucher_management_system/util/Console.java @@ -7,7 +7,7 @@ import java.util.Objects; public final class Console { - private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + private static BufferedReader reader; private Console() { // Don't let anyone instantiate this class. @@ -19,7 +19,7 @@ public static String readString() { System.out.println(); if (Objects.isNull(input)) { - throw new RuntimeException("입력 과정에서 오류가 발생했습니다."); + throw new UncheckedIOException(new IOException("입력 과정에서 오류가 발생했습니다.")); } return input; @@ -31,7 +31,7 @@ public static int readInt() { private static String readLine() { try { - return reader.readLine(); + return getInstance().readLine(); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -40,4 +40,27 @@ private static String readLine() { public static void print(Object object) { System.out.println(object + System.lineSeparator()); // thanks to SH, IJ } + + public static void close() { + if (reader != null) { + closeReader(); + + reader = null; + } + } + + private static BufferedReader getInstance() { + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(System.in)); + } + return reader; + } + + private static void closeReader() { + try { + reader.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/src/main/java/team/marco/voucher_management_system/util/UUIDConverter.java b/src/main/java/team/marco/voucher_management_system/util/UUIDConverter.java index 17dc7a938b..b63925a2a7 100644 --- a/src/main/java/team/marco/voucher_management_system/util/UUIDConverter.java +++ b/src/main/java/team/marco/voucher_management_system/util/UUIDConverter.java @@ -1,5 +1,6 @@ package team.marco.voucher_management_system.util; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.UUID; @@ -10,6 +11,14 @@ private UUIDConverter() { public static UUID convert(byte[] bytes) { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + try { + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + } catch (BufferUnderflowException e) { + throw new IllegalArgumentException("UUID는 16Byte여야 합니다."); + } + } + + public static UUID convert(String string) { + return UUID.fromString(string); } } diff --git a/src/main/java/team/marco/voucher_management_system/web_app/WebApplication.java b/src/main/java/team/marco/voucher_management_system/web_app/WebApplication.java new file mode 100644 index 0000000000..aa5fa557e8 --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/WebApplication.java @@ -0,0 +1,14 @@ +package team.marco.voucher_management_system.web_app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; +import team.marco.voucher_management_system.VoucherManagementSystemApplication; + +@Import(VoucherManagementSystemApplication.class) +@SpringBootApplication +public class WebApplication { + public static void main(String[] args) { + SpringApplication.run(WebApplication.class, args); + } +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/advice/ViewAdvice.java b/src/main/java/team/marco/voucher_management_system/web_app/advice/ViewAdvice.java new file mode 100644 index 0000000000..0b49a18ddc --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/advice/ViewAdvice.java @@ -0,0 +1,14 @@ +package team.marco.voucher_management_system.web_app.advice; + +import java.util.NoSuchElementException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import team.marco.voucher_management_system.web_app.controller.CustomerController; + +@ControllerAdvice(basePackageClasses = CustomerController.class) +public class ViewAdvice { + @ExceptionHandler(NoSuchElementException.class) + public String noSuchElementHandler() { + return "error/404"; + } +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/controller/CustomerController.java b/src/main/java/team/marco/voucher_management_system/web_app/controller/CustomerController.java new file mode 100644 index 0000000000..88ca40e75a --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/controller/CustomerController.java @@ -0,0 +1,70 @@ +package team.marco.voucher_management_system.web_app.controller; + +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import team.marco.voucher_management_system.model.Customer; +import team.marco.voucher_management_system.service.CustomerService; +import team.marco.voucher_management_system.web_app.dto.CreateCustomerRequest; + +@Controller +@RequestMapping(path = "/customers") +public class CustomerController { + private final CustomerService customerService; + + public CustomerController(CustomerService customerService) { + this.customerService = customerService; + } + + @GetMapping + public String viewCustomers(Model model) { + List customers = customerService.getCustomers(); + + model.addAttribute("customers", customers); + + return "views/customers"; + } + + @GetMapping("/new") + public String viewNewCustomer() { + return "views/new-customer"; + } + + @PostMapping("/new") + public String createCustomer(@Valid CreateCustomerRequest createCustomerRequest) { + customerService.create(createCustomerRequest.name(), createCustomerRequest.email()); + + return "redirect:/customers"; + } + + @GetMapping("/{id}") + public String viewCustomerById(@PathVariable UUID id, Model model) { + Customer customer = customerService.findById(id); + + model.addAttribute("customer", customer); + + return "views/customer-detail"; + } + + @PutMapping("/{id}") + public String changeCustomerName(@PathVariable UUID id, String name) { + customerService.update(id, name); + + return "redirect:/customers"; + } + + @DeleteMapping("/{id}") + public String deleteCustomerById(@PathVariable UUID id) { + customerService.deleteById(id); + + return "redirect:/customers"; + } +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherController.java b/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherController.java new file mode 100644 index 0000000000..d9021d94b4 --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherController.java @@ -0,0 +1,81 @@ +package team.marco.voucher_management_system.web_app.controller; + +import jakarta.validation.Valid; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import team.marco.voucher_management_system.model.Voucher; +import team.marco.voucher_management_system.service.VoucherService; +import team.marco.voucher_management_system.type_enum.VoucherType; +import team.marco.voucher_management_system.web_app.dto.CreateVoucherRequest; + +@RestController +@RequestMapping( + value = "/api/vouchers", + produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) +public class VoucherController { + private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; + private static final int CREATED = 201; + + private final VoucherService voucherService; + + public VoucherController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping + public List findAll() { + return voucherService.getVouchers(); + } + + @GetMapping(params = {"from", "to"}) + public List findByCreateAt(@RequestParam + @DateTimeFormat(pattern = DATE_PATTERN) + LocalDateTime from, + @RequestParam + @DateTimeFormat(pattern = DATE_PATTERN) + LocalDateTime to) { + return voucherService.findByCreateAt(from, to); + } + + @GetMapping(params = "type") + public List findByType(@RequestParam VoucherType type) { + return voucherService.findByType(type); + } + + @PostMapping + public ResponseEntity create(@Valid CreateVoucherRequest createVoucherRequest) { + Voucher voucher = voucherService.create(createVoucherRequest); + + return ResponseEntity.status(CREATED) + .body(voucher); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + Optional optionalVoucher = voucherService.findById(id); + + return optionalVoucher.map(ResponseEntity::ok) + .orElse(ResponseEntity.noContent() + .build()); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + voucherService.deleteById(id); + + return ResponseEntity.ok() + .build(); + } +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherErrorController.java b/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherErrorController.java new file mode 100644 index 0000000000..2f6fa53bff --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/controller/VoucherErrorController.java @@ -0,0 +1,46 @@ +package team.marco.voucher_management_system.web_app.controller; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class VoucherErrorController implements ErrorController { + @GetMapping("/error") + public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) { + Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + int statusCode = Integer.parseInt(status.toString()); + + response.setStatus(statusCode); + + List header = getHeader(request); + + model.addAttribute("status", status); + model.addAttribute("message", HttpStatus.valueOf(statusCode).getReasonPhrase()); + model.addAttribute("header", header); + + return "error/error"; + } + + private List getHeader(HttpServletRequest request) { + List headerItems = new ArrayList<>(); + Enumeration headerNames = request.getHeaderNames(); + + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + + headerItems.add(MessageFormat.format("{0} : {1}", headerName, request.getHeader(headerName))); + } + + return headerItems; + } +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateCustomerRequest.java b/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateCustomerRequest.java new file mode 100644 index 0000000000..3e16e17a09 --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateCustomerRequest.java @@ -0,0 +1,11 @@ +package team.marco.voucher_management_system.web_app.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record CreateCustomerRequest( + @NotBlank + String name, + @Email + String email) { +} diff --git a/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateVoucherRequest.java b/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateVoucherRequest.java new file mode 100644 index 0000000000..afea2813ff --- /dev/null +++ b/src/main/java/team/marco/voucher_management_system/web_app/dto/CreateVoucherRequest.java @@ -0,0 +1,12 @@ +package team.marco.voucher_management_system.web_app.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import team.marco.voucher_management_system.type_enum.VoucherType; + +public record CreateVoucherRequest( + @NotNull + VoucherType type, + @Positive + int data) { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 12e3e24d8d..16526fd259 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,19 @@ spring: url: jdbc:mysql://localhost:3306/voucher_management_system username: root password: + thymeleaf: + prefix: classpath:templates/ + suffix: .html + view-names: + - views/* + - error/* + mvc: + hidden-method: + filter: + enabled: true + +server: + port: 8080 file: path: diff --git a/src/main/resources/sample_data.json b/src/main/resources/sample_data.json index ced95cadef..77292e0a43 100644 --- a/src/main/resources/sample_data.json +++ b/src/main/resources/sample_data.json @@ -1,12 +1,30 @@ [ { - "id": "2f1bdafa-c02f-430f-823a-da6704deac70", - "type": "PERCENT", - "data": 100 + "id": "dd777f5b-7113-428c-af9a-e37465006c0e", + "createAt": [ + 2023, + 11, + 5, + 15, + 40, + 31, + 8486000 + ], + "type": "FIXED", + "data": 1000 }, { - "id": "a5124642-c6f4-4f22-9e2b-9d25f98499d4", + "id": "7273b0f3-6db5-444d-944c-8c5b8523fd4b", + "createAt": [ + 2023, + 11, + 5, + 15, + 58, + 56, + 259204000 + ], "type": "FIXED", - "data": 10000 + "data": 20 } ] diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 63e5f37948..d5cd3c8f14 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,8 +1,9 @@ CREATE TABLE voucher ( - id BINARY(16) PRIMARY KEY, - type VARCHAR(16) NOT NULL, - data INT NOT NULL + id BINARY(16) PRIMARY KEY, + type VARCHAR(16) NOT NULL, + data INT NOT NULL, + created_at datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP (6) ); CREATE TABLE customer diff --git a/src/main/resources/templates/error/404.html b/src/main/resources/templates/error/404.html new file mode 100644 index 0000000000..bc3805e644 --- /dev/null +++ b/src/main/resources/templates/error/404.html @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 404 Page not found + + + + diff --git a/src/main/resources/templates/error/error.html b/src/main/resources/templates/error/error.html new file mode 100644 index 0000000000..411a0fb5a8 --- /dev/null +++ b/src/main/resources/templates/error/error.html @@ -0,0 +1,17 @@ + + + + + + + +

error page

+
+

status :

+

message :

+
+
+

header

+

+ + diff --git a/src/main/resources/templates/views/customer-detail.html b/src/main/resources/templates/views/customer-detail.html new file mode 100644 index 0000000000..75c1679969 --- /dev/null +++ b/src/main/resources/templates/views/customer-detail.html @@ -0,0 +1,32 @@ + + + + + + + + Customer Detail + + +

Customer Detail

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + diff --git a/src/main/resources/templates/views/customers.html b/src/main/resources/templates/views/customers.html new file mode 100644 index 0000000000..1c2255176d --- /dev/null +++ b/src/main/resources/templates/views/customers.html @@ -0,0 +1,42 @@ + + + + + + + Customers + + + +

Customers

+ + + + + + + + + + + + + + + + + + + + +
IDNameEmailCreatedAtLastLoginAt
+ +
+
+
+ + diff --git a/src/main/resources/templates/views/new-customer.html b/src/main/resources/templates/views/new-customer.html new file mode 100644 index 0000000000..2c4bc8f1d2 --- /dev/null +++ b/src/main/resources/templates/views/new-customer.html @@ -0,0 +1,25 @@ + + + + + + + + New Customer + + +

New Customer

+
+
+ + +
+
+ + +
+ +
+ diff --git a/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTest.java b/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTest.java new file mode 100644 index 0000000000..7213ca04c1 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTest.java @@ -0,0 +1,121 @@ +package team.marco.voucher_management_system; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import team.marco.voucher_management_system.common.StdIOTest; +import team.marco.voucher_management_system.util.Console; + +class VoucherManagementSystemApplicationTest extends StdIOTest { + private static final String BEAN_OVERRIDING = "--spring.main.allow-bean-definition-overriding=true"; + private static final String ACTIVE_PROD = "--spring.profiles.active=prod"; + private static final String SELECT_APPLICATION_MESSAGE = """ + === 실행할 애플리케이션을 선택해주세요. === + 0. Console Application + 1. Web Application"""; + private static final String UNSUPPORTED_APPLICATION_MESSAGE = "사용할 수 없는 애플리케이션 입니다."; + + private String[] args; + + @BeforeEach + void setup() { + args = new String[0]; + + /* + * To avoid exception + * BeanDefinitionOverrideException : Cause By DataSource from TestJdbcConfiguration.class + * NoUniqueBeanDefinitionException : Cause By memoryVoucherRepository, jdbcVoucherRepository + */ + addProgramArgument(BEAN_OVERRIDING, ACTIVE_PROD); + Console.close(); + } + + @Test + @DisplayName("애플리케이션 선택 화면을 출력한다.") + void testSelectApplication() { + // given + setStdin("0"); + + // when + VoucherManagementSystemApplication.main(args); + + // then + String stdout = getStdout(); + + assertThat(stdout).startsWith(SELECT_APPLICATION_MESSAGE); + } + + @Test + @DisplayName("잘못된 입력일 경우 메시지를 출력한다.") + void testReselectApplication() { + // given + setStdin("\n", "IT CANT BE COMMAND INPUT", "0", "exit"); + + // when + VoucherManagementSystemApplication.main(args); + + // then + String stdout = getStdout(); + + assertThat(stdout).contains(UNSUPPORTED_APPLICATION_MESSAGE); + } + + @Test + @DisplayName("콘솔 애플리케이션을 실행할 수 있다.") + void testRunConsoleApplication() { + // given + setStdin("\n", "0", "exit"); + + // when + VoucherManagementSystemApplication.main(args); + + // then + String stdout = getStdout(); + + assertThat(stdout).contains(""" + === 쿠폰 관리 프로그램 === + exit: 프로그램 종료 + create: 쿠폰 생성 + list: 쿠폰 목록 조회 + blacklist: 블랙 리스트 유저 조회 + customer: 고객 관리 메뉴 + wallet: 지갑 관리 메뉴"""); + } + + @Test + @DisplayName("콘솔 애플리케이션을 종료할 수 있다.") + void testCloseConsoleApplication() { + // given + setStdin("\n", "0", "exit"); + + // when + VoucherManagementSystemApplication.main(args); + + // then + String stdout = getStdout(); + + assertThat(stdout).contains("프로그램이 종료되었습니다."); + } + + @Test + @DisplayName("웹 애플리케이션을 실행할 수 있다.") + void testRunWebApplication() { + // given + setStdin("1"); + + // when + VoucherManagementSystemApplication.main(args); + + // then + String stdout = getStdout(); + + assertThat(stdout).contains("t.m.v.web_app.WebApplication -- Started WebApplication"); + } + + private void addProgramArgument(String... args) { + this.args = ArrayUtils.addAll(this.args, args); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTests.java b/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTests.java deleted file mode 100644 index 3ba77e6edc..0000000000 --- a/src/test/java/team/marco/voucher_management_system/VoucherManagementSystemApplicationTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package team.marco.voucher_management_system; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class VoucherManagementSystemApplicationTests { - @Test - void contextLoads() { - } -} diff --git a/src/test/java/team/marco/voucher_management_system/common/StdIOTest.java b/src/test/java/team/marco/voucher_management_system/common/StdIOTest.java new file mode 100644 index 0000000000..062c38dcc3 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/common/StdIOTest.java @@ -0,0 +1,29 @@ +package team.marco.voucher_management_system.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.BeforeEach; + +public abstract class StdIOTest { + private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream(); + + @BeforeEach + final void init() { + System.setOut(new PrintStream(outputStreamCaptor)); + } + + protected void setStdin(String input) { + System.setIn(new ByteArrayInputStream(input.getBytes())); + } + + protected void setStdin(String delimiter, String... inputs) { + String joinedInput = String.join(delimiter, inputs); + + setStdin(joinedInput); + } + + protected String getStdout() { + return outputStreamCaptor.toString(); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcConfiguration.java b/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcConfiguration.java index cd62b2e5bb..3e8e7afc82 100644 --- a/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcConfiguration.java +++ b/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcConfiguration.java @@ -7,7 +7,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import team.marco.voucher_management_system.repository.JdbcVoucherRepository; @TestConfiguration public class TestJdbcConfiguration { @@ -31,9 +30,4 @@ public JdbcTemplate jdbcTemplate(DataSource dataSource) { public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) { return new NamedParameterJdbcTemplate(dataSource); } - - @Bean - public JdbcVoucherRepository jdbcVoucherRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { - return new JdbcVoucherRepository(namedParameterJdbcTemplate); - } } diff --git a/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcRepositoryConfiguration.java b/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcRepositoryConfiguration.java new file mode 100644 index 0000000000..19ca1abe3b --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/configuration/TestJdbcRepositoryConfiguration.java @@ -0,0 +1,36 @@ +package team.marco.voucher_management_system.configuration; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import team.marco.voucher_management_system.facade.VoucherCustomerFacade; +import team.marco.voucher_management_system.facade.VoucherCustomerFacadeImpl; +import team.marco.voucher_management_system.repository.JdbcCustomerRepository; +import team.marco.voucher_management_system.repository.JdbcVoucherRepository; +import team.marco.voucher_management_system.repository.JdbcWalletRepository; + +@TestConfiguration +@ComponentScan(basePackageClasses = TestJdbcConfiguration.class) +public class TestJdbcRepositoryConfiguration { + @Bean + public JdbcVoucherRepository jdbcVoucherRepository(NamedParameterJdbcTemplate jdbcTemplate) { + return new JdbcVoucherRepository(jdbcTemplate); + } + + @Bean + public JdbcCustomerRepository jdbcCustomerRepository(NamedParameterJdbcTemplate jdbcTemplate) { + return new JdbcCustomerRepository(jdbcTemplate); + } + + @Bean + public JdbcWalletRepository jdbcWalletRepository(NamedParameterJdbcTemplate jdbcTemplate) { + return new JdbcWalletRepository(jdbcTemplate); + } + + @Bean + public VoucherCustomerFacade voucherCustomerFacade(JdbcVoucherRepository voucherRepository, + JdbcCustomerRepository customerRepository) { + return new VoucherCustomerFacadeImpl(voucherRepository, customerRepository); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/configuration/TestPropertyConfiguration.java b/src/test/java/team/marco/voucher_management_system/configuration/TestPropertyConfiguration.java index af682cb63b..6cb1a282a8 100644 --- a/src/test/java/team/marco/voucher_management_system/configuration/TestPropertyConfiguration.java +++ b/src/test/java/team/marco/voucher_management_system/configuration/TestPropertyConfiguration.java @@ -3,8 +3,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.PropertySource; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; import team.marco.voucher_management_system.factory.YamlPropertiesFactory; -import team.marco.voucher_management_system.properties.FilePathProperties; @TestConfiguration @EnableConfigurationProperties({FilePathProperties.class, TestDataSourceProperties.class}) diff --git a/src/test/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImplTest.java b/src/test/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImplTest.java new file mode 100644 index 0000000000..89feb25483 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/facade/VoucherCustomerFacadeImplTest.java @@ -0,0 +1,171 @@ +package team.marco.voucher_management_system.facade; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.javafaker.Faker; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import team.marco.voucher_management_system.configuration.TestJdbcRepositoryConfiguration; +import team.marco.voucher_management_system.model.Customer; +import team.marco.voucher_management_system.model.FixedAmountVoucher; +import team.marco.voucher_management_system.model.Voucher; +import team.marco.voucher_management_system.repository.CustomerRepository; +import team.marco.voucher_management_system.repository.VoucherRepository; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringJUnitConfig(TestJdbcRepositoryConfiguration.class) +class VoucherCustomerFacadeImplTest { + private static final String DELETE_FROM_VOUCHER = "DELETE FROM voucher;"; + private static final String DELETE_FROM_CUSTOMER = "DELETE FROM customer"; + private static final Faker faker = new Faker(); + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private VoucherRepository voucherRepository; + @Autowired + private CustomerRepository customerRepository; + @Autowired + private VoucherCustomerFacade voucherCustomerFacade; + + @BeforeAll + @AfterEach + void truncateTable() { + jdbcTemplate.execute(DELETE_FROM_VOUCHER); + jdbcTemplate.execute(DELETE_FROM_CUSTOMER); + } + + @Test + @DisplayName("id 리스트에 있는 모든 Voucher를 조회할 수 있다.") + void testGetVouchers() { + // given + List vouchers = List.of( + generateVoucher(), + generateVoucher(), + generateVoucher()); + List voucherIds = vouchers.stream() + .map(Voucher::getId) + .toList(); + + vouchers.forEach(voucherRepository::save); + + // when + List retrievedVouchers = voucherCustomerFacade.getVouchers(voucherIds); + + // then + List retrievedVoucherIds = retrievedVouchers.stream() + .map(Voucher::getId) + .toList(); + + assertThat(retrievedVoucherIds).containsExactlyInAnyOrderElementsOf(voucherIds); + } + + @Test + @DisplayName("id 리스트에 있는 모든 고객을 조회할 수 있다.") + void testGetCustomers() { + // given + List customers = List.of( + generateCustomer(), + generateCustomer(), + generateCustomer()); + List customerIds = customers.stream() + .map(Customer::getId) + .toList(); + + customers.forEach(customerRepository::create); + + // when + List retrievedCustomers = voucherCustomerFacade.getCustomers(customerIds); + + // then + List retrievedCustomerIds = retrievedCustomers.stream() + .map(Customer::getId) + .toList(); + + assertThat(retrievedCustomerIds).containsExactlyInAnyOrderElementsOf(customerIds); + } + + @Nested + @DisplayName("Voucher 조회 테스트") + class TestHasVoucher { + @Test + @DisplayName("id가 일치하는 Voucher가 존재하면 true를 반환한다.") + void found() { + // given + Voucher voucher = generateVoucher(); + + voucherRepository.save(voucher); + + // when + boolean hasVoucher = voucherCustomerFacade.hasVoucher(voucher.getId()); + + // then + assertThat(hasVoucher).isTrue(); + } + + @Test + @DisplayName("id가 일치하는 Voucher가 없으면 false를 반환한다.") + void notFound() { + // given + Voucher voucher = generateVoucher(); + + // when + boolean hasVoucher = voucherCustomerFacade.hasVoucher(voucher.getId()); + + // then + assertThat(hasVoucher).isFalse(); + } + } + + @Nested + @DisplayName("Voucher 조회 테스트") + class TestHasCustomer { + @Test + @DisplayName("id가 일치하는 고객이 존재하면 true를 반환한다.") + void found() { + // given + Customer customer = generateCustomer(); + + customerRepository.create(customer); + + // when + boolean hasCustomer = voucherCustomerFacade.hasCustomer(customer.getId()); + + // then + assertThat(hasCustomer).isTrue(); + } + + @Test + @DisplayName("id가 일치하는 고객이 없으면 false를 반환한다.") + void notFound() { + // given + Customer customer = generateCustomer(); + + // when + boolean hasCustomer = voucherCustomerFacade.hasCustomer(customer.getId()); + + // then + assertThat(hasCustomer).isFalse(); + } + } + + private Voucher generateVoucher() { + return new FixedAmountVoucher(100); + } + + private Customer generateCustomer() { + String name = faker.name().firstName(); + String email = faker.internet().emailAddress(); + + return new Customer(name, email); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/model/BlacklistUserTest.java b/src/test/java/team/marco/voucher_management_system/model/BlacklistUserTest.java index 7f5fec2d21..204c26b2f9 100644 --- a/src/test/java/team/marco/voucher_management_system/model/BlacklistUserTest.java +++ b/src/test/java/team/marco/voucher_management_system/model/BlacklistUserTest.java @@ -1,32 +1,60 @@ package team.marco.voucher_management_system.model; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.hamcrest.MatcherAssert.assertThat; +import com.github.javafaker.Faker; import java.util.UUID; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.hamcrest.Matchers; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class BlacklistUserTest { + private static final Faker faker = new Faker(); + @Test @DisplayName("이름이 빈 문자열이 아니면 생성에 성공해야한다.") - void successCreation() { + void testSuccessCreation() { // given UUID id = UUID.randomUUID(); - String name = "test"; + String name = faker.name().name(); // when + ThrowingCallable targetMethod = () -> new BlacklistUser(id, name); + // then - new BlacklistUser(id, name); + assertThatNoException().isThrownBy(targetMethod); } @Test @DisplayName("이름이 빈 문자열이면 생성에 실패해야한다.") - void failCreation() { + void testFailCreation() { UUID id = UUID.randomUUID(); String name = ""; // when + ThrowingCallable targetMethod = () -> new BlacklistUser(id, name); + + // then + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage("이름은 빈 문자열 일 수 없습니다."); + } + + @Test + @DisplayName("블랙리스트 사용자의 정보에는 id와 이름이 포함되어야 한다.") + void testGetInfo() { + UUID id = UUID.randomUUID(); + String name = faker.name().name(); + + // when + BlacklistUser blacklistUser = new BlacklistUser(id, name); + // then - assertThrows(IllegalArgumentException.class, () -> new BlacklistUser(id, name)); + String blacklistUserInfo = blacklistUser.getInfo(); + + assertThat(blacklistUserInfo, Matchers.containsString(id.toString())); + assertThat(blacklistUserInfo, Matchers.containsString(name)); } } diff --git a/src/test/java/team/marco/voucher_management_system/model/CustomerTest.java b/src/test/java/team/marco/voucher_management_system/model/CustomerTest.java index f7eba36bde..6e14fb853c 100644 --- a/src/test/java/team/marco/voucher_management_system/model/CustomerTest.java +++ b/src/test/java/team/marco/voucher_management_system/model/CustomerTest.java @@ -1,46 +1,77 @@ package team.marco.voucher_management_system.model; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; -import static org.junit.jupiter.api.Assertions.assertThrows; +import com.github.javafaker.Faker; import java.time.LocalDateTime; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class CustomerTest { + private static final String INVALID_NAME_MESSAGE = "이름은 공백일 수 없습니다."; + private static final String INVALID_EMAIL_MESSAGE = "이메일 형식이 올바르지 않습니다."; + private static final Faker faker = new Faker(); private Customer generateCustomer() { - return new Customer("test", "test@test.test"); + String name = faker.name().name(); + String email = faker.internet().emailAddress(); + + return new Customer(name, email); + } + + @Test + @DisplayName("로그인을 하면 lastLoginAt 필드 값이 바뀌어야 한다.") + void testLogin() { + // given + Customer customer = generateCustomer(); + LocalDateTime beforeLastLoginAt = customer.getLastLoginAt(); + + // when + customer.login(); + + // then + LocalDateTime afterLastLoginAt = customer.getLastLoginAt(); + + assertThat(afterLastLoginAt, notNullValue()); + assertThat(afterLastLoginAt, is(not(beforeLastLoginAt))); } @Nested @DisplayName("고객 생성 이름 테스트") class TestCreationName { @Test - @DisplayName("이름이 빈 문자열이 아니면 생성에 성공해야한다.") + @DisplayName("이름이 빈 문자열이 아니면 생성에 성공해야 한다.") void success() { // given - String name = "test"; - String email = "test@test.test"; + String name = faker.name().name(); + String email = faker.internet().emailAddress(); // when + ThrowingCallable targetMethod = () -> new Customer(name, email); + // then - new Customer(name, email); + assertThatNoException().isThrownBy(targetMethod); } @Test - @DisplayName("이름이 빈 문자열이면 생성에 실패해야한다.") + @DisplayName("이름이 빈 문자열이면 생성에 실패해야 한다.") void fail() { String name = ""; - String email = "test@test.test"; + String email = faker.internet().emailAddress(); // when + ThrowingCallable targetMethod = () -> new Customer(name, email); + // then - assertThrows(IllegalArgumentException.class, () -> new Customer(name, email)); + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(INVALID_NAME_MESSAGE); } } @@ -48,26 +79,31 @@ void fail() { @DisplayName("고객 생성 이메일 테스트") class TestCreationEmail { @Test - @DisplayName("이메일이 형식에 맞으면 생성에 성공해야한다.") + @DisplayName("이메일이 형식에 맞으면 생성에 성공해야 한다.") void success() { // given - String name = "test"; - String email = "test@test.test"; + String name = faker.name().name(); + String email = faker.internet().emailAddress(); // when + ThrowingCallable targetMethod = () -> new Customer(name, email); + // then - new Customer(name, email); + assertThatNoException().isThrownBy(targetMethod); } @Test - @DisplayName("이메일이 형식에 맞지 않으면 생성에 실패해야한다.") + @DisplayName("이메일이 형식에 맞지 않으면 생성에 실패해야 한다.") void fail() { - String name = "test"; - String email = "test"; + String name = faker.name().name(); + String email = faker.name().name(); // when + ThrowingCallable targetMethod = () -> new Customer(name, email); + // then - assertThrows(IllegalArgumentException.class, () -> new Customer(name, email)); + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(INVALID_EMAIL_MESSAGE); } } @@ -75,43 +111,33 @@ void fail() { @DisplayName("고객 이름 변경 테스트") class TestChangeName { @Test - @DisplayName("이름이 빈 문자열이 아니면 변경에 성공해야한다.") + @DisplayName("이름이 빈 문자열이 아니면 변경에 성공해야 한다.") void success() { // given Customer customer = generateCustomer(); - String name = "test2"; + String name = faker.name().name(); // when - // then customer.changeName(name); + + // then + String customerName = customer.getName(); + + assertThat(customerName, is(name)); } @Test - @DisplayName("이름이 빈 문자열이면 변경에 실패해야한다.") + @DisplayName("이름이 빈 문자열이면 변경에 실패해야 한다.") void fail() { Customer customer = generateCustomer(); String name = ""; // when + ThrowingCallable targetMethod = () -> customer.changeName(name); + // then - assertThrows(IllegalArgumentException.class, () -> customer.changeName(name)); + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(INVALID_NAME_MESSAGE); } } - - @Test - @DisplayName("로그인을 하면 lastLoginAt 필드 값이 바뀌어야한다.") - void testLogin() { - // given - Customer customer = generateCustomer(); - LocalDateTime beforeLastLoginAt = customer.getLastLoginAt(); - - // when - customer.login(); - - // then - LocalDateTime afterLastLoginAt = customer.getLastLoginAt(); - - assertThat(afterLastLoginAt, notNullValue()); - assertThat(afterLastLoginAt, is(not(beforeLastLoginAt))); - } } diff --git a/src/test/java/team/marco/voucher_management_system/model/FixedAmountVoucherTest.java b/src/test/java/team/marco/voucher_management_system/model/FixedAmountVoucherTest.java index f867d0d61a..81904197af 100644 --- a/src/test/java/team/marco/voucher_management_system/model/FixedAmountVoucherTest.java +++ b/src/test/java/team/marco/voucher_management_system/model/FixedAmountVoucherTest.java @@ -1,25 +1,44 @@ package team.marco.voucher_management_system.model; +import static java.text.MessageFormat.format; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.junit.jupiter.api.Assertions.assertThrows; +import com.github.javafaker.Faker; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import team.marco.voucher_management_system.type_enum.VoucherType; -class FixedAmountVoucherTest { +class FixedAmountVoucherTest extends VoucherTest { private static final VoucherType FIXED_VOUCHER_TYPE = VoucherType.FIXED; private static final int MAXIMUM_AMOUNT = (int) 1e9; private static final int MINIMUM_AMOUNT = 1; + private static final Faker faker = new Faker(); + + @Override + protected VoucherType getTargetType() { + return FIXED_VOUCHER_TYPE; + } + + @Override + protected int generateValidData() { + return faker.number().numberBetween(MINIMUM_AMOUNT, MAXIMUM_AMOUNT); + } + + @Override + protected Voucher generateVoucher(int data) { + return new FixedAmountVoucher(data); + } @Test - @DisplayName("올바른 파라미터를 넘겼을 때 바우처가 생성되야한다.") + @DisplayName("올바른 파라미터를 넘겼을 때 바우처가 생성되야 한다.") void testCreation() { // given - int amount = 10_000; + int amount = generateValidData(); // when FixedAmountVoucher fixedAmountVoucher = new FixedAmountVoucher(amount); @@ -33,7 +52,7 @@ void testCreation() { } @Test - @DisplayName("생성 시 최소 할인 금액 보다 작을 경우 오류를 발생시켜야한다.") + @DisplayName("생성 시 최소 할인 금액 보다 작을 경우 오류를 발생 시켜야 한다.") void testCreationLessThanMinimumAmount() { // given int lessThanMinimumAmount = MINIMUM_AMOUNT - 1; @@ -41,13 +60,18 @@ void testCreationLessThanMinimumAmount() { assertThat(lessThanMinimumAmount, greaterThanOrEqualTo(Integer.MIN_VALUE)); // when + ThrowingCallable targetMethod = () -> new FixedAmountVoucher(lessThanMinimumAmount); + // then - assertThrows(IllegalArgumentException.class, - () -> new FixedAmountVoucher(lessThanMinimumAmount)); + String expectedMessage = format("{0}: 할인 금액은 {1} 보다 작을 수 없습니다.", + lessThanMinimumAmount, MINIMUM_AMOUNT); + + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(expectedMessage); } @Test - @DisplayName("생성 시 최소 할인 금액 보다 작을 경우 오류를 발생시켜야한다.") + @DisplayName("생성 시 최대 할인 금액 보다 클 경우 오류를 발생 시켜야 한다.") void testCreationGreaterThanMaximumAmount() { // given int greaterThanMaximumAmount = MAXIMUM_AMOUNT + 1; @@ -55,8 +79,13 @@ void testCreationGreaterThanMaximumAmount() { assertThat(greaterThanMaximumAmount, lessThanOrEqualTo(Integer.MAX_VALUE)); // when + ThrowingCallable targetMethod = () -> new FixedAmountVoucher(greaterThanMaximumAmount); + // then - assertThrows(IllegalArgumentException.class, - () -> new FixedAmountVoucher(greaterThanMaximumAmount)); + String expectedMessage = format("{0}: 할인 금액은 {1} 보다 클 수 없습니다.", + greaterThanMaximumAmount, MAXIMUM_AMOUNT); + + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(expectedMessage); } } diff --git a/src/test/java/team/marco/voucher_management_system/model/PercentDiscountVoucherTest.java b/src/test/java/team/marco/voucher_management_system/model/PercentDiscountVoucherTest.java index 35f6bfa1ac..de2d39d9d4 100644 --- a/src/test/java/team/marco/voucher_management_system/model/PercentDiscountVoucherTest.java +++ b/src/test/java/team/marco/voucher_management_system/model/PercentDiscountVoucherTest.java @@ -1,25 +1,44 @@ package team.marco.voucher_management_system.model; +import static java.text.MessageFormat.format; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.junit.jupiter.api.Assertions.assertThrows; +import com.github.javafaker.Faker; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import team.marco.voucher_management_system.type_enum.VoucherType; -class PercentDiscountVoucherTest { +class PercentDiscountVoucherTest extends VoucherTest { private static final VoucherType PERCENT_VOUCHER_TYPE = VoucherType.PERCENT; private static final int MAXIMUM_PERCENT = 100; private static final int MINIMUM_PERCENT = 1; + private static final Faker faker = new Faker(); + + @Override + protected VoucherType getTargetType() { + return PERCENT_VOUCHER_TYPE; + } + + @Override + protected int generateValidData() { + return faker.number().numberBetween(MINIMUM_PERCENT, MAXIMUM_PERCENT); + } + + @Override + protected Voucher generateVoucher(int data) { + return new PercentDiscountVoucher(data); + } @Test - @DisplayName("올바른 파라미터를 넘겼을 때 바우처가 생성되야한다.") + @DisplayName("올바른 파라미터를 넘겼을 때 바우처가 생성되야 한다.") void testCreation() { // given - int percent = 50; + int percent = generateValidData(); // when PercentDiscountVoucher percentDiscountVoucher = new PercentDiscountVoucher(percent); @@ -33,7 +52,7 @@ void testCreation() { } @Test - @DisplayName("생성 시 최소 할인 퍼센트 보다 작을 경우 오류를 발생시켜야한다.") + @DisplayName("생성 시 최소 할인 퍼센트 보다 작을 경우 오류를 발생 시켜야 한다.") void testCreationLessThanMinimumPercent() { // given int lessThanMinimumPercent = MINIMUM_PERCENT - 1; @@ -41,13 +60,19 @@ void testCreationLessThanMinimumPercent() { assertThat(lessThanMinimumPercent, greaterThanOrEqualTo(Integer.MIN_VALUE)); // when - // then - assertThrows(IllegalArgumentException.class, - () -> new PercentDiscountVoucher(lessThanMinimumPercent)); + ThrowingCallable targetMethod = () -> new PercentDiscountVoucher(lessThanMinimumPercent); + + // + String expectedMessage = format("{0}: 할인율은 {1}% 보다 작을 수 없습니다.", + lessThanMinimumPercent, MINIMUM_PERCENT); + + // when + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(expectedMessage); } @Test - @DisplayName("생성 시 최대 할인 퍼센트 보다 클 경우 오류를 발생시켜야한다.") + @DisplayName("생성 시 최대 할인 퍼센트 보다 클 경우 오류를 발생 시켜야 한다.") void testCreationGreaterThanMaximumPercent() { // given int greaterThanMaximumPercent = MAXIMUM_PERCENT + 1; @@ -55,8 +80,14 @@ void testCreationGreaterThanMaximumPercent() { assertThat(greaterThanMaximumPercent, lessThanOrEqualTo(Integer.MAX_VALUE)); // when - // then - assertThrows(IllegalArgumentException.class, - () -> new PercentDiscountVoucher(greaterThanMaximumPercent)); + ThrowingCallable targetMethod = () -> new PercentDiscountVoucher(greaterThanMaximumPercent); + + // + String expectedMessage = format("{0}: 할인율은 {1}%를 초과할 수 없습니다.", + greaterThanMaximumPercent, MAXIMUM_PERCENT); + + // when + assertThatIllegalArgumentException().isThrownBy(targetMethod) + .withMessage(expectedMessage); } } diff --git a/src/test/java/team/marco/voucher_management_system/model/VoucherTest.java b/src/test/java/team/marco/voucher_management_system/model/VoucherTest.java new file mode 100644 index 0000000000..4e7fb5f3cf --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/model/VoucherTest.java @@ -0,0 +1,163 @@ +package team.marco.voucher_management_system.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.text.NumberFormat; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import team.marco.voucher_management_system.type_enum.VoucherType; + +abstract class VoucherTest { + abstract protected VoucherType getTargetType(); + + abstract protected int generateValidData(); + + abstract protected Voucher generateVoucher(int data); + + @Test + @DisplayName("쿠폰의 타입은 null일 수 없고 VoucherType에 정의되야 한다.") + void testGetType() { + // given + Voucher voucher = generateVoucher(generateValidData()); + + // when + VoucherType voucherType = voucher.getType(); + + // then + assertThat(voucherType).isNotNull(); + assertThat(voucherType).isInstanceOf(VoucherType.class); + } + + @Test + @DisplayName("쿠폰 생성 시 주어진 data와 동일한 쿠폰이 생성되야 한다.") + void testGetData() { + // given + int initData = generateValidData(); + Voucher voucher = generateVoucher(initData); + + // when + int voucherData = voucher.getData(); + + // then + assertThat(voucherData).isEqualTo(initData); + } + + @Test + @DisplayName("쿠폰 정보에는 ID 정보와 주어진 data의 콤마 포맷 정보가 포함되야 한다.") + void testGetInfo() { + // given + int initData = generateValidData(); + Voucher voucher = generateVoucher(initData); + + // when + String voucherInfo = voucher.getInfo(); + + // then + UUID id = voucher.getId(); + + assertThat(voucherInfo).contains(id.toString()); + assertThat(voucherInfo).contains(NumberFormat.getIntegerInstance().format(initData)); + } + + @Nested + @DisplayName("id 판별 테스트") + class TestIsSameId { + @Test + @DisplayName("동일한 id일 경우 true를 반환한다.") + void isSameId() { + // given + Voucher voucher = generateVoucher(generateValidData()); + UUID voucherId = voucher.getId(); + + // when + boolean isSameId = voucher.isSameId(voucherId); + + // then + assertThat(isSameId).isTrue(); + } + + @Test + @DisplayName("동일하지 않은 id일 경우 false를 반환한다.") + void isNotSameId() { + // given + Voucher voucher = generateVoucher(generateValidData()); + Voucher otherVoucher = generateVoucher(generateValidData()); + UUID voucherId = otherVoucher.getId(); + + // when + boolean isSameId = voucher.isSameId(voucherId); + + // then + assertThat(isSameId).isFalse(); + } + } + + @Nested + @DisplayName("type 판별 테스트") + class TestIsSameType { + @Test + @DisplayName("동일한 type일 경우 true를 반환한다.") + void isSameType() { + // given + Voucher voucher = generateVoucher(generateValidData()); + VoucherType targetType = getTargetType(); + + // when + boolean isSameType = voucher.isSameType(targetType); + + // then + assertThat(isSameType).isTrue(); + } + + @Test + @DisplayName("동일하지 않은 type일 경우 false를 반환한다.") + void isNotSameType() { + // given + Voucher voucher = generateVoucher(generateValidData()); + VoucherType targetType = getTargetType(); + VoucherType otherType = Arrays.stream(VoucherType.values()) + .filter(voucherType -> voucherType != targetType) + .findFirst() + .orElseThrow(); + + // when + boolean isSameType = voucher.isSameType(otherType); + + // then + assertThat(isSameType).isFalse(); + } + } + + @Test + void isCreatedBetween() { + // given + List vouchers = List.of( + generateVoucher(generateValidData()), + generateVoucher(generateValidData()), + generateVoucher(generateValidData()), + generateVoucher(generateValidData()), + generateVoucher(generateValidData())); + + LocalDateTime from = vouchers.get(1).getCreateAt(); + LocalDateTime to = vouchers.get(3).getCreateAt(); + + // when + List actualVoucherIds = vouchers.stream() + .filter(voucher -> voucher.isCreatedBetween(from, to)) + .map(Voucher::getId) + .toList(); + + // then + List expectedVoucherIds = Stream.of(vouchers.get(1), vouchers.get(2), vouchers.get(3)) + .map(Voucher::getId) + .toList(); + + assertThat(actualVoucherIds).isEqualTo(expectedVoucherIds); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/properties/FilePathPropertiesTest.java b/src/test/java/team/marco/voucher_management_system/properties/FilePathPropertiesTest.java index 8cab3ce9c0..1468b42525 100644 --- a/src/test/java/team/marco/voucher_management_system/properties/FilePathPropertiesTest.java +++ b/src/test/java/team/marco/voucher_management_system/properties/FilePathPropertiesTest.java @@ -10,6 +10,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import team.marco.voucher_management_system.configuration.TestPropertyConfiguration; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; @SpringJUnitConfig(classes = TestPropertyConfiguration.class) @EnableConfigurationProperties(FilePathProperties.class) diff --git a/src/test/java/team/marco/voucher_management_system/repository/BlacklistRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/BlacklistRepositoryTest.java index 18c9a167f6..c2e48758bc 100644 --- a/src/test/java/team/marco/voucher_management_system/repository/BlacklistRepositoryTest.java +++ b/src/test/java/team/marco/voucher_management_system/repository/BlacklistRepositoryTest.java @@ -15,8 +15,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import team.marco.voucher_management_system.configuration.TestPropertyConfiguration; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; +import team.marco.voucher_management_system.console_app.repository.BlacklistRepository; import team.marco.voucher_management_system.model.BlacklistUser; -import team.marco.voucher_management_system.properties.FilePathProperties; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringJUnitConfig(classes = {TestPropertyConfiguration.class, BlacklistRepository.class}) diff --git a/src/test/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepositoryTest.java index ef4e23d597..31327447f4 100644 --- a/src/test/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepositoryTest.java +++ b/src/test/java/team/marco/voucher_management_system/repository/JSONFileVoucherRepositoryTest.java @@ -17,8 +17,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import team.marco.voucher_management_system.configuration.TestPropertyConfiguration; +import team.marco.voucher_management_system.console_app.properties.FilePathProperties; +import team.marco.voucher_management_system.console_app.repository.JSONFileVoucherRepository; import team.marco.voucher_management_system.model.Voucher; -import team.marco.voucher_management_system.properties.FilePathProperties; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringJUnitConfig(TestPropertyConfiguration.class) @@ -27,7 +28,13 @@ class JSONFileVoucherRepositoryTest extends VoucherRepositoryTest { private static final String SETUP_DATA; static { - SETUP_DATA = "[{\"id\": \"a5124642-c6f4-4f22-9e2b-9d25f98499d4\",\"type\": \"FIXED\",\"data\": 10000}]"; + SETUP_DATA = """ + [{ + "id": "a5124642-c6f4-4f22-9e2b-9d25f98499d4", + "type": "FIXED", + "data": 10000, + "createAt": [2023,10,8,6,0,43,644828000] + }]"""; } @Autowired diff --git a/src/test/java/team/marco/voucher_management_system/repository/JdbcCustomerRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/JdbcCustomerRepositoryTest.java new file mode 100644 index 0000000000..698a3cf0c2 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/repository/JdbcCustomerRepositoryTest.java @@ -0,0 +1,273 @@ +package team.marco.voucher_management_system.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.github.javafaker.Faker; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import team.marco.voucher_management_system.configuration.TestJdbcRepositoryConfiguration; +import team.marco.voucher_management_system.model.Customer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringJUnitConfig(TestJdbcRepositoryConfiguration.class) +class JdbcCustomerRepositoryTest { + private static final String DELETE_FROM_CUSTOMER = "DELETE FROM customer"; + private static final Faker faker = new Faker(); + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private JdbcCustomerRepository repository; + + @BeforeAll + @AfterEach + void truncateTable() { + jdbcTemplate.execute(DELETE_FROM_CUSTOMER); + } + + @Test + @DisplayName("추가한 사용자를 모두 찾을 수 있어야 한다.") + void testFindAll() { + // given + int count = 33; + List generatedCustomers = addTestCustomers(count); + + // when + List customers = repository.findAll(); + + // then + assertThat(customers.size()).isEqualTo(count); + + List retrievedIds = customers.stream() + .map(Customer::getId) + .toList(); + List generatedIds = generatedCustomers.stream() + .map(Customer::getId) + .toList(); + + assertThat(retrievedIds).containsExactlyInAnyOrderElementsOf(generatedIds); + } + + @Test + @DisplayName("사용자의 이름을 수정할 수 있어야 한다.") + void testUpdate() { + // given + Customer generatedCustomer = generateCustomer(); + String name = faker.name().name(); + + repository.create(generatedCustomer); + + // when + generatedCustomer.changeName(name); + repository.update(generatedCustomer); + + // then + Optional optionalCustomer = repository.findById(generatedCustomer.getId()); + + assertThat(optionalCustomer.isPresent()).isTrue(); + + Customer customer = optionalCustomer.get(); + + assertThat(customer.getName()).isEqualTo(generatedCustomer.getName()); + assertThat(customer.getName()).isEqualTo(name); + } + + @Test + @DisplayName("특정 문자를 포함하는 이름의 사용자를 모두 조회할 수 있어야 한다.") + void testFindByName() { + // given + String match = "t"; + List addedCustomers = addTestCustomers(10); + + // when + List customers = repository.findByName(match); + + // then + List addedIds = addedCustomers.stream() + .filter(customer -> isContain(customer.getName(), match)) + .map(Customer::getId) + .toList(); + List customerIds = customers.stream() + .map(Customer::getId) + .toList(); + + assertThat(customerIds).containsExactlyInAnyOrderElementsOf(addedIds); + } + + @Test + @DisplayName("특정 문자를 포함하는 이메일의 사용자를 모두 조회할 수 있어야 한다.") + void testFindByEmail() { + // given + String match = "t"; + List addedCustomers = addTestCustomers(10); + + // when + List customers = repository.findByEmail(match); + + // then + List addedIds = addedCustomers.stream() + .filter(customer -> isContain(customer.getEmail(), match)) + .map(Customer::getId) + .toList(); + List customerIds = customers.stream() + .map(Customer::getId) + .toList(); + + assertThat(customerIds).containsExactlyInAnyOrderElementsOf(addedIds); + } + + private Customer generateCustomer() { + String name = faker.name().firstName(); + String email = faker.internet().emailAddress(); + + return new Customer(name, email); + } + + private List addTestCustomers(int count) { + Set emails = new HashSet<>(); + List generatedCustomers = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + Customer customer = generateCustomer(); + + if (emails.contains(customer.getEmail())) { + continue; + } + + emails.add(customer.getEmail()); + repository.create(customer); + generatedCustomers.add(customer); + } + + return generatedCustomers.stream().toList(); + } + + private boolean isContain(String target, String match) { + return target.toLowerCase().contains(match.toLowerCase()); + } + + @Nested + @DisplayName("고객 삭제 테스트") + class TestDeleteById { + @Test + @DisplayName("id가 일치하는 고객이 존재할 경우 삭제할 수 있어야 한다.") + void success() { + // given + Customer existCustomer = generateCustomer(); + + addTestCustomers(10); + repository.create(existCustomer); + + // when + int delete = repository.deleteById(existCustomer.getId()); + + // then + assertThat(delete).isEqualTo(1); + } + + @Test + @DisplayName("id가 일치하는 고객이 없을 경우 0을 반환한다.") + void failToNotExistId() { + // given + Customer notExistCustomer = generateCustomer(); + + addTestCustomers(10); + + // when + int delete = repository.deleteById(notExistCustomer.getId()); + + // then + assertThat(delete).isEqualTo(0); + } + } + + @Nested + @DisplayName("고객 추가 테스트") + class TestCreate { + @Test + @DisplayName("고객을 추가할 수 있어야 한다.") + void success() { + // given + Customer customer = generateCustomer(); + + // when + int count = repository.create(customer); + + // then + assertThat(count).isEqualTo(1); + } + + @Test + @DisplayName("동일한 이메일의 고객을 추가할 경우 예외가 발생한다.") + void failToDuplicateEmail() { + // given + Customer customer = new Customer("test1", "test@test.test"); + Customer duplicatedEmailCustomer = new Customer("test2", "test@test.test"); + + // when + int initInsertion = repository.create(customer); + + ThrowingCallable targetMethod = () -> repository.create(duplicatedEmailCustomer); + + // then + assertThat(initInsertion).isEqualTo(1); + assertThatExceptionOfType(DuplicateKeyException.class).isThrownBy(targetMethod); + } + } + + @Nested + @DisplayName("고객 id 조회 테스트") + class TestFindById { + @Test + @DisplayName("id가 일치하는 고객이 존재할 경우 조회할 수 있어야 한다.") + void success() { + // given + Customer existCustomer = generateCustomer(); + + addTestCustomers(10); + repository.create(existCustomer); + + // when + Optional existOptionalCustomer = repository.findById(existCustomer.getId()); + + // then + assertThat(existOptionalCustomer.isPresent()).isTrue(); + + Customer retrievedCustomer = existOptionalCustomer.get(); + + assertThat(retrievedCustomer).usingRecursiveComparison() + .isEqualTo(existCustomer); + } + + @Test + @DisplayName("id가 일치하는 고객이 없을 경우 예외를 발생해야 한다.") + void failToNotExistId() { + // given + Customer notExistCustomer = generateCustomer(); + + addTestCustomers(10); + + // when + Optional existOptionalCustomer = repository.findById(notExistCustomer.getId()); + + // then + assertThat(existOptionalCustomer.isEmpty()).isTrue(); + } + } +} diff --git a/src/test/java/team/marco/voucher_management_system/repository/JdbcVoucherRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/JdbcVoucherRepositoryTest.java index e7b3104817..c233f4de1b 100644 --- a/src/test/java/team/marco/voucher_management_system/repository/JdbcVoucherRepositoryTest.java +++ b/src/test/java/team/marco/voucher_management_system/repository/JdbcVoucherRepositoryTest.java @@ -6,13 +6,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import team.marco.voucher_management_system.configuration.TestJdbcConfiguration; -import team.marco.voucher_management_system.configuration.TestPropertyConfiguration; +import team.marco.voucher_management_system.configuration.TestJdbcRepositoryConfiguration; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@SpringJUnitConfig({TestPropertyConfiguration.class, TestJdbcConfiguration.class}) +@SpringJUnitConfig(TestJdbcRepositoryConfiguration.class) class JdbcVoucherRepositoryTest extends VoucherRepositoryTest { - private static final String TRUNCATE_SQL = "DELETE FROM voucher;"; + private static final String DELETE_FROM_VOUCHER = "DELETE FROM voucher;"; @Autowired private JdbcTemplate jdbcTemplate; @@ -27,6 +26,6 @@ protected VoucherRepository getRepository() { @BeforeAll @AfterEach void truncateTable() { - jdbcTemplate.execute(TRUNCATE_SQL); + jdbcTemplate.execute(DELETE_FROM_VOUCHER); } } diff --git a/src/test/java/team/marco/voucher_management_system/repository/JdbcWalletRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/JdbcWalletRepositoryTest.java new file mode 100644 index 0000000000..1374be073d --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/repository/JdbcWalletRepositoryTest.java @@ -0,0 +1,202 @@ +package team.marco.voucher_management_system.repository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import team.marco.voucher_management_system.configuration.TestJdbcRepositoryConfiguration; +import team.marco.voucher_management_system.model.Customer; +import team.marco.voucher_management_system.model.FixedAmountVoucher; +import team.marco.voucher_management_system.model.Voucher; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringJUnitConfig(TestJdbcRepositoryConfiguration.class) +class JdbcWalletRepositoryTest { + private static final String DELETE_FROM_WALLET = "DELETE FROM wallet"; + private static final String DELETE_FROM_VOUCHER = "DELETE FROM voucher"; + private static final String DELETE_FROM_CUSTOMER = "DELETE FROM customer"; + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private JdbcWalletRepository walletRepository; + @Autowired + private JdbcVoucherRepository voucherRepository; + @Autowired + private JdbcCustomerRepository customerRepository; + + @BeforeAll + void validateReferenceRepository() { + // To test wallet repository, implement jdbc and customer repository + truncateTables(); + + validate(voucherRepository); + validate(customerRepository); + + truncateTables(); + } + + @AfterEach + void truncateTables() { + jdbcTemplate.execute(DELETE_FROM_WALLET); + jdbcTemplate.execute(DELETE_FROM_VOUCHER); + jdbcTemplate.execute(DELETE_FROM_CUSTOMER); + } + + @Test + @DisplayName("특정 고객 지갑에 있는 모든 쿠폰을 조회할 수 있다.") + void getVoucherIds() { + // given + UUID customerId = addCustomer(0).getId(); + List voucherIds = List.of(addVoucher().getId(), addVoucher().getId(), addVoucher().getId()); + + voucherIds.forEach(voucherId -> walletRepository.link(customerId, voucherId)); + + // when + List retrievedIds = walletRepository.getVoucherIds(customerId); + + // then + assertThat(retrievedIds).containsExactlyInAnyOrderElementsOf(voucherIds); + } + + @Test + @DisplayName("특정 쿠폰을 보유한 모든 사용자를 조회할 수 있다.") + void getCustomerIds() { + // given + UUID voucherId = addVoucher().getId(); + List customerIds = new ArrayList<>(); + + for (int i = 0; i < 5; i++) { + UUID customerId = addCustomer(i).getId(); + + customerIds.add(customerId); + walletRepository.link(customerId, voucherId); + } + + // when + List retrievedIds = walletRepository.getCustomerIds(voucherId); + + // then + assertThat(retrievedIds).containsExactlyInAnyOrderElementsOf(customerIds); + } + + private void validate(VoucherRepository voucherRepository) { + Voucher voucher = addVoucher(); + + List voucherIds = voucherRepository.findAll() + .stream() + .map(Voucher::getId) + .toList(); + + assert voucherIds.contains(voucher.getId()); + } + + private void validate(CustomerRepository customerRepository) { + Customer customer = addCustomer(0); + + List customerIds = customerRepository.findAll() + .stream() + .map(Customer::getId) + .toList(); + + assert customerIds.contains(customer.getId()); + } + + private Voucher addVoucher() { + Voucher voucher = new FixedAmountVoucher(100); + + voucherRepository.save(voucher); + + return voucher; + } + + private Customer addCustomer(int seed) { + Customer customer = new Customer("test", "test%d@test".formatted(seed)); + + customerRepository.create(customer); + + return customer; + } + + @Nested + @DisplayName("고객 지갑 쿠폰 추가 테스트") + class TestLink { + @Test + @DisplayName("고객의 지갑에 쿠폰을 추가할 수 있다.") + void success() { + // given + Voucher voucher = addVoucher(); + Customer customer = addCustomer(0); + + // when + int count = walletRepository.link(customer.getId(), voucher.getId()); + + // then + assertThat(count).isEqualTo(1); + } + + @Test + @DisplayName("이미 추가된 쿠폰은 지갑에 추가될 수 없다.") + void failToAlreadyExist() { + // given + Voucher voucher = addVoucher(); + Customer customer = addCustomer(0); + + walletRepository.link(customer.getId(), voucher.getId()); + + // when + ThrowingCallable targetMethod = () -> walletRepository.link(customer.getId(), voucher.getId()); + + // then + assertThatExceptionOfType(DataIntegrityViolationException.class) + .isThrownBy(targetMethod); + } + } + + @Nested + @DisplayName("고객 지갑 쿠폰 반납 테스트") + class TestUnlink { + @Test + @DisplayName("고객의 지갑에 쿠폰을 반납할 수 있다.") + void success() { + // given + Voucher voucher = addVoucher(); + Customer customer = addCustomer(0); + + walletRepository.link(customer.getId(), voucher.getId()); + + // when + int count = walletRepository.unlink(customer.getId(), voucher.getId()); + + // then + assertThat(count).isEqualTo(1); + } + + @Test + @DisplayName("지갑에 없는 쿠폰은 지갑에 반납될 수 없다.") + void failToNotExist() { + // given + Voucher voucher = addVoucher(); + Customer customer = addCustomer(0); + + // when + int count = walletRepository.unlink(customer.getId(), voucher.getId()); + + // then + assertThat(count).isEqualTo(0); + } + } +} diff --git a/src/test/java/team/marco/voucher_management_system/repository/MemoryVoucherRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/MemoryVoucherRepositoryTest.java index 5d494fee68..afcb84e7b5 100644 --- a/src/test/java/team/marco/voucher_management_system/repository/MemoryVoucherRepositoryTest.java +++ b/src/test/java/team/marco/voucher_management_system/repository/MemoryVoucherRepositoryTest.java @@ -1,5 +1,7 @@ package team.marco.voucher_management_system.repository; +import team.marco.voucher_management_system.console_app.repository.MemoryVoucherRepository; + class MemoryVoucherRepositoryTest extends VoucherRepositoryTest { protected VoucherRepository getRepository() { return new MemoryVoucherRepository(); diff --git a/src/test/java/team/marco/voucher_management_system/repository/VoucherRepositoryTest.java b/src/test/java/team/marco/voucher_management_system/repository/VoucherRepositoryTest.java index d8358b5861..35e12bd982 100644 --- a/src/test/java/team/marco/voucher_management_system/repository/VoucherRepositoryTest.java +++ b/src/test/java/team/marco/voucher_management_system/repository/VoucherRepositoryTest.java @@ -1,26 +1,43 @@ package team.marco.voucher_management_system.repository; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.notNullValue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import com.github.javafaker.Faker; +import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; import java.util.List; -import org.assertj.core.api.Assertions; +import java.util.Optional; +import java.util.UUID; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import team.marco.voucher_management_system.model.FixedAmountVoucher; +import team.marco.voucher_management_system.model.PercentDiscountVoucher; import team.marco.voucher_management_system.model.Voucher; +import team.marco.voucher_management_system.type_enum.VoucherType; abstract class VoucherRepositoryTest { - protected abstract VoucherRepository getRepository(); + private static final int MAXIMUM_AMOUNT = (int) 1e9; + private static final int MINIMUM_AMOUNT = 1; + private static final int MAXIMUM_PERCENT = 100; + private static final int MINIMUM_PERCENT = 1; + private static final Faker faker = new Faker(); protected Voucher generateVoucher() { - return new FixedAmountVoucher(10_000); + int randomNumber = faker.random().nextInt(1, 100); + + if (randomNumber % 2 == 0) { + return new PercentDiscountVoucher(faker.random().nextInt(MINIMUM_PERCENT, MAXIMUM_PERCENT)); + } + + return new FixedAmountVoucher(faker.random().nextInt(MINIMUM_AMOUNT, MAXIMUM_AMOUNT)); } + protected abstract VoucherRepository getRepository(); + @Test @DisplayName("Repository는 null일 수 없다.") void testNonNullRepository() { @@ -30,24 +47,61 @@ void testNonNullRepository() { VoucherRepository repository = getRepository(); // then - assertThat(repository, notNullValue()); + assertThat(repository).isNotNull(); } @Test - @DisplayName("Voucher 추가가 가능해야한다.") + @DisplayName("바우처 추가가 가능해야 한다.") void testSave() { // given VoucherRepository repository = getRepository(); Voucher voucher = generateVoucher(); // when - repository.save(voucher); + ThrowingCallable targetMethod = () -> repository.save(voucher); // then + assertThatNoException().isThrownBy(targetMethod); + } + + @Nested + @DisplayName("id로 바우처 삭제 테스트") + class TestDeleteById { + @Test + @DisplayName("id가 일치하는 바우처가 존재할 경우 삭제할 수 있다.") + void success() { + // given + VoucherRepository repository = getRepository(); + Voucher generatedVoucher = generateVoucher(); + UUID id = generatedVoucher.getId(); + + repository.save(generatedVoucher); + + // when + int count = repository.deleteById(id); + + // then + assertThat(count).isEqualTo(1); + } + + @Test + @DisplayName("id가 일치하는 바우처가 없을 경우 숫자 0을 반환한다.") + void notExist() { + // given + VoucherRepository repository = getRepository(); + Voucher notExistVoucher = generateVoucher(); + UUID notExistVoucherId = notExistVoucher.getId(); + + // when + int count = repository.deleteById(notExistVoucherId); + + // then + assertThat(count).isEqualTo(0); + } } @Test - @DisplayName("추가한 모든 Voucher를 조회할 수 있어야한다.") + @DisplayName("추가한 모든 바우처를 조회할 수 있어야 한다.") void testFindAll() { // given VoucherRepository repository = getRepository(); @@ -57,20 +111,122 @@ void testFindAll() { generatedVouchers.add(generateVoucher()); } - generatedVouchers.sort(Comparator.comparing(Voucher::getId)); - // when generatedVouchers.forEach(repository::save); // then - List retrievedVouchers = repository.findAll() + List generatedIds = generatedVouchers.stream() + .map(Voucher::getId) + .toList(); + List retrievedIds = repository.findAll() .stream() - .sorted(Comparator.comparing(Voucher::getId)) + .map(Voucher::getId) .toList(); - assertThat(retrievedVouchers, not(empty())); + assertThat(retrievedIds).isNotEmpty(); + + assertThat(retrievedIds).containsExactlyInAnyOrderElementsOf(generatedIds); + } + + @Nested + @DisplayName("바우처 id 조회 테스트") + class TestFindById { + @Test + @DisplayName("id가 일치하는 바우처가 존재할 경우 조회할 수 있어야 한다.") + void success() { + // given + VoucherRepository repository = getRepository(); + Voucher generatedVoucher = generateVoucher(); + UUID id = generatedVoucher.getId(); + + repository.save(generatedVoucher); + + // when + Optional optionalVoucher = repository.findById(id); + + // then + assertThat(optionalVoucher).isNotEmpty(); + + Voucher voucher = optionalVoucher.get(); + + assertThat(voucher.getId()).isEqualTo(id); + } + + @Test + @DisplayName("id가 일치하는 바우처 없을 경우 빈 optional 객체를 반환한다.") + void emptyVoucher() { + // given + VoucherRepository repository = getRepository(); + Voucher notExistVoucher = generateVoucher(); + UUID notExistVoucherId = notExistVoucher.getId(); + + // when + Optional optionalVoucher = repository.findById(notExistVoucherId); + + // then + assertThat(optionalVoucher).isEmpty(); + } + } + + @Test + @DisplayName("type이 일치하는 바우처를 모두 반환한다.") + void testFindByType() { + // given + VoucherRepository repository = getRepository(); + List vouchers = List.of( + generateVoucher(), + generateVoucher(), + generateVoucher()); + List voucherTypes = Arrays.stream(VoucherType.values()).toList(); + + vouchers.forEach(repository::save); + + // when + List counts = voucherTypes.stream() + .map(repository::findByType) + .map(List::size) + .toList(); + + // then + List expectedCounts = voucherTypes.stream() + .map(voucherType -> vouchers.stream() + .filter(voucher -> voucher.isSameType(voucherType)) + .count()) + .map(Math::toIntExact) + .toList(); + + assertThat(counts).containsExactlyElementsOf(expectedCounts); + } + + @Test + @DisplayName("생성 일자를 기준으로 조회할 수 있다.") + void testFindByCreateAt() { + // given + VoucherRepository repository = getRepository(); + List vouchers = List.of( + generateVoucher(), + generateVoucher(), + generateVoucher(), + generateVoucher(), + generateVoucher()); + + vouchers.forEach(repository::save); + + LocalDateTime from = vouchers.get(1).getCreateAt(); + LocalDateTime to = vouchers.get(3).getCreateAt(); + + // when + List retrievedVouchers = repository.findByCreateAt(from, to); + + // then + List retrievedIds = retrievedVouchers.stream() + .map(Voucher::getId) + .toList(); + List expectedIds = vouchers.stream() + .filter(voucher -> voucher.isCreatedBetween(from, to)) + .map(Voucher::getId) + .toList(); - Assertions.assertThat(retrievedVouchers).usingRecursiveComparison() - .isEqualTo(generatedVouchers); + assertThat(retrievedIds).containsExactlyInAnyOrderElementsOf(expectedIds); } } diff --git a/src/test/java/team/marco/voucher_management_system/util/ConsoleTest.java b/src/test/java/team/marco/voucher_management_system/util/ConsoleTest.java new file mode 100644 index 0000000000..bc32713455 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/util/ConsoleTest.java @@ -0,0 +1,109 @@ +package team.marco.voucher_management_system.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; + +import com.github.javafaker.Faker; +import java.io.UncheckedIOException; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import team.marco.voucher_management_system.common.StdIOTest; + +class ConsoleTest extends StdIOTest { + private final static Faker faker = new Faker(); + private final static String EOF = "\u001a"; + + @AfterEach + void closeConsole() { + Console.close(); + } + + @Nested + @DisplayName("readString() 테스트") + class TestReadString { + @Test + @DisplayName("콘솔 입력을 읽을 수 있다.") + void normal() { + // given + String input = faker.address().country(); + + setStdin(input); + + // when + String readString = Console.readString(); + + // then + assertThat(readString).isEqualTo(input); + } + + @Test + @DisplayName("EOF 발생 시 예외를 발생한다.") + void EOF() { + // given + setStdin(EOF); + + Console.readString(); // skip EOF character + + // when + ThrowingCallable target = Console::readString; + + // then + assertThatException().isThrownBy(target) + .isInstanceOf(UncheckedIOException.class) + .withMessageContaining("입력 과정에서 오류가 발생했습니다."); + } + } + + @Nested + @DisplayName("readInt() 테스트") + class TestReadInt { + @Test + @DisplayName("콘솔 입력을 숫자로 변환할 수 있다.") + void normal() { + // given + String input = faker.number().digit(); + + setStdin(input); + + // when + int readInt = Console.readInt(); + + // then + assertThat(readInt).isEqualTo(Integer.parseInt(input)); + } + + @Test + @DisplayName("숫자로 변환할 수 없을 시 예외를 발생한다.") + void numberFormatException() { + // given + String input = faker.name().firstName(); + + setStdin(input); + + // when + ThrowingCallable target = Console::readInt; + + // then + assertThatException().isThrownBy(target) + .isInstanceOf(NumberFormatException.class); + } + } + + @Test + @DisplayName("사용자 입력을 콘솔로 출력할 수 있다.") + void testPrint() { + // given + String userOutput = faker.name().firstName(); + + // when + Console.print(userOutput); + + // then + String stdout = getStdout(); + + assertThat(stdout).startsWith(userOutput); + } +} diff --git a/src/test/java/team/marco/voucher_management_system/util/UUIDConverterTest.java b/src/test/java/team/marco/voucher_management_system/util/UUIDConverterTest.java new file mode 100644 index 0000000000..e8ad306045 --- /dev/null +++ b/src/test/java/team/marco/voucher_management_system/util/UUIDConverterTest.java @@ -0,0 +1,67 @@ +package team.marco.voucher_management_system.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.nio.ByteBuffer; +import java.util.UUID; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class UUIDConverterTest { + @Nested + @DisplayName("byte[] to UUID 테스트") + class TestConvertBytes { + @Test + void success() { + // given + UUID originId = UUID.randomUUID(); + byte[] uuidBytes = convertUUIDToBytes(originId); + + // when + UUID uuid = UUIDConverter.convert(uuidBytes); + + // then + assertThat(uuid).isEqualTo(originId); + } + + @Test + @DisplayName("16Byte 보다 작을 경우 예외를 발생한다.") + void underflowException() { + // given + byte[] uuidBytes = new byte[8]; + + // when + ThrowingCallable target = () -> UUIDConverter.convert(uuidBytes); + + // then + assertThatIllegalArgumentException().isThrownBy(target) + .withMessage("UUID는 16Byte여야 합니다."); + } + } + + @Test + @DisplayName("string to UUID 테스트") + void testConvertString() { + // given + UUID originId = UUID.randomUUID(); + String uuidString = originId.toString(); + + // when + UUID uuid = UUIDConverter.convert(uuidString); + + // then + assertThat(uuid).isEqualTo(originId); + } + + private byte[] convertUUIDToBytes(UUID uuid) { + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]); + + byteBuffer.putLong(uuid.getMostSignificantBits()); + byteBuffer.putLong(uuid.getLeastSignificantBits()); + + return byteBuffer.array(); + } +}