Skip to content

Commit

Permalink
test: 키오스크 상품 기능 테스트(Presentation Layer)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmxx219 committed May 2, 2024
1 parent 414b468 commit ffc5f63
Show file tree
Hide file tree
Showing 20 changed files with 683 additions and 63 deletions.
5 changes: 3 additions & 2 deletions Testing/cafekiosk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ repositories {

dependencies {
//Spring boot
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

//h2
runtimeOnly 'com.h2database:h2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class CafekioskApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sample.cafekiosk.spring.api;

import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class ApiControllerAdvice {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public ApiResponse<Object> bindException(BindException e) {
return ApiResponse.of(
HttpStatus.BAD_REQUEST,
e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),
null
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sample.cafekiosk.spring.api;

import org.springframework.http.HttpStatus;

import lombok.Getter;
import sample.cafekiosk.spring.api.service.product.response.ProductResponse;

@Getter
public class ApiResponse<T> {

private int code;
private HttpStatus status;
private String message;
private T data;

public ApiResponse(HttpStatus status, String message, T data) {
this.code = status.value();
this.status = status;
this.message = message;
this.data = data;
}

public static <T> ApiResponse<T> of(HttpStatus httpStatus, String message, T data) {
return new ApiResponse<>(httpStatus, message, data);
}

public static <T> ApiResponse<T> of(HttpStatus httpStatus, T data) {
return of(httpStatus, httpStatus.name(), data);
}

public static <T> ApiResponse<T> ok(T data) {
return of(HttpStatus.OK, HttpStatus.OK.name(), data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import sample.cafekiosk.spring.api.ApiResponse;
import sample.cafekiosk.spring.api.controller.order.request.OrderCreateRequest;
import sample.cafekiosk.spring.api.service.order.OrderService;
import sample.cafekiosk.spring.api.service.order.response.OrderResponse;
Expand All @@ -18,8 +20,8 @@ public class OrderController {
private final OrderService orderService;

@PostMapping("/api/v1/orders/new")
public OrderResponse createOrder(@RequestBody OrderCreateRequest request) {
public ApiResponse<OrderResponse> createOrder(@Valid @RequestBody OrderCreateRequest request) {
LocalDateTime registeredDateTime = LocalDateTime.now();
return orderService.createOrder(request, registeredDateTime);
return ApiResponse.ok(orderService.createOrder(request.toServiceRequest(), registeredDateTime));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@

import java.util.List;

import jakarta.validation.constraints.NotEmpty;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sample.cafekiosk.spring.api.service.order.request.OrderCreateServiceRequest;

@Getter
@NoArgsConstructor
public class OrderCreateRequest {

@NotEmpty(message = "상품 번호 리스트는 필수입니다.")
private List<String> productNumbers;

@Builder
public OrderCreateRequest(List<String> productNumbers) {
this.productNumbers = productNumbers;
}

public OrderCreateServiceRequest toServiceRequest() {
return OrderCreateServiceRequest.builder()
.productNumbers(productNumbers)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import sample.cafekiosk.spring.api.ApiResponse;
import sample.cafekiosk.spring.api.controller.product.dto.request.ProductCreateRequest;
import sample.cafekiosk.spring.api.service.product.ProductService;
import sample.cafekiosk.spring.api.service.product.response.ProductResponse;

Expand All @@ -15,9 +21,14 @@ public class ProductController {

private final ProductService productService;

@PostMapping("/api/v1/products/new")
public ApiResponse<ProductResponse> createProduct(@Valid @RequestBody ProductCreateRequest request) {
return ApiResponse.ok(productService.createProduct(request.toServiceRequest()));
}

@GetMapping("/api/v1/products/selling")
public List<ProductResponse> getSellingProducts() {
return productService.getSellingProducts();
public ApiResponse<List<ProductResponse>> getSellingProducts() {
return ApiResponse.ok(productService.getSellingProducts());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package sample.cafekiosk.spring.api.controller.product.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sample.cafekiosk.spring.api.service.product.request.ProductCreateServiceRequest;
import sample.cafekiosk.spring.domain.product.Product;
import sample.cafekiosk.spring.domain.product.ProductSellingStatus;
import sample.cafekiosk.spring.domain.product.ProductType;

@Getter
@NoArgsConstructor
public class ProductCreateRequest {

@NotNull(message = "상품 타입은 필수입니다.")
private ProductType type;

@NotNull(message = "상품 판매상태는 필수입니다.")
private ProductSellingStatus sellingStatus;

@NotBlank(message = "상품 이름은 필수입니다.")
private String name;

@Positive(message = "상품 가격은 양수여야 합니다.")
private int price;

@Builder
private ProductCreateRequest(ProductType type, ProductSellingStatus sellingStatus, String name, int price) {
this.type = type;
this.sellingStatus = sellingStatus;
this.name = name;
this.price = price;
}

public Product toEntity(String nextProductNumber) {
return Product.builder()
.productNumber(nextProductNumber)
.type(type)
.sellingStatus(sellingStatus)
.name(name)
.price(price)
.build();
}

public ProductCreateServiceRequest toServiceRequest() {
return ProductCreateServiceRequest.builder()
.type(type)
.sellingStatus(sellingStatus)
.name(name)
.price(price)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import lombok.RequiredArgsConstructor;
import sample.cafekiosk.spring.api.controller.order.request.OrderCreateRequest;
import sample.cafekiosk.spring.api.service.order.request.OrderCreateServiceRequest;
import sample.cafekiosk.spring.api.service.order.response.OrderResponse;
import sample.cafekiosk.spring.domain.order.Order;
import sample.cafekiosk.spring.domain.order.OrderRepository;
Expand All @@ -33,7 +34,7 @@ public class OrderService {
* 재고 감소 -> 대표적인 동시성 문제(고민)
* optimistic lock / pessimistic lock / ...
*/
public OrderResponse createOrder(OrderCreateRequest request, LocalDateTime registeredDateTime) {
public OrderResponse createOrder(OrderCreateServiceRequest request, LocalDateTime registeredDateTime) {
List<String> productNumbers = request.getProductNumbers();
List<Product> products = findProductsBy(productNumbers);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sample.cafekiosk.spring.api.service.order.request;

import java.util.List;

import jakarta.validation.constraints.NotEmpty;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class OrderCreateServiceRequest {

@NotEmpty(message = "상품 번호 리스트는 필수입니다.")
private List<String> productNumbers;

@Builder
public OrderCreateServiceRequest(List<String> productNumbers) {
this.productNumbers = productNumbers;
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,63 @@
package sample.cafekiosk.spring.api.service.product;

import static sample.cafekiosk.spring.domain.product.ProductSellingStatus.*;
import static sample.cafekiosk.spring.domain.product.ProductType.*;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;
import sample.cafekiosk.spring.api.controller.product.dto.request.ProductCreateRequest;
import sample.cafekiosk.spring.api.service.product.request.ProductCreateServiceRequest;
import sample.cafekiosk.spring.api.service.product.response.ProductResponse;
import sample.cafekiosk.spring.domain.product.Product;
import sample.cafekiosk.spring.domain.product.ProductRepository;
import sample.cafekiosk.spring.domain.product.ProductSellingStatus;

/**
* readOnly = true : 읽기전용
* CRUD 에서 CUD 동작 x, only Read
* JPA : CUD 스냅샷 저장, 변경감지 x (성능 향상)
*
* CQRS - Command와 Query를 분리하자
*/
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class ProductService {

private final ProductRepository productRepository;

@Transactional
public ProductResponse createProduct(ProductCreateServiceRequest request) {
// nextProductNumber -> DB에서 마지막 저장된 Product의 상품 번호를 읽어와서 +1
String nextProductNumber = createNextProductNumber();

Product product = request.toEntity(nextProductNumber);
Product savedProduct = productRepository.save(product);

return ProductResponse.of(savedProduct);
}

public List<ProductResponse> getSellingProducts() {
List<Product> products = productRepository.findAllBySellingStatusIn(ProductSellingStatus.forDisplay());
List<Product> products = productRepository.findAllBySellingStatusIn(forDisplay());

return products.stream()
.map(ProductResponse::of)
.collect(Collectors.toList());
}

private String createNextProductNumber() {
String latestProductNumber = productRepository.findLatestProductNumber();
if(latestProductNumber == null) {
return "001";
}

int latestProductNumberInt = Integer.parseInt(latestProductNumber);
int nextProductNumberInt = latestProductNumberInt + 1;

return String.format("%03d", nextProductNumberInt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package sample.cafekiosk.spring.api.service.product.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sample.cafekiosk.spring.domain.product.Product;
import sample.cafekiosk.spring.domain.product.ProductSellingStatus;
import sample.cafekiosk.spring.domain.product.ProductType;

@Getter
@NoArgsConstructor
public class ProductCreateServiceRequest {

@NotNull(message = "상품 타입은 필수입니다.")
private ProductType type;

@NotNull(message = "상품 판매상태는 필수입니다.")
private ProductSellingStatus sellingStatus;

@NotBlank(message = "상품 이름은 필수입니다.")
private String name;

@Positive(message = "상품 가격은 양수여야 합니다.")
private int price;

@Builder
private ProductCreateServiceRequest(ProductType type, ProductSellingStatus sellingStatus, String name, int price) {
this.type = type;
this.sellingStatus = sellingStatus;
this.name = name;
this.price = price;
}

public Product toEntity(String nextProductNumber) {
return Product.builder()
.productNumber(nextProductNumber)
.type(type)
.sellingStatus(sellingStatus)
.name(name)
.price(price)
.build();
}
}
Loading

0 comments on commit ffc5f63

Please sign in to comment.