diff --git a/pom.xml b/pom.xml
index 1249edaf5..3e5289c14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -177,6 +177,12 @@
test
+
+ com.h2database
+ h2
+ test
+
+
org.projectlombok
diff --git a/src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java b/src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
index 4bf993014..3c88ce4b2 100644
--- a/src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
+++ b/src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
@@ -27,7 +27,7 @@ public class RedisConfig {
template.setConnectionFactory(factory);
// 使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
- // 使用 JSON 序列化方式(库是 FastJSON ),序列化 VALUE 。
+ // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(RedisSerializer.json());
return template;
}
diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java b/src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java
index 3fc668e2c..53fc5f6a5 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/infra/enums/InfErrorCodeConstants.java
@@ -11,7 +11,7 @@ public interface InfErrorCodeConstants {
// ========== 参数配置 1001000000 ==========
ErrorCode CONFIG_NOT_FOUND = new ErrorCode(1001000001, "参数配置不存在");
- ErrorCode CONFIG_NAME_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
+ ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1001000002, "参数配置 key 重复");
ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1001000003, "不能删除类型为系统内置的参数配置");
ErrorCode CONFIG_GET_VALUE_ERROR_IF_SENSITIVE = new ErrorCode(1001000004, "不允许获取敏感配置到前端");
diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java
index 719f2fd26..1f23e878a 100644
--- a/src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java
+++ b/src/main/java/cn/iocoder/dashboard/modules/infra/service/config/impl/InfConfigServiceImpl.java
@@ -117,10 +117,10 @@ public class InfConfigServiceImpl implements InfConfigService {
}
// 如果 id 为空,说明不用比较是否为相同 id 的参数配置
if (id == null) {
- throw ServiceExceptionUtil.exception(CONFIG_NAME_DUPLICATE);
+ throw ServiceExceptionUtil.exception(CONFIG_KEY_DUPLICATE);
}
if (!config.getId().equals(id)) {
- throw ServiceExceptionUtil.exception(CONFIG_NAME_DUPLICATE);
+ throw ServiceExceptionUtil.exception(CONFIG_KEY_DUPLICATE);
}
}
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
new file mode 100644
index 000000000..1302e4cfc
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/modules/infra/service/config/InfConfigServiceImplTest.java
@@ -0,0 +1,81 @@
+package cn.iocoder.dashboard.modules.infra.service.config;
+
+import cn.iocoder.dashboard.common.exception.ServiceException;
+import cn.iocoder.dashboard.modules.infra.controller.config.vo.InfConfigCreateReqVO;
+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.AssertUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.dashboard.modules.infra.enums.InfErrorCodeConstants.CONFIG_KEY_DUPLICATE;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@SpringBootTest
+@ActiveProfiles("unit-test")
+@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
+public class InfConfigServiceImplTest {
+
+ @Resource
+ private InfConfigServiceImpl configService;
+
+ @Resource
+ private InfConfigMapper configMapper;
+
+ @MockBean
+ private InfConfigProducer configProducer;
+
+ @Test
+ public void testCreateConfig_success() {
+ // 入参
+ InfConfigCreateReqVO reqVO = new InfConfigCreateReqVO();
+ reqVO.setGroup("test_group");
+ reqVO.setName("test_name");
+ reqVO.setValue("test_value");
+ reqVO.setSensitive(true);
+ reqVO.setRemark("test_remark");
+ reqVO.setKey("test_key");
+ // mock
+
+ // 调用
+ Long configId = configService.createConfig(reqVO);
+ // 校验
+ assertNotNull(configId);
+ // 校验记录的属性是否正确
+ InfConfigDO config = configMapper.selectById(configId);
+ AssertUtils.assertEquals(reqVO, config);
+ assertEquals(InfConfigTypeEnum.CUSTOM.getType(), config.getType());
+ // 校验调用
+ verify(configProducer, times(1)).sendConfigRefreshMessage();
+ }
+
+ @Test
+ @Sql(statements = "INSERT INTO `inf_config`(`group`, `type`, `name`, `key`, `value`, `sensitive`) VALUES ('test_group', 1, 'test_name', 'test_key', 'test_value', 1);")
+ public void testCreateConfig_keyDuplicate() {
+ // 入参
+ InfConfigCreateReqVO reqVO = new InfConfigCreateReqVO();
+ reqVO.setGroup("test_group");
+ reqVO.setName("test_name");
+ reqVO.setValue("test_value");
+ reqVO.setSensitive(true);
+ reqVO.setRemark("test_remark");
+ reqVO.setKey("test_key");
+ // mock
+
+ // 调用
+ ServiceException serviceException = assertThrows(ServiceException.class, () -> configService.createConfig(reqVO));
+ // 断言
+ AssertUtils.assertEquals(CONFIG_KEY_DUPLICATE, serviceException);
+ }
+
+}
diff --git a/src/test/java/cn/iocoder/dashboard/modules/tool/dal/mysql/coegen/ToolInformationSchemaColumnMapperTest.java b/src/test/java/cn/iocoder/dashboard/modules/tool/dal/mysql/coegen/ToolInformationSchemaColumnMapperTest.java
index 61832913d..8c52de093 100644
--- a/src/test/java/cn/iocoder/dashboard/modules/tool/dal/mysql/coegen/ToolInformationSchemaColumnMapperTest.java
+++ b/src/test/java/cn/iocoder/dashboard/modules/tool/dal/mysql/coegen/ToolInformationSchemaColumnMapperTest.java
@@ -3,19 +3,23 @@ package cn.iocoder.dashboard.modules.tool.dal.mysql.coegen;
import cn.iocoder.dashboard.modules.tool.dal.dataobject.codegen.ToolSchemaColumnDO;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
import javax.annotation.Resource;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
+@ActiveProfiles("unit-test")
public class ToolInformationSchemaColumnMapperTest {
@Resource
private ToolSchemaColumnMapper toolInformationSchemaColumnMapper;
@Test
+ @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testSelectListByTableName() {
List columns = toolInformationSchemaColumnMapper
.selectListByTableName("inf_config");
diff --git a/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java b/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java
new file mode 100644
index 000000000..ec4b4234c
--- /dev/null
+++ b/src/test/java/cn/iocoder/dashboard/util/AssertUtils.java
@@ -0,0 +1,54 @@
+package cn.iocoder.dashboard.util;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.dashboard.common.exception.ErrorCode;
+import cn.iocoder.dashboard.common.exception.ServiceException;
+import org.junit.jupiter.api.Assertions;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+/**
+ * 单元测试,assert 断言工具类
+ *
+ * @author 芋道源码
+ */
+public class AssertUtils {
+
+ /**
+ * 比对两个对象的属性是否一致
+ *
+ * 注意,如果 expected 存在的属性,actual 不存在的时候,会进行忽略
+ *
+ * @param expected 期望对象
+ * @param actual 实际对象
+ */
+ public static void assertEquals(Object expected, Object actual) {
+ Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
+ Arrays.stream(expectedFields).forEach(expectedField -> {
+ // 忽略不存在的属性
+ Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
+ if (actualField == null) {
+ return;
+ }
+ // 比对
+ Assertions.assertEquals(
+ ReflectUtil.getFieldValue(expected, expectedField),
+ ReflectUtil.getFieldValue(actual, actualField),
+ String.format("Field(%s) 不匹配", expectedField.getName())
+ );
+ });
+ }
+
+ /**
+ * 比对抛出的 ServiceException 是否匹配
+ *
+ * @param errorCode 错误码对象
+ * @param serviceException 业务异常
+ */
+ public static void assertEquals(ErrorCode errorCode, ServiceException serviceException) {
+ Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
+ Assertions.assertEquals(errorCode.getMessage(), serviceException.getMessage(), "错误提示不匹配");
+ }
+
+}
diff --git a/src/test/resources/application-unit-test.yaml b/src/test/resources/application-unit-test.yaml
new file mode 100644
index 000000000..2990ef681
--- /dev/null
+++ b/src/test/resources/application-unit-test.yaml
@@ -0,0 +1,110 @@
+spring:
+ main:
+ lazy-initialization: true
+
+ # 去除的自动配置项
+ autoconfigure:
+ exclude:
+ - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
+ - org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration # 单元测试,禁用 SpringSecurity
+ - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 单元测试,禁用 Quartz
+ - com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration # 单元测试,禁用 Lock4j 分布式锁
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+ # 数据源配置项
+ datasource:
+ name: ruoyi-vue-pro
+ url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具
+
+ # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
+ redis:
+ host: 127.0.0.1 # 地址
+ port: 6379 # 端口
+ database: 0 # 数据库索引
+
+--- #################### 定时任务相关配置 ####################
+
+# Quartz 配置项,对应 QuartzProperties 配置类(单元测试,禁用 Quartz)
+
+--- #################### 配置中心相关配置 ####################
+
+# Apollo 配置中心
+apollo:
+ bootstrap:
+ enabled: false # 单元测试,禁用配置中心
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项(单元测试,禁用 Lock4j)
+
+# Resilience4j 配置项
+resilience4j:
+ ratelimiter:
+ instances:
+ backendA:
+ limit-for-period: 1 # 每个周期内,允许的请求数。默认为 50
+ limit-refresh-period: 60s # 每个周期的时长,单位:微秒。默认为 500
+ timeout-duration: 1s # 被限流时,阻塞等待的时长,单位:微秒。默认为 5s
+ register-health-indicator: true # 是否注册到健康监测
+
+--- #################### 监控相关配置 ####################
+
+# Actuator 监控端点的配置项
+management:
+ endpoints:
+ enabled-by-default: false
+
+# Spring Boot Admin 配置项
+spring:
+ boot:
+ admin:
+ # Spring Boot Admin Client 客户端的相关配置
+ client:
+ enabled: false
+ # Spring Boot Admin Server 服务端的相关配置
+ context-path: /admin # 配置 Spring
+
+# 日志文件配置
+logging:
+ file:
+ path: ${user.home}/logs/ # 日志文件的路径
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ info:
+ version: 1.0.0
+ base-package: cn.iocoder.dashboard
+ web:
+ api-prefix: /api
+ controller-package: ${yudao.info.base-package}
+ security:
+ token-header: Authorization
+ token-secret: abcdefghijklmnopqrstuvwxyz
+ token-timeout: 1d
+ session-timeout: 30m
+ mock-enable: true
+ mock-secret: test
+ swagger:
+ enable: false # 单元测试,禁用 Swagger
+ captcha:
+ timeout: 5m
+ width: 160
+ height: 60
+ file:
+ base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/
+ codegen:
+ base-package: ${yudao.info.base-package}.modules
+ db-schemas: ${spring.datasource.name}
+ xss:
+ enable: false
+ exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
+ - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
+ - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
deleted file mode 100644
index 68d30a039..000000000
--- a/src/test/resources/application.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-spring:
- application:
- name: dashboard
-
- profiles:
- active: local
-
- # Servlet 配置
- servlet:
- # 文件上传相关配置项
- multipart:
- max-file-size: 16MB # 单个文件大小
- max-request-size: 32MB # 设置总上传的文件大小
-
- # Jackson 配置项
- jackson:
- serialization:
- write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
- write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
- write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
- fail-on-empty-beans: false # 允许序列化无属性的 Bean
-
- main:
- lazy-initialization: true
-
- # 去除的自动配置项
- autoconfigure:
- exclude:
- - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
- - org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
- - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
-
-# MyBatis Plus 的配置项
-mybatis-plus:
- configuration:
- map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
-# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印日志
- global-config:
- db-config:
- id-type: AUTO # 自增 ID
- logic-delete-value: 1 # 逻辑已删除值(默认为 1)
- logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- mapper-locations: classpath*:mapper/*.xml
- type-aliases-package: ${yudao.info.base-package}.modules.*.dal.dataobject
diff --git a/src/test/resources/sql/clean.sql b/src/test/resources/sql/clean.sql
new file mode 100644
index 000000000..2887b4e9d
--- /dev/null
+++ b/src/test/resources/sql/clean.sql
@@ -0,0 +1,9 @@
+-- inf 开头的 DB
+DELETE FROM "inf_config";
+
+-- sys 开头的 DB
+DELETE FROM "sys_dept";
+DELETE FROM "sys_dict_data";
+DELETE FROM "sys_role";
+DELETE FROM "sys_role_menu";
+DELETE FROM "sys_menu";
diff --git a/src/test/resources/sql/create_tables.sql b/src/test/resources/sql/create_tables.sql
new file mode 100644
index 000000000..7a24ed567
--- /dev/null
+++ b/src/test/resources/sql/create_tables.sql
@@ -0,0 +1,102 @@
+-- inf 开头的 DB
+
+CREATE TABLE "inf_config" (
+ "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "group" varchar(50) NOT NULL,
+ "type" tinyint NOT NULL,
+ "name" varchar(100) NOT NULL DEFAULT '',
+ "key" varchar(100) NOT NULL DEFAULT '',
+ "value" varchar(500) NOT NULL DEFAULT '',
+ "sensitive" bit NOT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '参数配置表';
+
+-- sys 开头的 DB
+
+CREATE TABLE "sys_dept" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar(30) NOT NULL DEFAULT '',
+ "parent_id" bigint NOT NULL DEFAULT '0',
+ "sort" int NOT NULL DEFAULT '0',
+ "leader" varchar(20) DEFAULT NULL,
+ "phone" varchar(11) DEFAULT NULL,
+ "email" varchar(50) DEFAULT NULL,
+ "status" tinyint NOT NULL,
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '部门表';
+
+CREATE TABLE "sys_dict_data" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "sort" int NOT NULL DEFAULT '0',
+ "label" varchar(100) NOT NULL DEFAULT '',
+ "value" varchar(100) NOT NULL DEFAULT '',
+ "dict_type" varchar(100) NOT NULL DEFAULT '',
+ "status" tinyint NOT NULL DEFAULT '0',
+ "remark" varchar(500) DEFAULT NULL,
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '字典数据表';
+
+CREATE TABLE "sys_role" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar(30) NOT NULL,
+ "code" varchar(100) NOT NULL,
+ "sort" int NOT NULL,
+ "data_scope" tinyint NOT NULL DEFAULT '1',
+ "data_scope_dept_ids" varchar(500) NOT NULL DEFAULT '',
+ "status" tinyint NOT NULL,
+ "type" tinyint NOT NULL,
+ "remark" varchar(500) DEFAULT NULL,
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '角色信息表';
+
+CREATE TABLE "sys_role_menu" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "role_id" bigint NOT NULL,
+ "menu_id" bigint NOT NULL,
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '角色和菜单关联表';
+
+CREATE TABLE "sys_menu" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar(50) NOT NULL,
+ "permission" varchar(100) NOT NULL DEFAULT '',
+ "menu_type" tinyint NOT NULL,
+ "sort" int NOT NULL DEFAULT '0',
+ "parent_id" bigint NOT NULL DEFAULT '0',
+ "path" varchar(200) DEFAULT '',
+ "icon" varchar(100) DEFAULT '#',
+ "component" varchar(255) DEFAULT NULL,
+ "status" tinyint NOT NULL DEFAULT '0',
+ "create_by" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "update_by" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '菜单权限表';