diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java index 19a83b2..36ae95f 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java @@ -118,6 +118,7 @@ public class CrmProductCategoryServiceImpl implements CrmProductCategoryService if (crmProductService.getProductByCategoryId(id) > 0) { throw exception(PRODUCT_CATEGORY_USED); } + // 2. 删除 productCategoryMapper.deleteById(id); diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java index 785fa22..ce88f2a 100644 --- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java +++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java @@ -135,9 +135,9 @@ public class CrmProductServiceImpl implements CrmProductService { success = CRM_PRODUCT_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteProduct(Long id) { - // 校验存在 + // 1.1 校验存在 CrmProductDO product = validateProductExists(id); - // 校验未被合同/商机引用,避免删除后产生悬空引用 + // 1.2 校验未被合同/商机引用,避免删除后产生悬空引用 if (contractService.getContractProductCountByProductId(id) > 0) { throw exception(PRODUCT_DELETE_FAIL_CONTRACT_EXISTS); } @@ -145,10 +145,10 @@ public class CrmProductServiceImpl implements CrmProductService { throw exception(PRODUCT_DELETE_FAIL_BUSINESS_EXISTS); } - // 删除 + // 2. 删除 productMapper.deleteById(id); - // 记录操作日志上下文 + // 3. 记录操作日志上下文 LogRecordContext.putVariable("product", product); } diff --git a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImplTest.java b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImplTest.java new file mode 100644 index 0000000..88d5e8b --- /dev/null +++ b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImplTest.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductCategoryMapper; +import com.mzt.logapi.context.LogRecordContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +/** + * {@link CrmProductCategoryServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +public class CrmProductCategoryServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private CrmProductCategoryServiceImpl productCategoryService; + + @Mock + private CrmProductCategoryMapper productCategoryMapper; + @Mock + private CrmProductService crmProductService; + + @BeforeEach + public void setUp() { + LogRecordContext.putEmptySpan(); + } + + @AfterEach + public void tearDown() { + LogRecordContext.clear(); + } + + @Test + public void testDeleteProductCategory_success() { + // mock 数据 + CrmProductCategoryDO category = new CrmProductCategoryDO().setId(1L).setName("芋道分类").setParentId(0L); + when(productCategoryMapper.selectById(1L)).thenReturn(category); + // mock 方法(无子分类、未被产品使用) + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(0L); + when(crmProductService.getProductByCategoryId(1L)).thenReturn(0L); + + // 调用 + productCategoryService.deleteProductCategory(1L); + // 断言 + verify(productCategoryMapper).deleteById(1L); + // 断言操作日志上下文,为删除前的分类 + CrmProductCategoryDO logCategory = (CrmProductCategoryDO) LogRecordContext.getVariable("productCategory"); + assertSame(category, logCategory); + assertEquals("芋道分类", logCategory.getName()); + } + + @Test + public void testDeleteProductCategory_notExists() { + // 调用,并断言异常 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_NOT_EXISTS); + // 断言 + verify(productCategoryMapper, never()).deleteById(anyLong()); + assertNull(LogRecordContext.getVariable("productCategory")); + } + + @Test + public void testDeleteProductCategory_existsChildren() { + // mock 数据 + when(productCategoryMapper.selectById(1L)).thenReturn(new CrmProductCategoryDO().setId(1L).setName("芋道分类")); + // mock 方法(存在子分类) + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(1L); + + // 调用,并断言异常 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_EXISTS_CHILDREN); + // 断言 + verify(productCategoryMapper, never()).deleteById(anyLong()); + verifyNoInteractions(crmProductService); + assertNull(LogRecordContext.getVariable("productCategory")); + } + + @Test + public void testDeleteProductCategory_used() { + // mock 数据 + when(productCategoryMapper.selectById(1L)).thenReturn(new CrmProductCategoryDO().setId(1L).setName("芋道分类")); + // mock 方法(被产品使用) + when(productCategoryMapper.selectCountByParentId(1L)).thenReturn(0L); + when(crmProductService.getProductByCategoryId(1L)).thenReturn(1L); + + // 调用,并断言异常 + assertServiceException(() -> productCategoryService.deleteProductCategory(1L), PRODUCT_CATEGORY_USED); + // 断言 + verify(productCategoryMapper, never()).deleteById(anyLong()); + assertNull(LogRecordContext.getVariable("productCategory")); + } + +} diff --git a/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImplTest.java b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImplTest.java new file mode 100644 index 0000000..551deb0 --- /dev/null +++ b/yudao-module-crm/src/test/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImplTest.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductMapper; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.mzt.logapi.context.LogRecordContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +/** + * {@link CrmProductServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +public class CrmProductServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private CrmProductServiceImpl productService; + + @Mock + private CrmProductMapper productMapper; + @Mock + private CrmProductCategoryService productCategoryService; + @Mock + private CrmContractService contractService; + @Mock + private CrmBusinessService businessService; + @Mock + private CrmPermissionService permissionService; + @Mock + private AdminUserApi adminUserApi; + + @BeforeEach + public void setUp() { + LogRecordContext.putEmptySpan(); + } + + @AfterEach + public void tearDown() { + LogRecordContext.clear(); + } + + @Test + public void testDeleteProduct_success() { + // mock 数据 + CrmProductDO product = new CrmProductDO().setId(1L).setName("芋道产品"); + when(productMapper.selectById(1L)).thenReturn(product); + // mock 方法(未被合同、商机引用) + when(contractService.getContractProductCountByProductId(1L)).thenReturn(0L); + when(businessService.getBusinessProductCountByProductId(1L)).thenReturn(0L); + + // 调用 + productService.deleteProduct(1L); + // 断言 + verify(productMapper).deleteById(1L); + // 断言操作日志上下文,为删除前的产品 + CrmProductDO logProduct = (CrmProductDO) LogRecordContext.getVariable("product"); + assertSame(product, logProduct); + assertEquals("芋道产品", logProduct.getName()); + } + + @Test + public void testDeleteProduct_notExists() { + // 调用,并断言异常 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_NOT_EXISTS); + // 断言 + verify(productMapper, never()).deleteById(anyLong()); + assertNull(LogRecordContext.getVariable("product")); + } + + @Test + public void testDeleteProduct_contractExists() { + // mock 数据 + when(productMapper.selectById(1L)).thenReturn(new CrmProductDO().setId(1L).setName("芋道产品")); + // mock 方法(被合同引用) + when(contractService.getContractProductCountByProductId(1L)).thenReturn(1L); + + // 调用,并断言异常 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_DELETE_FAIL_CONTRACT_EXISTS); + // 断言 + verify(productMapper, never()).deleteById(anyLong()); + assertNull(LogRecordContext.getVariable("product")); + } + + @Test + public void testDeleteProduct_businessExists() { + // mock 数据 + when(productMapper.selectById(1L)).thenReturn(new CrmProductDO().setId(1L).setName("芋道产品")); + // mock 方法(被商机引用) + when(contractService.getContractProductCountByProductId(1L)).thenReturn(0L); + when(businessService.getBusinessProductCountByProductId(1L)).thenReturn(1L); + + // 调用,并断言异常 + assertServiceException(() -> productService.deleteProduct(1L), PRODUCT_DELETE_FAIL_BUSINESS_EXISTS); + // 断言 + verify(productMapper, never()).deleteById(anyLong()); + assertNull(LogRecordContext.getVariable("product")); + } + +} diff --git a/yudao-module-mall/yudao-module-product/pom.xml b/yudao-module-mall/yudao-module-product/pom.xml index d6377bc..96131bf 100644 --- a/yudao-module-mall/yudao-module-product/pom.xml +++ b/yudao-module-mall/yudao-module-product/pom.xml @@ -24,7 +24,7 @@ ${revision} - + cn.iocoder.boot yudao-module-trade-api diff --git a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java b/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java index e2056fd..0d69c55 100755 --- a/yudao-module-mall/yudao-module-product/src/test/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImplTest.java +++ b/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; import cn.hutool.core.date.DateUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.RandomUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; @@ -17,29 +17,37 @@ import cn.iocoder.yudao.module.product.service.category.ProductCategoryServiceIm import cn.iocoder.yudao.module.product.service.property.ProductPropertyService; import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuServiceImpl; +import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; import com.google.common.collect.Lists; import jakarta.annotation.Resource; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS; +import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_RECYCLE; +import static java.util.Collections.singletonList; import static org.assertj.core.util.Lists.newArrayList; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; // TODO @芋艿:review 下单元测试 @@ -48,10 +56,12 @@ import static org.mockito.Mockito.when; * * @author 芋道源码 */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 +// DONE 芋艿:后续 fix 补充的单测 @Import(ProductSpuServiceImpl.class) public class ProductSpuServiceImplTest extends BaseDbUnitTest { + private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); + @Resource private ProductSpuServiceImpl productSpuService; @@ -84,18 +94,7 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { @Test public void testCreateSpu_success() { // 准备参数 - ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuSaveReqVO.class, o->{ - // 限制范围为正整数 - o.setCostPrice(generaInt()); - o.setPrice(generaInt()); - o.setMarketPrice(generaInt()); - o.setStock(generaInt()); - o.setFirstBrokeragePrice(generaInt()); - o.setSecondBrokeragePrice(generaInt()); - // 限制分数为两位数 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - }); + ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomSkuSaveReqVO(); ProductSpuSaveReqVO createReqVO = randomPojo(ProductSpuSaveReqVO.class,o->{ o.setCategoryId(generateId()); o.setBrandId(generateId()); @@ -104,291 +103,215 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); }); + // mock 方法(分类层级) when(categoryService.getCategoryLevel(eq(createReqVO.getCategoryId()))).thenReturn(2); - Long spu = productSpuService.createSpu(createReqVO); - ProductSpuDO productSpuDO = productSpuMapper.selectById(spu); - assertPojoEquals(createReqVO, productSpuDO); + + // 调用 + Long spuId = productSpuService.createSpu(createReqVO); + // 断言:销量、浏览量为初始值 + ProductSpuDO productSpuDO = productSpuMapper.selectById(spuId); + assertPojoEquals(createReqVO, productSpuDO, "salesCount", "browseCount"); + assertEquals(ProductSpuStatusEnum.ENABLE.getStatus(), productSpuDO.getStatus()); + assertEquals(0, productSpuDO.getSalesCount()); + assertEquals(0, productSpuDO.getBrowseCount()); + // 断言:基于 SKU 初始化价格、库存 + assertEquals(skuCreateOrUpdateReqVO.getPrice(), productSpuDO.getPrice()); + assertEquals(skuCreateOrUpdateReqVO.getMarketPrice(), productSpuDO.getMarketPrice()); + assertEquals(skuCreateOrUpdateReqVO.getCostPrice(), productSpuDO.getCostPrice()); + assertEquals(skuCreateOrUpdateReqVO.getStock() * 3, productSpuDO.getStock()); + // 断言:创建 SKU + verify(productSkuService).createSkuList(eq(spuId), eq(createReqVO.getSkus())); } @Test public void testUpdateSpu_success() { + // mock 数据 + ProductSpuDO dbSpu = randomSpuDO(); + productSpuMapper.insert(dbSpu); // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }); - productSpuMapper.insert(createReqVO); - // 准备参数 - ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomPojo(ProductSkuSaveReqVO.class, o->{ - // 限制范围为正整数 - o.setCostPrice(generaInt()); - o.setPrice(generaInt()); - o.setMarketPrice(generaInt()); - o.setStock(generaInt()); - o.setFirstBrokeragePrice(generaInt()); - o.setSecondBrokeragePrice(generaInt()); - // 限制分数为两位数 - o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); - }); - // 准备参数 + ProductSkuSaveReqVO skuCreateOrUpdateReqVO = randomSkuSaveReqVO(); ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class, o -> { - o.setId(createReqVO.getId()); // 设置更新的 ID + o.setId(dbSpu.getId()); // 设置更新的 ID o.setCategoryId(generateId()); o.setBrandId(generateId()); o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 o.setGiveIntegral(generaInt()); // 限制范围为正整数 o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 o.setSalesCount(generaInt()); // 限制范围为正整数 o.setBrowseCount(generaInt()); // 限制范围为正整数 o.setSkus(newArrayList(skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO,skuCreateOrUpdateReqVO)); }); + // mock 方法(分类层级) when(categoryService.getCategoryLevel(eq(reqVO.getCategoryId()))).thenReturn(2); + // 调用 productSpuService.updateSpu(reqVO); - // 校验是否更新正确 + // 校验是否更新正确:状态保持不变 ProductSpuDO spu = productSpuMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, spu); + assertEquals(dbSpu.getStatus(), spu.getStatus()); + assertEquals(skuCreateOrUpdateReqVO.getStock() * 3, spu.getStock()); + // 断言:更新 SKU + verify(productSkuService).updateSkuList(eq(reqVO.getId()), eq(reqVO.getSkus())); } @Test - public void testValidateSpuExists_exception() { + public void testUpdateSpu_notExists() { + // 准备参数 ProductSpuSaveReqVO reqVO = randomPojo(ProductSpuSaveReqVO.class); - // 调用 - Assertions.assertThrows(ServiceException.class, () -> productSpuService.updateSpu(reqVO)); + + // 调用, 并断言异常 + assertServiceException(() -> productSpuService.updateSpu(reqVO), SPU_NOT_EXISTS); } @Test - void deleteSpu() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setStatus(-1); // 加入回收站才可删除 - }); - productSpuMapper.insert(createReqVO); + public void testValidateDeliveryTypes_success() { + // 调用,并断言:快递发货、用户自提,及其组合 + assertEquals(Collections.emptySet(), validateDeliveryTypes(singletonList(DeliveryTypeEnum.EXPRESS.getType()))); + assertEquals(Collections.emptySet(), validateDeliveryTypes(singletonList(DeliveryTypeEnum.PICK_UP.getType()))); + assertEquals(Collections.emptySet(), validateDeliveryTypes(Arrays.asList( + DeliveryTypeEnum.EXPRESS.getType(), DeliveryTypeEnum.PICK_UP.getType()))); + } + + @Test + public void testValidateDeliveryTypes_invalid() { + // 调用,并断言:不在枚举范围内 + Set invalidMessages = Collections.singleton("配送方式不正确"); + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(0))); + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(3))); + assertEquals(invalidMessages, validateDeliveryTypes(singletonList(-1))); + assertEquals(invalidMessages, validateDeliveryTypes(Arrays.asList(DeliveryTypeEnum.EXPRESS.getType(), 3))); + assertEquals(invalidMessages, validateDeliveryTypes(Arrays.asList(DeliveryTypeEnum.EXPRESS.getType(), null))); + // 调用,并断言:为空 + Set emptyMessages = Collections.singleton("配送方式不能为空"); + assertEquals(emptyMessages, validateDeliveryTypes(null)); + assertEquals(emptyMessages, validateDeliveryTypes(Collections.emptyList())); + } + + @Test + public void testDeleteSpu_success() { + // mock 数据 + ProductSpuDO dbSpu = randomSpuDO(o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus())); // 加入回收站才可删除 + productSpuMapper.insert(dbSpu); // 调用 - productSpuService.deleteSpu(createReqVO.getId()); + productSpuService.deleteSpu(dbSpu.getId()); + // 断言 + assertNull(productSpuMapper.selectById(dbSpu.getId())); + verify(productSkuService).deleteSkuBySpuId(eq(dbSpu.getId())); + } + + @Test + public void testDeleteSpu_notRecycle() { + // mock 数据 + ProductSpuDO dbSpu = randomSpuDO(o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus())); + productSpuMapper.insert(dbSpu); - Assertions.assertNull(productSpuMapper.selectById(createReqVO.getId())); + // 调用, 并断言异常 + assertServiceException(() -> productSpuService.deleteSpu(dbSpu.getId()), SPU_NOT_RECYCLE); + // 断言 + assertNotNull(productSpuMapper.selectById(dbSpu.getId())); + verify(productSkuService, never()).deleteSkuBySpuId(any()); } @Test - void getSpu() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }); - productSpuMapper.insert(createReqVO); + public void testGetSpu() { + // mock 数据 + ProductSpuDO dbSpu = randomSpuDO(); + productSpuMapper.insert(dbSpu); - ProductSpuDO spu = productSpuService.getSpu(createReqVO.getId()); - assertPojoEquals(createReqVO, spu); + // 调用 + ProductSpuDO spu = productSpuService.getSpu(dbSpu.getId()); + // 断言 + assertPojoEquals(dbSpu, spu); } @Test - void getSpuList() { - // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); + public void testGetSpuList() { + // mock 数据 + ArrayList dbSpus = Lists.newArrayList(randomSpuDO(), randomSpuDO()); + productSpuMapper.insertBatch(dbSpus); + // 准备参数:倒序传入 + List ids = Arrays.asList(dbSpus.get(1).getId(), dbSpus.get(0).getId()); // 调用 - List spuList = productSpuService.getSpuList(createReqVOs.stream().map(ProductSpuDO::getId).collect(Collectors.toList())); - Assertions.assertIterableEquals(createReqVOs, spuList); + List spuList = productSpuService.getSpuList(ids); + // 断言:按照 ids 顺序返回 + assertEquals(ids, convertList(spuList, ProductSpuDO::getId)); + assertPojoEquals(dbSpus.get(1), spuList.get(0)); + assertPojoEquals(dbSpus.get(0), spuList.get(1)); } @Test - void getSpuPage_alarmStock_empty() { + public void testGetSpuPage_alertStockEmpty() { + // mock 数据 + ArrayList dbSpus = Lists.newArrayList(randomSpuDO(o -> o.setStock(11)), + randomSpuDO(o -> o.setStock(11))); + productSpuMapper.insertBatch(dbSpus); // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(11); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(11); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); - // 调用 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); + // 调用 PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - - PageResult result = PageResult.empty(); - Assertions.assertIterableEquals(result.getList(), spuPage.getList()); - assertEquals(spuPage.getTotal(), result.getTotal()); + // 断言 + assertEquals(0, spuPage.getTotal()); + assertEquals(0, spuPage.getList().size()); } @Test - void getSpuPage_alarmStock() { + public void testGetSpuPage_alertStock() { + // mock 数据 + ArrayList dbSpus = Lists.newArrayList(randomSpuDO(o -> o.setStock(5)), + randomSpuDO(o -> o.setStock(9))); + productSpuMapper.insertBatch(dbSpus); + // 测试 stock 不匹配 + productSpuMapper.insert(randomSpuDO(o -> o.setStock(11))); + // 测试 status 不匹配(回收站) + productSpuMapper.insert(randomSpuDO(o -> o.setStock(5).setStatus(ProductSpuStatusEnum.RECYCLE.getStatus()))); // 准备参数 - ArrayList createReqVOs = Lists.newArrayList(randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(5); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - }), randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(9); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - })); - productSpuMapper.insertBatch(createReqVOs); - // 调用 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); + + // 调用 PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - assertEquals(createReqVOs.size(), spuPage.getTotal()); + // 断言 + assertEquals(dbSpus.size(), spuPage.getTotal()); + assertEquals(convertSet(dbSpus, ProductSpuDO::getId), convertSet(spuPage.getList(), ProductSpuDO::getId)); } @Test - void testGetSpuPage() { - // 准备参数 - ProductSpuDO createReqVO = randomPojo(ProductSpuDO.class,o->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 + public void testGetSpuPage() { + // mock 数据 + ProductSpuDO dbSpu = randomSpuDO(o -> { // 等会查询到 + o.setName("芋道商品"); + o.setCategoryId(1L); + o.setStatus(ProductSpuStatusEnum.ENABLE.getStatus()); + o.setCreateTime(buildTime(2023, 2, 10)); }); - + productSpuMapper.insert(dbSpu); + // 测试 name 不匹配 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setName("土豆商品"))); + // 测试 categoryId 不匹配 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCategoryId(2L))); + // 测试 createTime 不匹配 + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setCreateTime(buildTime(2023, 3, 10)))); + // 测试 status 不匹配(出售中) + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus()))); + productSpuMapper.insert(cloneIgnoreId(dbSpu, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus()))); // 准备参数 - productSpuMapper.insert(createReqVO); - // 测试 status 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.DISABLE.getStatus()))); - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setStatus(ProductSpuStatusEnum.RECYCLE.getStatus()))); - // 测试 SpecType 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setSpecType(true))); - // 测试 BrandId 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setBrandId(generateId()))); - // 测试 CategoryId 不匹配 - productSpuMapper.insert(cloneIgnoreId(createReqVO, o -> o.setCategoryId(generateId()))); - - // 调用 ProductSpuPageReqVO productSpuPageReqVO = new ProductSpuPageReqVO(); - // 查询条件 按需打开 - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.ALERT_STOCK); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.RECYCLE_BIN); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.IN_WAREHOUSE); - //productSpuPageReqVO.setTabType(ProductSpuPageReqVO.SOLD_OUT); - //productSpuPageReqVO.setName(createReqVO.getName()); - //productSpuPageReqVO.setCategoryId(createReqVO.getCategoryId()); + productSpuPageReqVO.setName("芋道"); + productSpuPageReqVO.setCategoryId(1L); + productSpuPageReqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + productSpuPageReqVO.setTabType(ProductSpuPageReqVO.FOR_SALE); + // 调用 PageResult spuPage = productSpuService.getSpuPage(productSpuPageReqVO); - + // 断言 assertEquals(1, spuPage.getTotal()); + assertEquals(1, spuPage.getList().size()); + assertPojoEquals(dbSpu, spuPage.getList().get(0)); } /** @@ -427,23 +350,49 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { // 准备参数 Map stockIncrCounts = MapUtil.builder(1L, 10).put(2L, -20).build(); // mock 方法(数据) - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o ->{ - o.setCategoryId(generateId()); - o.setBrandId(generateId()); - o.setDeliveryTemplateId(generateId()); - o.setSort(RandomUtil.randomInt(1,100)); // 限制排序范围 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setVirtualSalesCount(generaInt()); // 限制范围为正整数 - o.setPrice(generaInt()); // 限制范围为正整数 - o.setMarketPrice(generaInt()); // 限制范围为正整数 - o.setCostPrice(generaInt()); // 限制范围为正整数 - o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 - o.setSalesCount(generaInt()); // 限制范围为正整数 - o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setId(1L).setStock(20); - })); - productSpuMapper.insert(randomPojo(ProductSpuDO.class, o -> { + productSpuMapper.insert(randomSpuDO(o -> o.setId(1L).setStock(20))); + productSpuMapper.insert(randomSpuDO(o -> o.setId(2L).setStock(30))); + + // 调用 + productSpuService.updateSpuStock(stockIncrCounts); + // 断言 + assertEquals(productSpuService.getSpu(1L).getStock(), 30); + assertEquals(productSpuService.getSpu(2L).getStock(), 10); + } + + /** + * 校验 {@link ProductSpuSaveReqVO#getDeliveryTypes()} 字段 + * + * @param deliveryTypes 配送方式数组 + * @return 校验不通过的提示 + */ + private static Set validateDeliveryTypes(List deliveryTypes) { + ProductSpuSaveReqVO reqVO = new ProductSpuSaveReqVO(); + reqVO.setDeliveryTypes(deliveryTypes); + Set> violations = VALIDATOR.validateProperty(reqVO, "deliveryTypes"); + return convertSet(violations, ConstraintViolation::getMessage); + } + + // ========== 随机对象 ========== + + private ProductSkuSaveReqVO randomSkuSaveReqVO() { + return randomPojo(ProductSkuSaveReqVO.class, o->{ + // 限制范围为正整数 + o.setCostPrice(generaInt()); + o.setPrice(generaInt()); + o.setMarketPrice(generaInt()); + o.setStock(generaInt()); + o.setFirstBrokeragePrice(generaInt()); + o.setSecondBrokeragePrice(generaInt()); + // 限制分数为两位数 + o.setWeight(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + o.setVolume(RandomUtil.randomDouble(10,2, RoundingMode.HALF_UP)); + }); + } + + @SafeVarargs + private final ProductSpuDO randomSpuDO(Consumer... consumers) { + Consumer consumer = o -> { o.setCategoryId(generateId()); o.setBrandId(generateId()); o.setDeliveryTemplateId(generateId()); @@ -454,17 +403,10 @@ public class ProductSpuServiceImplTest extends BaseDbUnitTest { o.setMarketPrice(generaInt()); // 限制范围为正整数 o.setCostPrice(generaInt()); // 限制范围为正整数 o.setStock(generaInt()); // 限制范围为正整数 - o.setGiveIntegral(generaInt()); // 限制范围为正整数 o.setSalesCount(generaInt()); // 限制范围为正整数 o.setBrowseCount(generaInt()); // 限制范围为正整数 - o.setId(2L).setStock(30); - })); - - // 调用 - productSpuService.updateSpuStock(stockIncrCounts); - // 断言 - assertEquals(productSpuService.getSpu(1L).getStock(), 30); - assertEquals(productSpuService.getSpu(2L).getStock(), 10); + }; + return randomPojo(ProductSpuDO.class, ArrayUtils.append(consumer, consumers)); } } diff --git a/yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql index 94aaf78..c73ebcc 100644 --- a/yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-product/src/test/resources/sql/create_tables.sql @@ -28,13 +28,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` ( `keyword` varchar(256) NOT NULL COMMENT '关键字', `introduction` varchar(256) NOT NULL COMMENT '商品简介', `description` text NOT NULL COMMENT '商品详情', - `bar_code` varchar(64) NOT NULL COMMENT '条形码', `category_id` bigint NOT NULL COMMENT '商品分类编号', - `brand_id` int DEFAULT NULL COMMENT '商品品牌编号', + `brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号', `pic_url` varchar(256) NOT NULL COMMENT '商品封面图', `slider_pic_urls` varchar(2000) DEFAULT '' COMMENT '商品轮播图地址\n 数组,以逗号分隔\n 最多上传15张', - `video_url` varchar(256) DEFAULT NULL COMMENT '商品视频', - `unit` tinyint NOT NULL COMMENT '单位', `sort` int NOT NULL DEFAULT '0' COMMENT '排序字段', `status` tinyint NOT NULL COMMENT '商品状态: 0 上架(开启) 1 下架(禁用)-1 回收', `spec_type` bit(1) NOT NULL COMMENT '规格类型:0 单规格 1 多规格', @@ -42,16 +39,10 @@ CREATE TABLE IF NOT EXISTS `product_spu` ( `market_price` int NOT NULL COMMENT '市场价,单位使用:分', `cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价,单位: 分', `stock` int NOT NULL DEFAULT '0' COMMENT '库存', + `delivery_types` varchar(255) NOT NULL COMMENT '配送方式数组', `delivery_template_id` bigint NOT NULL COMMENT '物流配置模板编号', - `recommend_hot` bit(1) NOT NULL COMMENT '是否热卖推荐: 0 默认 1 热卖', - `recommend_benefit` bit(1) NOT NULL COMMENT '是否优惠推荐: 0 默认 1 优选', - `recommend_best` bit(1) NOT NULL COMMENT '是否精品推荐: 0 默认 1 精品', - `recommend_new` bit(1) NOT NULL COMMENT '是否新品推荐: 0 默认 1 新品', - `recommend_good` bit(1) NOT NULL COMMENT '是否优品推荐', `give_integral` int NOT NULL COMMENT '赠送积分', - `give_coupon_template_ids` varchar(512) DEFAULT '' COMMENT '赠送的优惠劵编号的数组', `sub_commission_type` bit(1) NOT NULL COMMENT '分销类型', - `activity_orders` varchar(16) NOT NULL DEFAULT '' COMMENT '活动显示排序0=默认, 1=秒杀,2=砍价,3=拼团', `sales_count` int DEFAULT '0' COMMENT '商品销量', `virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量', `browse_count` int DEFAULT '0' COMMENT '商品点击量', diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java index 8402734..cebabe1 100644 --- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java +++ b/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 { } // 删除 diyTemplateMapper.deleteById(id); - // 级联删除模板下自动生成的默认页面(createDiyTemplate 的「首页/我的」),避免页面列表残留孤儿页 + // 删除模板下的页面 diyPageService.getDiyPageByTemplateId(id) .forEach(page -> diyPageService.deleteDiyPage(page.getId())); } diff --git a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImplTest.java b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImplTest.java new file mode 100644 index 0000000..dde0e81 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImplTest.java @@ -0,0 +1,150 @@ +package cn.iocoder.yudao.module.promotion.service.coupon; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi; +import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; +import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper; +import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper; +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import javax.sql.DataSource; +import java.util.function.Consumer; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_DELETE_FAIL_USED; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link CouponServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import({CouponServiceImpl.class, CouponTemplateServiceImpl.class}) +public class CouponServiceImplTest extends BaseDbUnitTest { + + @Resource + private CouponServiceImpl couponService; + + @Resource + private CouponMapper couponMapper; + @Resource + private CouponTemplateMapper couponTemplateMapper; + + @Resource + private DataSource dataSource; + + @MockitoBean + private MemberUserApi memberUserApi; + @MockitoBean + private ProductCategoryApi productCategoryApi; + @MockitoBean + private ProductSpuApi productSpuApi; + + @Test + public void testDeleteCoupon_success() { + // mock 数据 + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5)); + couponTemplateMapper.insert(dbTemplate); + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId()) + .setStatus(CouponStatusEnum.UNUSED.getStatus())); + couponMapper.insert(dbCoupon); + + // 调用 + couponService.deleteCoupon(dbCoupon.getId()); + // 断言:优惠劵被删除,模板领取数量 -1 + assertNull(couponMapper.selectById(dbCoupon.getId())); + assertEquals(4, couponTemplateMapper.selectById(dbTemplate.getId()).getTakeCount()); + } + + @Test + public void testDeleteCoupon_templateDeleted() { + // mock 数据(模板已删除) + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5)); + couponTemplateMapper.insert(dbTemplate); + couponTemplateMapper.deleteById(dbTemplate.getId()); + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId()) + .setStatus(CouponStatusEnum.UNUSED.getStatus())); + couponMapper.insert(dbCoupon); + + // 调用 + couponService.deleteCoupon(dbCoupon.getId()); + // 断言:优惠劵被删除,已删除模板的领取数量不变 + assertNull(couponMapper.selectById(dbCoupon.getId())); + assertEquals(5, selectTemplateTakeCount(dbTemplate.getId())); + } + + @Test + public void testDeleteCoupon_templateNotExists() { + // mock 数据(模板不存在) + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(randomLongId()) + .setStatus(CouponStatusEnum.EXPIRE.getStatus())); + couponMapper.insert(dbCoupon); + + // 调用 + couponService.deleteCoupon(dbCoupon.getId()); + // 断言:优惠劵被删除 + assertNull(couponMapper.selectById(dbCoupon.getId())); + } + + @Test + public void testDeleteCoupon_used() { + // mock 数据 + CouponTemplateDO dbTemplate = randomCouponTemplateDO(o -> o.setTakeCount(5)); + couponTemplateMapper.insert(dbTemplate); + CouponDO dbCoupon = randomCouponDO(o -> o.setTemplateId(dbTemplate.getId()) + .setStatus(CouponStatusEnum.USED.getStatus())); + couponMapper.insert(dbCoupon); + + // 调用, 并断言异常 + assertServiceException(() -> couponService.deleteCoupon(dbCoupon.getId()), COUPON_DELETE_FAIL_USED); + // 断言:优惠劵未删除,模板领取数量不变 + CouponDO coupon = couponMapper.selectById(dbCoupon.getId()); + assertNotNull(coupon); + assertEquals(CouponStatusEnum.USED.getStatus(), coupon.getStatus()); + assertEquals(5, couponTemplateMapper.selectById(dbTemplate.getId()).getTakeCount()); + } + + @Test + public void testDeleteCoupon_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> couponService.deleteCoupon(id), COUPON_NOT_EXISTS); + } + + /** + * 查询模板的领取数量,包括已删除的模板 + * + * @param id 模板编号 + * @return 领取数量 + */ + private Integer selectTemplateTakeCount(Long id) { + return new JdbcTemplate(dataSource).queryForObject( + "SELECT take_count FROM promotion_coupon_template WHERE id = ?", Integer.class, id); + } + + // ========== 随机对象 ========== + + @SafeVarargs + private static CouponTemplateDO randomCouponTemplateDO(Consumer... consumers) { + return randomPojo(CouponTemplateDO.class, consumers); + } + + @SafeVarargs + private static CouponDO randomCouponDO(Consumer... consumers) { + return randomPojo(CouponDO.class, consumers); + } + +} diff --git a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImplTest.java b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImplTest.java index 52c9e92..58deaaf 100644 --- a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.DiyPageUpdateReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO; import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyPageMapper; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import jakarta.annotation.Resource; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_PAGE_NAME_USED; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_PAGE_NOT_EXISTS; import static org.junit.jupiter.api.Assertions.*; @@ -27,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*; * * @author owen */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 +// DONE 芋艿:后续 fix 补充的单测 @Import(DiyPageServiceImpl.class) public class DiyPageServiceImplTest extends BaseDbUnitTest { @@ -49,6 +50,21 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest { // 校验记录的属性是否正确 DiyPageDO diyPage = diyPageMapper.selectById(diyPageId); assertPojoEquals(reqVO, diyPage); + assertEquals("{}", diyPage.getProperty()); + } + + @Test + public void testCreateDiyPage_nameUsed() { + // mock 数据(独立页面) + diyPageMapper.insert(randomPojo(DiyPageDO.class, o -> o.setTemplateId(null).setName("芋道首页"))); + // 准备参数 + DiyPageCreateReqVO reqVO = randomPojo(DiyPageCreateReqVO.class, o -> { + o.setTemplateId(null); + o.setName("芋道首页"); + }); + + // 调用, 并断言异常 + assertServiceException(() -> diyPageService.createDiyPage(reqVO), DIY_PAGE_NAME_USED, "芋道首页"); } @Test @@ -101,21 +117,24 @@ public class DiyPageServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetDiyPagePage() { // mock 数据 DiyPageDO dbDiyPage = randomPojo(DiyPageDO.class, o -> { // 等会查询到 - o.setName(null); - o.setCreateTime(null); + o.setTemplateId(null); // 独立页面 + o.setName("芋道首页"); + o.setCreateTime(buildTime(2023, 2, 10)); }); diyPageMapper.insert(dbDiyPage); // 测试 name 不匹配 - diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setName(null))); + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setName("土豆首页"))); // 测试 createTime 不匹配 - diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setCreateTime(null))); + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setCreateTime(buildTime(2023, 3, 10)))); + // 测试 templateId 不匹配(模板下的页面,不在独立页面列表) + diyPageMapper.insert(cloneIgnoreId(dbDiyPage, o -> o.setTemplateId(1L))); // 准备参数 DiyPagePageReqVO reqVO = new DiyPagePageReqVO(); - reqVO.setName(null); + reqVO.setName("芋道"); reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); // 调用 diff --git a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImplTest.java b/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImplTest.java index 0c76f45..82b5866 100644 --- a/yudao-module-mall/yudao-module-promotion/src/test/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateCreateReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template.DiyTemplateUpdateReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyTemplateDO; +import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyPageMapper; import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyTemplateMapper; -import org.junit.jupiter.api.Disabled; +import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; -import jakarta.annotation.Resource; +import java.util.List; +import java.util.function.Consumer; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_TEMPLATE_NOT_EXISTS; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.DIY_TEMPLATE_USED_CANNOT_DELETE; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doCallRealMethod; /** * {@link DiyTemplateServiceImpl} 的单元测试类 * * @author owen */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 -@Import(DiyTemplateServiceImpl.class) +// DONE 芋艿:后续 fix 补充的单测 +@Import({DiyTemplateServiceImpl.class, DiyPageServiceImpl.class}) public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @Resource @@ -36,6 +46,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @Resource private DiyTemplateMapper diyTemplateMapper; + @Resource + private DiyPageMapper diyPageMapper; + + @MockitoSpyBean + private DiyPageService diyPageService; @Test public void testCreateDiyTemplate_success() { @@ -49,6 +64,15 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { // 校验记录的属性是否正确 DiyTemplateDO diyTemplate = diyTemplateMapper.selectById(diyTemplateId); assertPojoEquals(reqVO, diyTemplate); + assertEquals("{}", diyTemplate.getProperty()); + // 校验默认页面:首页、我的 + List pages = diyPageMapper.selectListByTemplateId(diyTemplateId); + assertEquals(asSet("首页", "我的"), convertSet(pages, DiyPageDO::getName)); + String remark = String.format("模板【%s】自动创建", reqVO.getName()); + pages.forEach(page -> { + assertEquals(remark, page.getRemark()); + assertEquals("{}", page.getProperty()); + }); } @Test @@ -79,9 +103,20 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { @Test public void testDeleteDiyTemplate_success() { - // mock 数据 - DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class); + // mock 数据(模板) + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false)); diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据 + DiyTemplateDO otherDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false)); + diyTemplateMapper.insert(otherDiyTemplate); + // mock 数据(页面):该模板下的全部页面,包括默认页面及后续新增页面 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("首页"))); + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("我的"))); + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()).setName("活动页"))); + // mock 数据(页面):其它模板下的页面、独立页面 + DiyPageDO otherTemplatePage = randomDiyPageDO(o -> o.setTemplateId(otherDiyTemplate.getId())); + diyPageMapper.insert(otherTemplatePage); + DiyPageDO standalonePage = randomDiyPageDO(o -> o.setTemplateId(null)); + diyPageMapper.insert(standalonePage); // 准备参数 Long id = dbDiyTemplate.getId(); @@ -89,6 +124,11 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { diyTemplateService.deleteDiyTemplate(id); // 校验数据不存在了 assertNull(diyTemplateMapper.selectById(id)); + assertEquals(0, diyPageMapper.selectListByTemplateId(id).size()); + // 校验其它模板、其它模板下的页面、独立页面保留 + assertNotNull(diyTemplateMapper.selectById(otherDiyTemplate.getId())); + assertNotNull(diyPageMapper.selectById(otherTemplatePage.getId())); + assertNotNull(diyPageMapper.selectById(standalonePage.getId())); } @Test @@ -101,37 +141,65 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testDeleteDiyTemplate_used() { + // mock 数据 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(true)); + diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据 + DiyPageDO dbDiyPage = randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId())); + diyPageMapper.insert(dbDiyPage); + // 准备参数 + Long id = dbDiyTemplate.getId(); + + // 调用, 并断言异常 + assertServiceException(() -> diyTemplateService.deleteDiyTemplate(id), DIY_TEMPLATE_USED_CANNOT_DELETE); + // 校验模板、页面都未删除 + assertNotNull(diyTemplateMapper.selectById(id)); + assertNotNull(diyPageMapper.selectById(dbDiyPage.getId())); + } + + @Test + public void testDeleteDiyTemplate_deletePageFailRollback() { + // mock 数据 + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(false)); + diyTemplateMapper.insert(dbDiyTemplate);// @Sql: 先插入出一条存在的数据 + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()))); + diyPageMapper.insert(randomDiyPageDO(o -> o.setTemplateId(dbDiyTemplate.getId()))); + // 准备参数 + Long id = dbDiyTemplate.getId(); + // mock 方法(第 1 个页面真实删除,第 2 个页面删除失败) + doCallRealMethod().doThrow(new IllegalStateException("删除页面失败")) + .when(diyPageService).deleteDiyPage(anyLong()); + + // 调用, 并断言异常 + assertThrows(IllegalStateException.class, () -> diyTemplateService.deleteDiyTemplate(id)); + // 校验模板、页面都已回滚 + assertNotNull(diyTemplateMapper.selectById(id)); + assertEquals(2, diyPageMapper.selectListByTemplateId(id).size()); + } + + @Test + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解 public void testGetDiyTemplatePage() { // mock 数据 DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> { // 等会查询到 - o.setName(null); - o.setUsed(null); - o.setUsedTime(null); - o.setRemark(null); - o.setPreviewPicUrls(null); - o.setProperty(null); - o.setCreateTime(null); + o.setName("芋道商城"); + o.setUsed(true); + o.setUsedTime(buildTime(2023, 2, 10)); + o.setCreateTime(buildTime(2023, 2, 10)); }); diyTemplateMapper.insert(dbDiyTemplate); // 测试 name 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName(null))); + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName("土豆商城"))); // 测试 used 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(null))); + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(false))); // 测试 usedTime 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(null))); - // 测试 remark 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setRemark(null))); - // 测试 previewPicUrls 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setPreviewPicUrls(null))); - // 测试 property 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setProperty(null))); + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(buildTime(2023, 3, 10)))); // 测试 createTime 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(null))); + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(buildTime(2023, 3, 10)))); // 准备参数 DiyTemplatePageReqVO reqVO = new DiyTemplatePageReqVO(); - reqVO.setName(null); - reqVO.setUsed(null); + reqVO.setName("芋道"); + reqVO.setUsed(true); reqVO.setUsedTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); @@ -144,33 +212,25 @@ public class DiyTemplateServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetDiyTemplateList() { + // DONE 请修改 null 为需要的值,然后删除 @Disabled 注解(DiyTemplateService 无列表查询方法,原 testGetDiyTemplateList 改为测试 getUsedDiyTemplate) + public void testGetUsedDiyTemplate() { // mock 数据 - DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> { // 等会查询到 - o.setName(null); - o.setUsed(null); - o.setUsedTime(null); - o.setRemark(null); - o.setPreviewPicUrls(null); - o.setProperty(null); - o.setCreateTime(null); - }); + DiyTemplateDO dbDiyTemplate = randomPojo(DiyTemplateDO.class, o -> o.setUsed(true)); diyTemplateMapper.insert(dbDiyTemplate); - // 测试 name 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName(null))); // 测试 used 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsed(null))); - // 测试 usedTime 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setUsedTime(null))); - // 测试 remark 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setRemark(null))); - // 测试 previewPicUrls 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setPreviewPicUrls(null))); - // 测试 property 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setProperty(null))); - // 测试 createTime 不匹配 - diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setCreateTime(null))); + diyTemplateMapper.insert(cloneIgnoreId(dbDiyTemplate, o -> o.setName("土豆商城").setUsed(false))); + + // 调用 + DiyTemplateDO usedDiyTemplate = diyTemplateService.getUsedDiyTemplate(); + // 断言 + assertPojoEquals(dbDiyTemplate, usedDiyTemplate); + } + + // ========== 随机对象 ========== + + @SafeVarargs + private static DiyPageDO randomDiyPageDO(Consumer... consumers) { + return randomPojo(DiyPageDO.class, consumers); } } diff --git a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql index fae2e52..54bcece 100644 --- a/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-promotion/src/test/resources/sql/create_tables.sql @@ -277,8 +277,8 @@ CREATE TABLE IF NOT EXISTS "promotion_diy_template" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" varchar NOT NULL, - "used" bit NOT NULL, - "used_time" varchar, + "used" bit NOT NULL DEFAULT FALSE, + "used_time" datetime, "remark" varchar, "preview_pic_urls" varchar, "property" varchar NOT NULL, @@ -293,7 +293,7 @@ CREATE TABLE IF NOT EXISTS "promotion_diy_template" CREATE TABLE IF NOT EXISTS "promotion_diy_page" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, - "template_id" bigint NOT NULL, + "template_id" bigint, "name" varchar NOT NULL, "remark" varchar, "preview_pic_urls" varchar, @@ -303,6 +303,6 @@ CREATE TABLE IF NOT EXISTS "promotion_diy_page" "updater" varchar DEFAULT '', "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL, + "tenant_id" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id") ) COMMENT '装修页面'; diff --git a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java index 690f41b..a5f8957 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java +++ b/yudao-module-mall/yudao-module-trade/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.java @@ -1,50 +1,63 @@ package cn.iocoder.yudao.module.trade.service.order; import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.enums.TerminalEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.member.api.address.MemberAddressApi; -import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.address.dto.MemberAddressRespDTO; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; -import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi; -import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi; -import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi; -import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; -import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO; +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; +import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; +import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper; import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper; import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO; import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; -import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig; +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; -import cn.iocoder.yudao.module.trade.service.cart.CartServiceImpl; +import cn.iocoder.yudao.module.trade.framework.order.core.aop.TradeOrderLogAspect; +import cn.iocoder.yudao.module.trade.service.cart.CartService; import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; -import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressServiceImpl; -import cn.iocoder.yudao.module.trade.service.message.TradeMessageServiceImpl; +import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService; +import cn.iocoder.yudao.module.trade.service.message.TradeMessageService; import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler; -import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl; -import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator; +import cn.iocoder.yudao.module.trade.service.price.TradePriceService; +import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import jakarta.annotation.Resource; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -53,10 +66,8 @@ import static org.mockito.Mockito.when; * @author LeeYan9 * @since 2022-09-07 */ -@Disabled // TODO 芋艿:后续 fix 补充的单测 -@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class, - DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class -}) +// DONE 芋艿:后续 fix 补充的单测 +@Import(TradeOrderUpdateServiceImpl.class) public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { @Resource @@ -68,34 +79,33 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { private TradeOrderItemMapper tradeOrderItemMapper; @MockitoBean - private MemberUserApi memberUserApi; + private TradeNoRedisDAO tradeNoRedisDAO; @MockitoBean - private ProductSpuApi productSpuApi; + private TradeOrderHandler tradeOrderHandler; @MockitoBean - private ProductSkuApi productSkuApi; + private CartService cartService; @MockitoBean - private ProductCommentApi productCommentApi; - // @MockitoBean -// private PriceApi priceApi; + private TradePriceService tradePriceService; @MockitoBean - private PayOrderApi payOrderApi; + private DeliveryExpressService deliveryExpressService; @MockitoBean - private MemberAddressApi addressApi; + private TradeMessageService tradeMessageService; @MockitoBean - private CouponApi couponApi; - + private DeliveryPickUpStoreService pickUpStoreService; @MockitoBean - private TradeOrderProperties tradeOrderProperties; + private PayOrderApi payOrderApi; @MockitoBean - private TradeNoRedisDAO tradeNoRedisDAO; + private MemberAddressApi addressApi; @MockitoBean - private TradeOrderHandler tradeOrderHandler; + private ProductCommentApi productCommentApi; @MockitoBean - private TradePriceCalculator tradePriceCalculator; + private SocialClientApi socialClientApi; @MockitoBean - private NotifyMessageSendApi notifyMessageSendApi; + private PayRefundApi payRefundApi; @MockitoBean - private DeliveryExpressService deliveryExpressService; + private CombinationRecordApi combinationRecordApi; + @MockitoBean + private TradeOrderProperties tradeOrderProperties; @BeforeEach public void setUp() { @@ -104,159 +114,134 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID()); } -// @Test -// public void testCreateTradeOrder_success() { -// // 准备参数 -// Long userId = 100L; -// String userIp = "127.0.0.1"; -//// AppTradeOrderCreateReqVO reqVO = new AppTradeOrderCreateReqVO() -//// .setAddressId(10L).setCouponId(101L).setRemark("我是备注").setFromCart(true) -//// .setItems(Arrays.asList(new AppTradeOrderCreateReqVO.Item().setSkuId(1L).setCount(3), -//// new AppTradeOrderCreateReqVO.Item().setSkuId(2L).setCount(4))); -// AppTradeOrderCreateReqVO reqVO = null; -// // TODO 芋艿:重新高下 -// // mock 方法(商品 SKU 检查) -// ProductSkuRespDTO sku01 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(1L).setSpuId(11L) -// .setPrice(50).setStock(100) -// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(111L).setValueId(222L)))); -// ProductSkuRespDTO sku02 = randomPojo(ProductSkuRespDTO.class, o -> o.setId(2L).setSpuId(21L) -// .setPrice(20).setStock(50)) -// .setProperties(singletonList(new ProductPropertyValueDetailRespDTO().setPropertyId(333L).setValueId(444L))); -// when(productSkuApi.getSkuList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(sku01, sku02)); -// // mock 方法(商品 SPU 检查) -// ProductSpuRespDTO spu01 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(11L) -// .setStatus(ProductSpuStatusEnum.ENABLE.getStatus()).setName("商品 1")); -// ProductSpuRespDTO spu02 = randomPojo(ProductSpuRespDTO.class, o -> o.setId(21L) -// .setStatus(ProductSpuStatusEnum.ENABLE.getStatus())); -// when(productSpuApi.getSpuList(eq(asSet(11L, 21L)))).thenReturn(Arrays.asList(spu01, spu02)); -// // mock 方法(用户收件地址的校验) -// MemberAddressRespDTO addressRespDTO = new MemberAddressRespDTO().setId(10L).setUserId(userId).setName("芋艿") -// .setMobile("15601691300").setAreaId(3306).setDetailAddress("土豆村"); -// when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(addressRespDTO); -// // mock 方法(价格计算) -// PriceCalculateRespDTO.OrderItem priceOrderItem01 = new PriceCalculateRespDTO.OrderItem() -// .setSpuId(11L).setSkuId(1L).setCount(3).setOriginalPrice(150).setOriginalUnitPrice(50) -// .setDiscountPrice(20).setPayPrice(130).setOrderPartPrice(7).setOrderDividePrice(35); -// PriceCalculateRespDTO.OrderItem priceOrderItem02 = new PriceCalculateRespDTO.OrderItem() -// .setSpuId(21L).setSkuId(2L).setCount(4).setOriginalPrice(80).setOriginalUnitPrice(20) -// .setDiscountPrice(40).setPayPrice(40).setOrderPartPrice(15).setOrderDividePrice(25); -// PriceCalculateRespDTO.Order priceOrder = new PriceCalculateRespDTO.Order() -// .setTotalPrice(230).setDiscountPrice(0).setCouponPrice(30) -// .setPointPrice(10).setDeliveryPrice(20).setPayPrice(80).setCouponId(101L).setCouponPrice(30) -// .setItems(Arrays.asList(priceOrderItem01, priceOrderItem02)); -// when(priceApi.calculatePrice(argThat(priceCalculateReqDTO -> { -// assertEquals(priceCalculateReqDTO.getUserId(), 100L); -// assertEquals(priceCalculateReqDTO.getCouponId(), 101L); -// assertEquals(priceCalculateReqDTO.getItems().get(0).getSkuId(), 1L); -// assertEquals(priceCalculateReqDTO.getItems().get(0).getCount(), 3); -// assertEquals(priceCalculateReqDTO.getItems().get(1).getSkuId(), 2L); -// assertEquals(priceCalculateReqDTO.getItems().get(1).getCount(), 4); -// return true; -// }))).thenReturn(new PriceCalculateRespDTO().setOrder(priceOrder)); -// // mock 方法(创建支付单) -// when(payOrderApi.createOrder(argThat(createReqDTO -> { -// assertEquals(createReqDTO.getAppId(), 888L); -// assertEquals(createReqDTO.getUserIp(), userIp); -// assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空 -// assertEquals(createReqDTO.getSubject(), "商品 1 等多件"); -// assertNull(createReqDTO.getBody()); -// assertEquals(createReqDTO.getPrice(), 80); -// assertNotNull(createReqDTO.getExpireTime()); -// return true; -// }))).thenReturn(1000L); -// -// // 调用方法 -// TradeOrderDO order = tradeOrderUpdateService.createOrder(userId, userIp, reqVO, null); -// // 断言 TradeOrderDO 订单 -// List tradeOrderDOs = tradeOrderMapper.selectList(); -// assertEquals(tradeOrderDOs.size(), 1); -// TradeOrderDO tradeOrderDO = tradeOrderDOs.get(0); -// assertEquals(tradeOrderDO.getId(), order.getId()); -// assertNotNull(tradeOrderDO.getNo()); -// assertEquals(tradeOrderDO.getType(), TradeOrderTypeEnum.NORMAL.getType()); -// assertEquals(tradeOrderDO.getTerminal(), TerminalEnum.H5.getTerminal()); -// assertEquals(tradeOrderDO.getUserId(), userId); -// assertEquals(tradeOrderDO.getUserIp(), userIp); -// assertEquals(tradeOrderDO.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus()); -// assertEquals(tradeOrderDO.getProductCount(), 7); -// assertNull(tradeOrderDO.getFinishTime()); -// assertNull(tradeOrderDO.getCancelTime()); -// assertNull(tradeOrderDO.getCancelType()); -// assertEquals(tradeOrderDO.getUserRemark(), "我是备注"); -// assertNull(tradeOrderDO.getRemark()); -// assertFalse(tradeOrderDO.getPayStatus()); -// assertNull(tradeOrderDO.getPayTime()); -// assertEquals(tradeOrderDO.getTotalPrice(), 230); -// assertEquals(tradeOrderDO.getDiscountPrice(), 0); -// assertEquals(tradeOrderDO.getAdjustPrice(), 0); -// assertEquals(tradeOrderDO.getPayPrice(), 80); -// assertEquals(tradeOrderDO.getPayOrderId(), 1000L); -// assertNull(tradeOrderDO.getPayChannelCode()); -// assertNull(tradeOrderDO.getLogisticsId()); -// assertNull(tradeOrderDO.getDeliveryTime()); -// assertNull(tradeOrderDO.getReceiveTime()); -// assertEquals(tradeOrderDO.getReceiverName(), "芋艿"); -// assertEquals(tradeOrderDO.getReceiverMobile(), "15601691300"); -// assertEquals(tradeOrderDO.getReceiverAreaId(), 3306); -// assertEquals(tradeOrderDO.getReceiverDetailAddress(), "土豆村"); -// assertEquals(tradeOrderDO.getRefundStatus(), TradeOrderRefundStatusEnum.NONE.getStatus()); -// assertEquals(tradeOrderDO.getRefundPrice(), 0); -// assertEquals(tradeOrderDO.getCouponPrice(), 30); -// assertEquals(tradeOrderDO.getPointPrice(), 10); -// // 断言 TradeOrderItemDO 订单(第 1 个) -// List tradeOrderItemDOs = tradeOrderItemMapper.selectList(); -// assertEquals(tradeOrderItemDOs.size(), 2); -// TradeOrderItemDO tradeOrderItemDO01 = tradeOrderItemDOs.get(0); -// assertNotNull(tradeOrderItemDO01.getId()); -// assertEquals(tradeOrderItemDO01.getUserId(), userId); -// assertEquals(tradeOrderItemDO01.getOrderId(), order.getId()); -// assertEquals(tradeOrderItemDO01.getSpuId(), 11L); -// assertEquals(tradeOrderItemDO01.getSkuId(), 1L); -// assertEquals(tradeOrderItemDO01.getProperties().size(), 1); -// assertEquals(tradeOrderItemDO01.getProperties().get(0).getPropertyId(), 111L); -// assertEquals(tradeOrderItemDO01.getProperties().get(0).getValueId(), 222L); -// //assertEquals(tradeOrderItemDO01.getSpuName(), sku01.getSpuName()); TODO 找不到spuName -// assertEquals(tradeOrderItemDO01.getPicUrl(), sku01.getPicUrl()); -// assertEquals(tradeOrderItemDO01.getCount(), 3); -//// assertEquals(tradeOrderItemDO01.getOriginalPrice(), 150); -// assertEquals(tradeOrderItemDO01.getPrice(), 50); -// assertEquals(tradeOrderItemDO01.getDiscountPrice(), 20); -// assertEquals(tradeOrderItemDO01.getPayPrice(), 130); -// assertEquals(tradeOrderItemDO01.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); -// // 断言 TradeOrderItemDO 订单(第 2 个) -// TradeOrderItemDO tradeOrderItemDO02 = tradeOrderItemDOs.get(1); -// assertNotNull(tradeOrderItemDO02.getId()); -// assertEquals(tradeOrderItemDO02.getUserId(), userId); -// assertEquals(tradeOrderItemDO02.getOrderId(), order.getId()); -// assertEquals(tradeOrderItemDO02.getSpuId(), 21L); -// assertEquals(tradeOrderItemDO02.getSkuId(), 2L); -// assertEquals(tradeOrderItemDO02.getProperties().size(), 1); -// assertEquals(tradeOrderItemDO02.getProperties().get(0).getPropertyId(), 333L); -// assertEquals(tradeOrderItemDO02.getProperties().get(0).getValueId(), 444L); -// //assertEquals(tradeOrderItemDO02.getSpuName(), sku02.getSpuName()); TODO 找不到spuName -// assertEquals(tradeOrderItemDO02.getPicUrl(), sku02.getPicUrl()); -// assertEquals(tradeOrderItemDO02.getCount(), 4); -//// assertEquals(tradeOrderItemDO02.getOriginalPrice(), 80); -// assertEquals(tradeOrderItemDO02.getPrice(), 20); -// assertEquals(tradeOrderItemDO02.getDiscountPrice(), 40); -// assertEquals(tradeOrderItemDO02.getPayPrice(), 40); -// assertEquals(tradeOrderItemDO02.getAfterSaleStatus(), TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); -// // 校验调用 -// verify(productSkuApi).updateSkuStock(argThat(updateStockReqDTO -> { -// assertEquals(updateStockReqDTO.getItems().size(), 2); -// assertEquals(updateStockReqDTO.getItems().get(0).getId(), 1L); -// assertEquals(updateStockReqDTO.getItems().get(0).getIncrCount(), 3); -// assertEquals(updateStockReqDTO.getItems().get(1).getId(), 2L); -// assertEquals(updateStockReqDTO.getItems().get(1).getIncrCount(), 4); -// return true; -// })); -// verify(couponApi).useCoupon(argThat(reqDTO -> { -// assertEquals(reqDTO.getId(), reqVO.getCouponId()); -// assertEquals(reqDTO.getUserId(), userId); -// assertEquals(reqDTO.getOrderId(), order.getId()); -// return true; -// })); -// } + @AfterEach + public void tearDown() { + RequestContextHolder.resetRequestAttributes(); + // 清理订单日志的上下文,单测没有 TradeOrderLogAspect 切面清理 + ReflectionTestUtils.invokeMethod(TradeOrderLogAspect.class, "clear"); + } + + @Test + public void testCreateOrder_success() { + // DONE 芋艿:重新高下 + // 准备参数 + Long userId = 100L; + String userIp = "127.0.0.1"; + AppTradeOrderCreateReqVO reqVO = new AppTradeOrderCreateReqVO(); + reqVO.setRemark("我是备注"); + reqVO.setItems(Arrays.asList(new AppTradeOrderSettlementReqVO.Item().setCartId(1L), + new AppTradeOrderSettlementReqVO.Item().setCartId(2L))); + reqVO.setCouponId(101L); + reqVO.setPointStatus(false); + reqVO.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()); + reqVO.setAddressId(10L); + // mock 请求(用户 IP、终端) + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRemoteAddr(userIp); + request.addHeader(WebFrameworkUtils.HEADER_TERMINAL, TerminalEnum.H5.getTerminal()); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + // mock 方法(购物车) + when(cartService.getCartList(eq(userId), eq(asSet(1L, 2L)))).thenReturn(Arrays.asList( + new CartDO().setId(1L).setUserId(userId).setSpuId(11L).setSkuId(1L).setCount(3).setSelected(true), + new CartDO().setId(2L).setUserId(userId).setSpuId(21L).setSkuId(2L).setCount(4).setSelected(true))); + // mock 方法(价格计算) + TradePriceCalculateRespBO.OrderItem priceOrderItem01 = new TradePriceCalculateRespBO.OrderItem() + .setSpuId(11L).setSkuId(1L).setCount(3).setCartId(1L).setSelected(true).setSpuName("商品 1") + .setPrice(50).setDiscountPrice(20).setDeliveryPrice(10).setCouponPrice(10).setPointPrice(5) + .setVipPrice(0).setPayPrice(105); + TradePriceCalculateRespBO.OrderItem priceOrderItem02 = new TradePriceCalculateRespBO.OrderItem() + .setSpuId(21L).setSkuId(2L).setCount(4).setCartId(2L).setSelected(true).setSpuName("商品 2") + .setPrice(20).setDiscountPrice(10).setDeliveryPrice(10).setCouponPrice(20).setPointPrice(5) + .setVipPrice(0).setPayPrice(55); + TradePriceCalculateRespBO.Price price = new TradePriceCalculateRespBO.Price() + .setTotalPrice(230).setDiscountPrice(30).setDeliveryPrice(20).setCouponPrice(30) + .setPointPrice(10).setVipPrice(0).setPayPrice(160); + when(tradePriceService.calculateOrderPrice(argThat(calculateReqBO -> { + assertEquals(userId, calculateReqBO.getUserId()); + assertEquals(101L, calculateReqBO.getCouponId()); + assertEquals(2, calculateReqBO.getItems().size()); + assertEquals(1L, calculateReqBO.getItems().get(0).getSkuId()); + assertEquals(3, calculateReqBO.getItems().get(0).getCount()); + assertEquals(2L, calculateReqBO.getItems().get(1).getSkuId()); + assertEquals(4, calculateReqBO.getItems().get(1).getCount()); + return true; + }))).thenReturn(new TradePriceCalculateRespBO().setType(TradeOrderTypeEnum.NORMAL.getType()) + .setPrice(price).setItems(Arrays.asList(priceOrderItem01, priceOrderItem02))); + // mock 方法(用户收件地址) + when(addressApi.getAddress(eq(10L), eq(userId))).thenReturn(new MemberAddressRespDTO().setId(10L) + .setUserId(userId).setName("芋艿").setMobile("15601691300").setAreaId(3306).setDetailAddress("土豆村")); + // mock 方法(创建支付单) + when(payOrderApi.createOrder(argThat(createReqDTO -> { + assertEquals("mall", createReqDTO.getAppKey()); + assertEquals(userIp, createReqDTO.getUserIp()); + assertEquals(userId, createReqDTO.getUserId()); + assertNotNull(createReqDTO.getMerchantOrderId()); // 由于 tradeOrderId 后生成,只能校验非空 + assertEquals("商品 1", createReqDTO.getSubject()); + assertEquals(160, createReqDTO.getPrice()); + assertNotNull(createReqDTO.getExpireTime()); + return true; + }))).thenReturn(1000L); + + // 调用 + TradeOrderDO order = tradeOrderUpdateService.createOrder(userId, reqVO); + // 断言 TradeOrderDO 订单 + List tradeOrderDOs = tradeOrderMapper.selectList(); + assertEquals(1, tradeOrderDOs.size()); + TradeOrderDO tradeOrderDO = tradeOrderDOs.get(0); + assertEquals(order.getId(), tradeOrderDO.getId()); + assertNotNull(tradeOrderDO.getNo()); + assertEquals(TradeOrderTypeEnum.NORMAL.getType(), tradeOrderDO.getType()); + assertEquals(TerminalEnum.H5.getTerminal(), tradeOrderDO.getTerminal()); + assertEquals(userId, tradeOrderDO.getUserId()); + assertEquals(userIp, tradeOrderDO.getUserIp()); + assertEquals(TradeOrderStatusEnum.UNPAID.getStatus(), tradeOrderDO.getStatus()); + assertEquals(7, tradeOrderDO.getProductCount()); + assertEquals("我是备注", tradeOrderDO.getUserRemark()); + assertFalse(tradeOrderDO.getPayStatus()); + assertNull(tradeOrderDO.getPayTime()); + assertEquals(1000L, tradeOrderDO.getPayOrderId()); + assertEquals(230, tradeOrderDO.getTotalPrice()); + assertEquals(30, tradeOrderDO.getDiscountPrice()); + assertEquals(20, tradeOrderDO.getDeliveryPrice()); + assertEquals(0, tradeOrderDO.getAdjustPrice()); + assertEquals(160, tradeOrderDO.getPayPrice()); + assertEquals(DeliveryTypeEnum.EXPRESS.getType(), tradeOrderDO.getDeliveryType()); + assertEquals("芋艿", tradeOrderDO.getReceiverName()); + assertEquals("15601691300", tradeOrderDO.getReceiverMobile()); + assertEquals(3306, tradeOrderDO.getReceiverAreaId()); + assertEquals("土豆村", tradeOrderDO.getReceiverDetailAddress()); + assertEquals(TradeOrderRefundStatusEnum.NONE.getStatus(), tradeOrderDO.getRefundStatus()); + assertEquals(0, tradeOrderDO.getRefundPrice()); + assertEquals(101L, tradeOrderDO.getCouponId()); + assertEquals(30, tradeOrderDO.getCouponPrice()); + assertEquals(10, tradeOrderDO.getPointPrice()); + // 断言 TradeOrderItemDO 订单项 + List tradeOrderItemDOs = tradeOrderItemMapper.selectListByOrderId(order.getId()); + assertEquals(2, tradeOrderItemDOs.size()); + assertOrderItem(tradeOrderItemDOs.get(0), order, priceOrderItem01); + assertOrderItem(tradeOrderItemDOs.get(1), order, priceOrderItem02); + // 断言调用 + verify(tradeOrderHandler).beforeOrderCreate(any(), any()); + verify(tradeOrderHandler).afterOrderCreate(any(), any()); + verify(cartService).deleteCart(eq(userId), eq(asSet(1L, 2L))); + } + + private static void assertOrderItem(TradeOrderItemDO orderItem, TradeOrderDO order, + TradePriceCalculateRespBO.OrderItem priceOrderItem) { + assertNotNull(orderItem.getId()); + assertEquals(order.getUserId(), orderItem.getUserId()); + assertEquals(order.getId(), orderItem.getOrderId()); + assertEquals(priceOrderItem.getSpuId(), orderItem.getSpuId()); + assertEquals(priceOrderItem.getSpuName(), orderItem.getSpuName()); // DONE 找不到spuName + assertEquals(priceOrderItem.getSkuId(), orderItem.getSkuId()); + assertEquals(priceOrderItem.getCartId(), orderItem.getCartId()); + assertEquals(priceOrderItem.getCount(), orderItem.getCount()); + assertEquals(priceOrderItem.getPrice(), orderItem.getPrice()); + assertEquals(priceOrderItem.getDiscountPrice(), orderItem.getDiscountPrice()); + assertEquals(priceOrderItem.getPayPrice(), orderItem.getPayPrice()); + assertEquals(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), orderItem.getAfterSaleStatus()); + assertFalse(orderItem.getCommentStatus()); + } @Test public void testUpdateOrderPaid() { @@ -282,6 +267,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { assertTrue(dbOrder.getPayStatus()); assertNotNull(dbOrder.getPayTime()); assertEquals(dbOrder.getPayChannelCode(), "wx_pub"); + verify(tradeOrderHandler).afterPayOrder(argThat(o -> o.getId().equals(id)), anyList()); } @Test @@ -294,15 +280,12 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType()); }); tradeOrderMapper.insert(order); - - DeliveryExpressCreateReqVO expressCreateReqVO = new DeliveryExpressCreateReqVO(); - expressCreateReqVO.setCode("code").setName("Name").setLogo("logo").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus()); - Long deliveryExpressId = deliveryExpressService.createDeliveryExpress(expressCreateReqVO); // 准备参数 TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L) - .setLogisticsId(deliveryExpressId).setLogisticsNo("100"); - - // mock 方法(支付单) + .setLogisticsId(20L).setLogisticsNo("100"); + // mock 方法(物流公司) + when(deliveryExpressService.validateDeliveryExpress(eq(20L))).thenReturn( + randomPojo(DeliveryExpressDO.class, o -> o.setId(20L))); // 调用 tradeOrderUpdateService.deliveryOrder(deliveryReqVO); @@ -311,6 +294,9 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.DELIVERED.getStatus()); assertPojoEquals(dbOrder, deliveryReqVO); assertNotNull(dbOrder.getDeliveryTime()); + // 断言调用(站内信) + verify(tradeMessageService).sendMessageWhenDeliveryOrder(argThat(reqBO -> + reqBO.getOrderId().equals(1L) && reqBO.getUserId().equals(order.getUserId()))); } @Test @@ -324,7 +310,6 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { // 准备参数 Long id = 1L; Long userId = 10L; - // mock 方法(支付单) // 调用 tradeOrderUpdateService.receiveOrderByMember(userId, id); @@ -332,6 +317,7 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest { TradeOrderDO dbOrder = tradeOrderMapper.selectById(1L); assertEquals(dbOrder.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); assertNotNull(dbOrder.getReceiveTime()); + verify(tradeOrderHandler).afterReceiveOrder(argThat(o -> o.getId().equals(id))); } } diff --git a/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql b/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql index 61ed058..2fabfd4 100644 --- a/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mall/yudao-module-trade/src/test/resources/sql/create_tables.sql @@ -49,12 +49,15 @@ CREATE TABLE IF NOT EXISTS "trade_order" "refund_point" int NULL, "vip_price" int NULL, "give_coupons_map" varchar NULL, + "give_coupon_template_counts" varchar NULL, + "give_coupon_ids" varchar NULL, "seckill_activity_id" long NULL, "bargain_activity_id" long NULL, "bargain_record_id" long NULL, "combination_activity_id" long NULL, "combination_head_id" long NULL, "combination_record_id" long NULL, + "point_activity_id" bigint NULL, "creator" varchar DEFAULT '', "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "updater" varchar DEFAULT '', diff --git a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java b/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java index b3305f2..3c06df6 100644 --- a/yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java +++ b/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> result = selectMaps(new QueryWrapperX() .select(sumColumn)