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,14 +43,14 @@ public class DeliveryExpressTemplateFreeDO extends BaseDO {
43 /** 43 /**
44 * 包邮金额,单位:分 44 * 包邮金额,单位:分
45 * 45 *
46 - * 订单总金额 > 包邮金额时,才免运费 46 + * 订单总金额 >= 包邮金额时,才免运费
47 */ 47 */
48 private Integer freePrice; 48 private Integer freePrice;
49 49
50 /** 50 /**
51 * 包邮件数 51 * 包邮件数
52 * 52 *
53 - * 订单总件数 > 包邮件数时,才免运费 53 + * 订单总件数 >= 包邮件数时,才免运费
54 */ 54 */
55 private Integer freeCount; 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,14 +65,14 @@ public class DeliveryExpressTemplateRespBO {
65 /** 65 /**
66 * 包邮金额,单位:分 66 * 包邮金额,单位:分
67 * 67 *
68 - * 订单总金额 > 包邮金额时,才免运费 68 + * 订单总金额 >= 包邮金额时,才免运费
69 */ 69 */
70 private Integer freePrice; 70 private Integer freePrice;
71 71
72 /** 72 /**
73 * 包邮件数 73 * 包邮件数
74 * 74 *
75 - * 订单总件数 > 包邮件数时,才免运费 75 + * 订单总件数 >= 包邮件数时,才免运费
76 */ 76 */
77 private Integer freeCount; 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,7 +14,6 @@ import org.springframework.stereotype.Component;
14 14
15 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
16 16
17 -// TODO huihui:单测需要补充  
18 /** 17 /**
19 * 砍价活动的 {@link TradePriceCalculator} 实现类 18 * 砍价活动的 {@link TradePriceCalculator} 实现类
20 * 19 *
@@ -29,12 +28,12 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator @@ -29,12 +28,12 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
29 28
30 @Override 29 @Override
31 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 30 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
32 - // 1. 判断订单类型和是否具有拼团记录编号 31 + // 1. 判断订单类型和是否具有砍价记录编号
33 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) { 32 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) {
34 return; 33 return;
35 } 34 }
36 Assert.isTrue(param.getItems().size() == 1, "砍价时,只允许选择一个商品"); 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 // 2. 校验是否可以参与砍价 37 // 2. 校验是否可以参与砍价
39 TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0); 38 TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
40 BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain( 39 BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain(
@@ -44,7 +43,7 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator @@ -44,7 +43,7 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
44 Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount(); 43 Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount();
45 // TODO 芋艿:极端情况,优惠金额为负数,需要处理 44 // TODO 芋艿:极端情况,优惠金额为负数,需要处理
46 TradePriceCalculatorHelper.addPromotion(result, orderItem, 45 TradePriceCalculatorHelper.addPromotion(result, orderItem,
47 - param.getSeckillActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(), 46 + bargainActivity.getActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(),
48 StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)), 47 StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
49 discountPrice); 48 discountPrice);
50 // 3.2 更新 SKU 优惠金额 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,7 +213,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
213 } 213 }
214 double totalChargeValue = getTotalChargeValue(orderItems, chargeMode); 214 double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
215 double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); 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 private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) { 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 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ObjectUtil;
4 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 7 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
@@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper { @@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper {
61 // spu 信息 62 // spu 信息
62 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()) 63 orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
63 .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId()) 64 .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
64 - .setGivePoint(spu.getGiveIntegral()).setUsePoint(0); 65 + .setGivePoint(ObjectUtil.defaultIfNull(spu.getGiveIntegral(), 0)).setUsePoint(0);
65 if (StrUtil.isBlank(orderItem.getPicUrl())) { 66 if (StrUtil.isBlank(orderItem.getPicUrl())) {
66 orderItem.setPicUrl(spu.getPicUrl()); 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,12 +6,10 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
6 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 6 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; 7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 8 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
9 -import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;  
10 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
13 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; 12 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
14 -import org.junit.jupiter.api.Disabled;  
15 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
16 import org.mockito.InjectMocks; 14 import org.mockito.InjectMocks;
17 import org.mockito.Mock; 15 import org.mockito.Mock;
@@ -30,7 +28,6 @@ import static org.mockito.Mockito.when; @@ -30,7 +28,6 @@ import static org.mockito.Mockito.when;
30 * 28 *
31 * @author 芋道源码 29 * @author 芋道源码
32 */ 30 */
33 -@Disabled // TODO 芋艿:后续 fix 补充的单测  
34 public class TradePriceServiceImplTest extends BaseMockitoUnitTest { 31 public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
35 32
36 @InjectMocks 33 @InjectMocks
@@ -67,9 +64,8 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest { @@ -67,9 +64,8 @@ public class TradePriceServiceImplTest extends BaseMockitoUnitTest {
67 .setValueId(4L).setValueName("黑色"))) 64 .setValueId(4L).setValueName("黑色")))
68 ); 65 );
69 when(productSkuApi.getSkuList(Mockito.eq(asSet(100L, 200L, 300L)))).thenReturn(skuList); 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 TradePriceCalculateRespBO calculateRespBO = tradePriceService.calculateOrderPrice(calculateReqBO); 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,7 +11,6 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
11 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 11 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
14 -import org.junit.jupiter.api.Disabled;  
15 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.Test;
16 import org.mockito.InjectMocks; 15 import org.mockito.InjectMocks;
17 import org.mockito.Mock; 16 import org.mockito.Mock;
@@ -32,7 +31,6 @@ import static org.mockito.Mockito.when; @@ -32,7 +31,6 @@ import static org.mockito.Mockito.when;
32 * 31 *
33 * @author 芋道源码 32 * @author 芋道源码
34 */ 33 */
35 -@Disabled // TODO 芋艿:后续修复  
36 public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { 34 public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
37 35
38 @InjectMocks 36 @InjectMocks
@@ -75,7 +73,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest { @@ -75,7 +73,7 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
75 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L)) 73 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
76 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType()) 74 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
77 .setDiscountPercent(50).setDiscountLimitPrice(70)) 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 when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus()))) 77 when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus())))
80 .thenReturn(ListUtil.toList(coupon)); 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 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
5 import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; 6 import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
6 import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; 7 import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
7 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; 8 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
  9 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
8 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum; 10 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryExpressChargeModeEnum;
9 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; 11 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
10 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; 12 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
11 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService; 13 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressTemplateService;
  14 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
12 import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; 15 import cn.iocoder.yudao.module.trade.service.delivery.bo.DeliveryExpressTemplateRespBO;
13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 16 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
14 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 17 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
15 import org.junit.jupiter.api.BeforeEach; 18 import org.junit.jupiter.api.BeforeEach;
16 -import org.junit.jupiter.api.Disabled;  
17 import org.junit.jupiter.api.DisplayName; 19 import org.junit.jupiter.api.DisplayName;
18 import org.junit.jupiter.api.Test; 20 import org.junit.jupiter.api.Test;
19 import org.mockito.InjectMocks; 21 import org.mockito.InjectMocks;
20 import org.mockito.Mock; 22 import org.mockito.Mock;
21 23
22 import java.util.ArrayList; 24 import java.util.ArrayList;
  25 +import java.util.Collections;
23 26
24 import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; 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 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 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 import static java.util.Arrays.asList; 33 import static java.util.Arrays.asList;
  34 +import static java.util.Collections.singletonList;
27 import static org.assertj.core.api.Assertions.assertThat; 35 import static org.assertj.core.api.Assertions.assertThat;
28 import static org.mockito.ArgumentMatchers.eq; 36 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.when; 37 import static org.mockito.Mockito.when;
@@ -33,15 +41,15 @@ import static org.mockito.Mockito.when; @@ -33,15 +41,15 @@ import static org.mockito.Mockito.when;
33 * 41 *
34 * @author jason 42 * @author jason
35 */ 43 */
36 -@Disabled // TODO 芋艿:后续修复  
37 -public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { 44 +public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
38 45
39 @InjectMocks 46 @InjectMocks
40 private TradeDeliveryPriceCalculator calculator; 47 private TradeDeliveryPriceCalculator calculator;
41 48
42 @Mock 49 @Mock
43 private MemberAddressApi addressApi; 50 private MemberAddressApi addressApi;
44 - 51 + @Mock
  52 + private DeliveryPickUpStoreService deliveryPickUpStoreService;
45 @Mock 53 @Mock
46 private DeliveryExpressTemplateService deliveryExpressTemplateService; 54 private DeliveryExpressTemplateService deliveryExpressTemplateService;
47 @Mock 55 @Mock
@@ -55,7 +63,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @@ -55,7 +63,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
55 private DeliveryExpressTemplateRespBO.Free freeBO; 63 private DeliveryExpressTemplateRespBO.Free freeBO;
56 64
57 @BeforeEach 65 @BeforeEach
58 - public void init(){ 66 + public void init() {
59 // 准备参数 67 // 准备参数
60 reqBO = new TradePriceCalculateReqBO() 68 reqBO = new TradePriceCalculateReqBO()
61 .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()) 69 .setDeliveryType(DeliveryTypeEnum.EXPRESS.getType())
@@ -71,24 +79,23 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @@ -71,24 +79,23 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
71 .setPromotions(new ArrayList<>()) 79 .setPromotions(new ArrayList<>())
72 .setItems(asList( 80 .setItems(asList(
73 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(10L).setCount(2).setSelected(true) 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 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(20L).setCount(10).setSelected(true) 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 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(30L).setCount(1).setSelected(false) 87 new TradePriceCalculateRespBO.OrderItem().setDeliveryTemplateId(1L).setSkuId(30L).setCount(1).setSelected(false)
78 .setWeight(10d).setVolume(10d).setPrice(300) 88 .setWeight(10d).setVolume(10d).setPrice(300)
  89 + .setDeliveryTypes(asList(DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType()))
79 )); 90 ));
80 // 保证价格被初始化上 91 // 保证价格被初始化上
81 TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems()); 92 TradePriceCalculatorHelper.recountPayPrice(resultBO.getItems());
82 TradePriceCalculatorHelper.recountAllPrice(resultBO); 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 chargeBO = randomPojo(DeliveryExpressTemplateRespBO.Charge.class, 96 chargeBO = randomPojo(DeliveryExpressTemplateRespBO.Charge.class,
90 item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000)); 97 item -> item.setStartCount(10D).setStartPrice(1000).setExtraCount(10D).setExtraPrice(2000));
91 - // 准备运费模板包邮配置数据:订单总件数 < 包邮件数时 12 < 20 98 + // 准备运费模板包邮配置数据:默认不包邮(订单总件数 12 不大于 包邮件数 20)
92 freeBO = randomPojo(DeliveryExpressTemplateRespBO.Free.class, 99 freeBO = randomPojo(DeliveryExpressTemplateRespBO.Free.class,
93 item -> item.setFreeCount(20).setFreePrice(100)); 100 item -> item.setFreeCount(20).setFreePrice(100));
94 // 准备 SP 运费模板数据 101 // 准备 SP 运费模板数据
@@ -97,99 +104,320 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @@ -97,99 +104,320 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
97 .setCharge(chargeBO).setFree(freeBO)); 104 .setCharge(chargeBO).setFree(freeBO));
98 } 105 }
99 106
  107 + // ========== 配送方式校验 ==========
  108 +
100 @Test 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 calculator.calculate(reqBO, resultBO); 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 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); 218 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
110 assertThat(price) 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 assertThat(resultBO.getItems()).hasSize(3); 222 assertThat(resultBO.getItems()).hasSize(3);
114 - // 断言:SKU1  
115 assertThat(resultBO.getItems().get(0)) 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 .containsExactly(100, 2, 0, 0, 0, 0, 200); 225 .containsExactly(100, 2, 0, 0, 0, 0, 200);
118 - // 断言:SKU2  
119 assertThat(resultBO.getItems().get(1)) 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 .containsExactly(200, 10, 0, 0, 0, 0, 2000); 228 .containsExactly(200, 10, 0, 0, 0, 0, 2000);
122 - // 断言:SKU3 未选中  
123 assertThat(resultBO.getItems().get(2)) 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 .containsExactly(300, 1, 0, 0, 0, 0, 300); 231 .containsExactly(300, 1, 0, 0, 0, 0, 300);
126 } 232 }
127 233
128 @Test 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 when(deliveryExpressTemplateService.getExpressTemplateMapByIdsAndArea(eq(asSet(1L)), eq(10))) 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 calculator.calculate(reqBO, resultBO); 273 calculator.calculate(reqBO, resultBO);
  274 +
140 // 断言 275 // 断言
141 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); 276 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
142 assertThat(price) 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 assertThat(resultBO.getItems()).hasSize(3); 280 assertThat(resultBO.getItems()).hasSize(3);
146 - // 断言:SKU1 281 + // 断言:SKU1 分摊运费 (int)(3000 * 2/12) = 500
147 assertThat(resultBO.getItems().get(0)) 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 .containsExactly(100, 2, 0, 0, 0, 500, 700); 284 .containsExactly(100, 2, 0, 0, 0, 500, 700);
150 - // 断言:SKU2 285 + // 断言:SKU2 分摊运费 3000 - 500 = 2500
151 assertThat(resultBO.getItems().get(1)) 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 .containsExactly(200, 10, 0, 0, 0, 2500, 4500); 288 .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
154 - // 断言:SKU3 未选中 289 + // 断言:SKU3 未选中,不计运费
155 assertThat(resultBO.getItems().get(2)) 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 .containsExactly(300, 1, 0, 0, 0, 0, 300); 292 .containsExactly(300, 1, 0, 0, 0, 0, 300);
158 } 293 }
159 294
160 @Test 295 @Test
161 - @DisplayName("按件计算运费包邮的情况") 296 + @DisplayName("按件计算运费:包邮(订单总件数 >= 包邮件数 && 总金额 >= 包邮金额)")
162 public void testCalculate_expressTemplateFree() { 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 templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class, 301 templateRespBO.setFree(randomPojo(DeliveryExpressTemplateRespBO.Free.class,
169 item -> item.setFreeCount(10).setFreePrice(1000))); 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 calculator.calculate(reqBO, resultBO); 307 calculator.calculate(reqBO, resultBO);
  308 +
175 // 断言 309 // 断言
176 TradePriceCalculateRespBO.Price price = resultBO.getPrice(); 310 TradePriceCalculateRespBO.Price price = resultBO.getPrice();
177 assertThat(price) 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 assertThat(resultBO.getItems()).hasSize(3); 314 assertThat(resultBO.getItems()).hasSize(3);
181 - // 断言:SKU1  
182 assertThat(resultBO.getItems().get(0)) 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 .containsExactly(100, 2, 0, 0, 0, 0, 200); 317 .containsExactly(100, 2, 0, 0, 0, 0, 200);
185 - // 断言:SKU2  
186 assertThat(resultBO.getItems().get(1)) 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 .containsExactly(200, 10, 0, 0, 0, 0, 2000); 320 .containsExactly(200, 10, 0, 0, 0, 0, 2000);
189 - // 断言:SKU3 未选中  
190 assertThat(resultBO.getItems().get(2)) 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 .containsExactly(300, 1, 0, 0, 0, 0, 300); 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 package cn.iocoder.yudao.module.trade.service.price.calculator; 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 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 import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi; 6 import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
5 import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO; 7 import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
6 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum; 8 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
@@ -8,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum; @@ -8,7 +10,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
8 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 10 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
9 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
11 -import org.junit.jupiter.api.Disabled;  
12 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
13 import org.mockito.InjectMocks; 14 import org.mockito.InjectMocks;
14 import org.mockito.Mock; 15 import org.mockito.Mock;
@@ -27,7 +28,6 @@ import static org.mockito.Mockito.when; @@ -27,7 +28,6 @@ import static org.mockito.Mockito.when;
27 * 28 *
28 * @author 芋道源码 29 * @author 芋道源码
29 */ 30 */
30 -@Disabled // TODO 芋艿:后续修复  
31 public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest { 31 public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTest {
32 32
33 @InjectMocks 33 @InjectMocks
@@ -35,6 +35,10 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes @@ -35,6 +35,10 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes
35 35
36 @Mock 36 @Mock
37 private DiscountActivityApi discountActivityApi; 37 private DiscountActivityApi discountActivityApi;
  38 + @Mock
  39 + private MemberLevelApi memberLevelApi;
  40 + @Mock
  41 + private MemberUserApi memberUserApi;
38 42
39 @Test 43 @Test
40 public void testCalculate() { 44 public void testCalculate() {
@@ -92,16 +96,16 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes @@ -92,16 +96,16 @@ public class TradeDiscountActivityPriceCalculatorTest extends BaseMockitoUnitTes
92 assertEquals(orderItem01.getCouponPrice(), 0); 96 assertEquals(orderItem01.getCouponPrice(), 0);
93 assertEquals(orderItem01.getPointPrice(), 0); 97 assertEquals(orderItem01.getPointPrice(), 0);
94 assertEquals(orderItem01.getPayPrice(), 120); 98 assertEquals(orderItem01.getPayPrice(), 120);
95 - // 断言:SKU 2 99 + // 断言:SKU 2 未选中,不参与计算
96 TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1); 100 TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
97 assertEquals(orderItem02.getSkuId(), 20L); 101 assertEquals(orderItem02.getSkuId(), 20L);
98 assertEquals(orderItem02.getCount(), 3); 102 assertEquals(orderItem02.getCount(), 3);
99 assertEquals(orderItem02.getPrice(), 50); 103 assertEquals(orderItem02.getPrice(), 50);
100 - assertEquals(orderItem02.getDiscountPrice(), 60); 104 + assertEquals(orderItem02.getDiscountPrice(), 0);
101 assertEquals(orderItem02.getDeliveryPrice(), 0); 105 assertEquals(orderItem02.getDeliveryPrice(), 0);
102 assertEquals(orderItem02.getCouponPrice(), 0); 106 assertEquals(orderItem02.getCouponPrice(), 0);
103 assertEquals(orderItem02.getPointPrice(), 0); 107 assertEquals(orderItem02.getPointPrice(), 0);
104 - assertEquals(orderItem02.getPayPrice(), 90); 108 + assertEquals(orderItem02.getPayPrice(), 150);
105 // 断言:Promotion 部分 109 // 断言:Promotion 部分
106 assertEquals(result.getPromotions().size(), 1); 110 assertEquals(result.getPromotions().size(), 1);
107 TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0); 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,7 +9,6 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; 9 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; 10 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; 11 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
12 -import org.junit.jupiter.api.Disabled;  
13 import org.junit.jupiter.api.Test; 12 import org.junit.jupiter.api.Test;
14 import org.mockito.InjectMocks; 13 import org.mockito.InjectMocks;
15 import org.mockito.Mock; 14 import org.mockito.Mock;
@@ -22,13 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @@ -22,13 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertTrue; 21 import static org.junit.jupiter.api.Assertions.assertTrue;
23 import static org.mockito.Mockito.when; 22 import static org.mockito.Mockito.when;
24 23
25 -// TODO 芋艿:晚点 review  
26 /** 24 /**
27 * {@link TradePointUsePriceCalculator } 的单元测试类 25 * {@link TradePointUsePriceCalculator } 的单元测试类
28 * 26 *
29 * @author owen 27 * @author owen
30 */ 28 */
31 -@Disabled // TODO 芋艿:后续修复  
32 public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest { 29 public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
33 30
34 @InjectMocks 31 @InjectMocks
@@ -245,6 +242,10 @@ public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest { @@ -245,6 +242,10 @@ public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
245 TradePriceCalculatorHelper.recountPayPrice(result.getItems()); 242 TradePriceCalculatorHelper.recountPayPrice(result.getItems());
246 TradePriceCalculatorHelper.recountAllPrice(result); 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 tradePointUsePriceCalculator.calculate(param, result); 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,10 +2,29 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi; 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 import org.mockito.InjectMocks; 12 import org.mockito.InjectMocks;
6 import org.mockito.Mock; 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 * {@link TradeRewardActivityPriceCalculator} 的单元测试类 29 * {@link TradeRewardActivityPriceCalculator} 的单元测试类
11 * 30 *
@@ -19,212 +38,155 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest @@ -19,212 +38,155 @@ public class TradeRewardActivityPriceCalculatorTest extends BaseMockitoUnitTest
19 @Mock 38 @Mock
20 private RewardActivityApi rewardActivityApi; 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 }