Commit 652d77c0d7a180c54ce09f5c823220a321e1e230

Authored by YunaiV
1 parent d2cd9ffe

fix(trade): 按购物车合并数量校验商品库存

采用 #1268 的合并总量校验,收敛为一次 SKU 查询。
补充新增、合并、库存边界及拒绝后不写入的 7 个服务测试。

Refs #1268, #1267
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/cart/CartServiceImpl.java
... ... @@ -46,14 +46,14 @@ public class CartServiceImpl implements CartService {
46 46 public Long addCart(Long userId, AppCartAddReqVO addReqVO) {
47 47 // 查询 TradeCartDO
48 48 CartDO cart = cartMapper.selectByUserIdAndSkuId(userId, addReqVO.getSkuId());
49   - // 校验 SKU
50   - Integer count = addReqVO.getCount();
  49 + // 校验 SKU:已存在时,按合并后的数量校验库存 https://github.com/YunaiV/ruoyi-vue-pro/issues/1267
  50 + Integer count = cart != null ? cart.getCount() + addReqVO.getCount() : addReqVO.getCount();
51 51 ProductSkuRespDTO sku = checkProductSku(addReqVO.getSkuId(), count);
52 52  
53 53 // 情况一:存在,则进行数量更新
54 54 if (cart != null) {
55 55 cartMapper.updateById(new CartDO().setId(cart.getId()).setSelected(true)
56   - .setCount(cart.getCount() + count));
  56 + .setCount(count));
57 57 return cart.getId();
58 58 // 情况二:不存在,则进行插入
59 59 } else {
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/cart/CartServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.service.cart;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
  5 +import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
  6 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  7 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppCartAddReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.cart.vo.AppCartUpdateCountReqVO;
  9 +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
  10 +import cn.iocoder.yudao.module.trade.dal.mysql.cart.CartMapper;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.mockito.ArgumentCaptor;
  13 +import org.mockito.InjectMocks;
  14 +import org.mockito.Mock;
  15 +
  16 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  17 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
  18 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
  19 +import static org.junit.jupiter.api.Assertions.*;
  20 +import static org.mockito.ArgumentMatchers.any;
  21 +import static org.mockito.Mockito.*;
  22 +
  23 +/**
  24 + * {@link CartServiceImpl} 的单元测试
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +public class CartServiceImplTest extends BaseMockitoUnitTest {
  29 +
  30 + @InjectMocks
  31 + private CartServiceImpl cartService;
  32 +
  33 + @Mock
  34 + private CartMapper cartMapper;
  35 + @Mock
  36 + private ProductSpuApi productSpuApi;
  37 + @Mock
  38 + private ProductSkuApi productSkuApi;
  39 +
  40 + @Test
  41 + public void testAddCart_insert() {
  42 + // 准备参数
  43 + Long userId = 1L;
  44 + AppCartAddReqVO reqVO = new AppCartAddReqVO().setSkuId(10L).setCount(3);
  45 + // mock 方法
  46 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  47 + when(cartMapper.insert(any(CartDO.class))).thenAnswer(invocation -> {
  48 + invocation.<CartDO>getArgument(0).setId(100L);
  49 + return 1;
  50 + });
  51 +
  52 + // 调用
  53 + Long cartId = cartService.addCart(userId, reqVO);
  54 + // 断言
  55 + assertEquals(100L, cartId);
  56 + ArgumentCaptor<CartDO> cartCaptor = ArgumentCaptor.forClass(CartDO.class);
  57 + verify(cartMapper).insert(cartCaptor.capture());
  58 + CartDO cart = cartCaptor.getValue();
  59 + assertEquals(userId, cart.getUserId());
  60 + assertEquals(20L, cart.getSpuId());
  61 + assertEquals(10L, cart.getSkuId());
  62 + assertEquals(3, cart.getCount());
  63 + assertTrue(cart.getSelected());
  64 + verify(cartMapper, never()).updateById(any(CartDO.class));
  65 + }
  66 +
  67 + @Test
  68 + public void testAddCart_mergeBelowStock() {
  69 + // 准备参数
  70 + Long userId = 1L;
  71 + AppCartAddReqVO reqVO = new AppCartAddReqVO().setSkuId(10L).setCount(2);
  72 + // mock 数据
  73 + CartDO dbCart = new CartDO().setId(100L).setUserId(userId).setSpuId(20L).setSkuId(10L)
  74 + .setCount(3).setSelected(false);
  75 + when(cartMapper.selectByUserIdAndSkuId(eq(userId), eq(10L))).thenReturn(dbCart);
  76 + // mock 方法
  77 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  78 +
  79 + // 调用
  80 + Long cartId = cartService.addCart(userId, reqVO);
  81 + // 断言
  82 + assertEquals(100L, cartId);
  83 + ArgumentCaptor<CartDO> cartCaptor = ArgumentCaptor.forClass(CartDO.class);
  84 + verify(cartMapper).updateById(cartCaptor.capture());
  85 + CartDO cart = cartCaptor.getValue();
  86 + assertEquals(100L, cart.getId());
  87 + assertEquals(5, cart.getCount());
  88 + assertTrue(cart.getSelected());
  89 + verify(cartMapper, never()).insert(any(CartDO.class));
  90 + }
  91 +
  92 + @Test
  93 + public void testAddCart_mergeEqualsStock() {
  94 + // 准备参数
  95 + Long userId = 1L;
  96 + AppCartAddReqVO reqVO = new AppCartAddReqVO().setSkuId(10L).setCount(4);
  97 + // mock 数据
  98 + CartDO dbCart = new CartDO().setId(100L).setUserId(userId).setSpuId(20L).setSkuId(10L)
  99 + .setCount(6).setSelected(true);
  100 + when(cartMapper.selectByUserIdAndSkuId(eq(userId), eq(10L))).thenReturn(dbCart);
  101 + // mock 方法
  102 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  103 +
  104 + // 调用
  105 + Long cartId = cartService.addCart(userId, reqVO);
  106 + // 断言
  107 + assertEquals(100L, cartId);
  108 + ArgumentCaptor<CartDO> cartCaptor = ArgumentCaptor.forClass(CartDO.class);
  109 + verify(cartMapper).updateById(cartCaptor.capture());
  110 + assertEquals(10, cartCaptor.getValue().getCount());
  111 + }
  112 +
  113 + @Test
  114 + public void testAddCart_mergeOverStock() {
  115 + // 准备参数:本次数量、已有数量都未超过库存,但合并后超过
  116 + Long userId = 1L;
  117 + AppCartAddReqVO reqVO = new AppCartAddReqVO().setSkuId(10L).setCount(3);
  118 + // mock 数据
  119 + CartDO dbCart = new CartDO().setId(100L).setUserId(userId).setSpuId(20L).setSkuId(10L)
  120 + .setCount(8).setSelected(false);
  121 + when(cartMapper.selectByUserIdAndSkuId(eq(userId), eq(10L))).thenReturn(dbCart);
  122 + // mock 方法
  123 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  124 +
  125 + // 调用,并断言异常
  126 + assertServiceException(() -> cartService.addCart(userId, reqVO), SKU_STOCK_NOT_ENOUGH);
  127 + // 断言:未写入,且原购物项未被修改
  128 + verify(cartMapper, never()).updateById(any(CartDO.class));
  129 + verify(cartMapper, never()).insert(any(CartDO.class));
  130 + assertEquals(8, dbCart.getCount());
  131 + assertFalse(dbCart.getSelected());
  132 + }
  133 +
  134 + @Test
  135 + public void testAddCart_skuNotExists() {
  136 + // 准备参数
  137 + Long userId = 1L;
  138 + AppCartAddReqVO reqVO = new AppCartAddReqVO().setSkuId(10L).setCount(1);
  139 + // mock 数据
  140 + CartDO dbCart = new CartDO().setId(100L).setUserId(userId).setSpuId(20L).setSkuId(10L)
  141 + .setCount(1).setSelected(true);
  142 + when(cartMapper.selectByUserIdAndSkuId(eq(userId), eq(10L))).thenReturn(dbCart);
  143 + // mock 方法:SKU 已被删除
  144 + when(productSkuApi.getSku(eq(10L))).thenReturn(null);
  145 +
  146 + // 调用,并断言异常
  147 + assertServiceException(() -> cartService.addCart(userId, reqVO), SKU_NOT_EXISTS);
  148 + // 断言:未写入
  149 + verify(cartMapper, never()).updateById(any(CartDO.class));
  150 + verify(cartMapper, never()).insert(any(CartDO.class));
  151 + }
  152 +
  153 + @Test
  154 + public void testUpdateCartCount_equalsStock() {
  155 + // 准备参数
  156 + Long userId = 1L;
  157 + AppCartUpdateCountReqVO reqVO = new AppCartUpdateCountReqVO().setId(100L).setCount(10);
  158 + // mock 数据
  159 + when(cartMapper.selectById(eq(100L), eq(userId))).thenReturn(new CartDO().setId(100L).setUserId(userId)
  160 + .setSpuId(20L).setSkuId(10L).setCount(3).setSelected(true));
  161 + // mock 方法
  162 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  163 +
  164 + // 调用
  165 + cartService.updateCartCount(userId, reqVO);
  166 + // 断言
  167 + ArgumentCaptor<CartDO> cartCaptor = ArgumentCaptor.forClass(CartDO.class);
  168 + verify(cartMapper).updateById(cartCaptor.capture());
  169 + assertEquals(100L, cartCaptor.getValue().getId());
  170 + assertEquals(10, cartCaptor.getValue().getCount());
  171 + }
  172 +
  173 + @Test
  174 + public void testUpdateCartCount_overStock() {
  175 + // 准备参数
  176 + Long userId = 1L;
  177 + AppCartUpdateCountReqVO reqVO = new AppCartUpdateCountReqVO().setId(100L).setCount(11);
  178 + // mock 数据
  179 + when(cartMapper.selectById(eq(100L), eq(userId))).thenReturn(new CartDO().setId(100L).setUserId(userId)
  180 + .setSpuId(20L).setSkuId(10L).setCount(3).setSelected(true));
  181 + // mock 方法
  182 + when(productSkuApi.getSku(eq(10L))).thenReturn(new ProductSkuRespDTO().setId(10L).setSpuId(20L).setStock(10));
  183 +
  184 + // 调用,并断言异常
  185 + assertServiceException(() -> cartService.updateCartCount(userId, reqVO), SKU_STOCK_NOT_ENOUGH);
  186 + // 断言:未写入
  187 + verify(cartMapper, never()).updateById(any(CartDO.class));
  188 + }
  189 +
  190 +}
... ...