产品延期管理单元测试完善

This commit is contained in:
gaoqr
2025-12-09 14:06:27 +08:00
parent 5b6e13c092
commit 3be53feb11
9 changed files with 579 additions and 29 deletions
@@ -0,0 +1,492 @@
package com.cf.imes.module.system.service.funds.delay;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.mysql.funds.delay.ProductDelayRecordMapper;
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.system.enums.pay.DurationUnitEnum;
import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum;
import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum;
import com.cf.imes.module.system.service.funds.products.ProductsDetailService;
import com.cf.imes.module.system.service.funds.purchase.PurchaseService;
import com.cf.imes.module.system.service.organ.OrganService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomLongId;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_DELAY_UNIT_NOT_SUPPORT;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_NO_EXIST;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* {@link ProductDelayServiceImpl} 的单元测试类
*
* @author Gqr
* @since 2025/12/8 14:30
*/
@Import(ProductDelayServiceImpl.class)
public class ProductDelayServiceImplTest extends BaseDbAndRedisUnitTest {
@Resource
private ProductDelayServiceImpl productDelayService;
@Resource
private ProductDelayRecordMapper recordMapper;
@Resource
private PurchaseRecordMapper purchaseRecordMapper;
@MockitoBean
private OrganService organService;
@MockitoBean
private PurchaseService purchaseService;
@MockitoBean
private ProductsDetailService productsDetailService;
@Test
public void testCreateProductDelay_noPreDelayRecord_success() {
// 准备参数
Long organId = randomLongId();
Long purchaseId = randomLongId();
Long productId = randomLongId();
// Mock 组织
OrganizationDO organization = new OrganizationDO();
organization.setId(organId);
organization.setName("测试组织");
when(organService.validOrgan(organId)).thenReturn(organization);
// Mock 购买记录
PurchaseRecordDO purchaseRecord = PurchaseRecordDO.builder()
.id(purchaseId)
.productId(productId)
.productName("测试产品")
.startTime(LocalDate.now().minusMonths(6))
.endTime(LocalDate.now().plusMonths(6))
.build();
when(purchaseService.getRecord(purchaseId)).thenReturn(purchaseRecord);
// 构造请求
ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
reqVO.setOrganId(organId);
reqVO.setPurchaseId(purchaseId);
reqVO.setDelayDuration(3);
reqVO.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
// 调用
Long delayId = productDelayService.createProductDelay(reqVO);
// 断言
assertNotNull(delayId);
ProductDelayRecordDO dbRecord = recordMapper.selectById(delayId);
assertNotNull(dbRecord);
assertEquals(organId, dbRecord.getOrganId());
assertEquals("测试组织", dbRecord.getOrganName());
assertEquals(productId, dbRecord.getProductId());
assertEquals("测试产品", dbRecord.getProductName());
assertEquals(3, dbRecord.getDelayDuration());
assertEquals(DurationUnitEnum.MONTH.getCode(), dbRecord.getDelayDurationUnit());
assertEquals(purchaseRecord.getEndTime(), dbRecord.getEndTime());
assertEquals(purchaseRecord.getEndTime().plusMonths(3), dbRecord.getDelayTime());
assertEquals(purchaseId, dbRecord.getPurchaseId());
assertFalse(dbRecord.getPaid());
}
@Test
public void testCreateProductDelay_withPreDelayRecord_success() {
// 准备参数
Long organId = randomLongId();
Long purchaseId = randomLongId();
Long productId = randomLongId();
// Mock 组织
OrganizationDO organization = new OrganizationDO();
organization.setId(organId);
organization.setName("测试组织");
when(organService.validOrgan(organId)).thenReturn(organization);
// Mock 购买记录
PurchaseRecordDO purchaseRecord = PurchaseRecordDO.builder()
.id(purchaseId)
.productId(productId)
.productName("测试产品")
.startTime(LocalDate.now().minusMonths(6))
.endTime(LocalDate.now().plusMonths(6))
.build();
when(purchaseService.getRecord(purchaseId)).thenReturn(purchaseRecord);
// 插入前序延期记录
ProductDelayRecordDO preDelayRecord = ProductDelayRecordDO.builder()
.organId(organId)
.organName("测试组织")
.productId(productId)
.productName("测试产品")
.delayDuration(1)
.delayDurationUnit(DurationUnitEnum.YEAR.getCode())
.endTime(purchaseRecord.getEndTime())
.delayTime(purchaseRecord.getEndTime().plusYears(1))
.purchaseId(purchaseId)
.paid(true)
.build();
recordMapper.insert(preDelayRecord);
// 构造请求
ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
reqVO.setOrganId(organId);
reqVO.setPurchaseId(purchaseId);
reqVO.setDelayDuration(6);
reqVO.setDelayDurationUnit(DurationUnitEnum.YEAR.getCode());
// 调用
Long delayId = productDelayService.createProductDelay(reqVO);
// 断言
assertNotNull(delayId);
ProductDelayRecordDO dbRecord = recordMapper.selectById(delayId);
assertNotNull(dbRecord);
// 应该基于前序记录的延期时间计算
assertEquals(preDelayRecord.getDelayTime(), dbRecord.getEndTime());
assertEquals(preDelayRecord.getDelayTime().plusYears(6), dbRecord.getDelayTime());
}
@Test
public void testCreateProductDelay_expiredEndTime_success() {
// 准备参数
Long organId = randomLongId();
Long purchaseId = randomLongId();
Long productId = randomLongId();
// Mock 组织
OrganizationDO organization = new OrganizationDO();
organization.setId(organId);
organization.setName("测试组织");
when(organService.validOrgan(organId)).thenReturn(organization);
// Mock 购买记录(已过期)
PurchaseRecordDO purchaseRecord = PurchaseRecordDO.builder()
.id(purchaseId)
.productId(productId)
.productName("测试产品")
.startTime(LocalDate.now().minusMonths(12))
.endTime(LocalDate.now().minusMonths(1)) // 已过期
.build();
when(purchaseService.getRecord(purchaseId)).thenReturn(purchaseRecord);
// 构造请求
ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
reqVO.setOrganId(organId);
reqVO.setPurchaseId(purchaseId);
reqVO.setDelayDuration(3);
reqVO.setDelayDurationUnit(DurationUnitEnum.DAY.getCode());
// 调用
Long delayId = productDelayService.createProductDelay(reqVO);
// 断言
assertNotNull(delayId);
ProductDelayRecordDO dbRecord = recordMapper.selectById(delayId);
assertNotNull(dbRecord);
// 已过期的情况,应该从今天开始计算
assertEquals(LocalDate.now(), dbRecord.getEndTime());
assertEquals(LocalDate.now().plusDays(3), dbRecord.getDelayTime());
}
@Test
public void testCreateProductDelay_withExpiredPreDelayRecord_success() {
// 准备参数
Long organId = randomLongId();
Long purchaseId = randomLongId();
Long productId = randomLongId();
// Mock 组织
OrganizationDO organization = new OrganizationDO();
organization.setId(organId);
organization.setName("测试组织");
when(organService.validOrgan(organId)).thenReturn(organization);
// Mock 购买记录(未过期)
PurchaseRecordDO purchaseRecord = PurchaseRecordDO.builder()
.id(purchaseId)
.productId(productId)
.productName("测试产品")
.startTime(LocalDate.now().minusMonths(6))
.endTime(LocalDate.now().plusMonths(6))
.build();
when(purchaseService.getRecord(purchaseId)).thenReturn(purchaseRecord);
// 插入前序延期记录(延期时间未过期)
ProductDelayRecordDO preDelayRecord = ProductDelayRecordDO.builder()
.organId(organId)
.organName("测试组织")
.productId(productId)
.productName("测试产品")
.delayDuration(3)
.delayDurationUnit(DurationUnitEnum.MONTH.getCode())
.endTime(purchaseRecord.getEndTime())
.delayTime(LocalDate.now().plusMonths(9)) // 未过期的延期时间
.purchaseId(purchaseId)
.paid(true)
.build();
recordMapper.insert(preDelayRecord);
// 构造请求
ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
reqVO.setOrganId(organId);
reqVO.setPurchaseId(purchaseId);
reqVO.setDelayDuration(6);
reqVO.setDelayDurationUnit(4);
// 调用并断言异常
assertServiceException(() -> productDelayService.createProductDelay(reqVO),
PRODUCT_DELAY_UNIT_NOT_SUPPORT);
}
@Test
public void testCreateProductDelay_purchaseNotExist() {
// 准备参数
Long organId = randomLongId();
Long purchaseId = randomLongId();
// Mock 组织
OrganizationDO organization = new OrganizationDO();
organization.setId(organId);
organization.setName("测试组织");
when(organService.validOrgan(organId)).thenReturn(organization);
// Mock 购买记录不存在
when(purchaseService.getRecord(purchaseId)).thenReturn(null);
// 构造请求
ProductDelaySaveReqVO reqVO = new ProductDelaySaveReqVO();
reqVO.setOrganId(organId);
reqVO.setPurchaseId(purchaseId);
reqVO.setDelayDuration(3);
reqVO.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
// 调用并断言异常
assertServiceException(() -> productDelayService.createProductDelay(reqVO),
PRODUCT_PURCHASE_NO_EXIST);
}
@Test
public void testGetPage() {
// mock 数据
ProductDelayRecordDO record1 = randomPojo(ProductDelayRecordDO.class, o -> {
o.setOrganName("晨丰科技");
o.setProductName("ERP产品");
o.setDelayDuration(3);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
});
recordMapper.insert(record1);
// 不匹配的数据
recordMapper.insert(randomPojo(ProductDelayRecordDO.class, o -> {
o.setOrganName("其他组织");
o.setProductName("ERP产品");
o.setDelayDuration(4);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
}));
// 准备参数
ProductDelayPageReqVO reqVO = new ProductDelayPageReqVO();
reqVO.setOrganName("晨丰科技");
reqVO.setProductName("ERP产品");
reqVO.setDelayDuration(3);
reqVO.setPageNo(1);
reqVO.setPageSize(10);
// 调用
PageResult<ProductDelayRespVO> pageResult = productDelayService.getPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
ProductDelayRespVO respVO = pageResult.getList().get(0);
assertEquals("晨丰科技", respVO.getOrganName());
assertEquals("ERP产品", respVO.getProductName());
assertEquals(3, respVO.getDelayDuration());
}
@Test
public void testGetRecordsByPurchaseId() {
// 准备数据
Long purchaseId = randomLongId();
Long organId = randomLongId();
// mock 数据
ProductDelayRecordDO record1 = randomPojo(ProductDelayRecordDO.class, o -> {
o.setOrganName("晨丰科技");
o.setProductName("ERP产品");
o.setDelayDuration(3);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
o.setPurchaseId(purchaseId);
o.setOrganId(organId);
});
recordMapper.insert(record1);
// 不匹配的数据
recordMapper.insert(randomPojo(ProductDelayRecordDO.class, o -> {
o.setOrganName("其他组织");
o.setProductName("ERP产品");
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
}));
// 调用
List<ProductDelayRespVO> records = productDelayService.getRecordsByPurchaseId(purchaseId, organId);
// 断言
assertEquals(1, records.size());
// 验证按 ID 倒序
assertEquals(3, records.get(0).getDelayDuration());
}
@Test
public void testGetActiveRecordsByProductId_withRecords() {
// 准备数据
Long productId = randomLongId();
Long organId = randomLongId();
Long purchaseId = randomLongId();
// 插入活跃的购买记录
PurchaseRecordDO purchaseRecord = randomPojo(PurchaseRecordDO.class, o -> {
o.setId(purchaseId);
o.setOrganId(organId);
o.setProductId(productId);
o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus());
o.setCurrency(null);
o.setIsInvocing(null);
o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
});
purchaseRecordMapper.insert(purchaseRecord);
// 插入延期记录
ProductDelayRecordDO delayRecord = randomPojo(ProductDelayRecordDO.class, o -> {
o.setOrganId(organId);
o.setProductId(productId);
o.setPurchaseId(purchaseId);
o.setDelayDuration(3);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
o.setDelayTime(LocalDate.now().plusMonths(3));
o.setEndTime(LocalDate.now());
});
recordMapper.insert(delayRecord);
// 调用
PreviouProductDelayRespVO result = productDelayService.getActiveRecordsByProductId(productId, organId);
// 断言
assertNotNull(result);
assertNotNull(result.getPreviouPurchaseRecord());
assertEquals(purchaseId, result.getPreviouPurchaseRecord().getId());
assertNotNull(result.getDelayRespVOList());
assertEquals(1, result.getDelayRespVOList().size());
assertEquals(3, result.getDelayRespVOList().get(0).getDelayDuration());
}
@Test
public void testGetActiveRecordsByProductId_noPurchaseRecord() {
// 准备数据
Long productId = randomLongId();
Long organId = randomLongId();
// 调用(没有购买记录)
PreviouProductDelayRespVO result = productDelayService.getActiveRecordsByProductId(productId, organId);
// 断言
assertNotNull(result);
assertNull(result.getPreviouPurchaseRecord());
assertNull(result.getDelayRespVOList());
}
@Test
public void testUpdatePurchaseDelayRecordsPaidAndRemark() {
// 准备数据
Long organId = randomLongId();
ProductDelayRecordDO record1 = randomPojo(ProductDelayRecordDO.class, o -> {
o.setPaid(false);
o.setRemark(null);
o.setDelayDuration(3);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
o.setOrganId(organId);
});
recordMapper.insert(record1);
// 准备更新数据
record1.setRemark("备注1");
List<ProductDelayRecordDO> updateList = Arrays.asList(record1);
// 调用
productDelayService.updatePurchaseDelayRecordsPaidAndRemark(updateList, organId, true);
// 断言
ProductDelayRecordDO dbRecord1 = recordMapper.selectById(record1.getId());
assertTrue(dbRecord1.getPaid());
assertEquals("备注1", dbRecord1.getRemark());
}
@Test
public void testUpdatePurchaseDelayRecordsPaid() {
// 准备数据
Long purchaseId = randomLongId();
Long organId = randomLongId();
ProductDelayRecordDO record1 = randomPojo(ProductDelayRecordDO.class, o -> {
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
o.setOrganId(organId);
o.setPurchaseId(purchaseId);
o.setPaid(false);
});
recordMapper.insert(record1);
// 调用
productDelayService.updatePurchaseDelayRecordsPaid(purchaseId, organId);
// 断言
ProductDelayRecordDO dbRecord1 = recordMapper.selectById(record1.getId());
assertTrue(dbRecord1.getPaid());
}
@Test
public void testResetPurchaseDelayRecordsRemark() {
// 准备数据
Long purchaseId = randomLongId();
Long organId = randomLongId();
ProductDelayRecordDO record1 = randomPojo(ProductDelayRecordDO.class, o -> {
o.setRemark("测试备注");
o.setDelayDuration(3);
o.setDelayDurationUnit(DurationUnitEnum.MONTH.getCode());
o.setOrganId(organId);
o.setPurchaseId(purchaseId);
});
recordMapper.insert(record1);
// 调用
productDelayService.resetPurchaseDelayRecordsRemark(purchaseId, organId);
// 断言
ProductDelayRecordDO dbRecord1 = recordMapper.selectById(record1.getId());
assertNull(dbRecord1.getRemark());
}
}
@@ -38,4 +38,6 @@ DELETE FROM "process_group";
DELETE FROM "products";
DELETE FROM "products_details";
DELETE FROM "product_promotion";
DELETE FROM "advertisement";
DELETE FROM "advertisement";
DELETE FROM "product_delay_record";
DELETE FROM "purchase_record"
@@ -752,3 +752,59 @@ CREATE TABLE IF NOT EXISTS "advertisement" (
"deleted" BIT DEFAULT FALSE NOT NULL COMMENT '是否删除,0否1是',
PRIMARY KEY ("id")
) COMMENT '广告表 advertisement';
CREATE TABLE IF NOT EXISTS "product_delay_record"
(
"id" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '主键,自增',
"organ_id" BIGINT NOT NULL DEFAULT '0' COMMENT '组织ID',
"organ_name" VARCHAR(30) NOT NULL COMMENT '组织名称',
"product_id" BIGINT NOT NULL COMMENT '产品编号',
"product_name" VARCHAR(64) NOT NULL COMMENT '产品名称',
"delay_duration" INT NOT NULL COMMENT '延期时长',
"delay_duration_unit" TINYINT NOT NULL COMMENT '延期时长单位',
"end_time" DATE NOT NULL COMMENT '产品结束时间',
"delay_time" DATE NOT NULL COMMENT '延期后有效时长',
"purchase_id" VARCHAR(2048) DEFAULT NULL COMMENT '购买记录单号',
"remark" VARCHAR(1024) DEFAULT NULL COMMENT '备注',
"creator" VARCHAR(64) DEFAULT NULL COMMENT '创建者',
"create_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"updater" VARCHAR(64) DEFAULT NULL COMMENT '更新者',
"update_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
"paid" BIT DEFAULT FALSE COMMENT '是否支付',
"deleted" BIT NOT NULL DEFAULT FALSE COMMENT '是否删除,0否1是',
PRIMARY KEY ("id")
) COMMENT '产品延期记录表';
CREATE TABLE IF NOT EXISTS "purchase_record"
(
"id" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '主键,根据ID号规则生成',
"organ_id" BIGINT NOT NULL DEFAULT '0' COMMENT '组织ID',
"previous_id" BIGINT DEFAULT NULL COMMENT '前序订购记录id',
"product_id" BIGINT NOT NULL COMMENT '产品ID',
"product_details_id" BIGINT NOT NULL COMMENT '产品明细ID',
"product_promotion_id" BIGINT DEFAULT NULL COMMENT '优惠活动id',
"product_name" VARCHAR(64) NOT NULL COMMENT '产品名称',
"order_no" VARCHAR(64) DEFAULT NULL COMMENT '订单号',
"purchase_duration" INT NOT NULL DEFAULT '0' COMMENT '购买时长',
"purchase_duration_unit" TINYINT NOT NULL COMMENT '购买时长单位:0年、1月、2日',
"gift_duration" INT DEFAULT NULL COMMENT '赠送时长',
"gift_duration_unit" TINYINT DEFAULT NULL COMMENT '赠送时长单位:0年、1月、2日',
"start_time" DATE NOT NULL COMMENT '产品开始时间',
"end_time" DATE NOT NULL COMMENT '产品结束时间',
"payment_method" TINYINT NOT NULL DEFAULT '0' COMMENT '支付方式:0现金余额;1赠送金;2支付宝;3微信;4现金+赠送金;5现金+支付宝;6现金+微信;7赠送金+支付宝;8赠送金+微信;9现金+赠送金+支付宝;10现金+赠送金+微信',
"total_amount" DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '订单总额',
"original_amount" DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '原始价格',
"month_average_amount" DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '月均单价',
"external_spent" DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '外部支付金额',
"currency" TINYINT DEFAULT NULL COMMENT '货币类型',
"currency_unit" VARCHAR(10) DEFAULT NULL COMMENT '货币单位',
"is_invocing" TINYINT DEFAULT NULL COMMENT '是否开票:0可开票 1待开票 2已开票',
"initial" BIT NOT NULL DEFAULT FALSE COMMENT '是否首次订购',
"status" INT DEFAULT '1' COMMENT '购买记录状态,1有效、2等待支付、3作废',
"creator" VARCHAR(64) DEFAULT NULL COMMENT '创建者',
"create_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
"updater" VARCHAR(64) DEFAULT NULL COMMENT '更新者',
"update_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
"deleted" BIT NOT NULL DEFAULT FALSE COMMENT '是否删除:0否 1是',
PRIMARY KEY ("id")
) COMMENT '购买记录表';