diff --git a/build.gradle b/build.gradle index d72096c..84ec5d8 100644 --- a/build.gradle +++ b/build.gradle @@ -25,15 +25,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - compileOnly 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'com.fasterxml.jackson.core:jackson-core:2.11.4' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.4' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.4' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.11.4' runtimeOnly 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation "com.fasterxml.jackson.core:jackson-core:2.11.4" - implementation "com.fasterxml.jackson.core:jackson-annotations:2.11.4" - implementation "com.fasterxml.jackson.core:jackson-databind:2.11.4" - implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.11.4" } tasks.named('test') { diff --git a/src/main/java/com/swger/tddstudy/product/domain/Product.java b/src/main/java/com/swger/tddstudy/product/domain/Product.java index e71ebd9..b7b463f 100644 --- a/src/main/java/com/swger/tddstudy/product/domain/Product.java +++ b/src/main/java/com/swger/tddstudy/product/domain/Product.java @@ -1,28 +1,53 @@ package com.swger.tddstudy.product.domain; import com.swger.tddstudy.util.BaseEntity; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Table; import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; @Getter +@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@Builder @Entity +@Table(name = "Products") public class Product extends BaseEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NonNull + @Column(name = "name", nullable = false, length = 30) private String name; + @NonNull + @Column(name = "price", nullable = false) private int price; + @NonNull + @Column(name = "amount", nullable = false) private int amount; @Enumerated(EnumType.STRING) private SellingStatus sellingStatus; + + public ProductDto toProductDto() { + return ProductDto.builder().id(this.id).name(this.name).price(this.price) + .amount(this.amount).sellingStatus(this.sellingStatus.name()).build(); + } + } diff --git a/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java new file mode 100644 index 0000000..92fa5d6 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java @@ -0,0 +1,42 @@ +package com.swger.tddstudy.product.domain; + +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +public class ProductDto { + + private Long id; + private String name; + private int price; + private int amount; + private String sellingStatus; + + public ProductDto(String name, int price, int amount, String sellingStatus) { + this.name = name; + this.price = price; + this.amount = amount; + this.sellingStatus = sellingStatus; + } + + public Product toEntity() { + return Product.builder().name(this.name).price(this.price).amount(this.amount) + .sellingStatus(SellingStatus.valueOf(this.sellingStatus)).build(); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java b/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java new file mode 100644 index 0000000..03fcebc --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java @@ -0,0 +1,8 @@ +package com.swger.tddstudy.product.exception; + +public class ProductNotFoundException extends RuntimeException { + + public ProductNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java b/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java new file mode 100644 index 0000000..2c90f9d --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java @@ -0,0 +1,8 @@ +package com.swger.tddstudy.product.exception; + +public class ProductSoldOutExcpetion extends RuntimeException{ + + public ProductSoldOutExcpetion(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java b/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java new file mode 100644 index 0000000..8d0cf54 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java @@ -0,0 +1,10 @@ +package com.swger.tddstudy.product.exception; + +import org.springframework.web.client.HttpClientErrorException.Unauthorized; + +public class UnauthorizedException extends RuntimeException { + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java b/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java new file mode 100644 index 0000000..3360ab8 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java @@ -0,0 +1,11 @@ +package com.swger.tddstudy.product.repository; + +import com.swger.tddstudy.product.domain.Product; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProductRepository extends JpaRepository { + + @Override + Optional findById(Long id); +} diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java new file mode 100644 index 0000000..5f3915c --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java @@ -0,0 +1,34 @@ +package com.swger.tddstudy.product.request; + +import com.swger.tddstudy.product.domain.ProductDto; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductAddRequest { + + @NotBlank(message = "상품명을 입력하세요.") + @Size(min = 2, max = 10, message = "상품명을 2자 이상 30자 이하로 입력하세요.") + private String name; + + @NotNull(message = "상품 가격을 입력하세요.") + @Min(value = 0, message = "상품 가격은 0 이상으로 입력하세요.") + private int price; + + @NotNull(message = "상품 수량을 입력하세요.") + @Min(value = 1, message = "상품 수량은 1 이상으로 입력하세요.") + private int amount; + + public ProductDto toProductDto() { + return ProductDto.builder().name(this.name).price(this.price).amount(this.amount).build(); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java new file mode 100644 index 0000000..f78b065 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java @@ -0,0 +1,23 @@ +package com.swger.tddstudy.product.request; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductStockUpRequest { + + @NotNull(message = "상품 ID를 입력하세요.") + private Long id; + + @NotNull(message = "상품 수량을 입력하세요.") + @Min(value = 1, message = "상품 수량은 1 이상으로 입력하세요.") + private int amount; + +} diff --git a/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java new file mode 100644 index 0000000..94ebdbe --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java @@ -0,0 +1,64 @@ +package com.swger.tddstudy.product.restcontroller; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.exception.UnauthorizedException; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import com.swger.tddstudy.product.service.ProductService; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.service.UserService; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/product") +public class ProductRestController { + + private final ProductService productService; + private final UserService userService; + + @PostMapping("/add") + public ResponseEntity join(@Valid @RequestBody ProductAddRequest productAddRequest, + HttpServletRequest request) { + Map message = new HashMap<>(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new UnauthorizedException("권한이 없습니다."); + } + if (userService.isAdmin((Long) session.getAttribute("id"))) { + message.put("status", 200); + message.put("data", productService.productAdd(productAddRequest)); + return ResponseEntity.status(HttpStatus.OK).body(message); + } else { + throw new UnauthorizedException("관리자만 상품을 등록할 수 있습니다."); + } + } + + @PostMapping("/stock-up") + public ResponseEntity join(@Valid @RequestBody ProductStockUpRequest productStockUpRequest, + HttpServletRequest request) { + Map message = new HashMap<>(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new UnauthorizedException("권한이 없습니다."); + } + if (userService.isAdmin((Long) session.getAttribute("id"))) { + message.put("status", 200); + message.put("data", productService.productStockUp(productStockUpRequest)); + return ResponseEntity.status(HttpStatus.OK).body(message); + } else { + throw new UnauthorizedException("관리자만 상품 재고를 추가할 수 있습니다."); + } + } +} diff --git a/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java new file mode 100644 index 0000000..b958773 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java @@ -0,0 +1,34 @@ +package com.swger.tddstudy.product.restcontroller; + +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; +import com.swger.tddstudy.product.exception.UnauthorizedException; +import java.util.HashMap; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException.Unauthorized; + +@RestControllerAdvice +public class ProductRestControllerAdvice { + + @ExceptionHandler + public ResponseEntity unauthorizedException(UnauthorizedException e) { + Map errorMessage = new HashMap<>(); + errorMessage.put("error", e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); + } + + @ExceptionHandler + public ResponseEntity productSoldOutException(ProductSoldOutExcpetion e) { + Map errorMessage = new HashMap<>(); + errorMessage.put("error", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/service/ProductService.java b/src/main/java/com/swger/tddstudy/product/service/ProductService.java new file mode 100644 index 0000000..d080545 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/service/ProductService.java @@ -0,0 +1,83 @@ +package com.swger.tddstudy.product.service; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.ProductDto; +import com.swger.tddstudy.product.domain.SellingStatus; +import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; +import com.swger.tddstudy.product.repository.ProductRepository; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import java.util.Optional; +import javax.swing.plaf.basic.BasicTreeUI.TreeHomeAction; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Transactional +public class ProductService { + + private final ProductRepository productRepository; + + public ProductDto productAdd(ProductAddRequest productAddRequest) { + ProductDto productDto = productAddRequest.toProductDto(); + productDto.setSellingStatus("SELLING"); + return productRepository.save(productDto.toEntity()).toProductDto(); + } + + public ProductDto productStockUp(ProductStockUpRequest productStockUpRequest) { + Optional optionalProduct = productRepository.findById( + productStockUpRequest.getId()); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() == 0) { + product.setAmount(productStockUpRequest.getAmount()); + product.setSellingStatus(SellingStatus.SELLING); + return product.toProductDto(); + } else { + int result = product.getAmount() + productStockUpRequest.getAmount(); + product.setAmount(result); + return product.toProductDto(); + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } + + public ProductDto sale(Long id, int count) { + Optional optionalProduct = productRepository.findById(id); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() - count < 0) { + throw new ProductSoldOutExcpetion("재고가 부족합니다."); + } else if(product.getAmount() - count ==0) { + product.setAmount(0); + return sellingStatusUpdate(product.getId()); + } else{ + int result = product.getAmount() - count; + product.setAmount(result); + return product.toProductDto(); + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } + + // 상품 재고가 다 떨어졌다면, 상품의 판매 상태를 STOP_SELLING으로 변경 + public ProductDto sellingStatusUpdate(Long id) { + Optional optionalProduct = productRepository.findById(id); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() == 0) { + product.setSellingStatus(SellingStatus.STOP_SELLING); + return product.toProductDto(); + } else { + return product.toProductDto(); + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } +} diff --git a/src/main/java/com/swger/tddstudy/user/controller/UserController.java b/src/main/java/com/swger/tddstudy/user/controller/UserController.java index 6fae1d9..5c09f39 100644 --- a/src/main/java/com/swger/tddstudy/user/controller/UserController.java +++ b/src/main/java/com/swger/tddstudy/user/controller/UserController.java @@ -1,14 +1,16 @@ package com.swger.tddstudy.user.controller; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; +import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpSession; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequiredArgsConstructor @@ -23,7 +25,7 @@ public String joinForm() { } @GetMapping("/login-form") - public String loginForm() throws Exception { + public String loginForm() { return "userPages/login"; } @@ -35,7 +37,7 @@ public String join(@ModelAttribute JoinRequest joinRequest) { @PostMapping("/login") public String login(@ModelAttribute LoginRequest loginRequest, HttpSession session) { - UserVO loginResult = userService.login(loginRequest); + UserDto loginResult = userService.login(loginRequest); if (loginResult != null) { session.setAttribute("username", loginResult.getUsername()); session.setAttribute("id", loginResult.getId()); @@ -44,5 +46,4 @@ public String login(@ModelAttribute LoginRequest loginRequest, HttpSession sessi return "userPages/login"; } } - } diff --git a/src/main/java/com/swger/tddstudy/user/domain/User.java b/src/main/java/com/swger/tddstudy/user/domain/User.java index 3861cef..b707cbc 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/User.java +++ b/src/main/java/com/swger/tddstudy/user/domain/User.java @@ -1,9 +1,21 @@ package com.swger.tddstudy.user.domain; import com.swger.tddstudy.util.BaseEntity; -import lombok.*; - -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; @Getter @Setter @@ -13,6 +25,7 @@ @Entity @Table(name = "Users") public class User extends BaseEntity { + @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -36,9 +49,11 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private UserType type; - public UserVO toUserVO() { - UserVO userVO = UserVO.builder().id(this.id).username(this.username).password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()).type(this.type.name()).build(); - return userVO; + public UserDto toUserDto() { + UserDto userDto = UserDto.builder().id(this.id).username(this.username) + .password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()) + .type(this.type.name()).build(); + return userDto; } public void levelUp() { diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserVO.java b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java similarity index 54% rename from src/main/java/com/swger/tddstudy/user/domain/UserVO.java rename to src/main/java/com/swger/tddstudy/user/domain/UserDto.java index 1ef2533..f449b96 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserVO.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java @@ -1,6 +1,11 @@ package com.swger.tddstudy.user.domain; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Data @NoArgsConstructor @@ -8,7 +13,8 @@ @Getter @Setter @Builder -public class UserVO { +public class UserDto { + private Long id; private String username; private String password; @@ -16,7 +22,8 @@ public class UserVO { private String userLevel; private String type; - public UserVO(String username, String password, String nickname, String userLevel, String type) { + public UserDto(String username, String password, String nickname, String userLevel, + String type) { this.username = username; this.password = password; this.nickname = nickname; @@ -26,7 +33,7 @@ public UserVO(String username, String password, String nickname, String userLeve public User toEntity() { return User.builder().username(this.username) - .password(this.password).nickname(this.nickname) - .userLevel(UserLevel.valueOf(this.userLevel)).type(UserType.valueOf(this.type)).build(); + .password(this.password).nickname(this.nickname) + .userLevel(UserLevel.valueOf(this.userLevel)).type(UserType.valueOf(this.type)).build(); } } diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java b/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java index c83833b..be17687 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java @@ -12,7 +12,9 @@ public enum UserLevel { public UserLevel levelUp() { UserLevel[] levels = values(); - if (this.level >= levels.length - 1) return this; + if (this.level >= levels.length - 1) { + return this; + } return levels[this.level + 1]; } } diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserType.java b/src/main/java/com/swger/tddstudy/user/domain/UserType.java index 73912c7..bef358c 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserType.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserType.java @@ -8,5 +8,4 @@ public enum UserType { USER("일반회원"), ADMIN("관리자"); private final String text; - } diff --git a/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java b/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java index d4ba025..e78b858 100644 --- a/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java +++ b/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java @@ -1,6 +1,7 @@ package com.swger.tddstudy.user.exception; -public class LoginFailureException extends RuntimeException{ +public class LoginFailureException extends RuntimeException { + public LoginFailureException(String message) { super(message); } diff --git a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java index b938c8f..aa03d6b 100644 --- a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java +++ b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java @@ -1,11 +1,10 @@ package com.swger.tddstudy.user.repository; import com.swger.tddstudy.user.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); } diff --git a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java index 2689943..42f1605 100644 --- a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java @@ -1,16 +1,17 @@ package com.swger.tddstudy.user.request; -import com.swger.tddstudy.user.domain.UserVO; -import lombok.*; - +import com.swger.tddstudy.user.domain.UserDto; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder @NoArgsConstructor @AllArgsConstructor -@EqualsAndHashCode public class JoinRequest { @NotBlank(message = "이름을 입력하세요.") @@ -25,8 +26,8 @@ public class JoinRequest { @Size(min = 2, max = 10, message = "닉네임을 2자 이상 10자 이하로 입력하세요. ") private String nickname; - public UserVO toUserVO() { - return UserVO.builder().username(this.username).password(this.password).nickname(this.nickname).build(); + public UserDto toUserDto() { + return UserDto.builder().username(this.username).password(this.password) + .nickname(this.nickname).build(); } - } diff --git a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java index d777504..148ae2b 100644 --- a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java @@ -1,16 +1,17 @@ package com.swger.tddstudy.user.request; -import com.swger.tddstudy.user.domain.UserVO; -import lombok.*; - +import com.swger.tddstudy.user.domain.UserDto; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder @NoArgsConstructor @AllArgsConstructor -@EqualsAndHashCode public class LoginRequest { @NotBlank(message = "이름을 입력하세요.") @@ -21,7 +22,7 @@ public class LoginRequest { @Size(min = 8, max = 15, message = "비밀번호를 8자 이상 10자 이하로 입력하세요.") private String password; - public UserVO toUserVO() { - return UserVO.builder().username(this.username).password(this.password).build(); + public UserDto toUserDto() { + return UserDto.builder().username(this.username).password(this.password).build(); } } diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java similarity index 90% rename from src/main/java/com/swger/tddstudy/user/restController/UserRestController.java rename to src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java index 652ddc9..8b6a466 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java +++ b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java @@ -1,9 +1,13 @@ -package com.swger.tddstudy.user.restController; +package com.swger.tddstudy.user.restcontroller; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -12,11 +16,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpSession; -import javax.validation.Valid; -import java.util.HashMap; -import java.util.Map; - @RestController @RequiredArgsConstructor @RequestMapping("api/user") @@ -41,14 +40,14 @@ public ResponseEntity joinAdmin(@Valid @RequestBody JoinRequest joinRequest) } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest, HttpSession session) { + public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest, + HttpSession session) { Map message = new HashMap<>(); - UserVO loginResult = userService.login(loginRequest); + UserDto loginResult = userService.login(loginRequest); session.setAttribute("username", loginResult.getUsername()); session.setAttribute("id", loginResult.getId()); message.put("status", 200); message.put("data", loginResult); return ResponseEntity.status(HttpStatus.OK).body(message); } - } diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java similarity index 76% rename from src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java rename to src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java index 7cba0dd..6d1b8b7 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java +++ b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java @@ -1,6 +1,8 @@ -package com.swger.tddstudy.user.restController; +package com.swger.tddstudy.user.restcontroller; import com.swger.tddstudy.user.exception.LoginFailureException; +import java.util.HashMap; +import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -8,9 +10,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import java.util.HashMap; -import java.util.Map; - @RestControllerAdvice public class UserRestControllerAdvice { @@ -19,8 +18,8 @@ public ResponseEntity bindException(BindException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } @ExceptionHandler @@ -28,16 +27,16 @@ public ResponseEntity illegalArgException(IllegalArgumentException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } @ExceptionHandler - public ResponseEntity LoginFailureException(LoginFailureException e) { + public ResponseEntity loginFailureException(LoginFailureException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } } diff --git a/src/main/java/com/swger/tddstudy/user/service/UserService.java b/src/main/java/com/swger/tddstudy/user/service/UserService.java index ea3ef86..9992e06 100644 --- a/src/main/java/com/swger/tddstudy/user/service/UserService.java +++ b/src/main/java/com/swger/tddstudy/user/service/UserService.java @@ -1,46 +1,46 @@ package com.swger.tddstudy.user.service; import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.domain.UserLevel; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.domain.UserType; import com.swger.tddstudy.user.exception.LoginFailureException; import com.swger.tddstudy.user.repository.UserRepository; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; -import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.Optional; -import java.util.regex.Pattern; @Service @RequiredArgsConstructor @Transactional public class UserService { + private final UserRepository userRepository; - public UserVO save(JoinRequest joinRequest) { - UserVO userVO = joinRequest.toUserVO(); - userVO.setUserLevel("BRONZE"); - userVO.setType("USER"); - return userRepository.save(userVO.toEntity()).toUserVO(); + public UserDto save(JoinRequest joinRequest) { + UserDto userDto = joinRequest.toUserDto(); + userDto.setUserLevel("BRONZE"); + userDto.setType("USER"); + return userRepository.save(userDto.toEntity()).toUserDto(); } - public UserVO saveAdmin(JoinRequest joinRequest) { - UserVO userVO = joinRequest.toUserVO(); - userVO.setUserLevel("BRONZE"); - userVO.setType("ADMIN"); - return userRepository.save(userVO.toEntity()).toUserVO(); + public UserDto saveAdmin(JoinRequest joinRequest) { + UserDto userDto = joinRequest.toUserDto(); + userDto.setUserLevel("BRONZE"); + userDto.setType("ADMIN"); + return userRepository.save(userDto.toEntity()).toUserDto(); } - public UserVO login(LoginRequest loginRequest) { - Optional optionalUserEntity = userRepository.findByUsername(loginRequest.getUsername()); + public UserDto login(LoginRequest loginRequest) { + Optional optionalUserEntity = userRepository.findByUsername( + loginRequest.getUsername()); if (optionalUserEntity.isPresent()) { User loginEntity = optionalUserEntity.get(); if (loginEntity.getPassword().equals(loginRequest.getPassword())) { - return loginEntity.toUserVO(); + return loginEntity.toUserDto(); } else { throw new LoginFailureException("비밀번호가 일치하지 않습니다."); } @@ -48,4 +48,17 @@ public UserVO login(LoginRequest loginRequest) { throw new LoginFailureException("일치하는 회원정보가 없습니다."); } } + + public boolean isAdmin(Long id) { + Optional optionalUserEntity = userRepository.findById(id); + if (optionalUserEntity.isPresent()) { + if (optionalUserEntity.get().getType() == UserType.ADMIN) { + return true; + } else { + return false; + } + } else { + throw new LoginFailureException("일치하는 회원정보가 없습니다."); + } + } } diff --git a/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java b/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java new file mode 100644 index 0000000..edf07e4 --- /dev/null +++ b/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java @@ -0,0 +1,205 @@ +package com.swger.tddstudy.product.restcontroller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.swger.tddstudy.product.domain.ProductDto; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import com.swger.tddstudy.product.service.ProductService; +import com.swger.tddstudy.user.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(ProductRestController.class) +public class ProductRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + ProductService productService; + + @MockBean + UserService userService; + + @Nested + @DisplayName("상품 등록 컨트롤러 : ") + class ProductAddControllerTest { + + private ProductDto newProductDto() { + return ProductDto.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus("SELLING").build(); + } + + private ProductAddRequest newProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(10).build(); + } + + private ProductAddRequest newWrongProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(0).build(); + } + + @Test + @DisplayName("양식이 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.price").exists()) + .andExpect(jsonPath("$.data.amount").exists()) + .andExpect(jsonPath("$.data.sellingStatus").exists()).andDo(print()); + verify(productService, times(1)).productAdd(any(ProductAddRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newWrongProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productAdd(any(ProductAddRequest.class)); + } + + @Test + @DisplayName("양식에 맞으나 관리자가 아니면 실패") + void Test3() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(false); + String json = new ObjectMapper().writeValueAsString(newProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productAdd(any(ProductAddRequest.class)); + } + } + + @Nested + @DisplayName("상품 재고 추가 컨트롤러 : ") + class ProductStockUpControllerTest { + + private ProductDto newProductDto() { + return ProductDto.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus("SELLING").build(); + } + + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + private ProductStockUpRequest newWrongProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(0).build(); + } + + @Test + @DisplayName("양식이 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.price").exists()) + .andExpect(jsonPath("$.data.amount").exists()) + .andExpect(jsonPath("$.data.sellingStatus").exists()).andDo(print()); + verify(productService, times(1)).productStockUp(any(ProductStockUpRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newWrongProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productStockUp(any(ProductStockUpRequest.class)); + } + + @Test + @DisplayName("양식에 맞으나 관리자가 아니면 실패") + void Test3() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(false); + String json = new ObjectMapper().writeValueAsString(newProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productStockUp(any(ProductStockUpRequest.class)); + } + } +} + diff --git a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java new file mode 100644 index 0000000..2b6ca19 --- /dev/null +++ b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java @@ -0,0 +1,287 @@ +package com.swger.tddstudy.product.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.ProductDto; +import com.swger.tddstudy.product.domain.SellingStatus; +import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; +import com.swger.tddstudy.product.repository.ProductRepository; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import java.util.Optional; +import javax.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Transactional +@ExtendWith(MockitoExtension.class) +public class ProductServiceTest { + + @InjectMocks + private ProductService productService; + + @Mock + private ProductRepository productRepository; + + @Nested + @DisplayName("상품 판매 상태 변경 : ") + class ProductSellingStatusUpdateTest { + + private Product newSoldOutProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(0) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + @Test + @DisplayName("재고가 소진 되어 제품 판매 중지") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.sellingStatusUpdate(0L); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 0, "STOP_SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("재고가 소진 되지 않아 판매 지속") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.sellingStatusUpdate(0L); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일치하는 상품이 없는 경우 실패") + void Test3() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.sellingStatusUpdate(0L); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } + + @Nested + @DisplayName("새상품 등록 : ") + class ProductAddTest { + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private ProductAddRequest newProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(10).build(); + } + + @Test + @DisplayName("새상품 추가 성공") + void Test1() { + + // given + given(productRepository.save(any(Product.class))).willReturn(newProduct()); + + // when + ProductDto addedProductDto = productService.productAdd(newProductAddRequest()); + + // then + assertThat(addedProductDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + verify(productRepository, times(1)).save(any(Product.class)); + } + } + + @Nested + @DisplayName("상품 재고 추가 : ") + class ProductStockUpTest { + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private Product newSoldOutProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(0) + .sellingStatus(SellingStatus.STOP_SELLING).build(); + } + + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + @Test + @DisplayName("재고 추가 성공") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 20, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("재고가 0에서 추가된 경우 판매 상태도 변경") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일치하는 상품이 없는 경우 실패") + void Test3() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.productStockUp(newProductStockUpRequest()); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } + + @Nested + @DisplayName("상품 판매 시 수량 체크 : ") + class ProductSaleTest { + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private Product newLowCntProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(2) + .sellingStatus(SellingStatus.STOP_SELLING).build(); + } + + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + @Test + @DisplayName("판매 수량 만큼 수량 감소") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.sale(0L, 3); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 7, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("판매 후 수량이 0이 될 경우 판매 상태 변경") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newLowCntProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.sale(0L, 2); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 0, "STOP_SELLING"); + verify(productRepository, times(2)).findById(any(Long.class)); + } + + @Test + @DisplayName("수량이 부족한 경우 실패") + void Test3() { + + // given + Optional optionalProduct = Optional.ofNullable(newLowCntProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductSoldOutExcpetion.class, () -> { + productService.sale(0L, 3); + }); + assertEquals("재고가 부족합니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일치하는 상품이 없는 경우 실패") + void Test4() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.sale(0L, 3); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } +} diff --git a/src/test/java/com/swger/tddstudy/user/UserTest.java b/src/test/java/com/swger/tddstudy/user/UserTest.java deleted file mode 100644 index 09ba648..0000000 --- a/src/test/java/com/swger/tddstudy/user/UserTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.swger.tddstudy.user; - -import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.domain.UserLevel; -import com.swger.tddstudy.user.domain.UserType; -import com.swger.tddstudy.user.domain.UserVO; -import com.swger.tddstudy.user.request.JoinRequest; -import com.swger.tddstudy.user.request.LoginRequest; -import com.swger.tddstudy.user.service.UserService; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; - -import javax.transaction.Transactional; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -@Transactional -public class UserTest { - - @Autowired - private UserService userService; - - public UserVO newUser() { - return new UserVO("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("회원가입 테스트 (USER)") - public void userJoinTest1() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO savedUserVO = userService.save(joinRequest); - assertThat(savedUserVO).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("회원가입 테스트 (ADMIN)") - public void userJoinTest2() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO savedUserVO = userService.saveAdmin(joinRequest); - assertThat(savedUserVO).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); - } - - @Test - @Rollback(value = true) - @DisplayName("로그인 테스트") - public void userloginTest1() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - userService.save(joinRequest); - // 로그인 객체 생성 후 로그인 - LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); - UserVO loginResult = userService.login(loginRequest); - // 로그인 결과가 UserVO면 성공 - assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (BRONZE -> SILVER)") - public void levelUpTest1() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.BRONZE).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.SILVER); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (SILVER -> GOLD)") - public void levelUpTest2() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.SILVER).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (GOLD -> GOLD)") - public void levelUpTest3() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.GOLD).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); - } - -} diff --git a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java b/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java deleted file mode 100644 index 7fe809e..0000000 --- a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.swger.tddstudy.user.controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.swger.tddstudy.user.domain.UserVO; -import com.swger.tddstudy.user.request.JoinRequest; -import com.swger.tddstudy.user.request.LoginRequest; -import com.swger.tddstudy.user.restController.UserRestController; -import com.swger.tddstudy.user.service.UserService; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(UserRestController.class) -public class UserControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - UserService userService; - - @Test - @DisplayName("회원가입 테스트 (양식 맞음)") - void joinTest1() throws Exception { - - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO UserVO = new UserVO(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); - - given(userService.save(joinRequest)).willReturn( - UserVO - ); - - String json = new ObjectMapper().writeValueAsString(joinRequest); - - mockMvc.perform(post("/api/user/join") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").exists()) - .andExpect(jsonPath("$.data.username").exists()) - .andExpect(jsonPath("$.data.password").exists()) - .andExpect(jsonPath("$.data.nickname").exists()) - .andExpect(jsonPath("$.data.userLevel").exists()) - .andExpect(jsonPath("$.data.type").exists()) - .andDo(print()); - - verify(userService).save(joinRequest); - } - - @Test - @DisplayName("회원가입 테스트 (양식 틀림)") - void joinTest2() throws Exception { - JoinRequest joinRequest = new JoinRequest("abc", "abc123", "nickname"); - UserVO UserVO = new UserVO(0L, "abc", "abc123", "nickname", "BRONZE", "USER"); - - given(userService.save(joinRequest)).willReturn( - UserVO - ); - - String json = new ObjectMapper().writeValueAsString(joinRequest); - - mockMvc.perform(post("/api/user/join") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").exists()) - .andDo(print()); - } - - - @Test - @DisplayName("로그인 테스트") - void loginTest1() throws Exception { - UserVO userVO = new UserVO(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); - - LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); - - given(userService.login(loginRequest)).willReturn( - userVO - ); - - String json = new ObjectMapper().writeValueAsString(loginRequest); - - mockMvc.perform(post("/api/user/login") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").exists()) - .andExpect(jsonPath("$.data.username").exists()) - .andExpect(jsonPath("$.data.password").exists()) - .andExpect(jsonPath("$.data.nickname").exists()) - .andExpect(jsonPath("$.data.userLevel").exists()) - .andExpect(jsonPath("$.data.type").exists()) - .andDo(print()); - - verify(userService).login(loginRequest); - } -} diff --git a/src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java b/src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java new file mode 100644 index 0000000..5be27ba --- /dev/null +++ b/src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java @@ -0,0 +1,177 @@ +package com.swger.tddstudy.user.restcontroller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.exception.LoginFailureException; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.request.LoginRequest; +import com.swger.tddstudy.user.restcontroller.UserRestController; +import com.swger.tddstudy.user.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(UserRestController.class) +public class UserRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + UserService userService; + + @Nested + @DisplayName("회원가입 컨트롤러 : ") + class JoinControllerTest { + + private JoinRequest newJoinRequest() { + + return JoinRequest.builder().username("abc").password("abcd1234!").nickname("nickname") + .build(); + } + + private JoinRequest newWrongJoinRequest() { + + return JoinRequest.builder().username("abc").password("abc123").nickname("nickname") + .build(); + } + + private UserDto newUserDto() { + + return UserDto.builder().id(0L).username("abc").password("abcd1234!") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); + } + + private UserDto newWrongUserDto() { + + return UserDto.builder().id(0L).username("abc").password("abc123") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); + } + + @Test + @DisplayName("양식에 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(userService.save(any(JoinRequest.class))).willReturn( + newUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newJoinRequest()); + + // when, then + mockMvc.perform(post("/api/user/join") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.username").exists()) + .andExpect(jsonPath("$.data.password").exists()) + .andExpect(jsonPath("$.data.nickname").exists()) + .andExpect(jsonPath("$.data.userLevel").exists()) + .andExpect(jsonPath("$.data.type").exists()) + .andDo(print()); + verify(userService, times(1)).save(any(JoinRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(userService.save(any(JoinRequest.class))).willReturn( + newWrongUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newWrongJoinRequest()); + + // when, then + mockMvc.perform(post("/api/user/join") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()) + .andDo(print()); + verify(userService, never()).save(newJoinRequest()); + } + } + + @Nested + @DisplayName("로그인 컨트롤러 테스트 : ") + class LoginControllerTest { + + private LoginRequest newLoginRequest() { + + return LoginRequest.builder().username("abc").password("abcd1234!").build(); + } + + private LoginRequest newWrongLoginRequest() { + + return LoginRequest.builder().username("abc").password("abc123").build(); + } + + private UserDto newUserDto() { + + return UserDto.builder().id(0L).username("abc").password("abcd1234!") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); + + } + + @Test + @DisplayName("양식에 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(userService.login(any(LoginRequest.class))).willReturn( + newUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newLoginRequest()); + + // when, then + mockMvc.perform(post("/api/user/login") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.username").exists()) + .andExpect(jsonPath("$.data.password").exists()) + .andExpect(jsonPath("$.data.nickname").exists()) + .andExpect(jsonPath("$.data.userLevel").exists()) + .andExpect(jsonPath("$.data.type").exists()) + .andDo(print()); + verify(userService, times(1)).login(any(LoginRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(userService.login(any(LoginRequest.class))).willThrow( + LoginFailureException.class); + String json = new ObjectMapper().writeValueAsString(newWrongLoginRequest()); + + // when, then + mockMvc.perform(post("/api/user/login") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()) + .andDo(print()); + verify(userService, never()).login(any(LoginRequest.class)); + } + } +} diff --git a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java new file mode 100644 index 0000000..fef9a88 --- /dev/null +++ b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java @@ -0,0 +1,275 @@ +package com.swger.tddstudy.user.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.swger.tddstudy.user.domain.User; +import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.domain.UserLevel; +import com.swger.tddstudy.user.domain.UserType; +import com.swger.tddstudy.user.exception.LoginFailureException; +import com.swger.tddstudy.user.repository.UserRepository; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.request.LoginRequest; +import java.util.Optional; +import javax.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Transactional +@ExtendWith(MockitoExtension.class) +public class UserServiceTest { + + @InjectMocks + private UserService userService; + + @Mock + private UserRepository userRepository; + + @Nested + @DisplayName("회원가입 서비스 : ") + class JoinServiceTest { + + private JoinRequest newJoinRequest() { + + return JoinRequest.builder().username("abc").password("abcd1234!").nickname("nickname") + .build(); + } + + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newAdmin() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.ADMIN).build(); + } + + @Test + @DisplayName("일반회원 회원가입 성공") + void Test1() { + + // given + given(userRepository.save(any(User.class))).willReturn(newUser()); + + // when + UserDto savedUserDto = userService.save(newJoinRequest()); + + // then + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); + verify(userRepository, times(1)).save(any(User.class)); + } + + @Test + @DisplayName("관리자 회원가입 성공") + void Test2() { + + // given + given(userRepository.save(any(User.class))).willReturn(newAdmin()); + + // when + UserDto savedUserDto = userService.saveAdmin(newJoinRequest()); + + // then + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); + verify(userRepository, times(1)).save(any(User.class)); + } + } + + @Nested + @DisplayName("로그인 서비스 : ") + class LoginServiceTest { + + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private LoginRequest newLoginRequest() { + + return LoginRequest.builder().username("abc").password("abcd1234!").build(); + } + + private LoginRequest newWrongLoginRequest() { + + return LoginRequest.builder().username("abc").password("abcd5678!").build(); + } + + @Test + @DisplayName("이름에 해당하는 회원이 있고, 비밀번호까지 일치하는 경우 성공") + void Test1() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findByUsername(any(String.class))).willReturn(optionalUser); + + // when + UserDto loginResult = userService.login(newLoginRequest()); + + // then + assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + + @Test + @DisplayName("이름이 일치하는 회원이 없을 경우 실패") + void Test2() { + + // given + given(userRepository.findByUsername(any(String.class))).willReturn(Optional.empty()); + + // when, then + Throwable exception = assertThrows(LoginFailureException.class, () -> { + userService.login(newLoginRequest()); + }); + assertEquals("일치하는 회원정보가 없습니다.", exception.getMessage()); + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + + @Test + @DisplayName("비밀번호가 일치하지 않는 경우 실패") + void Test3() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findByUsername(any(String.class))).willReturn(optionalUser); + + // when, then + Throwable exception = assertThrows(LoginFailureException.class, () -> { + userService.login(newWrongLoginRequest()); + }); + assertEquals("비밀번호가 일치하지 않습니다.", exception.getMessage()); + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + } + + @Nested + @DisplayName("레벨업 서비스 : ") + class LevelUpServiceTest { + + private User newUserBronze() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newUserSilver() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.SILVER).type(UserType.USER).build(); + } + + private User newUserGold() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.GOLD).type(UserType.USER).build(); + } + + @Test + @DisplayName("BRONZE -> SILVER") + void Test1() { + + // given + User user = newUserBronze(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.SILVER); + } + + @Test + @DisplayName("SILVER -> GOLD") + void Test2() { + + // given + User user = newUserSilver(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); + } + + @Test + @DisplayName("GOLD -> GOLD") + void Test3() { + + // given + User user = newUserGold(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); + } + } + + @Nested + @DisplayName("관리자 확인 : ") + class IsAdminTest { + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newAdmin() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.ADMIN).build(); + } + + @Test + @DisplayName("일반 회원은 false 반환") + void Test1() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findById(any(Long.class))).willReturn(optionalUser); + + // when + Boolean isAdmin = userService.isAdmin(0L); + + // then + assertThat(isAdmin).isFalse(); + verify(userRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("관리자는 true 반환") + void Test2() { + + // given + Optional optionalUser = Optional.ofNullable(newAdmin()); + given(userRepository.findById(any(Long.class))).willReturn(optionalUser); + + // when + Boolean isAdmin = userService.isAdmin(0L); + + // then + assertThat(isAdmin).isTrue(); + verify(userRepository, times(1)).findById(any(Long.class)); + } + } +} \ No newline at end of file