test(user): 점주 서비스 - 주문 페이지 테스트 추가

Todo: JWT 구현 시 storeId 및 userId 가져오는 부분 기능 리팩터링 필요
- Spring Rest Docs 를 통한 테스트 추가
This commit is contained in:
bum12ark
2022-02-07 20:09:20 +09:00
parent 5c17696089
commit f05f1a1570
9 changed files with 274 additions and 45 deletions

View File

@@ -0,0 +1,74 @@
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:
[[overview]]
= 개요
[[overview-http-verbs]]
== HTTP 동사
본 REST API에서 사용하는 HTTP 동사(verbs)는 가능한한 표준 HTTP와 REST 규약을 따릅니다.
|===
| 동사 | 용례
| `GET`
| 리소스를 가져올 때 사용
| `POST`
| 새 리소스를 만들 때 사용
| `PUT`
| 기존 리소스를 수정할 때 사용
| `PATCH`
| 기존 리소스의 일부를 수정할 때 사용
| `DELETE`
| 기존 리소스를 삭제할 떄 사용
|===
[[overview-http-status-codes]]
== HTTP 상태 코드
본 REST API에서 사용하는 HTTP 상태 코드는 가능한 표준 HTTP와 REST 규약을 따릅니다.
|===
| 상태 코드 | 용례
| `200 OK`
| 요청을 성공적으로 처리함
| `201 Created`
| 새 리소스를 성공적으로 생성함. 응답의 `Location` 헤더에 해당 리소스의 URI가 담겨있다.
| `204 No Content`
| 기존 리소스를 성공적으로 수정함.
| `400 Bad Request`
| 잘못된 요청을 보낸 경우. 응답 본문에 더 오류에 대한 정보가 담겨있다.
| `404 Not Found`
| 요청한 리소스가 없음.
| `409 Conflict`
| 클라이언트의 요청이 서버의 상태와 충돌이 발생한 경우.
|===
[[snippets-write-convention]]
== snippets 작성 컨벤션
domain-httpRequestCode-etc
== 주문
=== 점주 서비스 - 주문 페이지
- 페이지 offset : 6
operation::orderMain-get[snippets='curl-request,http-request,http-response,request-parameters,response-fields']
=== 점주 서비스 - 주문 페이지 (잘못된 파라미터 형식)
operation::orderMain-get-badParameterException[snippets='curl-request,http-request,http-response,request-parameters,response-fields']

View File

@@ -0,0 +1,28 @@
package com.justpickup.orderservice.domain.order.dto;
import lombok.*;
import javax.validation.constraints.Pattern;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Data @NoArgsConstructor @AllArgsConstructor
public class OrderSearchCondition {
@Pattern(regexp = "^(19|20)\\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$",
message = "YYYY-MM-DD 형식에 맞게 작성되지 않았습니다.")
private String orderDate;
private Long lastOrderId;
public LocalDateTime getOrderStartTime() {
LocalDate orderTime = LocalDate.parse(orderDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return orderTime.atStartOfDay();
}
public LocalDateTime getOrderEndTime() {
LocalDate orderTime = LocalDate.parse(orderDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return LocalDateTime.of(orderTime, LocalTime.of(23, 59, 59));
}
}

View File

@@ -1,18 +1,16 @@
package com.justpickup.orderservice.domain.order.entity;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.domain.orderItem.entity.OrderItem;
import com.justpickup.orderservice.domain.transaction.entity.Transaction;
import com.justpickup.orderservice.global.entity.BaseEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.BatchSize;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Table(name = "orders")
@@ -28,6 +26,8 @@ public class Order extends BaseEntity {
private Long userCouponId;
private Long storeId;
private Long orderPrice;
private LocalDateTime orderTime;
@@ -40,6 +40,7 @@ public class Order extends BaseEntity {
@OneToOne(mappedBy = "order", fetch = FetchType.LAZY)
private Transaction transaction;
@BatchSize(size = 100)
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems;

View File

@@ -1,13 +1,13 @@
package com.justpickup.orderservice.domain.order.repository;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import com.justpickup.orderservice.domain.order.entity.Order;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import static com.justpickup.orderservice.domain.order.entity.QOrder.order;
@@ -18,18 +18,34 @@ public class OrderRepositoryCustom {
private final JPAQueryFactory queryFactory;
public List<Order> findOrderMainBetweenOrderDate(LocalDate orderDate) {
LocalDateTime start = orderDate.atStartOfDay();
LocalDateTime end = LocalDateTime.of(orderDate, LocalTime.of(23, 59, 59));
/*
SELECT *
FROM order
WHERE 조건문
AND id < 마지막 조회 ID
ORDER BY id desc
LIMIT 페이지 사이즈
*/
public List<Order> findOrderMain(OrderSearchCondition condition, Long storeId) {
LocalDateTime start = condition.getOrderStartTime();
LocalDateTime end = condition.getOrderEndTime();
return queryFactory
.selectFrom(order)
.join(order.orderItems).fetchJoin()
.join(order.transaction).fetchJoin()
.where(
order.orderTime.between(start, end)
order.orderTime.between(start, end),
order.storeId.eq(storeId),
orderIdLt(condition.getLastOrderId())
)
.orderBy(order.orderTime.desc())
.limit(6)
.distinct()
.fetch();
}
private BooleanExpression orderIdLt(Long lastOrderId) {
return lastOrderId != null ? order.id.lt(lastOrderId) : null;
}
}

View File

@@ -1,10 +1,10 @@
package com.justpickup.orderservice.domain.order.service;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import java.time.LocalDate;
import java.util.List;
public interface OrderService {
List<OrderDto> findOrderMain(LocalDate localDate);
List<OrderDto> findOrderMain(OrderSearchCondition condition, Long storeId);
}

View File

@@ -1,18 +1,18 @@
package com.justpickup.orderservice.domain.order.service;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import com.justpickup.orderservice.domain.order.repository.OrderRepository;
import com.justpickup.orderservice.domain.order.repository.OrderRepositoryCustom;
import com.justpickup.orderservice.global.client.store.GetItemResponse;
import com.justpickup.orderservice.global.client.store.StoreClient;
import com.justpickup.orderservice.global.client.user.UserClient;
import com.justpickup.orderservice.global.client.user.GetCustomerResponse;
import com.justpickup.orderservice.global.client.user.UserClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
@@ -28,23 +28,24 @@ public class OrderServiceImpl implements OrderService {
private final UserClient userClient;
@Override
public List<OrderDto> findOrderMain(LocalDate orderDate) {
public List<OrderDto> findOrderMain(OrderSearchCondition condition, Long storeId) {
// 주문 가져오기
List<OrderDto> orderDtoList = orderRepositoryCustom.findOrderMainBetweenOrderDate(orderDate)
.stream()
.map(OrderDto::createFullField)
.collect(Collectors.toList());
List<OrderDto> orderDtoList =
orderRepositoryCustom.findOrderMain(condition, storeId)
.stream()
.map(OrderDto::createFullField)
.collect(Collectors.toList());
// 사용자명 및 아이템 이름 가져오기
orderDtoList.forEach(orderDto -> {
GetCustomerResponse getCustomerResponse = userClient.getUser(orderDto.getUserId())
.getData();
GetCustomerResponse getCustomerResponse =
userClient.getUser(orderDto.getUserId()).getData();
orderDto.setUserName(getCustomerResponse.getUserName());
orderDto.getOrderItemDtoList()
.forEach(orderItemDto -> {
GetItemResponse getItemResponse = storeClient.getItem(orderItemDto.getItemId())
.getData();
GetItemResponse getItemResponse =
storeClient.getItem(orderItemDto.getItemId()).getData();
orderItemDto.setItemName(getItemResponse.getName());
});
});

View File

@@ -1,6 +1,7 @@
package com.justpickup.orderservice.domain.order.web;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.order.service.OrderService;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
@@ -16,8 +17,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,30 +29,21 @@ public class OrderController {
private final OrderService orderService;
@GetMapping("/orderMain")
public ResponseEntity orderMain(@Valid OrderMainRequest orderMainRequest) {
public ResponseEntity orderMain(@Valid OrderSearchCondition condition) {
// TODO: 2022/02/04 JWT 구현 시 변경 요망
Long userId = 1L;
Long storeId = 1L;
List<OrderDto> orderDto = orderService.findOrderMain(orderMainRequest.convertOrderTimeToLocalDate());
List<OrderDto> orderDto = orderService.findOrderMain(condition, storeId);
List<OrderMainResponse> orderMainResponses = orderDto.stream()
.map(OrderMainResponse::new)
.collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.OK)
.body(Result.createSuccessResult(orderMainResponses));
}
@Data @NoArgsConstructor @AllArgsConstructor
static class OrderMainRequest {
// yyyy-mm-dd 형태를 가지는 패턴 조사
@Pattern(regexp = "^(19|20)\\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$",
message = "YYYY-MM-DD 형식에 맞게 작성되지 않았습니다.")
private String orderTime;
public LocalDate convertOrderTimeToLocalDate() {
return LocalDate.parse(orderTime, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
@Data @NoArgsConstructor @AllArgsConstructor
static class OrderMainResponse {
private Long orderId;

View File

@@ -0,0 +1,18 @@
package com.justpickup.orderservice.config;
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
@TestConfiguration
public class TestConfig {
@Bean
public RestDocsMockMvcConfigurationCustomizer restDocsMockMvcConfigurationCustomizer() {
return configurer -> configurer.operationPreprocessors()
.withRequestDefaults(prettyPrint())
.withResponseDefaults(prettyPrint());
}
}

View File

@@ -0,0 +1,172 @@
package com.justpickup.orderservice.domain.order.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.orderservice.config.TestConfig;
import com.justpickup.orderservice.domain.order.dto.OrderDto;
import com.justpickup.orderservice.domain.order.dto.OrderSearchCondition;
import com.justpickup.orderservice.domain.order.entity.OrderStatus;
import com.justpickup.orderservice.domain.order.service.OrderService;
import com.justpickup.orderservice.domain.orderItem.dto.OrderItemDto;
import com.justpickup.orderservice.global.dto.Code;
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.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.time.LocalDateTime;
import java.util.List;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
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 static org.springframework.restdocs.request.RequestDocumentation.*;
@WebMvcTest(OrderController.class)
@Import(TestConfig.class)
@AutoConfigureRestDocs(uriHost = "127.0.0.1", uriPort = 8001)
class OrderControllerTest {
@Autowired
ObjectMapper objectMapper;
@Autowired
MockMvc mockMvc;
@MockBean
OrderService orderService;
@Test
@DisplayName("점주 서비스 - 주문 페이지")
void getOrderMain() throws Exception {
// GIVEN
String orderDate = "2022-02-03";
Long lastOrderId = 7L;
OrderSearchCondition condition = new OrderSearchCondition(orderDate, lastOrderId);
// TODO: 2022/02/07 jwt 구현 시 변경 요망
Long storeId = 1L;
given(orderService.findOrderMain(condition, storeId))
.willReturn(getOrderMainDtoList());
// WHEN
ResultActions actions = mockMvc.perform(get("/orderMain")
.param("orderDate", orderDate)
.param("lastOrderId", String.valueOf(lastOrderId))
);
// THEN
actions.andExpect(status().isOk())
.andExpect(jsonPath("code").value(Code.SUCCESS.name()))
.andExpect(jsonPath("message").isEmpty())
.andExpect(jsonPath("data").exists())
.andExpect(jsonPath("data[*].orderItemResponses").exists())
.andExpect(jsonPath("data[*].orderStatus").exists())
.andExpect(jsonPath("data[*].orderTime").exists())
.andDo(print())
.andDo(document("orderMain-get",
requestParameters(
parameterWithName("orderDate").description("주문 날짜 YYYY-MM-DD"),
parameterWithName("lastOrderId").optional().description("페이지의 마지막 주문 고유 번호")
),
responseFields(
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메시지"),
fieldWithPath("data[*].orderId").description("주문 고유 번호"),
fieldWithPath("data[*].userId").description("고객 고유 번호"),
fieldWithPath("data[*].userName").description("고객 이름"),
fieldWithPath("data[*].orderItemResponses[*].orderItemId").description("장바구니 고유번호"),
fieldWithPath("data[*].orderItemResponses[*].itemId").description("상품 고유번호"),
fieldWithPath("data[*].orderItemResponses[*].itemName").description("상품 이름"),
fieldWithPath("data[*].orderStatus").description("주문 상태"),
fieldWithPath("data[*].orderTime").description("주문 시간")
)
))
;
}
@Test
@DisplayName("점주 서비스 - 주문 페이지 (잘못된 파라미터 형식)")
void getOrderMainBadRequestException() throws Exception {
// GIVEN
String orderDate = "20220203";
Long lastOrderId = 7L;
// WHEN
ResultActions actions = mockMvc.perform(get("/orderMain")
.param("orderDate", orderDate)
.param("lastOrderId", String.valueOf(lastOrderId))
);
// THEN
actions.andExpect(status().isBadRequest())
.andExpect(jsonPath("code").value(Code.ERROR.name()))
.andExpect(jsonPath("message").isNotEmpty())
.andExpect(jsonPath("data").isEmpty())
.andDo(print())
.andDo(document("orderMain-get-badParameterException",
requestParameters(
parameterWithName("orderDate").description("주문 날짜 YYYY-MM-DD"),
parameterWithName("lastOrderId").optional().description("페이지의 마지막 주문 고유 번호")
),
responseFields(
fieldWithPath("code").description("결과 코드 SUCCESS/ERROR"),
fieldWithPath("message").description("메시지"),
fieldWithPath("data").description("데이터")
)
)
)
;
}
private List<OrderDto> getOrderMainDtoList() {
OrderItemDto orderItemDto_100 = OrderItemDto.builder()
.id(100L)
.itemId(100L)
.build();
orderItemDto_100.setItemName("아이템1");
OrderItemDto orderItemDto_101 = OrderItemDto.builder()
.id(101L)
.itemId(101L)
.build();
orderItemDto_101.setItemName("아이템2");
OrderItemDto orderItemDto_102 = OrderItemDto.builder()
.id(102L)
.itemId(102L)
.build();
orderItemDto_102.setItemName("아이템3");
OrderItemDto orderItemDto_103 = OrderItemDto.builder()
.id(103L)
.itemId(103L)
.build();
orderItemDto_103.setItemName("아이템2");
OrderDto orderDto_1 = OrderDto.builder()
.id(1L)
.userId(1L)
.orderItemDtoList(List.of(orderItemDto_100, orderItemDto_101))
.orderStatus(OrderStatus.PLACED)
.orderTime(LocalDateTime.of(2022, 2, 3, 14, 0, 0))
.build();
orderDto_1.setUserName("닉네임");
OrderDto orderDto_2 = OrderDto.builder()
.id(2L)
.userId(1L)
.orderItemDtoList(List.of(orderItemDto_102, orderItemDto_103))
.orderStatus(OrderStatus.CANCELED)
.orderTime(LocalDateTime.of(2022, 2, 3, 15, 0, 0))
.build();
orderDto_2.setUserName("닉네임");
return List.of(orderDto_1, orderDto_2);
}
}