|
|
@@ -0,0 +1,149 @@
|
|
|
+package com.etotem.cfc.unit;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.dto.CreateProductOrderDTO;
|
|
|
+import com.etotem.cfc.dto.OrderItemVO;
|
|
|
+import com.etotem.cfc.dto.ProductOrderDTO;
|
|
|
+import com.etotem.cfc.entity.Product;
|
|
|
+import com.etotem.cfc.entity.ProductOrder;
|
|
|
+import com.etotem.cfc.entity.ProductSku;
|
|
|
+import com.etotem.cfc.mapper.ProductMapper;
|
|
|
+import com.etotem.cfc.mapper.ProductOrderMapper;
|
|
|
+import com.etotem.cfc.service.InventoryService;
|
|
|
+import com.etotem.cfc.service.ProductOrderService;
|
|
|
+import com.etotem.cfc.service.ProductService;
|
|
|
+import com.etotem.cfc.service.ProductSkuService;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+
|
|
|
+import java.util.Collections;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 套餐商品(product_type='bundle')下单库存走套餐SKU:校验/扣减/回补均只作用于套餐SKU,
|
|
|
+ * 不触碰商品级库存(product.stock)与子商品SKU库存(无任何子商品扣减代码)。
|
|
|
+ */
|
|
|
+class ComboOrderStockTest {
|
|
|
+
|
|
|
+ private ProductOrderService orderService;
|
|
|
+ private ProductOrderMapper orderMapper;
|
|
|
+ private ProductMapper productMapper;
|
|
|
+ private ProductSkuService productSkuService;
|
|
|
+ private ProductService productService;
|
|
|
+ private InventoryService inventoryService;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ orderService = new ProductOrderService();
|
|
|
+ orderMapper = mock(ProductOrderMapper.class);
|
|
|
+ productMapper = mock(ProductMapper.class);
|
|
|
+ productSkuService = mock(ProductSkuService.class);
|
|
|
+ productService = mock(ProductService.class);
|
|
|
+ inventoryService = mock(InventoryService.class);
|
|
|
+ ReflectionTestUtils.setField(orderService, "orderMapper", orderMapper);
|
|
|
+ ReflectionTestUtils.setField(orderService, "productMapper", productMapper);
|
|
|
+ ReflectionTestUtils.setField(orderService, "productSkuService", productSkuService);
|
|
|
+ ReflectionTestUtils.setField(orderService, "productService", productService);
|
|
|
+ ReflectionTestUtils.setField(orderService, "inventoryService", inventoryService);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Product bundleProduct(Long id, String name) {
|
|
|
+ Product p = new Product();
|
|
|
+ p.setId(id);
|
|
|
+ p.setName(name);
|
|
|
+ p.setProductType("bundle");
|
|
|
+ p.setStatus("on_shelf");
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ProductSku combo(Long id, Long productId, int price) {
|
|
|
+ ProductSku s = new ProductSku();
|
|
|
+ s.setId(id);
|
|
|
+ s.setProductId(productId);
|
|
|
+ s.setPrice(price);
|
|
|
+ return s;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ProductOrder bundleOrder(String orderNo) {
|
|
|
+ ProductOrder o = new ProductOrder();
|
|
|
+ o.setId(1L);
|
|
|
+ o.setOrderNo(orderNo);
|
|
|
+ o.setProductId(99L);
|
|
|
+ o.setProductType("bundle");
|
|
|
+ o.setSkuId(900L);
|
|
|
+ o.setQuantity(2);
|
|
|
+ o.setStatus("pending");
|
|
|
+ o.setBuyerId(100L);
|
|
|
+ o.setTotalAmount(30000);
|
|
|
+ o.setMoneyAmount(30000);
|
|
|
+ o.setPointsUsed(0);
|
|
|
+ return o;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 断言1:下单套餐商品 → 库存校验走套餐SKU(product_skus.stock),不足则拒绝 */
|
|
|
+ @Test
|
|
|
+ void createBundle_invalidatesComboSkuStock() {
|
|
|
+ OrderItemVO item = new OrderItemVO();
|
|
|
+ item.setProductId(99L);
|
|
|
+ item.setSkuId(900L);
|
|
|
+ item.setQuantity(2);
|
|
|
+ CreateProductOrderDTO dto = new CreateProductOrderDTO();
|
|
|
+ dto.setItems(Collections.singletonList(item));
|
|
|
+ when(productMapper.selectById(99L)).thenReturn(bundleProduct(99L, "套餐X"));
|
|
|
+ when(productSkuService.getById(900L)).thenReturn(combo(900L, 99L, 15000));
|
|
|
+ when(productSkuService.hasStock(900L, 2)).thenReturn(false);
|
|
|
+
|
|
|
+ Result<ProductOrderDTO> res = orderService.create(dto, 100L);
|
|
|
+
|
|
|
+ assertNotNull(res);
|
|
|
+ assertNotEquals(200, res.getCode());
|
|
|
+ assertTrue(res.getMessage() != null && res.getMessage().contains("套餐库存不足"));
|
|
|
+ verify(productSkuService).hasStock(900L, 2); // 走套餐SKU库存校验
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 断言2:支付成功只扣套餐SKU库存(不扣商品级库存、无子商品扣减) */
|
|
|
+ @Test
|
|
|
+ void paymentSuccess_deductsComboSkuStockOnly() {
|
|
|
+ ProductOrder order = bundleOrder("B20261101");
|
|
|
+ when(orderMapper.selectOne(any())).thenReturn(order);
|
|
|
+ when(productMapper.selectById(99L)).thenReturn(bundleProduct(99L, "套餐X"));
|
|
|
+ when(productSkuService.getById(900L)).thenReturn(combo(900L, 99L, 15000));
|
|
|
+ when(productSkuService.decreaseStock(900L, 2)).thenReturn(true);
|
|
|
+
|
|
|
+ Result<String> res = (Result<String>) ReflectionTestUtils.invokeMethod(
|
|
|
+ orderService, "doHandlePaymentSuccess", "B20261101", "TX123");
|
|
|
+
|
|
|
+ assertNotNull(res);
|
|
|
+ assertNotEquals(-1, res.getCode() == null ? -1 : res.getCode());
|
|
|
+ // 只扣套餐SKU一次,商品级库存不扣,子商品SKU不被触碰
|
|
|
+ verify(productSkuService, times(1)).decreaseStock(900L, 2);
|
|
|
+ verify(productService, never()).decreaseStock(anyLong(), any());
|
|
|
+ // 出库流水记到套餐SKU
|
|
|
+ verify(inventoryService).recordOutbound(99L, 900L, 2, "order", 1L, null, "订单支付:B20261101");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 断言3:取消/关闭回补只加套餐SKU库存 */
|
|
|
+ @Test
|
|
|
+ void adminClose_restoresComboSkuStockOnly() {
|
|
|
+ ProductOrder order = bundleOrder("B20261101");
|
|
|
+ order.setStatus("paid");
|
|
|
+ when(orderMapper.selectOne(any())).thenReturn(order);
|
|
|
+
|
|
|
+ Result<String> res = orderService.adminClose("B20261101", 1L);
|
|
|
+
|
|
|
+ assertNotNull(res);
|
|
|
+ verify(productSkuService).increaseStock(900L, 2); // 只加套餐SKU库存
|
|
|
+ verify(productService, never()).increaseStock(anyLong(), any());
|
|
|
+ }
|
|
|
+}
|