Commit fdfca502e5859011cf8bf41d8dad3665de069f6d

Authored by YunaiV
1 parent f93e2579

test: 完善 PR 修复回归测试并启用商城历史用例

启用并修复商品、DIY 页面及模板、交易订单测试,补齐测试表结构与依赖。
增加 CRM 删除日志上下文、优惠券模板已删除场景的回归测试,并对齐相关注释。
关联 #1313、#1315、#1317、#1319、#1321、#1323。
Showing 15 changed files with 913 additions and 546 deletions
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java
@@ -118,6 +118,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService @@ -118,6 +118,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService
118 if (crmProductService.getProductByCategoryId(id) > 0) { 118 if (crmProductService.getProductByCategoryId(id) > 0) {
119 throw exception(PRODUCT_CATEGORY_USED); 119 throw exception(PRODUCT_CATEGORY_USED);
120 } 120 }
  121 +
121 // 2. 删除 122 // 2. 删除
122 productCategoryMapper.deleteById(id); 123 productCategoryMapper.deleteById(id);
123 124
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
@@ -135,9 +135,9 @@ public class CrmProductServiceImpl implements CrmProductService { @@ -135,9 +135,9 @@ public class CrmProductServiceImpl implements CrmProductService {
135 success = CRM_PRODUCT_DELETE_SUCCESS) 135 success = CRM_PRODUCT_DELETE_SUCCESS)
136 @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) 136 @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
137 public void deleteProduct(Long id) { 137 public void deleteProduct(Long id) {
138 - // 校验存在 138 + // 1.1 校验存在
139 CrmProductDO product = validateProductExists(id); 139 CrmProductDO product = validateProductExists(id);
140 - // 校验未被合同/商机引用,避免删除后产生悬空引用 140 + // 1.2 校验未被合同/商机引用,避免删除后产生悬空引用
141 if (contractService.getContractProductCountByProductId(id) > 0) { 141 if (contractService.getContractProductCountByProductId(id) > 0) {
142 throw exception(PRODUCT_DELETE_FAIL_CONTRACT_EXISTS); 142 throw exception(PRODUCT_DELETE_FAIL_CONTRACT_EXISTS);
143 } 143 }
@@ -145,10 +145,10 @@ public class CrmProductServiceImpl implements CrmProductService { @@ -145,10 +145,10 @@ public class CrmProductServiceImpl implements CrmProductService {
145 throw exception(PRODUCT_DELETE_FAIL_BUSINESS_EXISTS); 145 throw exception(PRODUCT_DELETE_FAIL_BUSINESS_EXISTS);
146 } 146 }
147 147
148 - // 删除 148 + // 2. 删除
149 productMapper.deleteById(id); 149 productMapper.deleteById(id);
150 150
151 - // 记录操作日志上下文 151 + // 3. 记录操作日志上下文
152 LogRecordContext.putVariable("product", product); 152 LogRecordContext.putVariable("product", product);
153 } 153 }
154 154
yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.product;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
  5 +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductCategoryMapper;
  6 +import com.mzt.logapi.context.LogRecordContext;
  7 +import org.junit.jupiter.api.AfterEach;
  8 +import org.junit.jupiter.api.BeforeEach;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.mockito.InjectMocks;
  11 +import org.mockito.Mock;
  12 +
  13 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  14 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
  15 +import static org.junit.jupiter.api.Assertions.*;
  16 +import static org.mockito.ArgumentMatchers.anyLong;
  17 +import static org.mockito.Mockito.*;
  18 +
  19 +/**
  20 + * {@link CrmProductCategoryServiceImpl} 的单元测试类
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +public class CrmProductCategoryServiceImplTest extends BaseMockitoUnitTest {
  25 +
  26 + @InjectMocks
  27 + private CrmProductCategoryServiceImpl productCategoryService;
  28 +
  29 + @Mock
  30 + private CrmProductCategoryMapper productCategoryMapper;
  31 + @Mock
  32 + private CrmProductService crmProductService;
  33 +
  34 + @BeforeEach
  35 + public void setUp() {
  36 + LogRecordContext.putEmptySpan();
  37 + }
  38 +
  39 + @AfterEach
  40 + public void tearDown() {
  41 + LogRecordContext.clear();
  42 + }
  43 +
  44 + @Test
  45 + public void testDeleteProductCategory_success() {
  46 + // mock 数据
  47 + CrmProductCategoryDO category = new CrmProductCategoryDO().setId(1L).setName("芋道分类").setParentId(0L);
  48 + when(productCategoryMapper.selectById(1L)).thenReturn(category);
  49 + // mock 方法(无子分类、未被产品使用)
  50 + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(0L);
  51 + when(crmProductService.getProductByCategoryId(1L)).thenReturn(0L);
  52 +
  53 + // 调用
  54 + productCategoryService.deleteProductCategory(1L);
  55 + // 断言
  56 + verify(productCategoryMapper).deleteById(1L);
  57 + // 断言操作日志上下文,为删除前的分类
  58 + CrmProductCategoryDO logCategory = (CrmProductCategoryDO) LogRecordContext.getVariable("productCategory");
  59 + assertSame(category, logCategory);
  60 + assertEquals("芋道分类", logCategory.getName());
  61 + }
  62 +
  63 + @Test
  64 + public void testDeleteProductCategory_notExists() {
  65 + // 调用,并断言异常
  66 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_NOT_EXISTS);
  67 + // 断言
  68 + verify(productCategoryMapper, never()).deleteById(anyLong());
  69 + assertNull(LogRecordContext.getVariable("productCategory"));
  70 + }
  71 +
  72 + @Test
  73 + public void testDeleteProductCategory_existsChildren() {
  74 + // mock 数据
  75 + when(productCategoryMapper.selectById(1L)).thenReturn(new CrmProductCategoryDO().setId(1L).setName("芋道分类"));
  76 + // mock 方法(存在子分类)
  77 + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(1L);
  78 +
  79 + // 调用,并断言异常
  80 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_EXISTS_CHILDREN);
  81 + // 断言
  82 + verify(productCategoryMapper, never()).deleteById(anyLong());
  83 + verifyNoInteractions(crmProductService);
  84 + assertNull(LogRecordContext.getVariable("productCategory"));
  85 + }
  86 +
  87 + @Test
  88 + public void testDeleteProductCategory_used() {
  89 + // mock 数据
  90 + when(productCategoryMapper.selectById(1L)).thenReturn(new CrmProductCategoryDO().setId(1L).setName("芋道分类"));
  91 + // mock 方法(被产品使用)
  92 + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(0L);
  93 + when(crmProductService.getProductByCategoryId(1L)).thenReturn(1L);
  94 +
  95 + // 调用,并断言异常
  96 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_USED);
  97 + // 断言
  98 + verify(productCategoryMapper, never()).deleteById(anyLong());
  99 + assertNull(LogRecordContext.getVariable("productCategory"));
  100 + }
  101 +
  102 +}
yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.product;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
  5 +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductMapper;
  6 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  7 +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
  8 +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
  9 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  10 +import com.mzt.logapi.context.LogRecordContext;
  11 +import org.junit.jupiter.api.AfterEach;
  12 +import org.junit.jupiter.api.BeforeEach;
  13 +import org.junit.jupiter.api.Test;
  14 +import org.mockito.InjectMocks;
  15 +import org.mockito.Mock;
  16 +
  17 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  18 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
  19 +import static org.junit.jupiter.api.Assertions.*;
  20 +import static org.mockito.ArgumentMatchers.anyLong;
  21 +import static org.mockito.Mockito.*;
  22 +
  23 +/**
  24 + * {@link CrmProductServiceImpl} 的单元测试类
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +public class CrmProductServiceImplTest extends BaseMockitoUnitTest {
  29 +
  30 + @InjectMocks
  31 + private CrmProductServiceImpl productService;
  32 +
  33 + @Mock
  34 + private CrmProductMapper productMapper;
  35 + @Mock
  36 + private CrmProductCategoryService productCategoryService;
  37 + @Mock
  38 + private CrmContractService contractService;
  39 + @Mock
  40 + private CrmBusinessService businessService;
  41 + @Mock
  42 + private CrmPermissionService permissionService;
  43 + @Mock
  44 + private AdminUserApi adminUserApi;
  45 +
  46 + @BeforeEach
  47 + public void setUp() {
  48 + LogRecordContext.putEmptySpan();
  49 + }
  50 +
  51 + @AfterEach
  52 + public void tearDown() {
  53 + LogRecordContext.clear();
  54 + }
  55 +
  56 + @Test
  57 + public void testDeleteProduct_success() {
  58 + // mock 数据
  59 + CrmProductDO product = new CrmProductDO().setId(1L).setName("芋道产品");
  60 + when(productMapper.selectById(1L)).thenReturn(product);
  61 + // mock 方法(未被合同、商机引用)
  62 + when(contractService.getContractProductCountByProductId(1L)).thenReturn(0L);
  63 + when(businessService.getBusinessProductCountByProductId(1L)).thenReturn(0L);
  64 +
  65 + // 调用
  66 + productService.deleteProduct(1L);
  67 + // 断言
  68 + verify(productMapper).deleteById(1L);
  69 + // 断言操作日志上下文,为删除前的产品
  70 + CrmProductDO logProduct = (CrmProductDO) LogRecordContext.getVariable("product");
  71 + assertSame(product, logProduct);
  72 + assertEquals("芋道产品", logProduct.getName());
  73 + }
  74 +
  75 + @Test
  76 + public void testDeleteProduct_notExists() {
  77 + // 调用,并断言异常
  78 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_NOT_EXISTS);
  79 + // 断言
  80 + verify(productMapper, never()).deleteById(anyLong());
  81 + assertNull(LogRecordContext.getVariable("product"));
  82 + }
  83 +
  84 + @Test
  85 + public void testDeleteProduct_contractExists() {
  86 + // mock 数据
  87 + when(productMapper.selectById(1L)).thenReturn(new CrmProductDO().setId(1L).setName("芋道产品"));
  88 + // mock 方法(被合同引用)
  89 + when(contractService.getContractProductCountByProductId(1L)).thenReturn(1L);
  90 +
  91 + // 调用,并断言异常
  92 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_DELETE_FAIL_CONTRACT_EXISTS);
  93 + // 断言
  94 + verify(productMapper, never()).deleteById(anyLong());
  95 + assertNull(LogRecordContext.getVariable("product"));
  96 + }
  97 +
  98 + @Test
  99 + public void testDeleteProduct_businessExists() {
  100 + // mock 数据
  101 + when(productMapper.selectById(1L)).thenReturn(new CrmProductDO().setId(1L).setName("芋道产品"));
  102 + // mock 方法(被商机引用)
  103 + when(contractService.getContractProductCountByProductId(1L)).thenReturn(0L);
  104 + when(businessService.getBusinessProductCountByProductId(1L)).thenReturn(1L);
  105 +
  106 + // 调用,并断言异常
  107 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_DELETE_FAIL_BUSINESS_EXISTS);
  108 + // 断言
  109 + verify(productMapper, never()).deleteById(anyLong());
  110 + assertNull(LogRecordContext.getVariable("product"));
  111 + }
  112 +
  113 +}
yudao-module-mall/yudao-module-product/pom.xml
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 <version>${revision}</version> 24 <version>${revision}</version>
25 </dependency> 25 </dependency>
26 26
27 - <!-- trade-api:配送方式枚举 DeliveryTypeEnum 校验(A68) --> 27 + <!-- trade-api:配送方式枚举 DeliveryTypeEnum 校验 -->
28 <dependency> 28 <dependency>
29 <groupId>cn.iocoder.boot</groupId> 29 <groupId>cn.iocoder.boot</groupId>
30 <artifactId>yudao-module-trade-api</artifactId> 30 <artifactId>yudao-module-trade-api</artifactId>
yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.product.service.spu; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.product.service.spu;
3 import cn.hutool.core.date.DateUtil; 3 import cn.hutool.core.date.DateUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.RandomUtil; 5 import cn.hutool.core.util.RandomUtil;
6 -import cn.iocoder.yudao.framework.common.exception.ServiceException;  
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
8 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 8 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
9 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO; 9 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
10 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; 10 import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO;
@@ -17,29 +17,37 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryServiceIm @@ -17,29 +17,37 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryServiceIm
17 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; 17 import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
18 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; 18 import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
19 import cn.iocoder.yudao.module.product.service.sku.ProductSkuServiceImpl; 19 import cn.iocoder.yudao.module.product.service.sku.ProductSkuServiceImpl;
  20 +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
20 import com.google.common.collect.Lists; 21 import com.google.common.collect.Lists;
21 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
22 -import org.junit.jupiter.api.Assertions;  
23 -import org.junit.jupiter.api.Disabled; 23 +import jakarta.validation.ConstraintViolation;
  24 +import jakarta.validation.Validation;
  25 +import jakarta.validation.Validator;
24 import org.junit.jupiter.api.Test; 26 import org.junit.jupiter.api.Test;
25 import org.springframework.context.annotation.Import; 27 import org.springframework.context.annotation.Import;
26 import org.springframework.test.context.bean.override.mockito.MockitoBean; 28 import org.springframework.test.context.bean.override.mockito.MockitoBean;
27 29
28 import java.math.RoundingMode; 30 import java.math.RoundingMode;
29 -import java.util.ArrayList;  
30 -import java.util.Date;  
31 -import java.util.List;  
32 -import java.util.Map; 31 +import java.util.*;
  32 +import java.util.function.Consumer;
33 import java.util.stream.Collectors; 33 import java.util.stream.Collectors;
34 import java.util.stream.Stream; 34 import java.util.stream.Stream;
35 35
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  37 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  38 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  39 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
36 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 40 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
37 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 41 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
  42 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
38 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 43 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  44 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
  45 +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_RECYCLE;
  46 +import static java.util.Collections.singletonList;
39 import static org.assertj.core.util.Lists.newArrayList; 47 import static org.assertj.core.util.Lists.newArrayList;
40 -import static org.junit.jupiter.api.Assertions.assertEquals; 48 +import static org.junit.jupiter.api.Assertions.*;
41 import static org.mockito.ArgumentMatchers.eq; 49 import static org.mockito.ArgumentMatchers.eq;
42 -import static org.mockito.Mockito.when; 50 +import static org.mockito.Mockito.*;
43 51
44 // TODO @芋艿:review 下单元测试 52 // TODO @芋艿:review 下单元测试
45 53
@@ -48,10 +56,12 @@ import static org.mockito.Mockito.when; @@ -48,10 +56,12 @@ import static org.mockito.Mockito.when;
48 * 56 *
49 * @author 芋道源码 57 * @author 芋道源码
50 */ 58 */
51 -@Disabled // TODO 芋艿:后续 fix 补充的单测 59 +// DONE 芋艿:后续 fix 补充的单测
52 @Import(ProductSpuServiceImpl.class) 60 @Import(ProductSpuServiceImpl.class)
53 public class ProductSpuServiceImplTest extends BaseDbUnitTest { 61 public class ProductSpuServiceImplTest extends BaseDbUnitTest {
54 62
  63 + private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
  64 +
55 @Resource 65 @Resource
56 private ProductSpuServiceImpl productSpuService; 66 private ProductSpuServiceImpl productSpuService;
57 67
@@ -84,18 +94,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -84,18 +94,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
84 @Test 94 @Test
85 public void testCreateSpu_success() { 95 public void testCreateSpu_success() {
86 // 准备参数 96 // 准备参数
87 - ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuSaveReqVO.class, o->{  
88 - // 限制范围为正整数  
89 - o.setCostPrice(generaInt());  
90 - o.setPrice(generaInt());  
91 - o.setMarketPrice(generaInt());  
92 - o.setStock(generaInt());  
93 - o.setFirstBrokeragePrice(generaInt());  
94 - o.setSecondBrokeragePrice(generaInt());  
95 - // 限制分数为两位数  
96 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));  
97 - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));  
98 - }); 97 + ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomSkuSaveReqVO();
99 ProductSpuSaveReqVO createReqVO = randomPojo(ProductSpuSaveReqVO.class,o->{ 98 ProductSpuSaveReqVO createReqVO = randomPojo(ProductSpuSaveReqVO.class,o->{
100 o.setCategoryId(generateId()); 99 o.setCategoryId(generateId());
101 o.setBrandId(generateId()); 100 o.setBrandId(generateId());
@@ -104,291 +103,215 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -104,291 +103,215 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
104 o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 103 o.setVirtualSalesCount(generaInt()); // 限制范围为正整数
105 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); 104 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO));
106 }); 105 });
  106 + // mock 方法(分类层级)
107 when(categoryService.getCategoryLevel(eq(createReqVO.getCategoryId()))).thenReturn(2); 107 when(categoryService.getCategoryLevel(eq(createReqVO.getCategoryId()))).thenReturn(2);
108 - Long spu = productSpuService.createSpu(createReqVO);  
109 - ProductSpuDO productSpuDO = productSpuMapper.selectById(spu);  
110 - assertPojoEquals(createReqVO, productSpuDO); 108 +
  109 + // 调用
  110 + Long spuId = productSpuService.createSpu(createReqVO);
  111 + // 断言:销量、浏览量为初始值
  112 + ProductSpuDO productSpuDO = productSpuMapper.selectById(spuId);
  113 + assertPojoEquals(createReqVO, productSpuDO, "salesCount", "browseCount");
  114 + assertEquals(ProductSpuStatusEnum.ENABLE.getStatus(), productSpuDO.getStatus());
  115 + assertEquals(0, productSpuDO.getSalesCount());
  116 + assertEquals(0, productSpuDO.getBrowseCount());
  117 + // 断言:基于 SKU 初始化价格、库存
  118 + assertEquals(skuCreateOrUpdateReqVO.getPrice(), productSpuDO.getPrice());
  119 + assertEquals(skuCreateOrUpdateReqVO.getMarketPrice(), productSpuDO.getMarketPrice());
  120 + assertEquals(skuCreateOrUpdateReqVO.getCostPrice(), productSpuDO.getCostPrice());
  121 + assertEquals(skuCreateOrUpdateReqVO.getStock() * 3, productSpuDO.getStock());
  122 + // 断言:创建 SKU
  123 + verify(productSkuService).createSkuList(eq(spuId), eq(createReqVO.getSkus()));
111 } 124 }
112 125
113 @Test 126 @Test
114 public void testUpdateSpu_success() { 127 public void testUpdateSpu_success() {
  128 + // mock 数据
  129 + ProductSpuDO dbSpu = randomSpuDO();
  130 + productSpuMapper.insert(dbSpu);
115 // 准备参数 131 // 准备参数
116 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{  
117 - o.setCategoryId(generateId());  
118 - o.setBrandId(generateId());  
119 - o.setDeliveryTemplateId(generateId());  
120 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
121 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
122 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
123 - o.setPrice(generaInt()); // 限制范围为正整数  
124 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
125 - o.setCostPrice(generaInt()); // 限制范围为正整数  
126 - o.setStock(generaInt()); // 限制范围为正整数  
127 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
128 - o.setSalesCount(generaInt()); // 限制范围为正整数  
129 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
130 - });  
131 - productSpuMapper.insert(createReqVO);  
132 - // 准备参数  
133 - ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuSaveReqVO.class, o->{  
134 - // 限制范围为正整数  
135 - o.setCostPrice(generaInt());  
136 - o.setPrice(generaInt());  
137 - o.setMarketPrice(generaInt());  
138 - o.setStock(generaInt());  
139 - o.setFirstBrokeragePrice(generaInt());  
140 - o.setSecondBrokeragePrice(generaInt());  
141 - // 限制分数为两位数  
142 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));  
143 - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));  
144 - });  
145 - // 准备参数 132 + ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomSkuSaveReqVO();
146 ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class, o -> { 133 ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class, o -> {
147 - o.setId(createReqVO.getId()); // 设置更新的 ID 134 + o.setId(dbSpu.getId()); // 设置更新的 ID
148 o.setCategoryId(generateId()); 135 o.setCategoryId(generateId());
149 o.setBrandId(generateId()); 136 o.setBrandId(generateId());
150 o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 137 o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围
151 o.setGiveIntegral(generaInt()); // 限制范围为正整数 138 o.setGiveIntegral(generaInt()); // 限制范围为正整数
152 o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 139 o.setVirtualSalesCount(generaInt()); // 限制范围为正整数
153 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
154 o.setSalesCount(generaInt()); // 限制范围为正整数 140 o.setSalesCount(generaInt()); // 限制范围为正整数
155 o.setBrowseCount(generaInt()); // 限制范围为正整数 141 o.setBrowseCount(generaInt()); // 限制范围为正整数
156 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); 142 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO));
157 }); 143 });
  144 + // mock 方法(分类层级)
158 when(categoryService.getCategoryLevel(eq(reqVO.getCategoryId()))).thenReturn(2); 145 when(categoryService.getCategoryLevel(eq(reqVO.getCategoryId()))).thenReturn(2);
  146 +
159 // 调用 147 // 调用
160 productSpuService.updateSpu(reqVO); 148 productSpuService.updateSpu(reqVO);
161 - // 校验是否更新正确 149 + // 校验是否更新正确:状态保持不变
162 ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的 150 ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的
163 assertPojoEquals(reqVO, spu); 151 assertPojoEquals(reqVO, spu);
  152 + assertEquals(dbSpu.getStatus(), spu.getStatus());
  153 + assertEquals(skuCreateOrUpdateReqVO.getStock() * 3, spu.getStock());
  154 + // 断言:更新 SKU
  155 + verify(productSkuService).updateSkuList(eq(reqVO.getId()), eq(reqVO.getSkus()));
164 } 156 }
165 157
166 @Test 158 @Test
167 - public void testValidateSpuExists_exception() { 159 + public void testUpdateSpu_notExists() {
  160 + // 准备参数
168 ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class); 161 ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class);
169 - // 调用  
170 - Assertions.assertThrows(ServiceException.class, () -> productSpuService.updateSpu(reqVO)); 162 +
  163 + // 调用, 并断言异常
  164 + assertServiceException(() -> productSpuService.updateSpu(reqVO), SPU_NOT_EXISTS);
171 } 165 }
172 166
173 @Test 167 @Test
174 - void deleteSpu() {  
175 - // 准备参数  
176 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{  
177 - o.setCategoryId(generateId());  
178 - o.setBrandId(generateId());  
179 - o.setDeliveryTemplateId(generateId());  
180 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
181 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
182 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
183 - o.setPrice(generaInt()); // 限制范围为正整数  
184 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
185 - o.setCostPrice(generaInt()); // 限制范围为正整数  
186 - o.setStock(generaInt()); // 限制范围为正整数  
187 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
188 - o.setSalesCount(generaInt()); // 限制范围为正整数  
189 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
190 - o.setStatus(-1); // 加入回收站才可删除  
191 - });  
192 - productSpuMapper.insert(createReqVO); 168 + public void testValidateDeliveryTypes_success() {
  169 + // 调用,并断言:快递发货、用户自提,及其组合
  170 + assertEquals(Collections.emptySet(), validateDeliveryTypes(singletonList(DeliveryTypeEnum.EXPRESS.getType())));
  171 + assertEquals(Collections.emptySet(), validateDeliveryTypes(singletonList(DeliveryTypeEnum.PICK_UP.getType())));
  172 + assertEquals(Collections.emptySet(), validateDeliveryTypes(Arrays.asList(
  173 + DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType())));
  174 + }
  175 +
  176 + @Test
  177 + public void testValidateDeliveryTypes_invalid() {
  178 + // 调用,并断言:不在枚举范围内
  179 + Set<String> invalidMessages = Collections.singleton("配送方式不正确");
  180 + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(0)));
  181 + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(3)));
  182 + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(-1)));
  183 + assertEquals(invalidMessages, validateDeliveryTypes(Arrays.asList(DeliveryTypeEnum.EXPRESS.getType(), 3)));
  184 + assertEquals(invalidMessages, validateDeliveryTypes(Arrays.asList(DeliveryTypeEnum.EXPRESS.getType(), null)));
  185 + // 调用,并断言:为空
  186 + Set<String> emptyMessages = Collections.singleton("配送方式不能为空");
  187 + assertEquals(emptyMessages, validateDeliveryTypes(null));
  188 + assertEquals(emptyMessages, validateDeliveryTypes(Collections.emptyList()));
  189 + }
  190 +
  191 + @Test
  192 + public void testDeleteSpu_success() {
  193 + // mock 数据
  194 + ProductSpuDO dbSpu = randomSpuDO(o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus())); // 加入回收站才可删除
  195 + productSpuMapper.insert(dbSpu);
193 196
194 // 调用 197 // 调用
195 - productSpuService.deleteSpu(createReqVO.getId()); 198 + productSpuService.deleteSpu(dbSpu.getId());
  199 + // 断言
  200 + assertNull(productSpuMapper.selectById(dbSpu.getId()));
  201 + verify(productSkuService).deleteSkuBySpuId(eq(dbSpu.getId()));
  202 + }
  203 +
  204 + @Test
  205 + public void testDeleteSpu_notRecycle() {
  206 + // mock 数据
  207 + ProductSpuDO dbSpu = randomSpuDO(o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus()));
  208 + productSpuMapper.insert(dbSpu);
196 209
197 - Assertions.assertNull(productSpuMapper.selectById(createReqVO.getId())); 210 + // 调用, 并断言异常
  211 + assertServiceException(() -> productSpuService.deleteSpu(dbSpu.getId()), SPU_NOT_RECYCLE);
  212 + // 断言
  213 + assertNotNull(productSpuMapper.selectById(dbSpu.getId()));
  214 + verify(productSkuService, never()).deleteSkuBySpuId(any());
198 } 215 }
199 216
200 @Test 217 @Test
201 - void getSpu() {  
202 - // 准备参数  
203 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{  
204 - o.setCategoryId(generateId());  
205 - o.setBrandId(generateId());  
206 - o.setDeliveryTemplateId(generateId());  
207 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
208 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
209 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
210 - o.setPrice(generaInt()); // 限制范围为正整数  
211 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
212 - o.setCostPrice(generaInt()); // 限制范围为正整数  
213 - o.setStock(generaInt()); // 限制范围为正整数  
214 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
215 - o.setSalesCount(generaInt()); // 限制范围为正整数  
216 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
217 - });  
218 - productSpuMapper.insert(createReqVO); 218 + public void testGetSpu() {
  219 + // mock 数据
  220 + ProductSpuDO dbSpu = randomSpuDO();
  221 + productSpuMapper.insert(dbSpu);
219 222
220 - ProductSpuDO spu = productSpuService.getSpu(createReqVO.getId());  
221 - assertPojoEquals(createReqVO, spu); 223 + // 调用
  224 + ProductSpuDO spu = productSpuService.getSpu(dbSpu.getId());
  225 + // 断言
  226 + assertPojoEquals(dbSpu, spu);
222 } 227 }
223 228
224 @Test 229 @Test
225 - void getSpuList() {  
226 - // 准备参数  
227 - ArrayList<ProductSpuDO> createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{  
228 - o.setCategoryId(generateId());  
229 - o.setBrandId(generateId());  
230 - o.setDeliveryTemplateId(generateId());  
231 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
232 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
233 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
234 - o.setPrice(generaInt()); // 限制范围为正整数  
235 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
236 - o.setCostPrice(generaInt()); // 限制范围为正整数  
237 - o.setStock(generaInt()); // 限制范围为正整数  
238 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
239 - o.setSalesCount(generaInt()); // 限制范围为正整数  
240 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
241 - }), randomPojo(ProductSpuDO.class,o->{  
242 - o.setCategoryId(generateId());  
243 - o.setBrandId(generateId());  
244 - o.setDeliveryTemplateId(generateId());  
245 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
246 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
247 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
248 - o.setPrice(generaInt()); // 限制范围为正整数  
249 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
250 - o.setCostPrice(generaInt()); // 限制范围为正整数  
251 - o.setStock(generaInt()); // 限制范围为正整数  
252 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
253 - o.setSalesCount(generaInt()); // 限制范围为正整数  
254 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
255 - }));  
256 - productSpuMapper.insertBatch(createReqVOs); 230 + public void testGetSpuList() {
  231 + // mock 数据
  232 + ArrayList<ProductSpuDO> dbSpus = Lists.newArrayList(randomSpuDO(), randomSpuDO());
  233 + productSpuMapper.insertBatch(dbSpus);
  234 + // 准备参数:倒序传入
  235 + List<Long> ids = Arrays.asList(dbSpus.get(1).getId(), dbSpus.get(0).getId());
257 236
258 // 调用 237 // 调用
259 - List<ProductSpuDO> spuList = productSpuService.getSpuList(createReqVOs.stream().map(ProductSpuDO::getId).collect(Collectors.toList()));  
260 - Assertions.assertIterableEquals(createReqVOs, spuList); 238 + List<ProductSpuDO> spuList = productSpuService.getSpuList(ids);
  239 + // 断言:按照 ids 顺序返回
  240 + assertEquals(ids, convertList(spuList, ProductSpuDO::getId));
  241 + assertPojoEquals(dbSpus.get(1), spuList.get(0));
  242 + assertPojoEquals(dbSpus.get(0), spuList.get(1));
261 } 243 }
262 244
263 @Test 245 @Test
264 - void getSpuPage_alarmStock_empty() { 246 + public void testGetSpuPage_alertStockEmpty() {
  247 + // mock 数据
  248 + ArrayList<ProductSpuDO> dbSpus = Lists.newArrayList(randomSpuDO(o -> o.setStock(11)),
  249 + randomSpuDO(o -> o.setStock(11)));
  250 + productSpuMapper.insertBatch(dbSpus);
265 // 准备参数 251 // 准备参数
266 - ArrayList<ProductSpuDO> createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{  
267 - o.setCategoryId(generateId());  
268 - o.setBrandId(generateId());  
269 - o.setDeliveryTemplateId(generateId());  
270 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
271 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
272 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
273 - o.setPrice(generaInt()); // 限制范围为正整数  
274 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
275 - o.setCostPrice(generaInt()); // 限制范围为正整数  
276 - o.setStock(11); // 限制范围为正整数  
277 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
278 - o.setSalesCount(generaInt()); // 限制范围为正整数  
279 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
280 - }), randomPojo(ProductSpuDO.class,o->{  
281 - o.setCategoryId(generateId());  
282 - o.setBrandId(generateId());  
283 - o.setDeliveryTemplateId(generateId());  
284 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
285 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
286 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
287 - o.setPrice(generaInt()); // 限制范围为正整数  
288 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
289 - o.setCostPrice(generaInt()); // 限制范围为正整数  
290 - o.setStock(11); // 限制范围为正整数  
291 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
292 - o.setSalesCount(generaInt()); // 限制范围为正整数  
293 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
294 - }));  
295 - productSpuMapper.insertBatch(createReqVOs);  
296 - // 调用  
297 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); 252 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO();
298 productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); 253 productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK);
299 254
  255 + // 调用
300 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO); 256 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
301 -  
302 - PageResult<Object> result = PageResult.empty();  
303 - Assertions.assertIterableEquals(result.getList(), spuPage.getList());  
304 - assertEquals(spuPage.getTotal(), result.getTotal()); 257 + // 断言
  258 + assertEquals(0, spuPage.getTotal());
  259 + assertEquals(0, spuPage.getList().size());
305 } 260 }
306 261
307 @Test 262 @Test
308 - void getSpuPage_alarmStock() { 263 + public void testGetSpuPage_alertStock() {
  264 + // mock 数据
  265 + ArrayList<ProductSpuDO> dbSpus = Lists.newArrayList(randomSpuDO(o -> o.setStock(5)),
  266 + randomSpuDO(o -> o.setStock(9)));
  267 + productSpuMapper.insertBatch(dbSpus);
  268 + // 测试 stock 不匹配
  269 + productSpuMapper.insert(randomSpuDO(o -> o.setStock(11)));
  270 + // 测试 status 不匹配(回收站)
  271 + productSpuMapper.insert(randomSpuDO(o -> o.setStock(5).setStatus(ProductSpuStatusEnum.RECYCLE.getStatus())));
309 // 准备参数 272 // 准备参数
310 - ArrayList<ProductSpuDO> createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{  
311 - o.setCategoryId(generateId());  
312 - o.setBrandId(generateId());  
313 - o.setDeliveryTemplateId(generateId());  
314 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
315 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
316 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
317 - o.setPrice(generaInt()); // 限制范围为正整数  
318 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
319 - o.setCostPrice(generaInt()); // 限制范围为正整数  
320 - o.setStock(5); // 限制范围为正整数  
321 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
322 - o.setSalesCount(generaInt()); // 限制范围为正整数  
323 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
324 - }), randomPojo(ProductSpuDO.class,o->{  
325 - o.setCategoryId(generateId());  
326 - o.setBrandId(generateId());  
327 - o.setDeliveryTemplateId(generateId());  
328 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
329 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
330 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
331 - o.setPrice(generaInt()); // 限制范围为正整数  
332 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
333 - o.setCostPrice(generaInt()); // 限制范围为正整数  
334 - o.setStock(9); // 限制范围为正整数  
335 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
336 - o.setSalesCount(generaInt()); // 限制范围为正整数  
337 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
338 - }));  
339 - productSpuMapper.insertBatch(createReqVOs);  
340 - // 调用  
341 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); 273 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO();
342 productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); 274 productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK);
  275 +
  276 + // 调用
343 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO); 277 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
344 - assertEquals(createReqVOs.size(), spuPage.getTotal()); 278 + // 断言
  279 + assertEquals(dbSpus.size(), spuPage.getTotal());
  280 + assertEquals(convertSet(dbSpus, ProductSpuDO::getId), convertSet(spuPage.getList(), ProductSpuDO::getId));
345 } 281 }
346 282
347 @Test 283 @Test
348 - void testGetSpuPage() {  
349 - // 准备参数  
350 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{  
351 - o.setCategoryId(generateId());  
352 - o.setBrandId(generateId());  
353 - o.setDeliveryTemplateId(generateId());  
354 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
355 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
356 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
357 - o.setPrice(generaInt()); // 限制范围为正整数  
358 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
359 - o.setCostPrice(generaInt()); // 限制范围为正整数  
360 - o.setStock(generaInt()); // 限制范围为正整数  
361 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
362 - o.setSalesCount(generaInt()); // 限制范围为正整数  
363 - o.setBrowseCount(generaInt()); // 限制范围为正整数 284 + public void testGetSpuPage() {
  285 + // mock 数据
  286 + ProductSpuDO dbSpu = randomSpuDO(o -> { // 等会查询到
  287 + o.setName("芋道商品");
  288 + o.setCategoryId(1L);
  289 + o.setStatus(ProductSpuStatusEnum.ENABLE.getStatus());
  290 + o.setCreateTime(buildTime(2023, 2, 10));
364 }); 291 });
365 - 292 + productSpuMapper.insert(dbSpu);
  293 + // 测试 name 不匹配
  294 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setName("土豆商品")));
  295 + // 测试 categoryId 不匹配
  296 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCategoryId(2L)));
  297 + // 测试 createTime 不匹配
  298 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCreateTime(buildTime(2023, 3, 10))));
  299 + // 测试 status 不匹配(出售中)
  300 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus())));
  301 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus())));
366 // 准备参数 302 // 准备参数
367 - productSpuMapper.insert(createReqVO);  
368 - // 测试 status 不匹配  
369 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus())));  
370 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus())));  
371 - // 测试 SpecType 不匹配  
372 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setSpecType(true)));  
373 - // 测试 BrandId 不匹配  
374 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setBrandId(generateId())));  
375 - // 测试 CategoryId 不匹配  
376 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setCategoryId(generateId())));  
377 -  
378 - // 调用  
379 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); 303 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO();
380 - // 查询条件 按需打开  
381 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK);  
382 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.RECYCLE_BIN);  
383 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE);  
384 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.IN_WAREHOUSE);  
385 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.SOLD_OUT);  
386 - //productSpuPageReqVO.setName(createReqVO.getName());  
387 - //productSpuPageReqVO.setCategoryId(createReqVO.getCategoryId()); 304 + productSpuPageReqVO.setName("芋道");
  305 + productSpuPageReqVO.setCategoryId(1L);
  306 + productSpuPageReqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
  307 + productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE);
388 308
  309 + // 调用
389 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO); 310 PageResult<ProductSpuDO> spuPage = productSpuService.getSpuPage(productSpuPageReqVO);
390 - 311 + // 断言
391 assertEquals(1, spuPage.getTotal()); 312 assertEquals(1, spuPage.getTotal());
  313 + assertEquals(1, spuPage.getList().size());
  314 + assertPojoEquals(dbSpu, spuPage.getList().get(0));
392 } 315 }
393 316
394 /** 317 /**
@@ -427,23 +350,49 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -427,23 +350,49 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
427 // 准备参数 350 // 准备参数
428 Map<Long, Integer> stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build(); 351 Map<Long, Integer> stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build();
429 // mock 方法(数据) 352 // mock 方法(数据)
430 - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o ->{  
431 - o.setCategoryId(generateId());  
432 - o.setBrandId(generateId());  
433 - o.setDeliveryTemplateId(generateId());  
434 - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围  
435 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
436 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数  
437 - o.setPrice(generaInt()); // 限制范围为正整数  
438 - o.setMarketPrice(generaInt()); // 限制范围为正整数  
439 - o.setCostPrice(generaInt()); // 限制范围为正整数  
440 - o.setStock(generaInt()); // 限制范围为正整数  
441 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
442 - o.setSalesCount(generaInt()); // 限制范围为正整数  
443 - o.setBrowseCount(generaInt()); // 限制范围为正整数  
444 - o.setId(1L).setStock(20);  
445 - }));  
446 - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> { 353 + productSpuMapper.insert(randomSpuDO(o -> o.setId(1L).setStock(20)));
  354 + productSpuMapper.insert(randomSpuDO(o -> o.setId(2L).setStock(30)));
  355 +
  356 + // 调用
  357 + productSpuService.updateSpuStock(stockIncrCounts);
  358 + // 断言
  359 + assertEquals(productSpuService.getSpu(1L).getStock(), 30);
  360 + assertEquals(productSpuService.getSpu(2L).getStock(), 10);
  361 + }
  362 +
  363 + /**
  364 + * 校验 {@link ProductSpuSaveReqVO#getDeliveryTypes()} 字段
  365 + *
  366 + * @param deliveryTypes 配送方式数组
  367 + * @return 校验不通过的提示
  368 + */
  369 + private static Set<String> validateDeliveryTypes(List<Integer> deliveryTypes) {
  370 + ProductSpuSaveReqVO reqVO = new ProductSpuSaveReqVO();
  371 + reqVO.setDeliveryTypes(deliveryTypes);
  372 + Set<ConstraintViolation<ProductSpuSaveReqVO>> violations = VALIDATOR.validateProperty(reqVO, "deliveryTypes");
  373 + return convertSet(violations, ConstraintViolation::getMessage);
  374 + }
  375 +
  376 + // ========== 随机对象 ==========
  377 +
  378 + private ProductSkuSaveReqVO randomSkuSaveReqVO() {
  379 + return randomPojo(ProductSkuSaveReqVO.class, o->{
  380 + // 限制范围为正整数
  381 + o.setCostPrice(generaInt());
  382 + o.setPrice(generaInt());
  383 + o.setMarketPrice(generaInt());
  384 + o.setStock(generaInt());
  385 + o.setFirstBrokeragePrice(generaInt());
  386 + o.setSecondBrokeragePrice(generaInt());
  387 + // 限制分数为两位数
  388 + o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
  389 + o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP));
  390 + });
  391 + }
  392 +
  393 + @SafeVarargs
  394 + private final ProductSpuDO randomSpuDO(Consumer<ProductSpuDO>... consumers) {
  395 + Consumer<ProductSpuDO> consumer = o -> {
447 o.setCategoryId(generateId()); 396 o.setCategoryId(generateId());
448 o.setBrandId(generateId()); 397 o.setBrandId(generateId());
449 o.setDeliveryTemplateId(generateId()); 398 o.setDeliveryTemplateId(generateId());
@@ -454,17 +403,10 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @@ -454,17 +403,10 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest {
454 o.setMarketPrice(generaInt()); // 限制范围为正整数 403 o.setMarketPrice(generaInt()); // 限制范围为正整数
455 o.setCostPrice(generaInt()); // 限制范围为正整数 404 o.setCostPrice(generaInt()); // 限制范围为正整数
456 o.setStock(generaInt()); // 限制范围为正整数 405 o.setStock(generaInt()); // 限制范围为正整数
457 - o.setGiveIntegral(generaInt()); // 限制范围为正整数  
458 o.setSalesCount(generaInt()); // 限制范围为正整数 406 o.setSalesCount(generaInt()); // 限制范围为正整数
459 o.setBrowseCount(generaInt()); // 限制范围为正整数 407 o.setBrowseCount(generaInt()); // 限制范围为正整数
460 - o.setId(2L).setStock(30);  
461 - }));  
462 -  
463 - // 调用  
464 - productSpuService.updateSpuStock(stockIncrCounts);  
465 - // 断言  
466 - assertEquals(productSpuService.getSpu(1L).getStock(), 30);  
467 - assertEquals(productSpuService.getSpu(2L).getStock(), 10); 408 + };
  409 + return randomPojo(ProductSpuDO.class, ArrayUtils.append(consumer, consumers));
468 } 410 }
469 411
470 } 412 }
yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql
@@ -28,13 +28,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` ( @@ -28,13 +28,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` (
28 `keyword` varchar(256) NOT NULL COMMENT '关键字', 28 `keyword` varchar(256) NOT NULL COMMENT '关键字',
29 `introduction` varchar(256) NOT NULL COMMENT '商品简介', 29 `introduction` varchar(256) NOT NULL COMMENT '商品简介',
30 `description` text NOT NULL COMMENT '商品详情', 30 `description` text NOT NULL COMMENT '商品详情',
31 - `bar_code` varchar(64) NOT NULL COMMENT '条形码',  
32 `category_id` bigint NOT NULL COMMENT '商品分类编号', 31 `category_id` bigint NOT NULL COMMENT '商品分类编号',
33 - `brand_id` int DEFAULT NULL COMMENT '商品品牌编号', 32 + `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
34 `pic_url` varchar(256) NOT NULL COMMENT '商品封面图', 33 `pic_url` varchar(256) NOT NULL COMMENT '商品封面图',
35 `slider_pic_urls` varchar(2000) DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张', 34 `slider_pic_urls` varchar(2000) DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张',
36 - `video_url` varchar(256) DEFAULT NULL COMMENT '商品视频',  
37 - `unit` tinyint NOT NULL COMMENT '单位',  
38 `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段', 35 `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
39 `status` tinyint NOT NULL COMMENT '商品状态: 0 上架(开启) 1 下架(禁用)-1 回收', 36 `status` tinyint NOT NULL COMMENT '商品状态: 0 上架(开启) 1 下架(禁用)-1 回收',
40 `spec_type` bit(1) NOT NULL COMMENT '规格类型:0 单规格 1 多规格', 37 `spec_type` bit(1) NOT NULL COMMENT '规格类型:0 单规格 1 多规格',
@@ -42,16 +39,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` ( @@ -42,16 +39,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` (
42 `market_price` int NOT NULL COMMENT '市场价,单位使用:分', 39 `market_price` int NOT NULL COMMENT '市场价,单位使用:分',
43 `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分', 40 `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分',
44 `stock` int NOT NULL DEFAULT '0' COMMENT '库存', 41 `stock` int NOT NULL DEFAULT '0' COMMENT '库存',
  42 + `delivery_types` varchar(255) NOT NULL COMMENT '配送方式数组',
45 `delivery_template_id` bigint NOT NULL COMMENT '物流配置模板编号', 43 `delivery_template_id` bigint NOT NULL COMMENT '物流配置模板编号',
46 - `recommend_hot` bit(1) NOT NULL COMMENT '是否热卖推荐: 0 默认 1 热卖',  
47 - `recommend_benefit` bit(1) NOT NULL COMMENT '是否优惠推荐: 0 默认 1 优选',  
48 - `recommend_best` bit(1) NOT NULL COMMENT '是否精品推荐: 0 默认 1 精品',  
49 - `recommend_new` bit(1) NOT NULL COMMENT '是否新品推荐: 0 默认 1 新品',  
50 - `recommend_good` bit(1) NOT NULL COMMENT '是否优品推荐',  
51 `give_integral` int NOT NULL COMMENT '赠送积分', 44 `give_integral` int NOT NULL COMMENT '赠送积分',
52 - `give_coupon_template_ids` varchar(512) DEFAULT '' COMMENT '赠送的优惠劵编号的数组',  
53 `sub_commission_type` bit(1) NOT NULL COMMENT '分销类型', 45 `sub_commission_type` bit(1) NOT NULL COMMENT '分销类型',
54 - `activity_orders` varchar(16) NOT NULL DEFAULT '' COMMENT '活动显示排序0=默认, 1=秒杀,2=砍价,3=拼团',  
55 `sales_count` int DEFAULT '0' COMMENT '商品销量', 46 `sales_count` int DEFAULT '0' COMMENT '商品销量',
56 `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量', 47 `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
57 `browse_count` int DEFAULT '0' COMMENT '商品点击量', 48 `browse_count` int DEFAULT '0' COMMENT '商品点击量',
yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java
@@ -102,7 +102,7 @@ public class DiyTemplateServiceImpl implements DiyTemplateService { @@ -102,7 +102,7 @@ public class DiyTemplateServiceImpl implements DiyTemplateService {
102 } 102 }
103 // 删除 103 // 删除
104 diyTemplateMapper.deleteById(id); 104 diyTemplateMapper.deleteById(id);
105 - // 级联删除模板下自动生成的默认页面(createDiyTemplate 的「首页/我的」),避免页面列表残留孤儿页 105 + // 删除模板下的页面
106 diyPageService.getDiyPageByTemplateId(id) 106 diyPageService.getDiyPageByTemplateId(id)
107 .forEach(page -> diyPageService.deleteDiyPage(page.getId())); 107 .forEach(page -> diyPageService.deleteDiyPage(page.getId()));
108 } 108 }
yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.service.coupon;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
  5 +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
  6 +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
  7 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
  9 +import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
  10 +import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
  11 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
  12 +import jakarta.annotation.Resource;
  13 +import org.junit.jupiter.api.Test;
  14 +import org.springframework.context.annotation.Import;
  15 +import org.springframework.jdbc.core.JdbcTemplate;
  16 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
  17 +
  18 +import javax.sql.DataSource;
  19 +import java.util.function.Consumer;
  20 +
  21 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  22 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  23 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  24 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_DELETE_FAIL_USED;
  25 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NOT_EXISTS;
  26 +import static org.junit.jupiter.api.Assertions.*;
  27 +
  28 +/**
  29 + * {@link CouponServiceImpl} 的单元测试类
  30 + *
  31 + * @author 芋道源码
  32 + */
  33 +@Import({CouponServiceImpl.class, CouponTemplateServiceImpl.class})
  34 +public class CouponServiceImplTest extends BaseDbUnitTest {
  35 +
  36 + @Resource
  37 + private CouponServiceImpl couponService;
  38 +
  39 + @Resource
  40 + private CouponMapper couponMapper;
  41 + @Resource
  42 + private CouponTemplateMapper couponTemplateMapper;
  43 +
  44 + @Resource
  45 + private DataSource dataSource;
  46 +
  47 + @MockitoBean
  48 + private MemberUserApi memberUserApi;
  49 + @MockitoBean
  50 + private ProductCategoryApi productCategoryApi;
  51 + @MockitoBean
  52 + private ProductSpuApi productSpuApi;
  53 +
  54 + @Test
  55 + public void testDeleteCoupon_success() {
  56 + // mock 数据
  57 + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5));
  58 + couponTemplateMapper.insert(dbTemplate);
  59 + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId())
  60 + .setStatus(CouponStatusEnum.UNUSED.getStatus()));
  61 + couponMapper.insert(dbCoupon);
  62 +
  63 + // 调用
  64 + couponService.deleteCoupon(dbCoupon.getId());
  65 + // 断言:优惠劵被删除,模板领取数量 -1
  66 + assertNull(couponMapper.selectById(dbCoupon.getId()));
  67 + assertEquals(4, couponTemplateMapper.selectById(dbTemplate.getId()).getTakeCount());
  68 + }
  69 +
  70 + @Test
  71 + public void testDeleteCoupon_templateDeleted() {
  72 + // mock 数据(模板已删除)
  73 + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5));
  74 + couponTemplateMapper.insert(dbTemplate);
  75 + couponTemplateMapper.deleteById(dbTemplate.getId());
  76 + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId())
  77 + .setStatus(CouponStatusEnum.UNUSED.getStatus()));
  78 + couponMapper.insert(dbCoupon);
  79 +
  80 + // 调用
  81 + couponService.deleteCoupon(dbCoupon.getId());
  82 + // 断言:优惠劵被删除,已删除模板的领取数量不变
  83 + assertNull(couponMapper.selectById(dbCoupon.getId()));
  84 + assertEquals(5, selectTemplateTakeCount(dbTemplate.getId()));
  85 + }
  86 +
  87 + @Test
  88 + public void testDeleteCoupon_templateNotExists() {
  89 + // mock 数据(模板不存在)
  90 + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(randomLongId())
  91 + .setStatus(CouponStatusEnum.EXPIRE.getStatus()));
  92 + couponMapper.insert(dbCoupon);
  93 +
  94 + // 调用
  95 + couponService.deleteCoupon(dbCoupon.getId());
  96 + // 断言:优惠劵被删除
  97 + assertNull(couponMapper.selectById(dbCoupon.getId()));
  98 + }
  99 +
  100 + @Test
  101 + public void testDeleteCoupon_used() {
  102 + // mock 数据
  103 + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5));
  104 + couponTemplateMapper.insert(dbTemplate);
  105 + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId())
  106 + .setStatus(CouponStatusEnum.USED.getStatus()));
  107 + couponMapper.insert(dbCoupon);
  108 +
  109 + // 调用, 并断言异常
  110 + assertServiceException(() -> couponService.deleteCoupon(dbCoupon.getId()), COUPON_DELETE_FAIL_USED);
  111 + // 断言:优惠劵未删除,模板领取数量不变
  112 + CouponDO coupon = couponMapper.selectById(dbCoupon.getId());
  113 + assertNotNull(coupon);
  114 + assertEquals(CouponStatusEnum.USED.getStatus(), coupon.getStatus());
  115 + assertEquals(5, couponTemplateMapper.selectById(dbTemplate.getId()).getTakeCount());
  116 + }
  117 +
  118 + @Test
  119 + public void testDeleteCoupon_notExists() {
  120 + // 准备参数
  121 + Long id = randomLongId();
  122 +
  123 + // 调用, 并断言异常
  124 + assertServiceException(() -> couponService.deleteCoupon(id), COUPON_NOT_EXISTS);
  125 + }
  126 +
  127 + /**
  128 + * 查询模板的领取数量,包括已删除的模板
  129 + *
  130 + * @param id 模板编号
  131 + * @return 领取数量
  132 + */
  133 + private Integer selectTemplateTakeCount(Long id) {
  134 + return new JdbcTemplate(dataSource).queryForObject(
  135 + "SELECT take_count FROM promotion_coupon_template WHERE id = ?", Integer.class, id);
  136 + }
  137 +
  138 + // ========== 随机对象 ==========
  139 +
  140 + @SafeVarargs
  141 + private static CouponTemplateDO randomCouponTemplateDO(Consumer<CouponTemplateDO>... consumers) {
  142 + return randomPojo(CouponTemplateDO.class, consumers);
  143 + }
  144 +
  145 + @SafeVarargs
  146 + private static CouponDO randomCouponDO(Consumer<CouponDO>... consumers) {
  147 + return randomPojo(CouponDO.class, consumers);
  148 + }
  149 +
  150 +}
yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImplTest.java
@@ -7,18 +7,19 @@ import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.DiyPagePag @@ -7,18 +7,19 @@ import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.DiyPagePag
7 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.DiyPageUpdateReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.DiyPageUpdateReqVO;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO;
9 import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyPageMapper; 9 import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyPageMapper;
10 -import org.junit.jupiter.api.Disabled;  
11 import org.junit.jupiter.api.Test; 10 import org.junit.jupiter.api.Test;
12 import org.springframework.context.annotation.Import; 11 import org.springframework.context.annotation.Import;
13 12
14 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
15 14
16 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; 15 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  16 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
17 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 17 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
19 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 19 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
20 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 20 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
21 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 21 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  22 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_PAGE_NAME_USED;
22 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_PAGE_NOT_EXISTS; 23 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_PAGE_NOT_EXISTS;
23 import static org.junit.jupiter.api.Assertions.*; 24 import static org.junit.jupiter.api.Assertions.*;
24 25
@@ -27,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*; @@ -27,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*;
27 * 28 *
28 * @author owen 29 * @author owen
29 */ 30 */
30 -@Disabled // TODO 芋艿:后续 fix 补充的单测 31 +// DONE 芋艿:后续 fix 补充的单测
31 @Import(DiyPageServiceImpl.class) 32 @Import(DiyPageServiceImpl.class)
32 public class DiyPageServiceImplTest extends BaseDbUnitTest { 33 public class DiyPageServiceImplTest extends BaseDbUnitTest {
33 34
@@ -49,6 +50,21 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest { @@ -49,6 +50,21 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest {
49 // 校验记录的属性是否正确 50 // 校验记录的属性是否正确
50 DiyPageDO diyPage = diyPageMapper.selectById(diyPageId); 51 DiyPageDO diyPage = diyPageMapper.selectById(diyPageId);
51 assertPojoEquals(reqVO, diyPage); 52 assertPojoEquals(reqVO, diyPage);
  53 + assertEquals("{}", diyPage.getProperty());
  54 + }
  55 +
  56 + @Test
  57 + public void testCreateDiyPage_nameUsed() {
  58 + // mock 数据(独立页面)
  59 + diyPageMapper.insert(randomPojo(DiyPageDO.class, o -> o.setTemplateId(null).setName("芋道首页")));
  60 + // 准备参数
  61 + DiyPageCreateReqVO reqVO = randomPojo(DiyPageCreateReqVO.class, o -> {
  62 + o.setTemplateId(null);
  63 + o.setName("芋道首页");
  64 + });
  65 +
  66 + // 调用, 并断言异常
  67 + assertServiceException(() -> diyPageService.createDiyPage(reqVO), DIY_PAGE_NAME_USED, "芋道首页");
52 } 68 }
53 69
54 @Test 70 @Test
@@ -101,21 +117,24 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest { @@ -101,21 +117,24 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest {
101 } 117 }
102 118
103 @Test 119 @Test
104 - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 120 + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解
105 public void testGetDiyPagePage() { 121 public void testGetDiyPagePage() {
106 // mock 数据 122 // mock 数据
107 DiyPageDO dbDiyPage = randomPojo(DiyPageDO.class, o -> { // 等会查询到 123 DiyPageDO dbDiyPage = randomPojo(DiyPageDO.class, o -> { // 等会查询到
108 - o.setName(null);  
109 - o.setCreateTime(null); 124 + o.setTemplateId(null); // 独立页面
  125 + o.setName("芋道首页");
  126 + o.setCreateTime(buildTime(2023, 2, 10));
110 }); 127 });
111 diyPageMapper.insert(dbDiyPage); 128 diyPageMapper.insert(dbDiyPage);
112 // 测试 name 不匹配 129 // 测试 name 不匹配
113 - diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setName(null))); 130 + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setName("土豆首页")));
114 // 测试 createTime 不匹配 131 // 测试 createTime 不匹配
115 - diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setCreateTime(null))); 132 + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setCreateTime(buildTime(2023, 3, 10))));
  133 + // 测试 templateId 不匹配(模板下的页面,不在独立页面列表)
  134 + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setTemplateId(1L)));
116 // 准备参数 135 // 准备参数
117 DiyPagePageReqVO reqVO = new DiyPagePageReqVO(); 136 DiyPagePageReqVO reqVO = new DiyPagePageReqVO();
118 - reqVO.setName(null); 137 + reqVO.setName("芋道");
119 reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); 138 reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
120 139
121 // 调用 140 // 调用
yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImplTest.java
@@ -5,30 +5,40 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; @@ -5,30 +5,40 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateCreateReqVO; 5 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateCreateReqVO;
6 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplatePageReqVO; 6 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplatePageReqVO;
7 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateUpdateReqVO; 7 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateUpdateReqVO;
  8 +import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyTemplateDO; 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyTemplateDO;
  10 +import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyPageMapper;
9 import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyTemplateMapper; 11 import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyTemplateMapper;
10 -import org.junit.jupiter.api.Disabled; 12 +import jakarta.annotation.Resource;
11 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
12 import org.springframework.context.annotation.Import; 14 import org.springframework.context.annotation.Import;
  15 +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
13 16
14 -import jakarta.annotation.Resource; 17 +import java.util.List;
  18 +import java.util.function.Consumer;
15 19
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  21 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
16 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; 22 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
  23 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
17 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; 24 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
18 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 25 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
19 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 26 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
20 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; 27 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
21 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 28 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
22 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_TEMPLATE_NOT_EXISTS; 29 import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_TEMPLATE_NOT_EXISTS;
  30 +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_TEMPLATE_USED_CANNOT_DELETE;
23 import static org.junit.jupiter.api.Assertions.*; 31 import static org.junit.jupiter.api.Assertions.*;
  32 +import static org.mockito.ArgumentMatchers.anyLong;
  33 +import static org.mockito.Mockito.doCallRealMethod;
24 34
25 /** 35 /**
26 * {@link DiyTemplateServiceImpl} 的单元测试类 36 * {@link DiyTemplateServiceImpl} 的单元测试类
27 * 37 *
28 * @author owen 38 * @author owen
29 */ 39 */
30 -@Disabled // TODO 芋艿:后续 fix 补充的单测  
31 -@Import(DiyTemplateServiceImpl.class) 40 +// DONE 芋艿:后续 fix 补充的单测
  41 +@Import({DiyTemplateServiceImpl.class, DiyPageServiceImpl.class})
32 public class DiyTemplateServiceImplTest extends BaseDbUnitTest { 42 public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
33 43
34 @Resource 44 @Resource
@@ -36,6 +46,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -36,6 +46,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
36 46
37 @Resource 47 @Resource
38 private DiyTemplateMapper diyTemplateMapper; 48 private DiyTemplateMapper diyTemplateMapper;
  49 + @Resource
  50 + private DiyPageMapper diyPageMapper;
  51 +
  52 + @MockitoSpyBean
  53 + private DiyPageService diyPageService;
39 54
40 @Test 55 @Test
41 public void testCreateDiyTemplate_success() { 56 public void testCreateDiyTemplate_success() {
@@ -49,6 +64,15 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -49,6 +64,15 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
49 // 校验记录的属性是否正确 64 // 校验记录的属性是否正确
50 DiyTemplateDO diyTemplate = diyTemplateMapper.selectById(diyTemplateId); 65 DiyTemplateDO diyTemplate = diyTemplateMapper.selectById(diyTemplateId);
51 assertPojoEquals(reqVO, diyTemplate); 66 assertPojoEquals(reqVO, diyTemplate);
  67 + assertEquals("{}", diyTemplate.getProperty());
  68 + // 校验默认页面:首页、我的
  69 + List<DiyPageDO> pages = diyPageMapper.selectListByTemplateId(diyTemplateId);
  70 + assertEquals(asSet("首页", "我的"), convertSet(pages, DiyPageDO::getName));
  71 + String remark = String.format("模板【%s】自动创建", reqVO.getName());
  72 + pages.forEach(page -> {
  73 + assertEquals(remark, page.getRemark());
  74 + assertEquals("{}", page.getProperty());
  75 + });
52 } 76 }
53 77
54 @Test 78 @Test
@@ -79,9 +103,20 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -79,9 +103,20 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
79 103
80 @Test 104 @Test
81 public void testDeleteDiyTemplate_success() { 105 public void testDeleteDiyTemplate_success() {
82 - // mock 数据  
83 - DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class); 106 + // mock 数据(模板)
  107 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false));
84 diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据 108 diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据
  109 + DiyTemplateDO otherDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false));
  110 + diyTemplateMapper.insert(otherDiyTemplate);
  111 + // mock 数据(页面):该模板下的全部页面,包括默认页面及后续新增页面
  112 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("首页")));
  113 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("我的")));
  114 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("活动页")));
  115 + // mock 数据(页面):其它模板下的页面、独立页面
  116 + DiyPageDO otherTemplatePage = randomDiyPageDO(o -> o.setTemplateId(otherDiyTemplate.getId()));
  117 + diyPageMapper.insert(otherTemplatePage);
  118 + DiyPageDO standalonePage = randomDiyPageDO(o -> o.setTemplateId(null));
  119 + diyPageMapper.insert(standalonePage);
85 // 准备参数 120 // 准备参数
86 Long id = dbDiyTemplate.getId(); 121 Long id = dbDiyTemplate.getId();
87 122
@@ -89,6 +124,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -89,6 +124,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
89 diyTemplateService.deleteDiyTemplate(id); 124 diyTemplateService.deleteDiyTemplate(id);
90 // 校验数据不存在了 125 // 校验数据不存在了
91 assertNull(diyTemplateMapper.selectById(id)); 126 assertNull(diyTemplateMapper.selectById(id));
  127 + assertEquals(0, diyPageMapper.selectListByTemplateId(id).size());
  128 + // 校验其它模板、其它模板下的页面、独立页面保留
  129 + assertNotNull(diyTemplateMapper.selectById(otherDiyTemplate.getId()));
  130 + assertNotNull(diyPageMapper.selectById(otherTemplatePage.getId()));
  131 + assertNotNull(diyPageMapper.selectById(standalonePage.getId()));
92 } 132 }
93 133
94 @Test 134 @Test
@@ -101,37 +141,65 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -101,37 +141,65 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
101 } 141 }
102 142
103 @Test 143 @Test
104 - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 144 + public void testDeleteDiyTemplate_used() {
  145 + // mock 数据
  146 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(true));
  147 + diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据
  148 + DiyPageDO dbDiyPage = randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()));
  149 + diyPageMapper.insert(dbDiyPage);
  150 + // 准备参数
  151 + Long id = dbDiyTemplate.getId();
  152 +
  153 + // 调用, 并断言异常
  154 + assertServiceException(() -> diyTemplateService.deleteDiyTemplate(id), DIY_TEMPLATE_USED_CANNOT_DELETE);
  155 + // 校验模板、页面都未删除
  156 + assertNotNull(diyTemplateMapper.selectById(id));
  157 + assertNotNull(diyPageMapper.selectById(dbDiyPage.getId()));
  158 + }
  159 +
  160 + @Test
  161 + public void testDeleteDiyTemplate_deletePageFailRollback() {
  162 + // mock 数据
  163 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false));
  164 + diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据
  165 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId())));
  166 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId())));
  167 + // 准备参数
  168 + Long id = dbDiyTemplate.getId();
  169 + // mock 方法(第 1 个页面真实删除,第 2 个页面删除失败)
  170 + doCallRealMethod().doThrow(new IllegalStateException("删除页面失败"))
  171 + .when(diyPageService).deleteDiyPage(anyLong());
  172 +
  173 + // 调用, 并断言异常
  174 + assertThrows(IllegalStateException.class, () -> diyTemplateService.deleteDiyTemplate(id));
  175 + // 校验模板、页面都已回滚
  176 + assertNotNull(diyTemplateMapper.selectById(id));
  177 + assertEquals(2, diyPageMapper.selectListByTemplateId(id).size());
  178 + }
  179 +
  180 + @Test
  181 + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解
105 public void testGetDiyTemplatePage() { 182 public void testGetDiyTemplatePage() {
106 // mock 数据 183 // mock 数据
107 DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> { // 等会查询到 184 DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> { // 等会查询到
108 - o.setName(null);  
109 - o.setUsed(null);  
110 - o.setUsedTime(null);  
111 - o.setRemark(null);  
112 - o.setPreviewPicUrls(null);  
113 - o.setProperty(null);  
114 - o.setCreateTime(null); 185 + o.setName("芋道商城");
  186 + o.setUsed(true);
  187 + o.setUsedTime(buildTime(2023, 2, 10));
  188 + o.setCreateTime(buildTime(2023, 2, 10));
115 }); 189 });
116 diyTemplateMapper.insert(dbDiyTemplate); 190 diyTemplateMapper.insert(dbDiyTemplate);
117 // 测试 name 不匹配 191 // 测试 name 不匹配
118 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName(null))); 192 + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName("土豆商城")));
119 // 测试 used 不匹配 193 // 测试 used 不匹配
120 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(null))); 194 + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(false)));
121 // 测试 usedTime 不匹配 195 // 测试 usedTime 不匹配
122 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(null)));  
123 - // 测试 remark 不匹配  
124 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setRemark(null)));  
125 - // 测试 previewPicUrls 不匹配  
126 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setPreviewPicUrls(null)));  
127 - // 测试 property 不匹配  
128 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setProperty(null))); 196 + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(buildTime(2023, 3, 10))));
129 // 测试 createTime 不匹配 197 // 测试 createTime 不匹配
130 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(null))); 198 + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(buildTime(2023, 3, 10))));
131 // 准备参数 199 // 准备参数
132 DiyTemplatePageReqVO reqVO = new DiyTemplatePageReqVO(); 200 DiyTemplatePageReqVO reqVO = new DiyTemplatePageReqVO();
133 - reqVO.setName(null);  
134 - reqVO.setUsed(null); 201 + reqVO.setName("芋道");
  202 + reqVO.setUsed(true);
135 reqVO.setUsedTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); 203 reqVO.setUsedTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
136 reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); 204 reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
137 205
@@ -144,33 +212,25 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @@ -144,33 +212,25 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest {
144 } 212 }
145 213
146 @Test 214 @Test
147 - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解  
148 - public void testGetDiyTemplateList() { 215 + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解(DiyTemplateService 无列表查询方法,原 testGetDiyTemplateList 改为测试 getUsedDiyTemplate)
  216 + public void testGetUsedDiyTemplate() {
149 // mock 数据 217 // mock 数据
150 - DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> { // 等会查询到  
151 - o.setName(null);  
152 - o.setUsed(null);  
153 - o.setUsedTime(null);  
154 - o.setRemark(null);  
155 - o.setPreviewPicUrls(null);  
156 - o.setProperty(null);  
157 - o.setCreateTime(null);  
158 - }); 218 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(true));
159 diyTemplateMapper.insert(dbDiyTemplate); 219 diyTemplateMapper.insert(dbDiyTemplate);
160 - // 测试 name 不匹配  
161 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName(null)));  
162 // 测试 used 不匹配 220 // 测试 used 不匹配
163 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(null)));  
164 - // 测试 usedTime 不匹配  
165 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(null)));  
166 - // 测试 remark 不匹配  
167 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setRemark(null)));  
168 - // 测试 previewPicUrls 不匹配  
169 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setPreviewPicUrls(null)));  
170 - // 测试 property 不匹配  
171 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setProperty(null)));  
172 - // 测试 createTime 不匹配  
173 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(null))); 221 + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName("土豆商城").setUsed(false)));
  222 +
  223 + // 调用
  224 + DiyTemplateDO usedDiyTemplate = diyTemplateService.getUsedDiyTemplate();
  225 + // 断言
  226 + assertPojoEquals(dbDiyTemplate, usedDiyTemplate);
  227 + }
  228 +
  229 + // ========== 随机对象 ==========
  230 +
  231 + @SafeVarargs
  232 + private static DiyPageDO randomDiyPageDO(Consumer<DiyPageDO>... consumers) {
  233 + return randomPojo(DiyPageDO.class, consumers);
174 } 234 }
175 235
176 } 236 }
yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql
@@ -277,8 +277,8 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_template&quot; @@ -277,8 +277,8 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_template&quot;
277 ( 277 (
278 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 278 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
279 "name" varchar NOT NULL, 279 "name" varchar NOT NULL,
280 - "used" bit NOT NULL,  
281 - "used_time" varchar, 280 + "used" bit NOT NULL DEFAULT FALSE,
  281 + "used_time" datetime,
282 "remark" varchar, 282 "remark" varchar,
283 "preview_pic_urls" varchar, 283 "preview_pic_urls" varchar,
284 "property" varchar NOT NULL, 284 "property" varchar NOT NULL,
@@ -293,7 +293,7 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_template&quot; @@ -293,7 +293,7 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_template&quot;
293 CREATE TABLE IF NOT EXISTS "promotion_diy_page" 293 CREATE TABLE IF NOT EXISTS "promotion_diy_page"
294 ( 294 (
295 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 295 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
296 - "template_id" bigint NOT NULL, 296 + "template_id" bigint,
297 "name" varchar NOT NULL, 297 "name" varchar NOT NULL,
298 "remark" varchar, 298 "remark" varchar,
299 "preview_pic_urls" varchar, 299 "preview_pic_urls" varchar,
@@ -303,6 +303,6 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_page&quot; @@ -303,6 +303,6 @@ CREATE TABLE IF NOT EXISTS &quot;promotion_diy_page&quot;
303 "updater" varchar DEFAULT '', 303 "updater" varchar DEFAULT '',
304 "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 304 "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
305 "deleted" bit NOT NULL DEFAULT FALSE, 305 "deleted" bit NOT NULL DEFAULT FALSE,
306 - "tenant_id" bigint NOT NULL, 306 + "tenant_id" bigint NOT NULL DEFAULT 0,
307 PRIMARY KEY ("id") 307 PRIMARY KEY ("id")
308 ) COMMENT '装修页面'; 308 ) COMMENT '装修页面';
yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java
1 package cn.iocoder.yudao.module.trade.service.order; 1 package cn.iocoder.yudao.module.trade.service.order;
2 2
3 import cn.hutool.core.util.IdUtil; 3 import cn.hutool.core.util.IdUtil;
4 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 +import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
6 import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; 7 import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
7 -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; 8 +import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO;
8 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; 9 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
9 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; 10 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
  11 +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
10 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 12 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
11 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; 13 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
12 -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;  
13 -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;  
14 -import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;  
15 -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;  
16 -import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO; 14 +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
  15 +import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
17 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; 16 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
  17 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
  18 +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
  19 +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
  20 +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
18 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 21 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
  22 +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
19 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; 23 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
20 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; 24 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
21 import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO; 25 import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
22 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; 26 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
  27 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
23 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; 28 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
24 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; 29 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
25 -import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; 30 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
26 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; 31 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
27 -import cn.iocoder.yudao.module.trade.service.cart.CartServiceImpl; 32 +import cn.iocoder.yudao.module.trade.framework.order.core.aop.TradeOrderLogAspect;
  33 +import cn.iocoder.yudao.module.trade.service.cart.CartService;
28 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; 34 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
29 -import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressServiceImpl;  
30 -import cn.iocoder.yudao.module.trade.service.message.TradeMessageServiceImpl; 35 +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
  36 +import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
31 import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler; 37 import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
32 -import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl;  
33 -import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; 38 +import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
  39 +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
34 import jakarta.annotation.Resource; 40 import jakarta.annotation.Resource;
  41 +import org.junit.jupiter.api.AfterEach;
35 import org.junit.jupiter.api.BeforeEach; 42 import org.junit.jupiter.api.BeforeEach;
36 -import org.junit.jupiter.api.Disabled;  
37 import org.junit.jupiter.api.Test; 43 import org.junit.jupiter.api.Test;
38 import org.springframework.context.annotation.Import; 44 import org.springframework.context.annotation.Import;
  45 +import org.springframework.mock.web.MockHttpServletRequest;
39 import org.springframework.test.context.bean.override.mockito.MockitoBean; 46 import org.springframework.test.context.bean.override.mockito.MockitoBean;
  47 +import org.springframework.test.util.ReflectionTestUtils;
  48 +import org.springframework.web.context.request.RequestContextHolder;
  49 +import org.springframework.web.context.request.ServletRequestAttributes;
40 50
41 import java.time.Duration; 51 import java.time.Duration;
  52 +import java.util.Arrays;
  53 +import java.util.List;
42 54
  55 +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
43 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; 56 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
44 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 57 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
45 import static org.junit.jupiter.api.Assertions.*; 58 import static org.junit.jupiter.api.Assertions.*;
46 -import static org.mockito.ArgumentMatchers.anyString;  
47 -import static org.mockito.ArgumentMatchers.eq; 59 +import static org.mockito.ArgumentMatchers.*;
  60 +import static org.mockito.Mockito.verify;
48 import static org.mockito.Mockito.when; 61 import static org.mockito.Mockito.when;
49 62
50 /** 63 /**
@@ -53,10 +66,8 @@ import static org.mockito.Mockito.when; @@ -53,10 +66,8 @@ import static org.mockito.Mockito.when;
53 * @author LeeYan9 66 * @author LeeYan9
54 * @since 2022-09-07 67 * @since 2022-09-07
55 */ 68 */
56 -@Disabled // TODO 芋艿:后续 fix 补充的单测  
57 -@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class,  
58 - DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class  
59 -}) 69 +// DONE 芋艿:后续 fix 补充的单测
  70 +@Import(TradeOrderUpdateServiceImpl.class)
60 public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { 71 public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
61 72
62 @Resource 73 @Resource
@@ -68,34 +79,33 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -68,34 +79,33 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
68 private TradeOrderItemMapper tradeOrderItemMapper; 79 private TradeOrderItemMapper tradeOrderItemMapper;
69 80
70 @MockitoBean 81 @MockitoBean
71 - private MemberUserApi memberUserApi; 82 + private TradeNoRedisDAO tradeNoRedisDAO;
72 @MockitoBean 83 @MockitoBean
73 - private ProductSpuApi productSpuApi; 84 + private TradeOrderHandler tradeOrderHandler;
74 @MockitoBean 85 @MockitoBean
75 - private ProductSkuApi productSkuApi; 86 + private CartService cartService;
76 @MockitoBean 87 @MockitoBean
77 - private ProductCommentApi productCommentApi;  
78 - // @MockitoBean  
79 -// private PriceApi priceApi; 88 + private TradePriceService tradePriceService;
80 @MockitoBean 89 @MockitoBean
81 - private PayOrderApi payOrderApi; 90 + private DeliveryExpressService deliveryExpressService;
82 @MockitoBean 91 @MockitoBean
83 - private MemberAddressApi addressApi; 92 + private TradeMessageService tradeMessageService;
84 @MockitoBean 93 @MockitoBean
85 - private CouponApi couponApi;  
86 - 94 + private DeliveryPickUpStoreService pickUpStoreService;
87 @MockitoBean 95 @MockitoBean
88 - private TradeOrderProperties tradeOrderProperties; 96 + private PayOrderApi payOrderApi;
89 @MockitoBean 97 @MockitoBean
90 - private TradeNoRedisDAO tradeNoRedisDAO; 98 + private MemberAddressApi addressApi;
91 @MockitoBean 99 @MockitoBean
92 - private TradeOrderHandler tradeOrderHandler; 100 + private ProductCommentApi productCommentApi;
93 @MockitoBean 101 @MockitoBean
94 - private TradePriceCalculator tradePriceCalculator; 102 + private SocialClientApi socialClientApi;
95 @MockitoBean 103 @MockitoBean
96 - private NotifyMessageSendApi notifyMessageSendApi; 104 + private PayRefundApi payRefundApi;
97 @MockitoBean 105 @MockitoBean
98 - private DeliveryExpressService deliveryExpressService; 106 + private CombinationRecordApi combinationRecordApi;
  107 + @MockitoBean
  108 + private TradeOrderProperties tradeOrderProperties;
99 109
100 @BeforeEach 110 @BeforeEach
101 public void setUp() { 111 public void setUp() {
@@ -104,159 +114,134 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -104,159 +114,134 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
104 when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID()); 114 when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID());
105 } 115 }
106 116
107 -// @Test  
108 -// public void testCreateTradeOrder_success() {  
109 -// // 准备参数  
110 -// Long userId = 100L;  
111 -// String userIp = "127.0.0.1";  
112 -//// AppTradeOrderCreateReqVO reqVO = new AppTradeOrderCreateReqVO()  
113 -//// .setAddressId(10L).setCouponId(101L).setRemark("我是备注").setFromCart(true)  
114 -//// .setItems(Arrays.asList(new AppTradeOrderCreateReqVO.Item().setSkuId(1L).setCount(3),  
115 -//// new AppTradeOrderCreateReqVO.Item().setSkuId(2L).setCount(4)));  
116 -// AppTradeOrderCreateReqVO reqVO = null;  
117 -// // TODO 芋艿:重新高下  
118 -// // mock 方法(商品 SKU 检查)  
119 -// ProductSkuRespDTO sku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(1L).setSpuId(11L)  
120 -// .setPrice(50).setStock(100)  
121 -// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(111L).setValueId(222L))));  
122 -// ProductSkuRespDTO sku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(2L).setSpuId(21L)  
123 -// .setPrice(20).setStock(50))  
124 -// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(333L).setValueId(444L)));  
125 -// when(productSkuApi.getSkuList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(sku01, sku02));  
126 -// // mock 方法(商品 SPU 检查)  
127 -// ProductSpuRespDTO spu01 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(11L)  
128 -// .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()).setName("商品 1"));  
129 -// ProductSpuRespDTO spu02 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(21L)  
130 -// .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()));  
131 -// when(productSpuApi.getSpuList(eq(asSet(11L, 21L)))).thenReturn(Arrays.asList(spu01, spu02));  
132 -// // mock 方法(用户收件地址的校验)  
133 -// MemberAddressRespDTO addressRespDTO = new MemberAddressRespDTO().setId(10L).setUserId(userId).setName("芋艿")  
134 -// .setMobile("15601691300").setAreaId(3306).setDetailAddress("土豆村");  
135 -// when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(addressRespDTO);  
136 -// // mock 方法(价格计算)  
137 -// PriceCalculateRespDTO.OrderItem priceOrderItem01 = new PriceCalculateRespDTO.OrderItem()  
138 -// .setSpuId(11L).setSkuId(1L).setCount(3).setOriginalPrice(150).setOriginalUnitPrice(50)  
139 -// .setDiscountPrice(20).setPayPrice(130).setOrderPartPrice(7).setOrderDividePrice(35);  
140 -// PriceCalculateRespDTO.OrderItem priceOrderItem02 = new PriceCalculateRespDTO.OrderItem()  
141 -// .setSpuId(21L).setSkuId(2L).setCount(4).setOriginalPrice(80).setOriginalUnitPrice(20)  
142 -// .setDiscountPrice(40).setPayPrice(40).setOrderPartPrice(15).setOrderDividePrice(25);  
143 -// PriceCalculateRespDTO.Order priceOrder = new PriceCalculateRespDTO.Order()  
144 -// .setTotalPrice(230).setDiscountPrice(0).setCouponPrice(30)  
145 -// .setPointPrice(10).setDeliveryPrice(20).setPayPrice(80).setCouponId(101L).setCouponPrice(30)  
146 -// .setItems(Arrays.asList(priceOrderItem01, priceOrderItem02));  
147 -// when(priceApi.calculatePrice(argThat(priceCalculateReqDTO -> {  
148 -// assertEquals(priceCalculateReqDTO.getUserId(), 100L);  
149 -// assertEquals(priceCalculateReqDTO.getCouponId(), 101L);  
150 -// assertEquals(priceCalculateReqDTO.getItems().get(0).getSkuId(), 1L);  
151 -// assertEquals(priceCalculateReqDTO.getItems().get(0).getCount(), 3);  
152 -// assertEquals(priceCalculateReqDTO.getItems().get(1).getSkuId(), 2L);  
153 -// assertEquals(priceCalculateReqDTO.getItems().get(1).getCount(), 4);  
154 -// return true;  
155 -// }))).thenReturn(new PriceCalculateRespDTO().setOrder(priceOrder));  
156 -// // mock 方法(创建支付单)  
157 -// when(payOrderApi.createOrder(argThat(createReqDTO -> {  
158 -// assertEquals(createReqDTO.getAppId(), 888L);  
159 -// assertEquals(createReqDTO.getUserIp(), userIp);  
160 -// assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空  
161 -// assertEquals(createReqDTO.getSubject(), "商品 1 等多件");  
162 -// assertNull(createReqDTO.getBody());  
163 -// assertEquals(createReqDTO.getPrice(), 80);  
164 -// assertNotNull(createReqDTO.getExpireTime());  
165 -// return true;  
166 -// }))).thenReturn(1000L);  
167 -//  
168 -// // 调用方法  
169 -// TradeOrderDO order = tradeOrderUpdateService.createOrder(userId, userIp, reqVO, null);  
170 -// // 断言 TradeOrderDO 订单  
171 -// List<TradeOrderDO> tradeOrderDOs = tradeOrderMapper.selectList();  
172 -// assertEquals(tradeOrderDOs.size(), 1);  
173 -// TradeOrderDO tradeOrderDO = tradeOrderDOs.get(0);  
174 -// assertEquals(tradeOrderDO.getId(), order.getId());  
175 -// assertNotNull(tradeOrderDO.getNo());  
176 -// assertEquals(tradeOrderDO.getType(), TradeOrderTypeEnum.NORMAL.getType());  
177 -// assertEquals(tradeOrderDO.getTerminal(), TerminalEnum.H5.getTerminal());  
178 -// assertEquals(tradeOrderDO.getUserId(), userId);  
179 -// assertEquals(tradeOrderDO.getUserIp(), userIp);  
180 -// assertEquals(tradeOrderDO.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus());  
181 -// assertEquals(tradeOrderDO.getProductCount(), 7);  
182 -// assertNull(tradeOrderDO.getFinishTime());  
183 -// assertNull(tradeOrderDO.getCancelTime());  
184 -// assertNull(tradeOrderDO.getCancelType());  
185 -// assertEquals(tradeOrderDO.getUserRemark(), "我是备注");  
186 -// assertNull(tradeOrderDO.getRemark());  
187 -// assertFalse(tradeOrderDO.getPayStatus());  
188 -// assertNull(tradeOrderDO.getPayTime());  
189 -// assertEquals(tradeOrderDO.getTotalPrice(), 230);  
190 -// assertEquals(tradeOrderDO.getDiscountPrice(), 0);  
191 -// assertEquals(tradeOrderDO.getAdjustPrice(), 0);  
192 -// assertEquals(tradeOrderDO.getPayPrice(), 80);  
193 -// assertEquals(tradeOrderDO.getPayOrderId(), 1000L);  
194 -// assertNull(tradeOrderDO.getPayChannelCode());  
195 -// assertNull(tradeOrderDO.getLogisticsId());  
196 -// assertNull(tradeOrderDO.getDeliveryTime());  
197 -// assertNull(tradeOrderDO.getReceiveTime());  
198 -// assertEquals(tradeOrderDO.getReceiverName(), "芋艿");  
199 -// assertEquals(tradeOrderDO.getReceiverMobile(), "15601691300");  
200 -// assertEquals(tradeOrderDO.getReceiverAreaId(), 3306);  
201 -// assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村");  
202 -// assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus());  
203 -// assertEquals(tradeOrderDO.getRefundPrice(), 0);  
204 -// assertEquals(tradeOrderDO.getCouponPrice(), 30);  
205 -// assertEquals(tradeOrderDO.getPointPrice(), 10);  
206 -// // 断言 TradeOrderItemDO 订单(第 1 个)  
207 -// List<TradeOrderItemDO> tradeOrderItemDOs = tradeOrderItemMapper.selectList();  
208 -// assertEquals(tradeOrderItemDOs.size(), 2);  
209 -// TradeOrderItemDO tradeOrderItemDO01 = tradeOrderItemDOs.get(0);  
210 -// assertNotNull(tradeOrderItemDO01.getId());  
211 -// assertEquals(tradeOrderItemDO01.getUserId(), userId);  
212 -// assertEquals(tradeOrderItemDO01.getOrderId(), order.getId());  
213 -// assertEquals(tradeOrderItemDO01.getSpuId(), 11L);  
214 -// assertEquals(tradeOrderItemDO01.getSkuId(), 1L);  
215 -// assertEquals(tradeOrderItemDO01.getProperties().size(), 1);  
216 -// assertEquals(tradeOrderItemDO01.getProperties().get(0).getPropertyId(), 111L);  
217 -// assertEquals(tradeOrderItemDO01.getProperties().get(0).getValueId(), 222L);  
218 -// //assertEquals(tradeOrderItemDO01.getSpuName(), sku01.getSpuName()); TODO 找不到spuName  
219 -// assertEquals(tradeOrderItemDO01.getPicUrl(), sku01.getPicUrl());  
220 -// assertEquals(tradeOrderItemDO01.getCount(), 3);  
221 -//// assertEquals(tradeOrderItemDO01.getOriginalPrice(), 150);  
222 -// assertEquals(tradeOrderItemDO01.getPrice(), 50);  
223 -// assertEquals(tradeOrderItemDO01.getDiscountPrice(), 20);  
224 -// assertEquals(tradeOrderItemDO01.getPayPrice(), 130);  
225 -// assertEquals(tradeOrderItemDO01.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());  
226 -// // 断言 TradeOrderItemDO 订单(第 2 个)  
227 -// TradeOrderItemDO tradeOrderItemDO02 = tradeOrderItemDOs.get(1);  
228 -// assertNotNull(tradeOrderItemDO02.getId());  
229 -// assertEquals(tradeOrderItemDO02.getUserId(), userId);  
230 -// assertEquals(tradeOrderItemDO02.getOrderId(), order.getId());  
231 -// assertEquals(tradeOrderItemDO02.getSpuId(), 21L);  
232 -// assertEquals(tradeOrderItemDO02.getSkuId(), 2L);  
233 -// assertEquals(tradeOrderItemDO02.getProperties().size(), 1);  
234 -// assertEquals(tradeOrderItemDO02.getProperties().get(0).getPropertyId(), 333L);  
235 -// assertEquals(tradeOrderItemDO02.getProperties().get(0).getValueId(), 444L);  
236 -// //assertEquals(tradeOrderItemDO02.getSpuName(), sku02.getSpuName()); TODO 找不到spuName  
237 -// assertEquals(tradeOrderItemDO02.getPicUrl(), sku02.getPicUrl());  
238 -// assertEquals(tradeOrderItemDO02.getCount(), 4);  
239 -//// assertEquals(tradeOrderItemDO02.getOriginalPrice(), 80);  
240 -// assertEquals(tradeOrderItemDO02.getPrice(), 20);  
241 -// assertEquals(tradeOrderItemDO02.getDiscountPrice(), 40);  
242 -// assertEquals(tradeOrderItemDO02.getPayPrice(), 40);  
243 -// assertEquals(tradeOrderItemDO02.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus());  
244 -// // 校验调用  
245 -// verify(productSkuApi).updateSkuStock(argThat(updateStockReqDTO -> {  
246 -// assertEquals(updateStockReqDTO.getItems().size(), 2);  
247 -// assertEquals(updateStockReqDTO.getItems().get(0).getId(), 1L);  
248 -// assertEquals(updateStockReqDTO.getItems().get(0).getIncrCount(), 3);  
249 -// assertEquals(updateStockReqDTO.getItems().get(1).getId(), 2L);  
250 -// assertEquals(updateStockReqDTO.getItems().get(1).getIncrCount(), 4);  
251 -// return true;  
252 -// }));  
253 -// verify(couponApi).useCoupon(argThat(reqDTO -> {  
254 -// assertEquals(reqDTO.getId(), reqVO.getCouponId());  
255 -// assertEquals(reqDTO.getUserId(), userId);  
256 -// assertEquals(reqDTO.getOrderId(), order.getId());  
257 -// return true;  
258 -// }));  
259 -// } 117 + @AfterEach
  118 + public void tearDown() {
  119 + RequestContextHolder.resetRequestAttributes();
  120 + // 清理订单日志的上下文,单测没有 TradeOrderLogAspect 切面清理
  121 + ReflectionTestUtils.invokeMethod(TradeOrderLogAspect.class, "clear");
  122 + }
  123 +
  124 + @Test
  125 + public void testCreateOrder_success() {
  126 + // DONE 芋艿:重新高下
  127 + // 准备参数
  128 + Long userId = 100L;
  129 + String userIp = "127.0.0.1";
  130 + AppTradeOrderCreateReqVO reqVO = new AppTradeOrderCreateReqVO();
  131 + reqVO.setRemark("我是备注");
  132 + reqVO.setItems(Arrays.asList(new AppTradeOrderSettlementReqVO.Item().setCartId(1L),
  133 + new AppTradeOrderSettlementReqVO.Item().setCartId(2L)));
  134 + reqVO.setCouponId(101L);
  135 + reqVO.setPointStatus(false);
  136 + reqVO.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType());
  137 + reqVO.setAddressId(10L);
  138 + // mock 请求(用户 IP、终端)
  139 + MockHttpServletRequest request = new MockHttpServletRequest();
  140 + request.setRemoteAddr(userIp);
  141 + request.addHeader(WebFrameworkUtils.HEADER_TERMINAL, TerminalEnum.H5.getTerminal());
  142 + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
  143 + // mock 方法(购物车)
  144 + when(cartService.getCartList(eq(userId), eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(
  145 + new CartDO().setId(1L).setUserId(userId).setSpuId(11L).setSkuId(1L).setCount(3).setSelected(true),
  146 + new CartDO().setId(2L).setUserId(userId).setSpuId(21L).setSkuId(2L).setCount(4).setSelected(true)));
  147 + // mock 方法(价格计算)
  148 + TradePriceCalculateRespBO.OrderItem priceOrderItem01 = new TradePriceCalculateRespBO.OrderItem()
  149 + .setSpuId(11L).setSkuId(1L).setCount(3).setCartId(1L).setSelected(true).setSpuName("商品 1")
  150 + .setPrice(50).setDiscountPrice(20).setDeliveryPrice(10).setCouponPrice(10).setPointPrice(5)
  151 + .setVipPrice(0).setPayPrice(105);
  152 + TradePriceCalculateRespBO.OrderItem priceOrderItem02 = new TradePriceCalculateRespBO.OrderItem()
  153 + .setSpuId(21L).setSkuId(2L).setCount(4).setCartId(2L).setSelected(true).setSpuName("商品 2")
  154 + .setPrice(20).setDiscountPrice(10).setDeliveryPrice(10).setCouponPrice(20).setPointPrice(5)
  155 + .setVipPrice(0).setPayPrice(55);
  156 + TradePriceCalculateRespBO.Price price = new TradePriceCalculateRespBO.Price()
  157 + .setTotalPrice(230).setDiscountPrice(30).setDeliveryPrice(20).setCouponPrice(30)
  158 + .setPointPrice(10).setVipPrice(0).setPayPrice(160);
  159 + when(tradePriceService.calculateOrderPrice(argThat(calculateReqBO -> {
  160 + assertEquals(userId, calculateReqBO.getUserId());
  161 + assertEquals(101L, calculateReqBO.getCouponId());
  162 + assertEquals(2, calculateReqBO.getItems().size());
  163 + assertEquals(1L, calculateReqBO.getItems().get(0).getSkuId());
  164 + assertEquals(3, calculateReqBO.getItems().get(0).getCount());
  165 + assertEquals(2L, calculateReqBO.getItems().get(1).getSkuId());
  166 + assertEquals(4, calculateReqBO.getItems().get(1).getCount());
  167 + return true;
  168 + }))).thenReturn(new TradePriceCalculateRespBO().setType(TradeOrderTypeEnum.NORMAL.getType())
  169 + .setPrice(price).setItems(Arrays.asList(priceOrderItem01, priceOrderItem02)));
  170 + // mock 方法(用户收件地址)
  171 + when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(new MemberAddressRespDTO().setId(10L)
  172 + .setUserId(userId).setName("芋艿").setMobile("15601691300").setAreaId(3306).setDetailAddress("土豆村"));
  173 + // mock 方法(创建支付单)
  174 + when(payOrderApi.createOrder(argThat(createReqDTO -> {
  175 + assertEquals("mall", createReqDTO.getAppKey());
  176 + assertEquals(userIp, createReqDTO.getUserIp());
  177 + assertEquals(userId, createReqDTO.getUserId());
  178 + assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空
  179 + assertEquals("商品 1", createReqDTO.getSubject());
  180 + assertEquals(160, createReqDTO.getPrice());
  181 + assertNotNull(createReqDTO.getExpireTime());
  182 + return true;
  183 + }))).thenReturn(1000L);
  184 +
  185 + // 调用
  186 + TradeOrderDO order = tradeOrderUpdateService.createOrder(userId, reqVO);
  187 + // 断言 TradeOrderDO 订单
  188 + List<TradeOrderDO> tradeOrderDOs = tradeOrderMapper.selectList();
  189 + assertEquals(1, tradeOrderDOs.size());
  190 + TradeOrderDO tradeOrderDO = tradeOrderDOs.get(0);
  191 + assertEquals(order.getId(), tradeOrderDO.getId());
  192 + assertNotNull(tradeOrderDO.getNo());
  193 + assertEquals(TradeOrderTypeEnum.NORMAL.getType(), tradeOrderDO.getType());
  194 + assertEquals(TerminalEnum.H5.getTerminal(), tradeOrderDO.getTerminal());
  195 + assertEquals(userId, tradeOrderDO.getUserId());
  196 + assertEquals(userIp, tradeOrderDO.getUserIp());
  197 + assertEquals(TradeOrderStatusEnum.UNPAID.getStatus(), tradeOrderDO.getStatus());
  198 + assertEquals(7, tradeOrderDO.getProductCount());
  199 + assertEquals("我是备注", tradeOrderDO.getUserRemark());
  200 + assertFalse(tradeOrderDO.getPayStatus());
  201 + assertNull(tradeOrderDO.getPayTime());
  202 + assertEquals(1000L, tradeOrderDO.getPayOrderId());
  203 + assertEquals(230, tradeOrderDO.getTotalPrice());
  204 + assertEquals(30, tradeOrderDO.getDiscountPrice());
  205 + assertEquals(20, tradeOrderDO.getDeliveryPrice());
  206 + assertEquals(0, tradeOrderDO.getAdjustPrice());
  207 + assertEquals(160, tradeOrderDO.getPayPrice());
  208 + assertEquals(DeliveryTypeEnum.EXPRESS.getType(), tradeOrderDO.getDeliveryType());
  209 + assertEquals("芋艿", tradeOrderDO.getReceiverName());
  210 + assertEquals("15601691300", tradeOrderDO.getReceiverMobile());
  211 + assertEquals(3306, tradeOrderDO.getReceiverAreaId());
  212 + assertEquals("土豆村", tradeOrderDO.getReceiverDetailAddress());
  213 + assertEquals(TradeOrderRefundStatusEnum.NONE.getStatus(), tradeOrderDO.getRefundStatus());
  214 + assertEquals(0, tradeOrderDO.getRefundPrice());
  215 + assertEquals(101L, tradeOrderDO.getCouponId());
  216 + assertEquals(30, tradeOrderDO.getCouponPrice());
  217 + assertEquals(10, tradeOrderDO.getPointPrice());
  218 + // 断言 TradeOrderItemDO 订单项
  219 + List<TradeOrderItemDO> tradeOrderItemDOs = tradeOrderItemMapper.selectListByOrderId(order.getId());
  220 + assertEquals(2, tradeOrderItemDOs.size());
  221 + assertOrderItem(tradeOrderItemDOs.get(0), order, priceOrderItem01);
  222 + assertOrderItem(tradeOrderItemDOs.get(1), order, priceOrderItem02);
  223 + // 断言调用
  224 + verify(tradeOrderHandler).beforeOrderCreate(any(), any());
  225 + verify(tradeOrderHandler).afterOrderCreate(any(), any());
  226 + verify(cartService).deleteCart(eq(userId), eq(asSet(1L, 2L)));
  227 + }
  228 +
  229 + private static void assertOrderItem(TradeOrderItemDO orderItem, TradeOrderDO order,
  230 + TradePriceCalculateRespBO.OrderItem priceOrderItem) {
  231 + assertNotNull(orderItem.getId());
  232 + assertEquals(order.getUserId(), orderItem.getUserId());
  233 + assertEquals(order.getId(), orderItem.getOrderId());
  234 + assertEquals(priceOrderItem.getSpuId(), orderItem.getSpuId());
  235 + assertEquals(priceOrderItem.getSpuName(), orderItem.getSpuName()); // DONE 找不到spuName
  236 + assertEquals(priceOrderItem.getSkuId(), orderItem.getSkuId());
  237 + assertEquals(priceOrderItem.getCartId(), orderItem.getCartId());
  238 + assertEquals(priceOrderItem.getCount(), orderItem.getCount());
  239 + assertEquals(priceOrderItem.getPrice(), orderItem.getPrice());
  240 + assertEquals(priceOrderItem.getDiscountPrice(), orderItem.getDiscountPrice());
  241 + assertEquals(priceOrderItem.getPayPrice(), orderItem.getPayPrice());
  242 + assertEquals(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), orderItem.getAfterSaleStatus());
  243 + assertFalse(orderItem.getCommentStatus());
  244 + }
260 245
261 @Test 246 @Test
262 public void testUpdateOrderPaid() { 247 public void testUpdateOrderPaid() {
@@ -282,6 +267,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -282,6 +267,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
282 assertTrue(dbOrder.getPayStatus()); 267 assertTrue(dbOrder.getPayStatus());
283 assertNotNull(dbOrder.getPayTime()); 268 assertNotNull(dbOrder.getPayTime());
284 assertEquals(dbOrder.getPayChannelCode(), "wx_pub"); 269 assertEquals(dbOrder.getPayChannelCode(), "wx_pub");
  270 + verify(tradeOrderHandler).afterPayOrder(argThat(o -> o.getId().equals(id)), anyList());
285 } 271 }
286 272
287 @Test 273 @Test
@@ -294,15 +280,12 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -294,15 +280,12 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
294 o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()); 280 o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType());
295 }); 281 });
296 tradeOrderMapper.insert(order); 282 tradeOrderMapper.insert(order);
297 -  
298 - DeliveryExpressCreateReqVO expressCreateReqVO = new DeliveryExpressCreateReqVO();  
299 - expressCreateReqVO.setCode("code").setName("Name").setLogo("logo").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus());  
300 - Long deliveryExpressId = deliveryExpressService.createDeliveryExpress(expressCreateReqVO);  
301 // 准备参数 283 // 准备参数
302 TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L) 284 TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L)
303 - .setLogisticsId(deliveryExpressId).setLogisticsNo("100");  
304 -  
305 - // mock 方法(支付单) 285 + .setLogisticsId(20L).setLogisticsNo("100");
  286 + // mock 方法(物流公司)
  287 + when(deliveryExpressService.validateDeliveryExpress(eq(20L))).thenReturn(
  288 + randomPojo(DeliveryExpressDO.class, o -> o.setId(20L)));
306 289
307 // 调用 290 // 调用
308 tradeOrderUpdateService.deliveryOrder(deliveryReqVO); 291 tradeOrderUpdateService.deliveryOrder(deliveryReqVO);
@@ -311,6 +294,9 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -311,6 +294,9 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
311 assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus()); 294 assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus());
312 assertPojoEquals(dbOrder, deliveryReqVO); 295 assertPojoEquals(dbOrder, deliveryReqVO);
313 assertNotNull(dbOrder.getDeliveryTime()); 296 assertNotNull(dbOrder.getDeliveryTime());
  297 + // 断言调用(站内信)
  298 + verify(tradeMessageService).sendMessageWhenDeliveryOrder(argThat(reqBO ->
  299 + reqBO.getOrderId().equals(1L) && reqBO.getUserId().equals(order.getUserId())));
314 } 300 }
315 301
316 @Test 302 @Test
@@ -324,7 +310,6 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -324,7 +310,6 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
324 // 准备参数 310 // 准备参数
325 Long id = 1L; 311 Long id = 1L;
326 Long userId = 10L; 312 Long userId = 10L;
327 - // mock 方法(支付单)  
328 313
329 // 调用 314 // 调用
330 tradeOrderUpdateService.receiveOrderByMember(userId, id); 315 tradeOrderUpdateService.receiveOrderByMember(userId, id);
@@ -332,6 +317,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @@ -332,6 +317,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
332 TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L); 317 TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L);
333 assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); 318 assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus());
334 assertNotNull(dbOrder.getReceiveTime()); 319 assertNotNull(dbOrder.getReceiveTime());
  320 + verify(tradeOrderHandler).afterReceiveOrder(argThat(o -> o.getId().equals(id)));
335 } 321 }
336 322
337 } 323 }
yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql
@@ -49,12 +49,15 @@ CREATE TABLE IF NOT EXISTS &quot;trade_order&quot; @@ -49,12 +49,15 @@ CREATE TABLE IF NOT EXISTS &quot;trade_order&quot;
49 "refund_point" int NULL, 49 "refund_point" int NULL,
50 "vip_price" int NULL, 50 "vip_price" int NULL,
51 "give_coupons_map" varchar NULL, 51 "give_coupons_map" varchar NULL,
  52 + "give_coupon_template_counts" varchar NULL,
  53 + "give_coupon_ids" varchar NULL,
52 "seckill_activity_id" long NULL, 54 "seckill_activity_id" long NULL,
53 "bargain_activity_id" long NULL, 55 "bargain_activity_id" long NULL,
54 "bargain_record_id" long NULL, 56 "bargain_record_id" long NULL,
55 "combination_activity_id" long NULL, 57 "combination_activity_id" long NULL,
56 "combination_head_id" long NULL, 58 "combination_head_id" long NULL,
57 "combination_record_id" long NULL, 59 "combination_record_id" long NULL,
  60 + "point_activity_id" bigint NULL,
58 "creator" varchar DEFAULT '', 61 "creator" varchar DEFAULT '',
59 "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 62 "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
60 "updater" varchar DEFAULT '', 63 "updater" varchar DEFAULT '',
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
@@ -39,7 +39,7 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac @@ -39,7 +39,7 @@ public interface PayWalletTransactionMapper extends BaseMapperX&lt;PayWalletTransac
39 } 39 }
40 40
41 default Integer selectPriceSum(Long walletId, Integer type, LocalDateTime[] createTime) { 41 default Integer selectPriceSum(Long walletId, Integer type, LocalDateTime[] createTime) {
42 - // SQL sum 查询;支出流水的 price 为负值,汇总取正值(与钱包 total_expense 的正值语义一致) 42 + // SQL sum 查询;支出流水的 price 为负值,取反后汇总为正值
43 String sumColumn = Objects.equals(type, TYPE_EXPENSE) ? "SUM(-price) AS priceSum" : "SUM(price) AS priceSum"; 43 String sumColumn = Objects.equals(type, TYPE_EXPENSE) ? "SUM(-price) AS priceSum" : "SUM(price) AS priceSum";
44 List<Map<String, Object>> result = selectMaps(new QueryWrapperX<PayWalletTransactionDO>() 44 List<Map<String, Object>> result = selectMaps(new QueryWrapperX<PayWalletTransactionDO>()
45 .select(sumColumn) 45 .select(sumColumn)