diff --git a/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java b/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java index 5ff255ddd..6dbfe6ca6 100644 --- a/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java +++ b/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java @@ -2,6 +2,7 @@ package cn.iocoder.dashboard.common.exception.util; import cn.iocoder.dashboard.common.exception.ErrorCode; import cn.iocoder.dashboard.common.exception.ServiceException; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,7 +92,8 @@ public class ServiceExceptionUtil { * @param params 参数 * @return 格式化后的提示 */ - private static String doFormat(int code, String messagePattern, Object... params) { + @VisibleForTesting + public static String doFormat(int code, String messagePattern, Object... params) { StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); int i = 0; int j; diff --git a/src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java b/src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java index 690a2ab4c..2385fc193 100644 --- a/src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java +++ b/src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java @@ -13,14 +13,17 @@ import java.io.Serializable; @Data public class PageParam implements Serializable { + private static final Integer PAGE_NO = 1; + private static final Integer PAGE_SIZE = 10; + @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1") @NotNull(message = "页码不能为空") @Min(value = 1, message = "页码最小值为 1") - private Integer pageNo; + private Integer pageNo = PAGE_NO; @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10") @NotNull(message = "每页条数不能为空") @Range(min = 1, max = 100, message = "条数范围为 [1, 100]") - private Integer pageSize; + private Integer pageSize = PAGE_SIZE; } diff --git a/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java b/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java index 433f83a35..3ae810982 100644 --- a/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java +++ b/src/main/java/cn/iocoder/dashboard/util/date/DateUtils.java @@ -1,6 +1,7 @@ package cn.iocoder.dashboard.util.date; import java.time.Duration; +import java.util.Calendar; import java.util.Date; /** @@ -22,4 +23,40 @@ public class DateUtils { return endTime.getTime() - startTime.getTime(); } + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day) { + return buildTime(year, mouth, day, 0, 0, 0); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @param hour 小时 + * @param minute 分钟 + * @param second 秒 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day, + int hour, int minute, int second) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, mouth - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒 + return calendar.getTime(); + } + } diff --git a/src/main/java/cn/iocoder/dashboard/util/object/ObjectUtils.java b/src/main/java/cn/iocoder/dashboard/util/object/ObjectUtils.java new file mode 100644 index 000000000..c8b2d2e80 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/util/object/ObjectUtils.java @@ -0,0 +1,22 @@ +package cn.iocoder.dashboard.util.object; + +import cn.hutool.core.util.ObjectUtil; + +import java.util.function.Consumer; + +/** + * Object 工具类 + * + * @author 芋道源码 + */ +public class ObjectUtils { + + public static T clone(T object, Consumer consumer) { + T result = ObjectUtil.clone(object); + if (result != null) { + consumer.accept(result); + } + return result; + } + +} diff --git a/src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java b/src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java index 055643048..d56aabf69 100644 --- a/src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java +++ b/src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java @@ -1,26 +1,31 @@ package cn.iocoder.dashboard.modules.infra.service.config; +import cn.hutool.core.util.ArrayUtil; import cn.iocoder.dashboard.BaseSpringBootUnitTest; -import cn.iocoder.dashboard.common.exception.ServiceException; +import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO; +import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigExportReqVO; +import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigPageReqVO; import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigUpdateReqVO; import cn.iocoder.dashboard.modules.infra.dal.dataobject.config.InfConfigDO; import cn.iocoder.dashboard.modules.infra.dal.mysql.config.InfConfigMapper; import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum; import cn.iocoder.dashboard.modules.infra.mq.producer.config.InfConfigProducer; import cn.iocoder.dashboard.modules.infra.service.config.impl.InfConfigServiceImpl; +import cn.iocoder.dashboard.util.object.ObjectUtils; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import javax.annotation.Resource; +import java.util.List; import java.util.function.Consumer; import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_KEY_DUPLICATE; -import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_NOT_EXISTS; -import static cn.iocoder.dashboard.util.AssertUtils.assertExceptionEquals; +import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.*; import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException; import static cn.iocoder.dashboard.util.RandomUtils.randomPojo; +import static cn.iocoder.dashboard.util.date.DateUtils.buildTime; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -36,11 +41,92 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest { @MockBean private InfConfigProducer configProducer; + @Test + public void testGetConfigPage() { + // mock 数据 + InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到 + o.setName("芋艿"); + o.setKey("yunai"); + o.setType(InfConfigTypeEnum.SYSTEM.getType()); + o.setCreateTime(buildTime(2021, 2, 1)); + }); + configMapper.insert(dbConfig); + // 测试 name 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆"))); + // 测试 key 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou"))); + // 测试 type 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType()))); + // 测试 createTime 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1)))); + // 准备参数 + InfConfigPageReqVO reqVO = new InfConfigPageReqVO(); + reqVO.setName("艿"); + reqVO.setKey("nai"); + reqVO.setType(InfConfigTypeEnum.SYSTEM.getType()); + reqVO.setBeginTime(buildTime(2021, 1, 15)); + reqVO.setEndTime(buildTime(2021, 2, 15)); + + // 调用 + PageResult pageResult = configService.getConfigPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbConfig, pageResult.getList().get(0)); + } + + @Test + public void testGetConfigList() { + // mock 数据 + InfConfigDO dbConfig = randomInfConfigDO(o -> { // 等会查询到 + o.setName("芋艿"); + o.setKey("yunai"); + o.setType(InfConfigTypeEnum.SYSTEM.getType()); + o.setCreateTime(buildTime(2021, 2, 1)); + }); + configMapper.insert(dbConfig); + // 测试 name 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setName("土豆"))); + // 测试 key 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setKey("tudou"))); + // 测试 type 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setType(InfConfigTypeEnum.CUSTOM.getType()))); + // 测试 createTime 不匹配 + configMapper.insert(ObjectUtils.clone(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1)))); + // 准备参数 + InfConfigExportReqVO reqVO = new InfConfigExportReqVO(); + reqVO.setName("艿"); + reqVO.setKey("nai"); + reqVO.setType(InfConfigTypeEnum.SYSTEM.getType()); + reqVO.setBeginTime(buildTime(2021, 1, 15)); + reqVO.setEndTime(buildTime(2021, 2, 15)); + + // 调用 + List list = configService.getConfigList(reqVO); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbConfig, list.get(0)); + } + + @Test + public void testGetConfigByKey() { + // mock 数据 + InfConfigDO dbConfig = randomInfConfigDO(); + configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + String key = dbConfig.getKey(); + + // 调用 + InfConfigDO config = configService.getConfigByKey(key); + // 断言 + assertNotNull(config); + assertPojoEquals(dbConfig, config); + } + @Test public void testCreateConfig_success() { // 准备参数 InfConfigCreateReqVO reqVO = randomPojo(InfConfigCreateReqVO.class); - // mock // 调用 Long configId = configService.createConfig(reqVO); @@ -63,11 +149,8 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest { o.setKey(reqVO.getKey()); // 模拟 key 重复 })); - // 调用 - ServiceException serviceException = assertThrows(ServiceException.class, - () -> configService.createConfig(reqVO)); - // 断言异常 - assertExceptionEquals(CONFIG_KEY_DUPLICATE, serviceException); + // 调用, 并断言异常 + assertServiceException(() -> configService.createConfig(reqVO), CONFIG_KEY_DUPLICATE); } @Test @@ -94,20 +177,51 @@ public class InfConfigServiceImplTest extends BaseSpringBootUnitTest { // 准备参数 InfConfigUpdateReqVO reqVO = randomPojo(InfConfigUpdateReqVO.class); + // 调用, 并断言异常 + assertServiceException(() -> configService.updateConfig(reqVO), CONFIG_NOT_EXISTS); + } + + @Test + public void testDeleteConfig_success() { + // mock 数据 + InfConfigDO dbConfig = randomInfConfigDO(o -> { + o.setType(InfConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型 + }); + configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbConfig.getId(); + // 调用 - ServiceException serviceException = assertThrows(ServiceException.class, - () -> configService.updateConfig(reqVO)); - // 断言异常 - assertExceptionEquals(CONFIG_NOT_EXISTS, serviceException); + configService.deleteConfig(id); + // 校验数据不存在了 + assertNull(configMapper.selectById(id)); + // 校验调用 + verify(configProducer, times(1)).sendConfigRefreshMessage(); + } + + @Test + public void testDeleteConfig_canNotDeleteSystemType() { + // mock 数据 + InfConfigDO dbConfig = randomInfConfigDO(o -> { + o.setType(InfConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除 + }); + configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbConfig.getId(); + + // 调用, 并断言异常 + assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE); } // ========== 随机对象 ========== @SafeVarargs + @SuppressWarnings("unchecked") private static InfConfigDO randomInfConfigDO(Consumer... consumers) { - InfConfigDO config = randomPojo(InfConfigDO.class, consumers); - config.setType(randomEle(InfConfigTypeEnum.values()).getType()); // 保证 key 的范围 - return config; + Consumer consumer = (o) -> { + o.setType(randomEle(InfConfigTypeEnum.values()).getType()); // 保证 key 的范围 + }; + return randomPojo(InfConfigDO.class, ArrayUtil.append(new Consumer[]{consumer}, consumers)); } } diff --git a/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java b/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java index 957a5028f..042208530 100644 --- a/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java +++ b/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java @@ -4,11 +4,15 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReflectUtil; import cn.iocoder.dashboard.common.exception.ErrorCode; import cn.iocoder.dashboard.common.exception.ServiceException; +import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; import java.lang.reflect.Field; import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * 单元测试,assert 断言工具类 * @@ -47,14 +51,19 @@ public class AssertUtils { } /** - * 比对抛出的 ServiceException 是否匹配 + * 执行方法,校验抛出的 Service 是否符合条件 * + * @param executable 业务异常 * @param errorCode 错误码对象 - * @param serviceException 业务异常 + * @param messageParams 消息参数 */ - public static void assertExceptionEquals(ErrorCode errorCode, ServiceException serviceException) { + public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) { + // 调用方法 + ServiceException serviceException = assertThrows(ServiceException.class, executable); + // 校验错误码 Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配"); - Assertions.assertEquals(errorCode.getMessage(), serviceException.getMessage(), "错误提示不匹配"); + String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMessage(), messageParams); + Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配"); } }