diff --git a/README.md b/README.md index 2d08eab..b00dddb 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ # BE +
+

tech stack

+

+ + + + +
+ + + + + +
diff --git a/build.gradle b/build.gradle index c78b6e4..544c347 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,10 @@ dependencies { // Spring Batch implementation 'org.springframework.boot:spring-boot-starter-batch' testImplementation 'org.springframework.batch:spring-batch-test' + + //h2 + testImplementation 'com.h2database:h2' + } tasks.named('test') { diff --git a/src/main/java/com/github/commerce/config/batch/UpdateCustomerGradeScheduler.java b/src/main/java/com/github/commerce/config/batch/UpdateCustomerGradeScheduler.java index 22dda67..ce814a3 100644 --- a/src/main/java/com/github/commerce/config/batch/UpdateCustomerGradeScheduler.java +++ b/src/main/java/com/github/commerce/config/batch/UpdateCustomerGradeScheduler.java @@ -23,8 +23,7 @@ public class UpdateCustomerGradeScheduler { //매월 1일 오전 12:00:00 구매 @Autowired private Job updateCustomerGradeJob; - //TODO. 일단은 매일 오전 12시로 해놓고 시연할 때는 30초로 간격 줄이기 - @Scheduled(cron = "*/10 * * * * *", zone = "Asia/Seoul") //초 분 시 일 월 요일 (*: 매번) - 매월 1일 오전 12:00:00 구매 금액에 따른 회원 등급 조정 + @Scheduled(cron = "0 0 0 1 * *", zone = "Asia/Seoul") //초 분 시 일 월 요일 (*: 매번) - 매월 1일 오전 12:00:00 구매 금액에 따른 회원 등급 조정 public void updateCustomerGradeJobRun() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { JobParameters jobParameters = new JobParameters( diff --git a/src/main/java/com/github/commerce/config/security/WebSecurityConfig.java b/src/main/java/com/github/commerce/config/security/WebSecurityConfig.java index 810844e..b74b698 100644 --- a/src/main/java/com/github/commerce/config/security/WebSecurityConfig.java +++ b/src/main/java/com/github/commerce/config/security/WebSecurityConfig.java @@ -34,7 +34,7 @@ public class WebSecurityConfig { private final JwtUtil jwtUtil; private final UserDetailsServiceImpl userDetailsService; private static final String[] PERMIT_URL_ARRAY = { - "/","/v1/api/user/**","/v1/api/product/**","/v1/api/coupon","/GuerrillaCommerce", + "/","/v1/api/user/**","/v1/api/product/**","/v1/api/coupon","/GuerrillaCommerce", "/v1/api/navi", "/api/v2/**", "/swagger-ui.html", "/swagger/**","/swagger-resources/**", "/webjars/**", "/v2/api-docs" }; diff --git a/src/main/java/com/github/commerce/entity/Product.java b/src/main/java/com/github/commerce/entity/Product.java index 19faa7a..67544e7 100644 --- a/src/main/java/com/github/commerce/entity/Product.java +++ b/src/main/java/com/github/commerce/entity/Product.java @@ -1,5 +1,6 @@ package com.github.commerce.entity; +import com.github.commerce.web.dto.product.GetProductDto; import com.github.commerce.web.dto.product.ProductRequest; import com.google.gson.Gson; import lombok.*; @@ -17,6 +18,24 @@ @AllArgsConstructor @Builder @Entity +@SqlResultSetMapping( + name = "GetProductDtoMapping", + classes = @ConstructorResult( + targetClass = GetProductDto.class, + columns = { + @ColumnResult(name = "id", type = Long.class), + @ColumnResult(name = "name", type = String.class), + @ColumnResult(name = "price", type = Integer.class), + @ColumnResult(name = "created_at", type = LocalDateTime.class), + @ColumnResult(name = "product_category", type = String.class), + @ColumnResult(name = "age_category", type = String.class), + @ColumnResult(name = "gender_category", type = String.class), + @ColumnResult(name = "left_amount", type = Integer.class), + @ColumnResult(name = "thumbnail_url", type = String.class), + @ColumnResult(name = "shop_name", type = String.class) + } + ) +) @Table(name = "products") public class Product { @Id diff --git a/src/main/java/com/github/commerce/repository/product/ProductRepositoryCustom.java b/src/main/java/com/github/commerce/repository/product/ProductRepositoryCustom.java new file mode 100644 index 0000000..af06ae6 --- /dev/null +++ b/src/main/java/com/github/commerce/repository/product/ProductRepositoryCustom.java @@ -0,0 +1,241 @@ +package com.github.commerce.repository.product; + +import com.github.commerce.web.dto.product.GetProductDto; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import java.util.List; + +@Repository +public class ProductRepositoryCustom { + + @PersistenceContext + private EntityManager entityManager; + + public List findByProductCategorySortByCreatedAt( + String inputProductCategory, + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE p.product_category = :inputProductCategory " + + "AND (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.created_at DESC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputProductCategory", inputProductCategory); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List findByProductCategorySortByPrice( + String inputProductCategory, + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE p.product_category = :inputProductCategory " + + "AND (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.price ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputProductCategory", inputProductCategory); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List findByProductCategorySortById( + String inputProductCategory, + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE p.product_category = :inputProductCategory " + + "AND (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.id ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputProductCategory", inputProductCategory); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List findAllSortByCreatedAt( + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.created_at DESC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List findAllSortByPrice( + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.price ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List findAllSortById( + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "ORDER BY p.id ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + public List searchProductSortByPrice( + String searchToken, + String inputAgeCategory, + String inputGenderCategory, + Pageable pageable) { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "AND p.name LIKE :searchToken " + + "ORDER BY p.price ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("searchToken", searchToken); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + } + + + public List searchProductSortByCreatedAt(String searchToken, String inputAgeCategory, String inputGenderCategory, Pageable pageable) + { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "AND p.name LIKE :searchToken " + + "ORDER BY p.created_at DESC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("searchToken", searchToken); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + + } + + public List searchProductSortById(String searchToken, String inputAgeCategory, String inputGenderCategory, Pageable pageable) { + String sql = "SELECT p.id, p.name, p.price, p.created_at, " + + "p.product_category, p.age_category, p.gender_category, " + + "p.left_amount, p.thumbnail_url, s.shop_name " + + "FROM commerce.products p LEFT JOIN commerce.sellers s ON p.sellers_id = s.id " + + "WHERE (:inputAgeCategory IS NULL OR p.age_category = :inputAgeCategory) " + + "AND (:inputGenderCategory IS NULL OR p.gender_category = :inputGenderCategory) " + + "AND p.is_deleted = false " + + "AND p.name LIKE :searchToken " + + "ORDER BY p.id ASC"; + + Query query = entityManager.createNativeQuery(sql, "GetProductDtoMapping"); + query.setParameter("searchToken", searchToken); + query.setParameter("inputAgeCategory", inputAgeCategory); + query.setParameter("inputGenderCategory", inputGenderCategory); + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + //noinspection unchecked + return query.getResultList(); + + } +} diff --git a/src/main/java/com/github/commerce/service/cart/CartService.java b/src/main/java/com/github/commerce/service/cart/CartService.java index ff5adbb..6b289bf 100644 --- a/src/main/java/com/github/commerce/service/cart/CartService.java +++ b/src/main/java/com/github/commerce/service/cart/CartService.java @@ -4,7 +4,6 @@ import com.github.commerce.entity.Product; import com.github.commerce.entity.User; import com.github.commerce.repository.cart.CartRepository; -import com.github.commerce.repository.product.ProductRepository; import com.github.commerce.service.cart.util.ValidatCartMethod; import com.github.commerce.web.dto.cart.CartDto; import com.github.commerce.web.dto.cart.CartRmqDto; @@ -21,9 +20,6 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -36,7 +32,6 @@ public class CartService { private final CartRepository cartRepository; private final ValidatCartMethod validatCartMethod; private final RabbitTemplate rabbitTemplate; - private final ProductRepository productRepository; @Transactional(readOnly = true) public List>> getAllCarts(Long userId){ @@ -159,12 +154,4 @@ public String deleteOne(Long cartId, Long userId){ return validatedCart.getId() + "번 장바구니 삭제"; } - public LocalDateTime getKoreanTime(){ - ZoneId koreanZone = ZoneId.of("Asia/Seoul"); - ZonedDateTime koreanTime = ZonedDateTime.now(koreanZone); - - // Convert it to LocalDateTime - return koreanTime.toLocalDateTime(); - - } } diff --git a/src/main/java/com/github/commerce/service/chat/ChatService.java b/src/main/java/com/github/commerce/service/chat/ChatService.java index daf3dab..b39dfe1 100644 --- a/src/main/java/com/github/commerce/service/chat/ChatService.java +++ b/src/main/java/com/github/commerce/service/chat/ChatService.java @@ -37,21 +37,10 @@ public ChatDto getChatRoom(String customRoomId){ Chat chatEntity = chatRepository.findByCustomRoomId(customRoomId).orElseThrow(()->new ChatException(ChatErrorCode.ROOM_NOT_FOUND)); Map> chats = chatEntity.getChats(); - Map> sortedChats = chats.entrySet() - .stream() - .sorted((entry1, entry2) -> { - String key1DateTimeStr = entry1.getKey().substring(0, 19); - String key2DateTimeStr = entry2.getKey().substring(0, 19); - LocalDateTime date1 = LocalDateTime.parse(key1DateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - LocalDateTime date2 = LocalDateTime.parse(key2DateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - return date1.compareTo(date2); - - }) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); - + Map> sortedChats = sortChatsByDate(chats); chatEntity.setChats(sortedChats); - return ChatDto.fromEntity(chatEntity); + return ChatDto.fromEntity(chatEntity); }; @@ -63,7 +52,7 @@ public Map getSellerChatList(Long sellerId, Long productId) { List chatList = chatRepositoryCustom.getSellerChatList(sellerId, productId); List resultList = new ArrayList<>(); chatList.forEach(chat -> { - if(chat.getChats() == null){ + if(chat.getChats() == null || chat.getChats().isEmpty()){ return; } Map productInfo = getProductImageAndName(chat.getProductId()); @@ -85,7 +74,7 @@ public Map getUserChatList(Long userId, Long sellerId) { List chatList = chatRepositoryCustom.getUserChatList(userId, sellerId); List resultList = new ArrayList<>(); chatList.forEach(chat -> { - if(chat.getChats() == null){ + if(chat.getChats() == null || chat.getChats().isEmpty()){ return; } Map productInfo = getProductImageAndName(chat.getProductId()); @@ -105,29 +94,39 @@ public void cleanupOldChats() { chatRepositoryCustom.cleanupOldChats(); } - private Map getProductImageAndName(Long productId){ - Optional productOptional = productRepository.findById(productId); + protected Map getProductImageAndName(Long productId){ + Product product = productRepository.findById(productId).orElseThrow(()-> new ChatException(ChatErrorCode.THIS_PRODUCT_DOES_NOT_EXIST)); Map result = new HashMap<>(); - if (productOptional.isPresent()) { - String urlList = productOptional.get().getThumbnailUrl(); - String productName = productOptional.get().getName(); - String[] urls = urlList.split(","); - - if (urls.length > 0) { - result.put("url", urls[0]); - } + String url = product.getThumbnailUrl(); + String productName = product.getName(); + //String[] urls = urlList.split(","); +// if (urls.length > 0) { +// result.put("url", urls[0]); +// } + result.put("url", url); result.put("name", productName); - } - return result.isEmpty() ? null : result; + + return result; } - private String getSellerImage(Long sellerId){ + protected String getSellerImage(Long sellerId){ Seller seller = sellerRepository.findById(sellerId).orElseThrow(()-> new ChatException(ChatErrorCode.SELLER_NOT_FOUND)); return seller.getShopImageUrl(); } + protected Map> sortChatsByDate(Map> chats) { + return chats.entrySet() + .stream() + .sorted(Comparator.comparing(entry -> extractDateTimeFromKey(entry.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); + //정렬된 순서대로 데이터를 유지하려면 LinkedHashMap이 필요합니다. 이렇게 하지 않으면, 정렬 순서가 Map에 저장될 때 무시될 수 있습니다. + } + private LocalDateTime extractDateTimeFromKey(String key) { + String dateTimeStr = key.substring(0, 19); + return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } } diff --git a/src/main/java/com/github/commerce/service/order/OrderService.java b/src/main/java/com/github/commerce/service/order/OrderService.java index d96d618..2f5639d 100644 --- a/src/main/java/com/github/commerce/service/order/OrderService.java +++ b/src/main/java/com/github/commerce/service/order/OrderService.java @@ -2,6 +2,7 @@ import com.github.commerce.entity.*; import com.github.commerce.repository.order.OrderRepository; +import com.github.commerce.service.order.util.OrderCacheMethod; import com.github.commerce.service.order.util.ValidateOrderMethod; import com.github.commerce.web.dto.order.OrderDto; import com.github.commerce.web.dto.order.OrderRmqDto; @@ -17,12 +18,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; @Slf4j @@ -32,9 +28,11 @@ public class OrderService { private final OrderRepository orderRepository; private final ValidateOrderMethod validateOrderMethod; private final RabbitTemplate rabbitTemplate; + private final OrderCacheMethod orderCacheMethod; @Transactional public List createOrder(List requestList, Long userId) { + String orderTag = UUID.randomUUID().toString().substring(0, 18); ListnameList = new ArrayList<>(); for(PostOrderDto.PostOrderRequest request : requestList) { @@ -62,10 +60,12 @@ public List createOrder(List requestList, .createdAt(LocalDateTime.now()) .quantity(inputQuantity) .orderState(1) + .orderTag(orderTag) .totalPrice((long) (validatedProduct.getPrice() * inputQuantity)) .options(inputOptionsJson) .build() ); + orderCacheMethod.putOrderTag(userId, orderTag); rabbitTemplate.convertAndSend("exchange", "postOrder", newOrder); nameList.add(validatedProduct.getName()+ "상품 주문요청"); @@ -74,11 +74,11 @@ public List createOrder(List requestList, } @Transactional - public String createOrderFromCart(List cartIdList, Long userId) { + public List createOrderFromCart(List cartIdList, Long userId) { User validatedUser = validateOrderMethod.validateUser(userId); - String orderTag = UUID.randomUUID().toString().substring(0, 19); + String orderTag = UUID.randomUUID().toString().substring(0, 18); - ListnameList = new ArrayList<>(); + ListnameList = new ArrayList<>(); cartIdList.forEach(cartId -> { Cart validatedCart = validateOrderMethod.validateCart(cartId, userId); Product product = validatedCart.getProducts(); @@ -86,7 +86,6 @@ public String createOrderFromCart(List cartIdList, Long userId) { validateOrderMethod.validateStock(validatedCart.getQuantity(), product); validatedCart.setOrderTag(orderTag); - //log.info("ordertag={}", validatedCart.getOrderTag()); OrderRmqDto newOrder = OrderRmqDto.fromEntity( Order.builder() @@ -102,12 +101,13 @@ public String createOrderFromCart(List cartIdList, Long userId) { .options(validatedCart.getOptions()) .build() ); + orderCacheMethod.putOrderTag(userId, orderTag); rabbitTemplate.convertAndSend("exchange", "postOrder", newOrder); nameList.add(product.getName()+ "상품 주문요청"); }); - return orderTag; + return nameList; } @@ -115,11 +115,17 @@ public String createOrderFromCart(List cartIdList, Long userId) { @Transactional(readOnly = true) public List>> getPurchasedOrderList(Long userId){ List sortedOrders = orderRepository.findPaidOrderByUserIdSortByCreatedAtDesc(userId); - // 카트 레코드를 날짜별로 그룹화 - Map> groupedOrders = sortedOrders.stream() - .collect(Collectors.groupingBy( - order -> order.getCreatedAt().toLocalDate(), - Collectors.mapping(OrderDto::fromEntity, Collectors.toList()))); + + Map> groupedOrders = new HashMap<>(); + + for (Order order : sortedOrders) { + LocalDate orderDate = order.getCreatedAt().toLocalDate(); + OrderDto orderDto = OrderDto.fromEntity(order); + + groupedOrders + .computeIfAbsent(orderDate, k -> new ArrayList<>()) + .add(orderDto); + } List>> result = new ArrayList<>(); result.add(groupedOrders); @@ -131,10 +137,16 @@ public List>> getSellerOrderList(Long userId) { Seller seller = validateOrderMethod.validateSellerByUserId(userId); List sortedOrders = orderRepository.findPaidOrderBySellerIdSortByCreatedAtDesc(seller.getId()); - Map> groupedOrders = sortedOrders.stream() - .collect(Collectors.groupingBy( - order -> order.getCreatedAt().toLocalDate(), - Collectors.mapping(OrderDto::fromEntity, Collectors.toList()))); + Map> groupedOrders = new HashMap<>(); + + for (Order order : sortedOrders) { + LocalDate orderDate = order.getCreatedAt().toLocalDate(); + OrderDto orderDto = OrderDto.fromEntity(order); + + groupedOrders + .computeIfAbsent(orderDate, k -> new ArrayList<>()) + .add(orderDto); + } List>> result = new ArrayList<>(); result.add(groupedOrders); @@ -145,17 +157,15 @@ public List>> getSellerOrderList(Long userId) { @Transactional(readOnly = true) public List getOrderList(Long userId){ - List sortedOrders = orderRepository.findAllByUsersIdOrderByCreatedAtDesc(userId); - // 카트 레코드를 날짜별로 그룹화 -// Map> groupedOrders = sortedOrders.stream() -// .collect(Collectors.groupingBy( -// order -> order.getCreatedAt().toLocalDate(), -// Collectors.mapping(OrderDto::fromEntity, Collectors.toList()))); -// -// List>> result = new ArrayList<>(); -// result.add(groupedOrders); + validateOrderMethod.validateUser(userId); + String orderTag = orderCacheMethod.getOrderTag(userId); + List orderList = orderRepository.findByUsersIdAndOrderTagAndOrderState(userId, orderTag, 1); + List orderDtoList = new ArrayList<>(); + orderList.forEach( + order -> orderDtoList.add(OrderDto.fromEntity(order)) + ); - return sortedOrders.stream().map(OrderDto::fromEntity).collect(Collectors.toList()); + return orderDtoList; } @Transactional(readOnly = true) @@ -219,21 +229,6 @@ public String deleteOne(Long orderId, Long userId) { return validatedOrder.getId() + "번 주문 삭제"; } - @Transactional - public List getOrderListFromCart(Long userId, String orderTag) { - validateOrderMethod.validateUser(userId); - List orderList = orderRepository.findByUsersIdAndOrderTagAndOrderState(userId, orderTag, 1); - return orderList.stream().map(OrderDto::fromEntity).collect(Collectors.toList()); - } - - @Transactional - public List getOrderListFromProduct(Long userId, Long productId) { - validateOrderMethod.validateUser(userId); - LocalDateTime adjustTime = getKoreanTime().minusSeconds(5); - List orderList = orderRepository.findByUsersIdAndProductsIdWithTime(userId, productId, adjustTime); - return orderList.stream().map(OrderDto::fromEntity).collect(Collectors.toList()); - } - // // 판매자에게 SSE 이벤트를 발생시키는 메서드 // private void sendEventToSeller(Long sellerUserId, String message) { // String eventData = "data: {" @@ -260,13 +255,5 @@ public List getOrderListFromProduct(Long userId, Long productId) { // .block(); // 블로킹 방식으로 요청을 보냅니다. // } - public LocalDateTime getKoreanTime(){ - ZoneId koreanZone = ZoneId.of("Asia/Seoul"); - ZonedDateTime koreanTime = ZonedDateTime.now(koreanZone); - - // Convert it to LocalDateTime - return koreanTime.toLocalDateTime(); - - } } diff --git a/src/main/java/com/github/commerce/service/order/util/OrderCacheMethod.java b/src/main/java/com/github/commerce/service/order/util/OrderCacheMethod.java new file mode 100644 index 0000000..1a1e9e8 --- /dev/null +++ b/src/main/java/com/github/commerce/service/order/util/OrderCacheMethod.java @@ -0,0 +1,22 @@ +package com.github.commerce.service.order.util; + +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class OrderCacheMethod { + //userId, orderTag + private final Map orderTagStorage = new ConcurrentHashMap<>(); + + public void putOrderTag(Long userId, String orderTag) { + orderTagStorage.put(userId, orderTag); + } + + public String getOrderTag(Long userId) { + return orderTagStorage.get(userId); + } + +} diff --git a/src/main/java/com/github/commerce/service/product/ProductService.java b/src/main/java/com/github/commerce/service/product/ProductService.java index 302431b..515b086 100644 --- a/src/main/java/com/github/commerce/service/product/ProductService.java +++ b/src/main/java/com/github/commerce/service/product/ProductService.java @@ -4,6 +4,7 @@ import com.github.commerce.repository.order.OrderRepository; import com.github.commerce.repository.product.ProductContentImageRepository; import com.github.commerce.repository.product.ProductRepository; +import com.github.commerce.repository.product.ProductRepositoryCustom; import com.github.commerce.repository.review.ReviewRepository; import com.github.commerce.service.product.exception.ProductErrorCode; import com.github.commerce.service.product.exception.ProductException; @@ -28,6 +29,7 @@ @RequiredArgsConstructor @Service public class ProductService { + private final ProductRepositoryCustom productRepositoryCustom; private final ProductRepository productRepository; private final ProductImageUploadService productImageUploadService; private final ValidateProductMethod validateProductMethod; @@ -44,13 +46,11 @@ public List searchProducts(Integer pageNumber, String searchWord, String searchToken = "%"+searchWord+"%"; if (Objects.equals(sortBy, "price")) { - return productRepository.searchProductSortByPrice(searchToken, inputAgeCategory,inputGenderCategory, pageable); - // return productList.stream().map(ProductDto::fromEntity).collect(Collectors.toList()); + return productRepositoryCustom.searchProductSortByPrice(searchToken, inputAgeCategory,inputGenderCategory, pageable); }else if(Objects.equals(sortBy, "createdAt")) { - return productRepository.searchProductSortByCreatedAt(searchToken,inputAgeCategory,inputGenderCategory, pageable); - // return productList.stream().map(ProductDto::fromEntity).collect(Collectors.toList()); + return productRepositoryCustom.searchProductSortByCreatedAt(searchToken,inputAgeCategory,inputGenderCategory, pageable); }else { - return productRepository.searchProductSortById(searchToken,inputAgeCategory,inputGenderCategory, pageable); + return productRepositoryCustom.searchProductSortById(searchToken,inputAgeCategory,inputGenderCategory, pageable); } } @@ -275,13 +275,14 @@ public List getProductsByCategory(Integer pageNumber, String prod String inputGenderCategory = GenderCategoryEnum.switchCategory(genderCategory); if (Objects.equals(sortBy, "createdAt")) { - return productRepository.findByProductCategorySortByCreatedAt(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); - // return products.stream().map(ProductDto::fromObjectResult).collect(Collectors.toList()); + return productRepositoryCustom.findByProductCategorySortByCreatedAt(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); + }else if(Objects.equals(sortBy, "price")){ - return productRepository.findByProductCategorySortByPrice(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); - // return products.stream().map(ProductDto::fromObjectResult).collect(Collectors.toList()); + return productRepositoryCustom.findByProductCategorySortByPrice(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); + } else{ - return productRepository.findByProductCategorySortById(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); + return productRepositoryCustom.findByProductCategorySortById(inputProductCategory, inputAgeCategory, inputGenderCategory, pageable); + } } @@ -292,13 +293,13 @@ public List getProductList(Integer pageNumber, String ageCategory String inputGenderCategory = GenderCategoryEnum.switchCategory(genderCategory); if (Objects.equals(sortBy, "price")) { - return productRepository.findAllSortByPrice(inputAgeCategory, inputGenderCategory, pageable); - //return products.stream().map(ProductDto::fromEntity).collect(Collectors.toList()); + return productRepositoryCustom.findAllSortByPrice(inputAgeCategory, inputGenderCategory, pageable); + } else if(Objects.equals(sortBy, "createdAt")) { - return productRepository.findAllSortByCreatedAt(inputAgeCategory, inputGenderCategory, pageable); - //return products.stream().map(ProductDto::fromEntity).collect(Collectors.toList()); + return productRepositoryCustom.findAllSortByCreatedAt(inputAgeCategory, inputGenderCategory, pageable); + } else { - return productRepository.findAllSortById(inputAgeCategory, inputGenderCategory, pageable); + return productRepositoryCustom.findAllSortById(inputAgeCategory, inputGenderCategory, pageable); } } diff --git a/src/main/java/com/github/commerce/web/controller/ServerCheckController.java b/src/main/java/com/github/commerce/web/controller/ServerCheckController.java index d9f19ce..09b1c60 100644 --- a/src/main/java/com/github/commerce/web/controller/ServerCheckController.java +++ b/src/main/java/com/github/commerce/web/controller/ServerCheckController.java @@ -9,7 +9,7 @@ @RestController public class ServerCheckController { - @GetMapping("v1/api/navi") + @GetMapping("/") public ResponseEntity getHealthCheck(){ return ResponseEntity.ok("살아있어요!!"); } diff --git a/src/main/java/com/github/commerce/web/controller/order/OrderController.java b/src/main/java/com/github/commerce/web/controller/order/OrderController.java index c6a1cd3..6052546 100644 --- a/src/main/java/com/github/commerce/web/controller/order/OrderController.java +++ b/src/main/java/com/github/commerce/web/controller/order/OrderController.java @@ -55,7 +55,7 @@ public ResponseEntity> createOrder( @ApiResponse(code = 400, message = "Bad Request") }) @PostMapping("/cart") - public ResponseEntity createOrderFromCart( + public ResponseEntity> createOrderFromCart( @AuthenticationPrincipal UserDetailsImpl userDetails, //@PathVariable Long cartId @RequestBody PostOrderDto.PostOrderRequestFromCart request @@ -92,51 +92,51 @@ public ResponseEntity> getOrderList( } } - /** - * 해당 장바구니를 통한 주문들 조회 - * @param - * @return - */ - @ApiOperation(value = "해당 장바구니를 통한 주문들 조회") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success", response = List.class), - @ApiResponse(code = 400, message = "Bad Request") - }) - @GetMapping("/cart/{orderTag}") - public ResponseEntity> getOrderListFromCart( - @AuthenticationPrincipal UserDetailsImpl userDetails, - @PathVariable String orderTag - ){ - Long userId = userDetails.getUser().getId(); - - return ResponseEntity.ok( - orderService.getOrderListFromCart(userId, orderTag) - ); - - } - - /** - * 유저의 해당 상품의 주문들 조회 - * @param - * @return - */ - @ApiOperation(value = "해당 장바구니를 통한 주문들 조회") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Success", response = List.class), - @ApiResponse(code = 400, message = "Bad Request") - }) - @GetMapping("/product/{productId}") - public ResponseEntity> getOrderListFromProduct( - @AuthenticationPrincipal UserDetailsImpl userDetails, - @PathVariable Long productId - ){ - Long userId = userDetails.getUser().getId(); - - return ResponseEntity.ok( - orderService.getOrderListFromProduct(userId, productId) - ); +// /** +// * 해당 장바구니를 통한 주문들 조회 +// * @param +// * @return +// */ +// @ApiOperation(value = "해당 장바구니를 통한 주문들 조회") +// @ApiResponses(value = { +// @ApiResponse(code = 200, message = "Success", response = List.class), +// @ApiResponse(code = 400, message = "Bad Request") +// }) +// @GetMapping("/cart/{orderTag}") +// public ResponseEntity> getOrderListFromCart( +// @AuthenticationPrincipal UserDetailsImpl userDetails, +// @PathVariable String orderTag +// ){ +// Long userId = userDetails.getUser().getId(); +// +// return ResponseEntity.ok( +// orderService.getOrderListFromCart(userId, orderTag) +// ); +// +// } - } +// /** +// * 유저의 해당 상품의 주문들 조회 +// * @param +// * @return +// */ +// @ApiOperation(value = "해당 장바구니를 통한 주문들 조회") +// @ApiResponses(value = { +// @ApiResponse(code = 200, message = "Success", response = List.class), +// @ApiResponse(code = 400, message = "Bad Request") +// }) +// @GetMapping("/product/{productId}") +// public ResponseEntity> getOrderListFromProduct( +// @AuthenticationPrincipal UserDetailsImpl userDetails, +// @PathVariable Long productId +// ){ +// Long userId = userDetails.getUser().getId(); +// +// return ResponseEntity.ok( +// orderService.getOrderListFromProduct(userId, productId) +// ); +// +// } @ApiOperation(value = "구매자의 구매내역 조회, 로그인필요") @ApiResponses(value = { diff --git a/src/main/java/com/github/commerce/web/controller/shop/ShopController.java b/src/main/java/com/github/commerce/web/controller/shop/ShopController.java index 287e515..0512baf 100644 --- a/src/main/java/com/github/commerce/web/controller/shop/ShopController.java +++ b/src/main/java/com/github/commerce/web/controller/shop/ShopController.java @@ -18,14 +18,14 @@ @Slf4j @RequiredArgsConstructor @RestController -@RequestMapping("v1/api/order/seller") -@Api(tags = "판매중인 내역 조회 API") +@RequestMapping("v1/api/product/seller") +@Api(tags = "판매중인 상품 조회 API") public class ShopController { private final ShopService shopService; @ApiOperation(value = "판매자의 판매중인 내역 조회, 로그인 필요") - @GetMapping("/selling-product") + @GetMapping public ResponseEntity> getSellingProducts(@AuthenticationPrincipal UserDetailsImpl userDetails){ Long userId = userDetails.getUser().getId(); return ResponseEntity.ok(shopService.getSellingProducts(userId)); diff --git a/src/test/java/com/github/commerce/CommerceApplicationTests.java b/src/test/java/com/github/commerce/CommerceApplicationTests.java index 9419eb3..a613200 100644 --- a/src/test/java/com/github/commerce/CommerceApplicationTests.java +++ b/src/test/java/com/github/commerce/CommerceApplicationTests.java @@ -2,7 +2,11 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +@TestPropertySource(locations = "classpath:application-test.yml") +@ActiveProfiles("test") @SpringBootTest class CommerceApplicationTests { diff --git a/src/test/java/com/github/commerce/service/cart/CartServiceTest.java b/src/test/java/com/github/commerce/service/cart/CartServiceTest.java new file mode 100644 index 0000000..336aa2d --- /dev/null +++ b/src/test/java/com/github/commerce/service/cart/CartServiceTest.java @@ -0,0 +1,240 @@ +package com.github.commerce.service.cart; + +import com.github.commerce.entity.Cart; +import com.github.commerce.entity.Product; +import com.github.commerce.entity.Seller; +import com.github.commerce.entity.User; +import com.github.commerce.repository.cart.CartRepository; +import com.github.commerce.service.cart.util.ValidatCartMethod; +import com.github.commerce.web.dto.cart.CartDto; +import com.github.commerce.web.dto.cart.CartRmqDto; +import com.github.commerce.web.dto.cart.PostCartDto; +import com.github.commerce.web.dto.cart.PutCartDto; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Stream; + +import static org.mockito.Mockito.*; + +@TestPropertySource(locations = "classpath:application-test.yml") +@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정 +class CartServiceTest { + + @InjectMocks + private CartService cartService; + + @Mock + private ValidatCartMethod validatCartMethod; + + @Mock + private RabbitTemplate rabbitTemplate; + + @Mock + private CartRepository cartRepository; + + static Stream provideCartTestData() { + return Stream.of( + Arguments.of(1L, 2), + Arguments.of(2L, 3), + Arguments.of(3L, 4) + ); + } + + static Stream provideCartTestDataForModification() { + return Stream.of( + Arguments.of(1L, 1L, 2, Arrays.asList("Option1")), + Arguments.of(2L, 2L, 3, Arrays.asList("Option2")), + Arguments.of(3L, 3L, 4, Arrays.asList("Option3")) + ); + } + +//This is redundant when using @Mock along with @ExtendWith(MockitoExtension.class). You should choose one approach and stick with it. +// @BeforeEach +// public void setup() { +// System.out.println("각각의 테스트 실행 전 수행"); +// MockitoAnnotations.openMocks(this); +// } + + @BeforeAll // static으로 만들어야 한다 + static void beforeAll() { + System.out.println("모든 테스트 실행 전 최초로 수행"); + } + + @AfterAll // static으로 만들어야 한다 + static void afterAll() { + System.out.println("모든 테스트 실행 후 마지막으로 수행"); + } + + @AfterEach + void tearDown() { + System.out.println("각각 테스트 실행 후 수행\n"); } + + + @Test + @DisplayName("모든 장바구니 가져오기") + void getAllCarts() { + // given + Long userId = 1L; + Cart cart = new Cart(); + cart.setCreatedAt(LocalDateTime.of(2022, 9, 26, 12, 0)); + cart.setProducts(Product.builder().price(1000).seller(new Seller()).build()); + cart.setUsers(new User()); + cart.setQuantity(1); + + List cartList = Collections.singletonList(cart); + when(cartRepository.findAllByUsersIdOrderByCreatedAtDesc(userId)).thenReturn(cartList); + + // when + List>> result = cartService.getAllCarts(userId); + + // then + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals(1, result.get(0).size()); + Assertions.assertTrue(result.get(0).containsKey(LocalDate.of(2022, 9, 26))); + } + + @Test + @DisplayName("커서를 사용하여 장바구니 가져오기") + void getAllCartWithCursor() { + // given + Long userId = 1L; + Long cursorId = 2L; + int pageSize = 10; + Cart cart = new Cart(); + cart.setCreatedAt(LocalDateTime.of(2022, 9, 26, 12, 0)); + cart.setProducts(Product.builder().price(1000).seller(new Seller()).build()); + cart.setUsers(new User()); + cart.setQuantity(1); + + Page page = new PageImpl<>(Collections.singletonList(cart), PageRequest.of(0, pageSize), 1); + when(cartRepository.findAllByUserId(userId, cursorId, PageRequest.of(0, pageSize))).thenReturn(page); + + // when + Page result = cartService.getAllCartWithCursor(userId, cursorId); + + // then + Assertions.assertEquals(1, result.getTotalElements()); + } + + @ParameterizedTest + @MethodSource("provideCartTestData") + //@ValueSource(longs = {1L, 2L, 3L}) 이 방식은 하나의 인자에 대해 여러 값을 제공할 뿐이다. + @DisplayName("장바구니 추가") + void addToCart(Long productId, Integer quantity) { + PostCartDto.PostCartRequest request = new PostCartDto.PostCartRequest(productId, quantity, Arrays.asList("Option1")); + List requestList = new ArrayList<>(); + requestList.add(request); + + Long userId = 1L; + User user = new User(); + Product product = new Product(); + product.setName("Product"+productId); + + // Mock 객체의 메소드가 호출될 때 어떤 행동을 할지 지정합니다. + when(validatCartMethod.validateUser(userId)).thenReturn(user); + when(validatCartMethod.validateProduct(productId)).thenReturn(product); + doNothing().when(validatCartMethod).validateDuplicateCart(any(), anyLong(), anyString()); + doNothing().when(validatCartMethod).validateStock(anyInt(), any()); + doNothing().when(rabbitTemplate).convertAndSend(anyString(), anyString(), any(CartRmqDto.class)); + + // When + List result = cartService.addToCart(requestList, userId); + + // Then + Assertions.assertEquals(requestList.size(), result.size()); + Assertions.assertEquals("Product"+productId+"상품을 장바구로 추가합니다.", result.get(0)); + + //Mock 객체의 메소드가 특정 방식으로 호출되었는지 검증합니다. + verify(validatCartMethod).validateUser(userId); + verify(validatCartMethod).validateProduct(productId); + verify(rabbitTemplate).convertAndSend(anyString(), anyString(), any(CartRmqDto.class)); + } + + @ParameterizedTest + @MethodSource("provideCartTestDataForModification") + @DisplayName("장바구니 수정") + void modifyCart(Long cartId, Long productId, Integer quantity, List options) { + PutCartDto.PutCartRequest request = new PutCartDto.PutCartRequest(cartId, productId, quantity, options); + List requestList = new ArrayList<>(); + requestList.add(request); + + Long userId = 1L; + User user = new User(); + Product product = new Product(); + product.setName("Product" + productId); + Cart cart = new Cart(); + + when(validatCartMethod.validateUser(userId)).thenReturn(user); + when(validatCartMethod.validateCart(cartId, userId)).thenReturn(cart); + when(validatCartMethod.validateProduct(productId)).thenReturn(product); + doNothing().when(validatCartMethod).validateStock(anyInt(), any()); + doNothing().when(rabbitTemplate).convertAndSend(anyString(), anyString(), any(CartRmqDto.class)); + + List result = cartService.modifyCart(requestList, userId); + + Assertions.assertEquals(requestList.size(), result.size()); + Assertions.assertEquals("Product" + productId + "상품을 장바구니서 수정합니다.", result.get(0)); + + verify(validatCartMethod).validateUser(userId); + verify(validatCartMethod).validateCart(cartId, userId); + verify(validatCartMethod).validateProduct(productId); + verify(rabbitTemplate).convertAndSend(anyString(), anyString(), any(CartRmqDto.class)); + } + + @Test + @DisplayName("모든 장바구니 삭제") + void deleteAll() { + // given + Long userId = 1L; + User user = new User(); + user.setUserName("TestUser"); + when(validatCartMethod.validateUser(userId)).thenReturn(user); + + // when + String result = cartService.deleteAll(userId); + + // then + Assertions.assertEquals("TestUser님의 장바구니 목록이 삭제되었습니다.", result); + verify(cartRepository).deleteAllByUsersId(userId); + } + @Test + @DisplayName("특정 장바구니 삭제") + void deleteOne() { + // given + Long userId = 1L; + Long cartId = 1L; + Cart cart = new Cart(); + cart.setId(cartId); + when(validatCartMethod.validateUser(userId)).thenReturn(new User()); + when(validatCartMethod.validateCart(cartId, userId)).thenReturn(cart); + + // when + String result = cartService.deleteOne(cartId, userId); + + // then + Assertions.assertEquals("1번 장바구니 삭제", result); + verify(cartRepository).deleteById(cartId); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/commerce/service/chat/ChatServiceTest.java b/src/test/java/com/github/commerce/service/chat/ChatServiceTest.java new file mode 100644 index 0000000..c7afe94 --- /dev/null +++ b/src/test/java/com/github/commerce/service/chat/ChatServiceTest.java @@ -0,0 +1,109 @@ +package com.github.commerce.service.chat; + +import com.github.commerce.entity.collection.Chat; +import com.github.commerce.repository.chat.ChatRepository; +import com.github.commerce.repository.chat.ChatRepositoryCustomImpl; +import com.github.commerce.repository.product.ProductRepository; +import com.github.commerce.repository.user.SellerRepository; +import com.github.commerce.service.chat.exception.ChatException; +import com.github.commerce.web.dto.chat.ChatDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.TestPropertySource; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@TestPropertySource(locations = "classpath:application-test.yml") +@RunWith(MockitoJUnitRunner.class) // @Mock 사용을 위해 설정 +class ChatServiceTest { + @InjectMocks + private ChatService chatService; + @Mock + private ChatRepository chatRepository; + @Mock + private ChatRepositoryCustomImpl chatRepositoryCustom; + @Mock + private ProductRepository productRepository; + @Mock + private SellerRepository sellerRepository; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + } + + + @Test + void getChatRoom() { + Chat mockChat = new Chat(); + Map> chats = new HashMap<>(); + Map innerMap = new HashMap<>(); + innerMap.put("test", "test"); + chats.put("2023-09-28T20:15:30Z", innerMap); // 예를 들면 이런 ISO 형식의 문자열을 사용 + chats.put("2023-09-28T19:15:30Z", innerMap); // 정렬을 확인하기 위해 두 개의 다른 날짜/시간 추가 + mockChat.setChats(chats); + + String customRoomId = "testRoom"; + mockChat.setCustomRoomId(customRoomId); + + when(chatRepository.findByCustomRoomId(customRoomId)).thenReturn(Optional.of(mockChat)); + //when(chatService.sortChatsByDate(anyMap())).thenReturn(anyMap()); + + ChatDto chatRoom = chatService.getChatRoom(customRoomId); + + assertNotNull(chatRoom); + verify(chatRepository).findByCustomRoomId(customRoomId); + //verify(chatService).sortChatsByDate(anyMap()); + + List sortedKeys = new ArrayList<>(chatRoom.getChats().keySet()); + assertTrue(sortedKeys.get(0).compareTo(sortedKeys.get(1)) < 0); // 키가 올바르게 정렬되었는지 확인 + + + } + + @Test + void getSellerChatListTest() { + Long sellerId = 1L; + Long productId = 1L; + String mockedShopImageUrl = "http://example.com/shop-image.jpg"; + List mockedChatList = new ArrayList<>(); + + when(chatService.getSellerImage(sellerId)).thenReturn(mockedShopImageUrl); + when(chatRepositoryCustom.getSellerChatList(sellerId, productId)).thenReturn(mockedChatList); + when(chatService.getProductImageAndName(anyLong())).thenReturn(createMockedProductInfo()); + + Map resultMap = chatService.getSellerChatList(sellerId, productId); + + assertEquals(mockedShopImageUrl, resultMap.get("shopImage")); + assertNotNull(resultMap.get("chatList")); + } + + + private Map createMockedProductInfo() { + Map productInfo = new HashMap<>(); + productInfo.put("name", "Test Product"); + productInfo.put("url", "http://example.com/product-image.jpg"); + return productInfo; + } + + @Test + void getUserChatList() { + } + + @Test + void cleanupOldChats() { + } +} \ No newline at end of file diff --git a/src/test/java/com/github/commerce/service/order/OrderServiceTest.java b/src/test/java/com/github/commerce/service/order/OrderServiceTest.java new file mode 100644 index 0000000..831e9e6 --- /dev/null +++ b/src/test/java/com/github/commerce/service/order/OrderServiceTest.java @@ -0,0 +1,289 @@ +package com.github.commerce.service.order; + +import com.github.commerce.entity.*; +import com.github.commerce.repository.order.OrderRepository; +import com.github.commerce.service.order.util.OrderCacheMethod; +import com.github.commerce.service.order.util.ValidateOrderMethod; +import com.github.commerce.web.dto.order.OrderDto; +import com.github.commerce.web.dto.order.OrderRmqDto; +import com.github.commerce.web.dto.order.PostOrderDto; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.TestPropertySource; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@TestPropertySource(locations = "classpath:application-test.yml") +@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정 +class OrderServiceTest { + @InjectMocks + private OrderService orderService; + @Mock + private OrderRepository orderRepository; + @Mock + private OrderCacheMethod orderCacheMethod; + @Mock + private ValidateOrderMethod validateOrderMethod; + @Mock + private RabbitTemplate rabbitTemplate; + + static Stream provideOrderTestData() { + return Stream.of( + Arguments.of(1L, 2, Arrays.asList("Option1")), + Arguments.of(2L, 3, Arrays.asList("Option2")), + Arguments.of(3L, 4, Arrays.asList("Option3")) + ); + } + + static Stream provideOrderFromCartTestData() { + return Stream.of( + Arguments.of(Arrays.asList(1L, 2L, 3L)) + ); + } + + @ParameterizedTest + @MethodSource("provideOrderTestData") + @DisplayName("주문 생성") + void createOrder(Long productId, Integer quantity, List options) { + PostOrderDto.PostOrderRequest request = new PostOrderDto.PostOrderRequest(productId, quantity, options); + List requestList = new ArrayList<>(); + requestList.add(request); + + Long userId = 1L; + User user = new User(); + Product product = new Product(); + product.setName("Product" + productId); + product.setPrice(100); + Seller seller = new Seller(); + product.setSeller(seller); + + + when(validateOrderMethod.validateUser(userId)).thenReturn(user); + when(validateOrderMethod.validateProduct(productId)).thenReturn(product); + doNothing().when(validateOrderMethod).validateStock(anyInt(), eq(product)); + doNothing().when(rabbitTemplate).convertAndSend(anyString(), anyString(), any(OrderRmqDto.class)); + + List result = orderService.createOrder(requestList, userId); + + Assertions.assertEquals(requestList.size(), result.size()); + Assertions.assertEquals("Product" + productId + "상품 주문요청", result.get(0)); + + verify(validateOrderMethod).validateUser(userId); + verify(validateOrderMethod).validateProduct(productId); + verify(validateOrderMethod).validateStock(quantity, product); + verify(rabbitTemplate).convertAndSend(anyString(), anyString(), any(OrderRmqDto.class)); + verify(orderCacheMethod).putOrderTag(eq(userId), anyString()); + + } + + @ParameterizedTest + @MethodSource("provideOrderFromCartTestData") + @DisplayName("장바구니로부터 주문 생성") + void createOrderFromCart(List cartIds) { + Long userId = 1L; + Long productId = 1L; + User dummyUser = new User(); + Cart dummyCart = new Cart(); + Product dummyProduct = new Product(); + Seller dummySeller = new Seller(); + dummyCart.setQuantity(1); + dummyCart.setProducts(Product.builder().seller(dummySeller).price(100).name("Product"+productId).build()); + + + when(validateOrderMethod.validateUser(anyLong())).thenReturn(dummyUser); + when(validateOrderMethod.validateCart(anyLong(), anyLong())).thenReturn(dummyCart); + doNothing().when(validateOrderMethod).validateStock(anyInt(), any(Product.class)); + doNothing().when(rabbitTemplate).convertAndSend(anyString(), anyString(), any(OrderRmqDto.class)); + + List result = orderService.createOrderFromCart(cartIds, userId); + + // then + assertEquals(cartIds.size(), result.size()); + Assertions.assertEquals("Product" + productId + "상품 주문요청", result.get(0)); + + verify(validateOrderMethod).validateUser(userId); + verify(rabbitTemplate, times(cartIds.size())).convertAndSend(anyString(), anyString(), any(OrderRmqDto.class)); + verify(orderCacheMethod, times(cartIds.size())).putOrderTag(anyLong(), anyString()); + } + + + @Test + void getPurchasedOrderList() { + // Arrange + // Arrange + Long userId = 1L; + + Product mockProduct = new Product(); + mockProduct.setId(100L); + mockProduct.setName("Mock Product"); + Seller mockSeller = new Seller(); + mockSeller.setShopName("Mock Shop"); + mockProduct.setSeller(mockSeller); + + Order mockOrder1 = new Order(); + mockOrder1.setCreatedAt(LocalDateTime.of(2023, 9, 26, 12, 0)); + mockOrder1.setProducts(mockProduct); + + Order mockOrder2 = new Order(); + mockOrder2.setCreatedAt(LocalDateTime.of(2023, 9, 26, 15, 0)); + mockOrder2.setProducts(mockProduct); + + when(orderRepository.findPaidOrderByUserIdSortByCreatedAtDesc(userId)) + .thenReturn(Arrays.asList(mockOrder1, mockOrder2)); + + // Act + List>> result = orderService.getPurchasedOrderList(userId); + + // Assert + assertEquals(1, result.size()); + Map> ordersMap = result.get(0); + assertEquals(1, ordersMap.size()); + assertEquals(2, ordersMap.get(LocalDate.of(2023, 9, 26)).size()); + } + + @Test + void getSellerOrderList() { + // Arrange + Long userId = 1L; + Product mockProduct = new Product(); + mockProduct.setId(100L); + mockProduct.setName("Mock Product"); + Seller mockSeller = new Seller(); + mockSeller.setShopName("Mock Shop"); + mockSeller.setId(2L); + mockProduct.setSeller(mockSeller); + + + when(validateOrderMethod.validateSellerByUserId(userId)).thenReturn(mockSeller); + + Order mockOrder1 = new Order(); + mockOrder1.setCreatedAt(LocalDateTime.of(2023, 9, 26, 12, 0)); + mockOrder1.setProducts(mockProduct); + + Order mockOrder2 = new Order(); + mockOrder2.setCreatedAt(LocalDateTime.of(2023, 9, 26, 15, 0)); + mockOrder2.setProducts(mockProduct); + + when(orderRepository.findPaidOrderBySellerIdSortByCreatedAtDesc(mockSeller.getId())) + .thenReturn(Arrays.asList(mockOrder1, mockOrder2)); + + // Act + List>> result = orderService.getSellerOrderList(userId); + + // Assert + assertEquals(1, result.size()); + Map> ordersMap = result.get(0); + assertEquals(1, ordersMap.size()); + assertEquals(2, ordersMap.get(LocalDate.of(2023, 9, 26)).size()); + } + + @Test + void getOrderList() { + // Arrange + Long userId = 1L; + User mockUser = new User(); + String mockOrderTag = "testTag"; + + Product mockProduct = new Product(); + mockProduct.setId(100L); + mockProduct.setName("Mock Product"); + Seller mockSeller = new Seller(); + mockSeller.setShopName("Mock Shop"); + mockProduct.setSeller(mockSeller); + + Order mockOrder1 = new Order(); + mockOrder1.setProducts(mockProduct); + Order mockOrder2 = new Order(); + mockOrder2.setProducts(mockProduct); + + when(validateOrderMethod.validateUser(userId)).thenReturn(mockUser); + when(orderCacheMethod.getOrderTag(userId)).thenReturn(mockOrderTag); + when(orderRepository.findByUsersIdAndOrderTagAndOrderState(userId, mockOrderTag, 1)) + .thenReturn(Arrays.asList(mockOrder1, mockOrder2)); + + // Act + List result = orderService.getOrderList(userId); + + // Assert + assertEquals(2, result.size()); + } + + @Test + void getOrderListByCursor() { + // Arrange + Long userId = 1L; + Long cursorId = 100L; + int pageSize = 10; + + Product mockProduct = new Product(); + mockProduct.setId(100L); + mockProduct.setName("Mock Product"); + Seller mockSeller = new Seller(); + mockSeller.setShopName("Mock Shop"); + mockProduct.setSeller(mockSeller); + + Order mockOrder1 = new Order(); + mockOrder1.setProducts(mockProduct); + Order mockOrder2 = new Order(); + mockOrder2.setProducts(mockProduct); + + List mockOrderList = Arrays.asList(mockOrder1, mockOrder2); + Page mockOrderPage = new PageImpl<>(mockOrderList, PageRequest.of(0, pageSize), mockOrderList.size()); + + when(orderRepository.findAllByUsersIdAndCursorId(userId, cursorId, PageRequest.of(0, pageSize))) + .thenReturn(mockOrderPage); + + // Act + Page result = orderService.getOrderListByCursor(userId, cursorId); + + // Assert + assertEquals(2, result.getContent().size()); + } + + @Test + void deleteOne() { + // Arrange + Long userId = 1L; + Long orderId = 2L; + + Order validatedOrder = new Order(); + validatedOrder.setId(orderId); + + when(validateOrderMethod.validateOrder(orderId, userId)).thenReturn(validatedOrder); + + // Act + String result = orderService.deleteOne(orderId, userId); + + // Assert + verify(validateOrderMethod).validateUser(userId); + verify(validateOrderMethod).validateOrder(orderId, userId); + verify(orderRepository).deleteById(orderId); + + String expectedMessage = orderId + "번 주문 삭제"; + assertEquals(expectedMessage, result); + } + + +} \ No newline at end of file diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 0000000..ae11b69 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,69 @@ +spring: + mvc: + path_match: + matching-strategy: ant_path_matcher + + datasource: + master: + hikari: + username: test + password: test + driver-class-name: org.h2.Driver + jdbc-url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 + slave: + hikari: + username: test + password: test + driver-class-name: org.h2.Driver + jdbc-url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 + slave2: + hikari: + username: test + password: test + driver-class-name: org.h2.Driver + jdbc-url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 + + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: update + show-sql: true + + data: + mongodb: + uri: mongodb://localhost:27017/testdb + + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + + servlet: + multipart: + max-file-size: 5MB + max-request-size: 5MB + enabled: true + + batch: + job: + enabled: false + jdbc: + initialize-schema: ALWAYS + +springdoc: + swagger-ui: + path: /api-doc.html + +jwt: + secret-key-source: 7YWM7Iqk7Yq466W8IOychO2VnCDtgqTrpbwg7IOd7ISx7ZWp64uI64uk + +cloud: + aws: + s3: + bucket: mockBucket + stack.auto: false + region.static: ap-northeast-2 + credentials: + accessKey: mockAccessKey + secretKey: mockSecretKey