Commit ef807be89ce128d4920632d1de92f6ee5f40351a

Authored by YunaiV
1 parent 41d56350

🐞 fix(trade): 修复价格计算 3 处缺陷并补齐单测

1. 运费模板「包邮件数 / 包邮金额」判断方向反向,统一改为 >= 并对齐 DO / BO 注释
2. 砍价活动促销明细 id 错用 seckillActivityId,改为 bargainActivity.getActivityId()
3. TradePriceCalculatorHelper.buildCalculateResp 对 spu.giveIntegral 兜底 0,防止拆箱 NPE
4. 恢复 / 重写 Delivery、Coupon、Discount、PointUse、Reward、PriceService 单测,新增 BargainActivity 单测,共 30 用例
Showing 11 changed files with 481 additions and 292 deletions
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java
... ... @@ -43,14 +43,14 @@ public class DeliveryExpressTemplateFreeDO extends BaseDO {
43 43 /**
44 44 * 包邮金额,单位:分
45 45 *
46   - * 订单总金额 > 包邮金额时,才免运费
  46 + * 订单总金额 >= 包邮金额时,才免运费
47 47 */
48 48 private Integer freePrice;
49 49  
50 50 /**
51 51 * 包邮件数
52 52 *
53   - * 订单总件数 > 包邮件数时,才免运费
  53 + * 订单总件数 >= 包邮件数时,才免运费
54 54 */
55 55 private Integer freeCount;
56 56  
... ...
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java
... ... @@ -65,14 +65,14 @@ public class DeliveryExpressTemplateRespBO {
65 65 /**
66 66 * 包邮金额,单位:分
67 67 *
68   - * 订单总金额 > 包邮金额时,才免运费
  68 + * 订单总金额 >= 包邮金额时,才免运费
69 69 */
70 70 private Integer freePrice;
71 71  
72 72 /**
73 73 * 包邮件数
74 74 *
75   - * 订单总件数 > 包邮件数时,才免运费
  75 + * 订单总件数 >= 包邮件数时,才免运费
76 76 */
77 77 private Integer freeCount;
78 78 }
... ...
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeBargainActivityPriceCalculator.java
... ... @@ -14,7 +14,6 @@ import org.springframework.stereotype.Component;
14 14  
15 15 import jakarta.annotation.Resource;
16 16  
17   -// TODO huihui:单测需要补充
18 17 /**
19 18 * 砍价活动的 {@link TradePriceCalculator} 实现类
20 19 *
... ... @@ -29,12 +28,12 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
29 28  
30 29 @Override
31 30 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
32   - // 1. 判断订单类型和是否具有拼团记录编号
  31 + // 1. 判断订单类型和是否具有砍价记录编号
33 32 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) {
34 33 return;
35 34 }
36 35 Assert.isTrue(param.getItems().size() == 1, "砍价时,只允许选择一个商品");
37   - Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,只允许选择一个商品");
  36 + Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,商品数量只允许为 1");
38 37 // 2. 校验是否可以参与砍价
39 38 TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
40 39 BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain(
... ... @@ -44,7 +43,7 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
44 43 Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount();
45 44 // TODO 芋艿:极端情况,优惠金额为负数,需要处理
46 45 TradePriceCalculatorHelper.addPromotion(result, orderItem,
47   - param.getSeckillActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(),
  46 + bargainActivity.getActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(),
48 47 StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
49 48 discountPrice);
50 49 // 3.2 更新 SKU 优惠金额
... ...
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
... ... @@ -213,7 +213,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
213 213 }
214 214 double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
215 215 double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
216   - return totalChargeValue <= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
  216 + return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
217 217 }
218 218  
219 219 private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
... ...
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjectUtil;
4 5 import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 7 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
... ... @@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper {
61 62 // spu 信息
62 63 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
63 64 .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
64   - .setGivePoint(spu.getGiveIntegral()).setUsePoint(0);
  65 + .setGivePoint(ObjectUtil.defaultIfNull(spu.getGiveIntegral(), 0)).setUsePoint(0);
65 66 if (StrUtil.isBlank(orderItem.getPicUrl())) {
66 67 orderItem.setPicUrl(spu.getPicUrl());
67 68 }
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/TradePriceServiceImplTest.java
... ... @@ -6,12 +6,10 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
6 6 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
7 7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
8 8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9   -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
10 9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
11 10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
13 12 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
14   -import org.junit.jupiter.api.Disabled;
15 13 import org.junit.jupiter.api.Test;
16 14 import org.mockito.InjectMocks;
17 15 import org.mockito.Mock;
... ... @@ -30,7 +28,6 @@ import static org.mockito.Mockito.when;
30 28 *
31 29 * @author 芋道源码
32 30 */
33   -@Disabled // TODO 芋艿:后续 fix 补充的单测
34 31 public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
35 32  
36 33 @InjectMocks
... ... @@ -67,9 +64,8 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
67 64 .setValueId(4L).setValueName("黑色")))
68 65 );
69 66 when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList);
70   - when(productSpuApi.getSpuList(Mockito.eq(asSet(1001L))))
71   - .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L)
72   - .setStatus(ProductSpuStatusEnum.ENABLE.getStatus())));
  67 + when(productSpuApi.validateSpuList(Mockito.eq(asSet(1001L))))
  68 + .thenReturn(singletonList(new ProductSpuRespDTO().setId(1001L).setName("小菜").setCategoryId(666L)));
73 69  
74 70 // 调用
75 71 TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculateOrderPrice(calculateReqBO);
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java
... ... @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
11 11 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
12 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
13 13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
14   -import org.junit.jupiter.api.Disabled;
15 14 import org.junit.jupiter.api.Test;
16 15 import org.mockito.InjectMocks;
17 16 import org.mockito.Mock;
... ... @@ -32,7 +31,6 @@ import static org.mockito.Mockito.when;
32 31 *
33 32 * @author 芋道源码
34 33 */
35   -@Disabled // TODO 芋艿:后续修复
36 34 public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
37 35  
38 36 @InjectMocks
... ... @@ -75,7 +73,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
75 73 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
76 74 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
77 75 .setDiscountPercent(50).setDiscountLimitPrice(70))
78   - .setValidStartTime(addTime(Duration.ofDays(1))).setValidEndTime(addTime(Duration.ofDays(2)));
  76 + .setValidStartTime(addTime(Duration.ofDays(-1))).setValidEndTime(addTime(Duration.ofDays(1)));
79 77 when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus())))
80 78 .thenReturn(ListUtil.toList(coupon));
81 79  
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
5 6 import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
6 7 import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
7 8 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  9 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
8 10 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
9 11 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
10 12 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
11 13 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  14 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
12 15 import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
13 16 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
14 17 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
15 18 import org.junit.jupiter.api.BeforeEach;
16   -import org.junit.jupiter.api.Disabled;
17 19 import org.junit.jupiter.api.DisplayName;
18 20 import org.junit.jupiter.api.Test;
19 21 import org.mockito.InjectMocks;
20 22 import org.mockito.Mock;
21 23  
22 24 import java.util.ArrayList;
  25 +import java.util.Collections;
23 26  
24 27 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  28 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
25 29 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  30 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PICK_UP_STORE_NOT_EXISTS;
  31 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND;
  32 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL;
26 33 import static java.util.Arrays.asList;
  34 +import static java.util.Collections.singletonList;
27 35 import static org.assertj.core.api.Assertions.assertThat;
28 36 import static org.mockito.ArgumentMatchers.eq;
29 37 import static org.mockito.Mockito.when;
... ... @@ -33,15 +41,15 @@ import static org.mockito.Mockito.when;
33 41 *
34 42 * @author jason
35 43 */
36   -@Disabled // TODO 芋艿:后续修复
37   -public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
  44 +public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
38 45  
39 46 @InjectMocks
40 47 private TradeDeliveryPriceCalculator calculator;
41 48  
42 49 @Mock
43 50 private MemberAddressApi addressApi;
44   -
  51 + @Mock
  52 + private DeliveryPickUpStoreService deliveryPickUpStoreService;
45 53 @Mock
46 54 private DeliveryExpressTemplateService deliveryExpressTemplateService;
47 55 @Mock
... ... @@ -55,7 +63,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
55 63 private DeliveryExpressTemplateRespBO.Free freeBO;
56 64  
57 65 @BeforeEach
58   - public void init(){
  66 + public void init() {
59 67 // 准备参数
60 68 reqBO = new TradePriceCalculateReqBO()
61 69 .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType())
... ... @@ -71,24 +79,23 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
71 79 .setPromotions(new ArrayList<>())
72 80 .setItems(asList(
73 81 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(10L).setCount(2).setSelected(true)
74   - .setWeight(10d).setVolume(10d).setPrice(100),
  82 + .setWeight(10d).setVolume(10d).setPrice(100)
  83 + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())),
75 84 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(20L).setCount(10).setSelected(true)
76   - .setWeight(10d).setVolume(10d).setPrice(200),
  85 + .setWeight(10d).setVolume(10d).setPrice(200)
  86 + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())),
77 87 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(30L).setCount(1).setSelected(false)
78 88 .setWeight(10d).setVolume(10d).setPrice(300)
  89 + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType()))
79 90 ));
80 91 // 保证价格被初始化上
81 92 TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems());
82 93 TradePriceCalculatorHelper.recountAllPrice(resultBO);
83 94  
84   - // 准备收件地址数据
85   - MemberAddressRespDTO addressResp = randomPojo(MemberAddressRespDTO.class, item -> item.setAreaId(10));
86   - when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp);
87   -
88   - // 准备运费模板费用配置数据
  95 + // 准备运费模板费用配置数据:起步 10 件 1000 分,续 10 件 2000 分
89 96 chargeBO = randomPojo(DeliveryExpressTemplateRespBO.Charge.class,
90 97 item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000));
91   - // 准备运费模板包邮配置数据:订单总件数 < 包邮件数时 12 < 20
  98 + // 准备运费模板包邮配置数据:默认不包邮(订单总件数 12 不大于 包邮件数 20)
92 99 freeBO = randomPojo(DeliveryExpressTemplateRespBO.Free.class,
93 100 item -> item.setFreeCount(20).setFreePrice(100));
94 101 // 准备 SP 运费模板数据
... ... @@ -97,99 +104,320 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
97 104 .setCharge(chargeBO).setFree(freeBO));
98 105 }
99 106  
  107 + // ========== 配送方式校验 ==========
  108 +
100 109 @Test
101   - @DisplayName("全场包邮")
102   - public void testCalculate_expressGlobalFree() {
103   - // mock 方法(全场包邮)
104   - when(tradeConfigService.getTradeConfig()).thenReturn(new TradeConfigDO().setDeliveryExpressFreeEnabled(true)
105   - .setDeliveryExpressFreePrice(2200));
  110 + @DisplayName("配送方式为空:直接返回,不计算运费")
  111 + public void testCalculate_deliveryTypeNull() {
  112 + // 准备参数
  113 + reqBO.setDeliveryType(null);
106 114  
107 115 // 调用
108 116 calculator.calculate(reqBO, resultBO);
  117 +
  118 + // 断言:未计算运费
  119 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  120 + }
  121 +
  122 + @Test
  123 + @DisplayName("配送方式与商品不匹配:抛出异常")
  124 + public void testCalculate_deliveryTypeMismatch() {
  125 + // 准备参数:商品 deliveryTypes 不包含 EXPRESS
  126 + resultBO.getItems().forEach(item -> item.setDeliveryTypes(singletonList(DeliveryTypeEnum.PICK_UP.getType())));
  127 +
  128 + // 调用并断言异常
  129 + assertServiceException(() -> calculator.calculate(reqBO, resultBO),
  130 + PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL);
  131 + }
  132 +
  133 + // ========== 自提模式 ==========
  134 +
  135 + @Test
  136 + @DisplayName("自提:未选择门店时直接返回")
  137 + public void testCalculate_pickUp_storeIdNull() {
  138 + // 准备参数
  139 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(null);
  140 +
  141 + // 调用
  142 + calculator.calculate(reqBO, resultBO);
  143 +
  144 + // 断言:未计算运费
  145 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  146 + }
  147 +
  148 + @Test
  149 + @DisplayName("自提:门店不存在时抛出异常")
  150 + public void testCalculate_pickUp_storeNotFound() {
  151 + // 准备参数
  152 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L);
  153 + // mock:门店不存在
  154 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn(null);
  155 +
  156 + // 调用并断言异常
  157 + assertServiceException(() -> calculator.calculate(reqBO, resultBO),
  158 + PICK_UP_STORE_NOT_EXISTS);
  159 + }
  160 +
  161 + @Test
  162 + @DisplayName("自提:门店被禁用时抛出异常")
  163 + public void testCalculate_pickUp_storeDisabled() {
  164 + // 准备参数
  165 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L);
  166 + // mock:门店被禁用
  167 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn(
  168 + new DeliveryPickUpStoreDO().setStatus(CommonStatusEnum.DISABLE.getStatus()));
  169 +
  170 + // 调用并断言异常
  171 + assertServiceException(() -> calculator.calculate(reqBO, resultBO),
  172 + PICK_UP_STORE_NOT_EXISTS);
  173 + }
  174 +
  175 + @Test
  176 + @DisplayName("自提:门店正常时不计算运费")
  177 + public void testCalculate_pickUp_ok() {
  178 + // 准备参数
  179 + reqBO.setDeliveryType(DeliveryTypeEnum.PICK_UP.getType()).setPickUpStoreId(99L);
  180 + // mock:门店正常
  181 + when(deliveryPickUpStoreService.getDeliveryPickUpStore(eq(99L))).thenReturn(
  182 + new DeliveryPickUpStoreDO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
  183 +
  184 + // 调用
  185 + calculator.calculate(reqBO, resultBO);
  186 +
  187 + // 断言:未计算运费
  188 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  189 + }
  190 +
  191 + // ========== 快递模式 ==========
  192 +
  193 + @Test
  194 + @DisplayName("快递:收件地址为空时直接返回")
  195 + public void testCalculate_express_addressIdNull() {
  196 + // 准备参数
  197 + reqBO.setAddressId(null);
  198 +
  199 + // 调用
  200 + calculator.calculate(reqBO, resultBO);
  201 +
  202 + // 断言:未计算运费
  203 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  204 + }
  205 +
  206 + @Test
  207 + @DisplayName("快递:全场包邮(开关开启 && 总金额满足全场包邮金额)")
  208 + public void testCalculate_globalFree() {
  209 + // mock:收件地址 + 全场包邮配置
  210 + mockAddress();
  211 + when(tradeConfigService.getTradeConfig()).thenReturn(new TradeConfigDO()
  212 + .setDeliveryExpressFreeEnabled(true).setDeliveryExpressFreePrice(2200));
  213 +
  214 + // 调用
  215 + calculator.calculate(reqBO, resultBO);
  216 +
  217 + // 断言
109 218 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
110 219 assertThat(price)
111   - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice")
112   - .containsExactly(2200, 0, 0, 0, 0, 2200);
  220 + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
  221 + .containsExactly(2200, 0, 0, 0, 0, 2200);
113 222 assertThat(resultBO.getItems()).hasSize(3);
114   - // 断言:SKU1
115 223 assertThat(resultBO.getItems().get(0))
116   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  224 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
117 225 .containsExactly(100, 2, 0, 0, 0, 0, 200);
118   - // 断言:SKU2
119 226 assertThat(resultBO.getItems().get(1))
120   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  227 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
121 228 .containsExactly(200, 10, 0, 0, 0, 0, 2000);
122   - // 断言:SKU3 未选中
123 229 assertThat(resultBO.getItems().get(2))
124   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  230 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
125 231 .containsExactly(300, 1, 0, 0, 0, 0, 300);
126 232 }
127 233  
128 234 @Test
129   - @DisplayName("按件计算运费不包邮的情况")
130   - public void testCalculate_expressTemplateCharge() {
131   - // SKU 1 : 100 * 2 = 200
132   - // SKU 2 :200 * 10 = 2000
133   - // 运费 首件 1000 + 续件 2000 = 3000
134   - // mock 方法
  235 + @DisplayName("快递:活动包邮(freeDelivery=true)时不计算运费")
  236 + public void testCalculate_activityFree() {
  237 + // 准备参数
  238 + resultBO.setFreeDelivery(true);
  239 + // mock:收件地址
  240 + mockAddress();
  241 +
  242 + // 调用
  243 + calculator.calculate(reqBO, resultBO);
  244 +
  245 + // 断言:未计算运费
  246 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  247 + }
  248 +
  249 + @Test
  250 + @DisplayName("快递:找不到运费模板时抛出异常")
  251 + public void testCalculate_express_templateNotFound() {
  252 + // mock:收件地址 + 空模板
  253 + mockAddress();
135 254 when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10)))
136   - .thenReturn(MapUtil.of(1L, templateRespBO));
  255 + .thenReturn(Collections.emptyMap());
  256 +
  257 + // 调用并断言异常
  258 + assertServiceException(() -> calculator.calculate(reqBO, resultBO),
  259 + PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND);
  260 + }
  261 +
  262 + @Test
  263 + @DisplayName("按件计算运费:不包邮的情况")
  264 + public void testCalculate_expressTemplateCharge() {
  265 + // SKU 1:100 * 2 = 200
  266 + // SKU 2:200 * 10 = 2000
  267 + // 运费:首件 1000 + 续件 ceil((12-10)/10)=1 倍 * 2000 = 3000
  268 + // mock 方法:默认 freeCount=20,订单 12 件 不大于 20,不包邮
  269 + mockAddress();
  270 + mockTemplate(templateRespBO);
137 271  
138 272 // 调用
139 273 calculator.calculate(reqBO, resultBO);
  274 +
140 275 // 断言
141 276 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
142 277 assertThat(price)
143   - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice")
144   - .containsExactly(2200, 0, 0, 0, 3000, 5200);
  278 + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
  279 + .containsExactly(2200, 0, 0, 0, 3000, 5200);
145 280 assertThat(resultBO.getItems()).hasSize(3);
146   - // 断言:SKU1
  281 + // 断言:SKU1 分摊运费 (int)(3000 * 2/12) = 500
147 282 assertThat(resultBO.getItems().get(0))
148   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  283 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
149 284 .containsExactly(100, 2, 0, 0, 0, 500, 700);
150   - // 断言:SKU2
  285 + // 断言:SKU2 分摊运费 3000 - 500 = 2500
151 286 assertThat(resultBO.getItems().get(1))
152   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  287 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
153 288 .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
154   - // 断言:SKU3 未选中
  289 + // 断言:SKU3 未选中,不计运费
155 290 assertThat(resultBO.getItems().get(2))
156   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  291 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
157 292 .containsExactly(300, 1, 0, 0, 0, 0, 300);
158 293 }
159 294  
160 295 @Test
161   - @DisplayName("按件计算运费包邮的情况")
  296 + @DisplayName("按件计算运费:包邮(订单总件数 >= 包邮件数 && 总金额 >= 包邮金额)")
162 297 public void testCalculate_expressTemplateFree() {
163   - // SKU 1 : 100 * 2 = 200
164   - // SKU 2 :200 * 10 = 2000
165   - // 运费 0
166   - // mock 方法
167   - // 准备运费模板包邮配置数据 包邮 订单总件数 > 包邮件数时 12 > 10
  298 + // SKU 1:100 * 2 = 200
  299 + // SKU 2:200 * 10 = 2000
  300 + // 总件数 12 >= 包邮件数 10 && 总金额 2200 >= 包邮金额 1000,包邮
168 301 templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
169 302 item -> item.setFreeCount(10).setFreePrice(1000)));
170   - when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10)))
171   - .thenReturn(MapUtil.of(1L, templateRespBO));
  303 + mockAddress();
  304 + mockTemplate(templateRespBO);
172 305  
173 306 // 调用
174 307 calculator.calculate(reqBO, resultBO);
  308 +
175 309 // 断言
176 310 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
177 311 assertThat(price)
178   - .extracting("totalPrice","discountPrice","couponPrice","pointPrice","deliveryPrice","payPrice")
179   - .containsExactly(2200, 0, 0, 0, 0, 2200);
  312 + .extracting("totalPrice", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
  313 + .containsExactly(2200, 0, 0, 0, 0, 2200);
180 314 assertThat(resultBO.getItems()).hasSize(3);
181   - // 断言:SKU1
182 315 assertThat(resultBO.getItems().get(0))
183   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  316 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
184 317 .containsExactly(100, 2, 0, 0, 0, 0, 200);
185   - // 断言:SKU2
186 318 assertThat(resultBO.getItems().get(1))
187   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  319 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
188 320 .containsExactly(200, 10, 0, 0, 0, 0, 2000);
189   - // 断言:SKU3 未选中
190 321 assertThat(resultBO.getItems().get(2))
191   - .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
  322 + .extracting("price", "count", "discountPrice", "couponPrice", "pointPrice", "deliveryPrice", "payPrice")
192 323 .containsExactly(300, 1, 0, 0, 0, 0, 300);
193 324 }
194 325  
  326 + @Test
  327 + @DisplayName("按件计算运费:边界场景(总件数 = 包邮件数 → 包邮)")
  328 + public void testCalculate_expressTemplateFree_countBoundary() {
  329 + // 总件数 12 等于 包邮件数 12 && 总金额 2200 >= 包邮金额 1000,包邮
  330 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
  331 + item -> item.setFreeCount(12).setFreePrice(1000)));
  332 + mockAddress();
  333 + mockTemplate(templateRespBO);
  334 +
  335 + // 调用
  336 + calculator.calculate(reqBO, resultBO);
  337 +
  338 + // 断言:包邮,运费 = 0
  339 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  340 + }
  341 +
  342 + @Test
  343 + @DisplayName("按件计算运费:总金额未达到包邮金额 → 不包邮")
  344 + public void testCalculate_expressTemplateFree_priceNotMatch() {
  345 + // 总件数 12 > 包邮件数 10,但总金额 2200 < 包邮金额 5000,不包邮
  346 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
  347 + item -> item.setFreeCount(10).setFreePrice(5000)));
  348 + mockAddress();
  349 + mockTemplate(templateRespBO);
  350 +
  351 + // 调用
  352 + calculator.calculate(reqBO, resultBO);
  353 +
  354 + // 断言:仍按运费模板计算 = 3000
  355 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(3000);
  356 + }
  357 +
  358 + @Test
  359 + @DisplayName("按件计算运费:边界场景(总金额 = 包邮金额 → 包邮)")
  360 + public void testCalculate_expressTemplateFree_priceBoundary() {
  361 + // 总件数 12 >= 包邮件数 10 && 总金额 2200 等于 包邮金额 2200,包邮
  362 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
  363 + item -> item.setFreeCount(10).setFreePrice(2200)));
  364 + mockAddress();
  365 + mockTemplate(templateRespBO);
  366 +
  367 + // 调用
  368 + calculator.calculate(reqBO, resultBO);
  369 +
  370 + // 断言:包邮,运费 = 0
  371 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(0);
  372 + }
  373 +
  374 + @Test
  375 + @DisplayName("按重量计算运费")
  376 + public void testCalculate_expressTemplate_byWeight() {
  377 + // SKU1 weight 10 * count 2 = 20
  378 + // SKU2 weight 10 * count 10 = 100
  379 + // 总重量 120kg
  380 + // 运费:首件 1000 + 续件 ceil((120-10)/10)=11 倍 * 2000 = 23000
  381 + templateRespBO.setChargeMode(DeliveryExpressChargeModeEnum.WEIGHT.getType());
  382 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
  383 + item -> item.setFreeCount(200).setFreePrice(100)));
  384 + mockAddress();
  385 + mockTemplate(templateRespBO);
  386 +
  387 + // 调用
  388 + calculator.calculate(reqBO, resultBO);
  389 +
  390 + // 断言
  391 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(23000);
  392 + }
  393 +
  394 + @Test
  395 + @DisplayName("按体积计算运费")
  396 + public void testCalculate_expressTemplate_byVolume() {
  397 + // SKU1 volume 10 * count 2 = 20
  398 + // SKU2 volume 10 * count 10 = 100
  399 + // 总体积 120m³,与按重量场景同
  400 + templateRespBO.setChargeMode(DeliveryExpressChargeModeEnum.VOLUME.getType());
  401 + templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
  402 + item -> item.setFreeCount(200).setFreePrice(100)));
  403 + mockAddress();
  404 + mockTemplate(templateRespBO);
  405 +
  406 + // 调用
  407 + calculator.calculate(reqBO, resultBO);
  408 +
  409 + // 断言
  410 + assertThat(resultBO.getPrice().getDeliveryPrice()).isEqualTo(23000);
  411 + }
  412 +
  413 + private void mockAddress() {
  414 + MemberAddressRespDTO addressResp = randomPojo(MemberAddressRespDTO.class, item -> item.setAreaId(10));
  415 + when(addressApi.getAddress(eq(10L), eq(1L))).thenReturn(addressResp);
  416 + }
  417 +
  418 + private void mockTemplate(DeliveryExpressTemplateRespBO template) {
  419 + when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10)))
  420 + .thenReturn(MapUtil.of(1L, template));
  421 + }
  422 +
195 423 }
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDiscountActivityPriceCalculatorTest.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
  5 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
4 6 import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
5 7 import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
6 8 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
... ... @@ -8,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
8 10 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
9 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
10 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
11   -import org.junit.jupiter.api.Disabled;
12 13 import org.junit.jupiter.api.Test;
13 14 import org.mockito.InjectMocks;
14 15 import org.mockito.Mock;
... ... @@ -27,7 +28,6 @@ import static org.mockito.Mockito.when;
27 28 *
28 29 * @author 芋道源码
29 30 */
30   -@Disabled // TODO 芋艿:后续修复
31 31 public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest {
32 32  
33 33 @InjectMocks
... ... @@ -35,6 +35,10 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes
35 35  
36 36 @Mock
37 37 private DiscountActivityApi discountActivityApi;
  38 + @Mock
  39 + private MemberLevelApi memberLevelApi;
  40 + @Mock
  41 + private MemberUserApi memberUserApi;
38 42  
39 43 @Test
40 44 public void testCalculate() {
... ... @@ -92,16 +96,16 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes
92 96 assertEquals(orderItem01.getCouponPrice(), 0);
93 97 assertEquals(orderItem01.getPointPrice(), 0);
94 98 assertEquals(orderItem01.getPayPrice(), 120);
95   - // 断言:SKU 2
  99 + // 断言:SKU 2 未选中,不参与计算
96 100 TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
97 101 assertEquals(orderItem02.getSkuId(), 20L);
98 102 assertEquals(orderItem02.getCount(), 3);
99 103 assertEquals(orderItem02.getPrice(), 50);
100   - assertEquals(orderItem02.getDiscountPrice(), 60);
  104 + assertEquals(orderItem02.getDiscountPrice(), 0);
101 105 assertEquals(orderItem02.getDeliveryPrice(), 0);
102 106 assertEquals(orderItem02.getCouponPrice(), 0);
103 107 assertEquals(orderItem02.getPointPrice(), 0);
104   - assertEquals(orderItem02.getPayPrice(), 90);
  108 + assertEquals(orderItem02.getPayPrice(), 150);
105 109 // 断言:Promotion 部分
106 110 assertEquals(result.getPromotions().size(), 1);
107 111 TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePointUsePriceCalculatorTest.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
9 9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
10 10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
11 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
12   -import org.junit.jupiter.api.Disabled;
13 12 import org.junit.jupiter.api.Test;
14 13 import org.mockito.InjectMocks;
15 14 import org.mockito.Mock;
... ... @@ -22,13 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
22 21 import static org.junit.jupiter.api.Assertions.assertTrue;
23 22 import static org.mockito.Mockito.when;
24 23  
25   -// TODO 芋艿:晚点 review
26 24 /**
27 25 * {@link TradePointUsePriceCalculator } 的单元测试类
28 26 *
29 27 * @author owen
30 28 */
31   -@Disabled // TODO 芋艿:后续修复
32 29 public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
33 30  
34 31 @InjectMocks
... ... @@ -245,6 +242,10 @@ public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
245 242 TradePriceCalculatorHelper.recountPayPrice(result.getItems());
246 243 TradePriceCalculatorHelper.recountAllPrice(result);
247 244  
  245 + // mock 方法(会员 信息):不使用积分仍需获取用户初始化 totalPoint
  246 + MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
  247 + when(memberUserApi.getUser(user.getId())).thenReturn(user);
  248 +
248 249 // 调用
249 250 tradePointUsePriceCalculator.calculate(param, result);
250 251 // 断言:没有使用积分
... ...
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculatorTest.java
... ... @@ -2,10 +2,29 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 4 import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
  5 +import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
  6 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
  7 +import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  8 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
  9 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
  10 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  11 +import org.junit.jupiter.api.Test;
5 12 import org.mockito.InjectMocks;
6 13 import org.mockito.Mock;
7 14  
8   -// TODO 芋艿:后续在修复
  15 +import java.util.ArrayList;
  16 +import java.util.LinkedHashMap;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
  19 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  20 +import static java.util.Arrays.asList;
  21 +import static java.util.Collections.singletonList;
  22 +import static org.junit.jupiter.api.Assertions.assertEquals;
  23 +import static org.junit.jupiter.api.Assertions.assertFalse;
  24 +import static org.junit.jupiter.api.Assertions.assertTrue;
  25 +import static org.mockito.ArgumentMatchers.eq;
  26 +import static org.mockito.Mockito.when;
  27 +
9 28 /**
10 29 * {@link TradeRewardActivityPriceCalculator} 的单元测试类
11 30 *
... ... @@ -19,212 +38,155 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
19 38 @Mock
20 39 private RewardActivityApi rewardActivityApi;
21 40  
22   -// @Test
23   -// public void testCalculate_match() {
24   -// // 准备参数
25   -// TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
26   -// .setItems(asList(
27   -// new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 匹配活动 1
28   -// new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 匹配活动 1
29   -// new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true) // 匹配活动 2
30   -// ));
31   -// TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
32   -// .setType(TradeOrderTypeEnum.NORMAL.getType())
33   -// .setPrice(new TradePriceCalculateRespBO.Price())
34   -// .setPromotions(new ArrayList<>()).setGiveCouponTemplateCounts(new LinkedHashMap<>())
35   -// .setItems(asList(
36   -// new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
37   -// .setPrice(100).setSpuId(1L),
38   -// new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
39   -// .setPrice(50).setSpuId(2L),
40   -// new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true)
41   -// .setPrice(30).setSpuId(3L)
42   -// ));
43   -// // 保证价格被初始化上
44   -// TradePriceCalculatorHelper.recountPayPrice(result.getItems());
45   -// TradePriceCalculatorHelper.recountAllPrice(result);
46   -//
47   -// // mock 方法(满减送 RewardActivity 信息)
48   -// when(rewardActivityApi.getRewardActivityListByStatusAndNow(CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()))
49   -// .thenReturn(asList(
50   -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
51   -// .setConditionType(PromotionConditionTypeEnum.PRICE.getType())
52   -// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
53   -// .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(20).setDiscountPrice(70)
54   -// .setFreeDelivery(false)))),
55   -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(2000L).setName("活动 2000 号")
56   -// .setConditionType(PromotionConditionTypeEnum.COUNT.getType())
57   -// .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(singletonList(3L))
58   -// .setRules(asList(new RewardActivityMatchRespDTO.Rule().setLimit(1).setDiscountPrice(10)
59   -// .setPoint(50).setFreeDelivery(false),
60   -// new RewardActivityMatchRespDTO.Rule().setLimit(2).setDiscountPrice(60)
61   -// .setPoint(100).setFreeDelivery(false), // 最大可满足,因为是 4 个
62   -// new RewardActivityMatchRespDTO.Rule().setLimit(10).setDiscountPrice(100)
63   -// .setFreeDelivery(false))))
64   -// ));
65   -//
66   -// // 调用
67   -// tradeRewardActivityPriceCalculator.calculate(param, result);
68   -// // 断言 Order 部分
69   -// TradePriceCalculateRespBO.Price price = result.getPrice();
70   -// assertEquals(price.getTotalPrice(), 470);
71   -// assertEquals(price.getDiscountPrice(), 130);
72   -// assertEquals(price.getPointPrice(), 0);
73   -// assertEquals(price.getDeliveryPrice(), 0);
74   -// assertEquals(price.getCouponPrice(), 0);
75   -// assertEquals(price.getPayPrice(), 340);
76   -// assertNull(result.getCouponId());
77   -// // 断言:SKU 1
78   -// assertEquals(result.getItems().size(), 3);
79   -// TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
80   -// assertEquals(orderItem01.getSkuId(), 10L);
81   -// assertEquals(orderItem01.getCount(), 2);
82   -// assertEquals(orderItem01.getPrice(), 100);
83   -// assertEquals(orderItem01.getDiscountPrice(), 40);
84   -// assertEquals(orderItem01.getDeliveryPrice(), 0);
85   -// assertEquals(orderItem01.getCouponPrice(), 0);
86   -// assertEquals(orderItem01.getPointPrice(), 0);
87   -// assertEquals(orderItem01.getPayPrice(), 160);
88   -// assertEquals(orderItem01.getGivePoint(), 0);
89   -// // 断言:SKU 2
90   -// TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
91   -// assertEquals(orderItem02.getSkuId(), 20L);
92   -// assertEquals(orderItem02.getCount(), 3);
93   -// assertEquals(orderItem02.getPrice(), 50);
94   -// assertEquals(orderItem02.getDiscountPrice(), 30);
95   -// assertEquals(orderItem02.getDeliveryPrice(), 0);
96   -// assertEquals(orderItem02.getCouponPrice(), 0);
97   -// assertEquals(orderItem02.getPointPrice(), 0);
98   -// assertEquals(orderItem02.getPayPrice(), 120);
99   -// assertEquals(orderItem02.getGivePoint(), 0);
100   -// // 断言:SKU 3
101   -// TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
102   -// assertEquals(orderItem03.getSkuId(), 30L);
103   -// assertEquals(orderItem03.getCount(), 4);
104   -// assertEquals(orderItem03.getPrice(), 30);
105   -// assertEquals(orderItem03.getDiscountPrice(), 60);
106   -// assertEquals(orderItem03.getDeliveryPrice(), 0);
107   -// assertEquals(orderItem03.getCouponPrice(), 0);
108   -// assertEquals(orderItem03.getPointPrice(), 0);
109   -// assertEquals(orderItem03.getPayPrice(), 60);
110   -// assertEquals(orderItem03.getGivePoint(), 100);
111   -// // 断言:Promotion 部分(第一个)
112   -// assertEquals(result.getPromotions().size(), 2);
113   -// TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
114   -// assertEquals(promotion01.getId(), 1000L);
115   -// assertEquals(promotion01.getName(), "活动 1000 号");
116   -// assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
117   -// assertEquals(promotion01.getTotalPrice(), 350);
118   -// assertEquals(promotion01.getDiscountPrice(), 70);
119   -// assertTrue(promotion01.getMatch());
120   -// assertEquals(promotion01.getDescription(), "满减送:省 0.70 元");
121   -// assertEquals(promotion01.getItems().size(), 2);
122   -// TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
123   -// assertEquals(promotionItem011.getSkuId(), 10L);
124   -// assertEquals(promotionItem011.getTotalPrice(), 200);
125   -// assertEquals(promotionItem011.getDiscountPrice(), 40);
126   -// TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
127   -// assertEquals(promotionItem012.getSkuId(), 20L);
128   -// assertEquals(promotionItem012.getTotalPrice(), 150);
129   -// assertEquals(promotionItem012.getDiscountPrice(), 30);
130   -// // 断言:Promotion 部分(第二个)
131   -// TradePriceCalculateRespBO.Promotion promotion02 = result.getPromotions().get(1);
132   -// assertEquals(promotion02.getId(), 2000L);
133   -// assertEquals(promotion02.getName(), "活动 2000 号");
134   -// assertEquals(promotion02.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
135   -// assertEquals(promotion02.getTotalPrice(), 120);
136   -// assertEquals(promotion02.getDiscountPrice(), 60);
137   -// assertTrue(promotion02.getMatch());
138   -// assertEquals(promotion02.getDescription(), "满减送:省 0.60 元");
139   -// TradePriceCalculateRespBO.PromotionItem promotionItem02 = promotion02.getItems().get(0);
140   -// assertEquals(promotion02.getItems().size(), 1);
141   -// assertEquals(promotionItem02.getSkuId(), 30L);
142   -// assertEquals(promotionItem02.getTotalPrice(), 120);
143   -// assertEquals(promotionItem02.getDiscountPrice(), 60);
144   -// }
145   -//
146   -// @Test
147   -// public void testCalculate_notMatch() {
148   -// // 准备参数
149   -// TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
150   -// .setItems(asList(
151   -// new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true),
152   -// new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true),
153   -// new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true)
154   -// ));
155   -// TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
156   -// .setType(TradeOrderTypeEnum.NORMAL.getType())
157   -// .setPrice(new TradePriceCalculateRespBO.Price())
158   -// .setPromotions(new ArrayList<>())
159   -// .setItems(asList(
160   -// new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
161   -// .setPrice(100).setSpuId(1L),
162   -// new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
163   -// .setPrice(50).setSpuId(2L)
164   -// ));
165   -// // 保证价格被初始化上
166   -// TradePriceCalculatorHelper.recountPayPrice(result.getItems());
167   -// TradePriceCalculatorHelper.recountAllPrice(result);
168   -//
169   -// // mock 方法(限时折扣 DiscountActivity 信息)
170   -// when(rewardActivityApi.getRewardActivityListByStatusAndNow(CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now()))
171   -// .thenReturn(singletonList(
172   -// randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
173   -// .setProductScopeValues(asList(1L, 2L)).setConditionType(PromotionConditionTypeEnum.PRICE.getType())
174   -// .setRules(singletonList(new RewardActivityMatchRespDTO.Rule().setLimit(351).setDiscountPrice(70))))
175   -// ));
176   -//
177   -// // 调用
178   -// tradeRewardActivityPriceCalculator.calculate(param, result);
179   -// // 断言 Order 部分
180   -// TradePriceCalculateRespBO.Price price = result.getPrice();
181   -// assertEquals(price.getTotalPrice(), 350);
182   -// assertEquals(price.getDiscountPrice(), 0);
183   -// assertEquals(price.getPointPrice(), 0);
184   -// assertEquals(price.getDeliveryPrice(), 0);
185   -// assertEquals(price.getCouponPrice(), 0);
186   -// assertEquals(price.getPayPrice(), 350);
187   -// assertNull(result.getCouponId());
188   -// // 断言:SKU 1
189   -// assertEquals(result.getItems().size(), 2);
190   -// TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
191   -// assertEquals(orderItem01.getSkuId(), 10L);
192   -// assertEquals(orderItem01.getCount(), 2);
193   -// assertEquals(orderItem01.getPrice(), 100);
194   -// assertEquals(orderItem01.getDiscountPrice(), 0);
195   -// assertEquals(orderItem01.getDeliveryPrice(), 0);
196   -// assertEquals(orderItem01.getCouponPrice(), 0);
197   -// assertEquals(orderItem01.getPointPrice(), 0);
198   -// assertEquals(orderItem01.getPayPrice(), 200);
199   -// // 断言:SKU 2
200   -// TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
201   -// assertEquals(orderItem02.getSkuId(), 20L);
202   -// assertEquals(orderItem02.getCount(), 3);
203   -// assertEquals(orderItem02.getPrice(), 50);
204   -// assertEquals(orderItem02.getDiscountPrice(), 0);
205   -// assertEquals(orderItem02.getDeliveryPrice(), 0);
206   -// assertEquals(orderItem02.getCouponPrice(), 0);
207   -// assertEquals(orderItem02.getPointPrice(), 0);
208   -// assertEquals(orderItem02.getPayPrice(), 150);
209   -// // 断言 Promotion 部分
210   -// assertEquals(result.getPromotions().size(), 1);
211   -// TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
212   -// assertEquals(promotion01.getId(), 1000L);
213   -// assertEquals(promotion01.getName(), "活动 1000 号");
214   -// assertEquals(promotion01.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
215   -// assertEquals(promotion01.getTotalPrice(), 350);
216   -// assertEquals(promotion01.getDiscountPrice(), 0);
217   -// assertFalse(promotion01.getMatch());
218   -// assertEquals(promotion01.getDescription(), "TODO"); // TODO 芋艿:后面再想想
219   -// assertEquals(promotion01.getItems().size(), 2);
220   -// TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
221   -// assertEquals(promotionItem011.getSkuId(), 10L);
222   -// assertEquals(promotionItem011.getTotalPrice(), 200);
223   -// assertEquals(promotionItem011.getDiscountPrice(), 0);
224   -// TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
225   -// assertEquals(promotionItem012.getSkuId(), 20L);
226   -// assertEquals(promotionItem012.getTotalPrice(), 150);
227   -// assertEquals(promotionItem012.getDiscountPrice(), 0);
228   -// }
  41 + @Test
  42 + public void testCalculate_match() {
  43 + // 准备参数:SPU 1、SPU 2 命中活动;SPU 3 不在活动范围
  44 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  45 + .setItems(asList(
  46 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true),
  47 + new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true),
  48 + new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(true)
  49 + ));
  50 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  51 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  52 + .setPrice(new TradePriceCalculateRespBO.Price())
  53 + .setPromotions(new ArrayList<>())
  54 + .setGiveCouponTemplateCounts(new LinkedHashMap<>())
  55 + .setItems(asList(
  56 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  57 + .setPrice(100).setSpuId(1L).setGivePoint(0),
  58 + new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
  59 + .setPrice(50).setSpuId(2L).setGivePoint(0),
  60 + new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(true)
  61 + .setPrice(30).setSpuId(3L).setGivePoint(0)
  62 + ));
  63 + // 保证价格被初始化上
  64 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  65 + TradePriceCalculatorHelper.recountAllPrice(result);
  66 +
  67 + // mock 方法(满减送活动)
  68 + when(rewardActivityApi.getMatchRewardActivityListBySpuIds(eq(asSet(1L, 2L, 3L)))).thenReturn(singletonList(
  69 + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
  70 + .setConditionType(PromotionConditionTypeEnum.PRICE.getType())
  71 + .setSpuIds(asList(1L, 2L))
  72 + .setRules(asList(
  73 + new RewardActivityMatchRespDTO.Rule().setLimit(100).setDiscountPrice(20)
  74 + .setPoint(50).setFreeDelivery(false),
  75 + new RewardActivityMatchRespDTO.Rule().setLimit(200).setDiscountPrice(50)
  76 + .setPoint(100).setFreeDelivery(true))))));
  77 +
  78 + // 调用:SKU1+SKU2 总 payPrice=200+150=350, 命中 limit=200 规则
  79 + tradeRewardActivityPriceCalculator.calculate(param, result);
  80 +
  81 + // 断言:Price 部分(SKU3 仍按原价计入)
  82 + TradePriceCalculateRespBO.Price price = result.getPrice();
  83 + assertEquals(price.getTotalPrice(), 470);
  84 + assertEquals(price.getDiscountPrice(), 50);
  85 + assertEquals(price.getPayPrice(), 420);
  86 + // 断言:包邮标记
  87 + assertTrue(result.getFreeDelivery());
  88 +
  89 + // 断言:SKU 1 优惠 50 * 200/350 = 28
  90 + TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
  91 + assertEquals(orderItem01.getSkuId(), 10L);
  92 + assertEquals(orderItem01.getDiscountPrice(), 28);
  93 + assertEquals(orderItem01.getPayPrice(), 172);
  94 + assertEquals(orderItem01.getGivePoint(), 57); // 100 * 200/350
  95 + // 断言:SKU 2 优惠 50 - 28 = 22
  96 + TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
  97 + assertEquals(orderItem02.getSkuId(), 20L);
  98 + assertEquals(orderItem02.getDiscountPrice(), 22);
  99 + assertEquals(orderItem02.getPayPrice(), 128);
  100 + assertEquals(orderItem02.getGivePoint(), 43); // 100 - 57
  101 + // 断言:SKU 3 不在活动范围,不参与
  102 + TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
  103 + assertEquals(orderItem03.getSkuId(), 30L);
  104 + assertEquals(orderItem03.getDiscountPrice(), 0);
  105 + assertEquals(orderItem03.getPayPrice(), 120);
  106 + assertEquals(orderItem03.getGivePoint(), 0);
  107 +
  108 + // 断言:Promotion 部分
  109 + assertEquals(result.getPromotions().size(), 1);
  110 + TradePriceCalculateRespBO.Promotion promotion = result.getPromotions().get(0);
  111 + assertEquals(promotion.getId(), 1000L);
  112 + assertEquals(promotion.getName(), "活动 1000 号");
  113 + assertEquals(promotion.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
  114 + assertEquals(promotion.getTotalPrice(), 350);
  115 + assertEquals(promotion.getDiscountPrice(), 50);
  116 + assertTrue(promotion.getMatch());
  117 + assertEquals(promotion.getDescription(), "满减送:省 0.50 元");
  118 + assertEquals(promotion.getItems().size(), 2);
  119 + }
  120 +
  121 + @Test
  122 + public void testCalculate_notMatch() {
  123 + // 准备参数
  124 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  125 + .setItems(singletonList(
  126 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true)
  127 + ));
  128 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  129 + .setType(TradeOrderTypeEnum.NORMAL.getType())
  130 + .setPrice(new TradePriceCalculateRespBO.Price())
  131 + .setPromotions(new ArrayList<>())
  132 + .setGiveCouponTemplateCounts(new LinkedHashMap<>())
  133 + .setItems(singletonList(
  134 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  135 + .setPrice(100).setSpuId(1L).setGivePoint(0)
  136 + ));
  137 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  138 + TradePriceCalculatorHelper.recountAllPrice(result);
  139 +
  140 + // mock 方法:要求 limit=300,订单总额 200 不满足
  141 + when(rewardActivityApi.getMatchRewardActivityListBySpuIds(eq(asSet(1L)))).thenReturn(singletonList(
  142 + randomPojo(RewardActivityMatchRespDTO.class, o -> o.setId(1000L).setName("活动 1000 号")
  143 + .setConditionType(PromotionConditionTypeEnum.PRICE.getType())
  144 + .setSpuIds(singletonList(1L))
  145 + .setRules(singletonList(new RewardActivityMatchRespDTO.Rule()
  146 + .setLimit(300).setDiscountPrice(70).setDescription("满 3 元减 0.7 元"))))));
  147 +
  148 + // 调用
  149 + tradeRewardActivityPriceCalculator.calculate(param, result);
  150 +
  151 + // 断言:未优惠
  152 + TradePriceCalculateRespBO.Price price = result.getPrice();
  153 + assertEquals(price.getTotalPrice(), 200);
  154 + assertEquals(price.getDiscountPrice(), 0);
  155 + assertEquals(price.getPayPrice(), 200);
  156 +
  157 + // 断言:促销明细记录为不匹配
  158 + assertEquals(result.getPromotions().size(), 1);
  159 + TradePriceCalculateRespBO.Promotion promotion = result.getPromotions().get(0);
  160 + assertEquals(promotion.getId(), 1000L);
  161 + assertEquals(promotion.getType(), PromotionTypeEnum.REWARD_ACTIVITY.getType());
  162 + assertFalse(promotion.getMatch());
  163 + assertEquals(promotion.getDiscountPrice(), 0);
  164 + assertEquals(promotion.getDescription(), "满减送:满 3 元减 0.7 元");
  165 + }
  166 +
  167 + @Test
  168 + public void testCalculate_skipNonNormalOrder() {
  169 + // 准备参数:秒杀订单
  170 + TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
  171 + .setItems(singletonList(
  172 + new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true)
  173 + ));
  174 + TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
  175 + .setType(TradeOrderTypeEnum.SECKILL.getType())
  176 + .setPrice(new TradePriceCalculateRespBO.Price())
  177 + .setPromotions(new ArrayList<>())
  178 + .setItems(singletonList(
  179 + new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
  180 + .setPrice(100).setSpuId(1L)
  181 + ));
  182 + TradePriceCalculatorHelper.recountPayPrice(result.getItems());
  183 + TradePriceCalculatorHelper.recountAllPrice(result);
  184 +
  185 + // 调用:非普通订单直接跳过,不会调用 API
  186 + tradeRewardActivityPriceCalculator.calculate(param, result);
  187 +
  188 + // 断言:未产生促销
  189 + assertEquals(result.getPromotions().size(), 0);
  190 + }
229 191  
230 192 }
... ...