🎉 Initial commit

This commit is contained in:
2026-07-27 14:26:14 +08:00
commit fd54f07889
6935 changed files with 617410 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.infra.framework.file.core.ftp;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.ftp.FtpMode;
import cn.iocoder.yudao.module.infra.framework.file.core.client.ftp.FtpFileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.ftp.FtpFileClientConfig;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* {@link FtpFileClient} 集成测试
*
* @author 芋道源码
*/
public class FtpFileClientTest {
// docker run -d \
// -p 2121:21 -p 30000-30009:30000-30009 \
// -e FTP_USER=foo \
// -e FTP_PASS=pass \
// -e PASV_ADDRESS=127.0.0.1 \
// -e PASV_MIN_PORT=30000 \
// -e PASV_MAX_PORT=30009 \
// -v $(pwd)/ftp-data:/home/vsftpd \
// fauria/vsftpd
@Test
@Disabled
public void test() {
// 创建客户端
FtpFileClientConfig config = new FtpFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath("/home/ftp");
config.setHost("127.0.0.1");
config.setPort(2121);
config.setUsername("foo");
config.setPassword("pass");
config.setMode(FtpMode.Passive.name());
FtpFileClient client = new FtpFileClient(0L, config);
client.init();
// 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath);
if (false) {
byte[] bytes = client.getContent(path);
System.out.println("文件内容:" + bytes);
}
if (false) {
client.delete(path);
}
}
}

View File

@@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.infra.framework.file.core.local;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.nio.charset.StandardCharsets;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
public class LocalFileClientTest {
@TempDir
public File tempDir;
@Test
public void testUpload_success() {
// 准备参数
LocalFileClient client = createClient();
byte[] content = "test".getBytes(StandardCharsets.UTF_8);
String path = "avatar/test.txt";
// 调用
String url = client.upload(content, path, "text/plain");
// 断言
assertEquals("http://127.0.0.1:48080/admin-api/infra/file/0/get/avatar/test.txt", url);
assertArrayEquals(content, FileUtil.readBytes(new File(tempDir, path)));
assertArrayEquals(content, client.getContent(path));
// 删除
client.delete(path);
assertFalse(FileUtil.exist(new File(tempDir, path)));
}
@Test
public void testUpload_encodeUrlPath() {
// 准备参数
LocalFileClient client = createClient();
byte[] content = "test".getBytes(StandardCharsets.UTF_8);
String path = "avatar/中文 100%+文件.txt";
// 调用
String url = client.upload(content, path, "text/plain");
// 断言
assertEquals("http://127.0.0.1:48080/admin-api/infra/file/0/get/avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.txt", url);
assertArrayEquals(content, FileUtil.readBytes(new File(tempDir, path)));
}
@Test
public void testUpload_pathInvalid() {
// 准备参数
LocalFileClient client = createClient();
byte[] content = "test".getBytes(StandardCharsets.UTF_8);
// 调用,并断言异常
assertThrows(ServiceException.class, () -> client.upload(content, "../test.txt", "text/plain"));
assertFalse(FileUtil.exist(new File(tempDir.getParentFile(), "test.txt")));
}
@Test
@Disabled
public void test() {
// 创建客户端
LocalFileClientConfig config = new LocalFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath("/Users/yunai/file_test");
LocalFileClient client = new LocalFileClient(0L, config);
client.init();
// 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath);
client.delete(path);
}
@Test
@Disabled
public void testGetContent_notFound() {
// 创建客户端
LocalFileClientConfig config = new LocalFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath("/Users/yunai/file_test");
LocalFileClient client = new LocalFileClient(0L, config);
client.init();
// 上传文件
byte[] content = client.getContent(randomString());
System.out.println();
}
private LocalFileClient createClient() {
LocalFileClientConfig config = new LocalFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath(tempDir.getAbsolutePath());
LocalFileClient client = new LocalFileClient(0L, config);
client.init();
return client;
}
}

View File

@@ -0,0 +1,203 @@
package cn.iocoder.yudao.module.infra.framework.file.core.s3;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig;
import jakarta.validation.Validation;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SuppressWarnings("resource")
public class S3FileClientTest {
@Test
public void testPresignGetUrl_publicAccess_encodeUrlPath() {
// 准备参数
S3FileClientConfig config = new S3FileClientConfig();
config.setDomain("https://static.iocoder.cn");
config.setEnablePublicAccess(true);
S3FileClient client = new S3FileClient(0L, config);
// 调用
String result = client.presignGetUrl("avatar/中文 100%+文件.jpg", 300);
// 断言
assertEquals("https://static.iocoder.cn/avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg", result);
}
@Test
public void testPresignGetUrl_publicAccess_decodeDomainUrl() {
// 准备参数
S3FileClientConfig config = new S3FileClientConfig();
config.setDomain("https://static.iocoder.cn");
config.setEnablePublicAccess(true);
S3FileClient client = new S3FileClient(0L, config);
// 调用
String result = client.presignGetUrl("https://static.iocoder.cn/avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg?token=1", 300);
// 断言
assertEquals("https://static.iocoder.cn/avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg", result);
}
@Test
public void testPresignGetUrl_privateAccess_rawPath() {
// 准备参数
S3FileClientConfig config = new S3FileClientConfig();
config.setAccessKey("admin");
config.setAccessSecret("password");
config.setBucket("yudao");
config.setDomain("http://127.0.0.1:9000/yudao");
config.setEndpoint("http://127.0.0.1:9000");
config.setEnablePathStyleAccess(true);
config.setEnablePublicAccess(false);
S3FileClient client = new S3FileClient(0L, config);
client.init();
// 调用
String result = client.presignGetUrl("avatar/中文 100%+文件.jpg", 300);
// 断言
assertTrue(result.contains("/yudao/avatar/%E4%B8%AD%E6%96%87%20100%25"));
assertTrue(result.contains("%E6%96%87%E4%BB%B6.jpg"));
assertFalse(result.contains("%25E4%25B8%25AD"));
}
@Test
@Disabled // MinIO如果要集成测试可以注释本行
public void testMinIO() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey("admin");
config.setAccessSecret("password");
config.setBucket("yudaoyuanma");
config.setDomain(null);
// 默认 9000 endpoint
config.setEndpoint("http://127.0.0.1:9000");
// 执行上传
testExecuteUpload(config);
}
@Test
@Disabled // 阿里云 OSS如果要集成测试可以注释本行
public void testAliyun() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY"));
config.setAccessSecret(System.getenv("ALIYUN_SECRET_KEY"));
config.setBucket("yunai-aoteman");
config.setDomain(null); // 如果有自定义域名则可以设置。http://ali-oss.iocoder.cn
// 默认北京的 endpoint
config.setEndpoint("oss-cn-beijing.aliyuncs.com");
// 执行上传
testExecuteUpload(config);
}
@Test
@Disabled // 腾讯云 COS如果要集成测试可以注释本行
public void testQCloud() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY"));
config.setAccessSecret(System.getenv("QCLOUD_SECRET_KEY"));
config.setBucket("aoteman-1255880240");
config.setDomain(null); // 如果有自定义域名则可以设置。http://tengxun-oss.iocoder.cn
// 默认上海的 endpoint
config.setEndpoint("cos.ap-shanghai.myqcloud.com");
// 执行上传
testExecuteUpload(config);
}
@Test
@Disabled // 七牛云存储,如果要集成测试,可以注释本行
public void testQiniu() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));
config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8");
config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP");
config.setBucket("ruoyi-vue-pro");
config.setDomain("http://test.yudao.iocoder.cn"); // 如果有自定义域名则可以设置。http://static.yudao.iocoder.cn
config.setEnablePathStyleAccess(false);
// 默认上海的 endpoint
config.setEndpoint("s3-cn-south-1.qiniucs.com");
// 执行上传
testExecuteUpload(config);
}
@Test
@Disabled // 七牛云存储(读私有桶),如果要集成测试,可以注释本行
public void testQiniu_privateGet() {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
// config.setAccessSecret(System.getenv("QINIU_SECRET_KEY"));
config.setAccessKey("b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8");
config.setAccessSecret("kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP");
config.setBucket("ruoyi-vue-pro-private");
config.setDomain("http://t151glocd.hn-bkt.clouddn.com"); // 如果有自定义域名则可以设置。http://static.yudao.iocoder.cn
config.setEnablePathStyleAccess(false);
// 默认上海的 endpoint
config.setEndpoint("s3-cn-south-1.qiniucs.com");
// 校验配置
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
// 创建 Client
S3FileClient client = new S3FileClient(0L, config);
client.init();
// 执行生成 URL 签名
String path = "output.png";
String presignedUrl = client.presignGetUrl(path, 300);
System.out.println(presignedUrl);
}
@Test
@Disabled // 华为云存储,如果要集成测试,可以注释本行
public void testHuaweiCloud() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("HUAWEI_CLOUD_ACCESS_KEY"));
// config.setAccessSecret(System.getenv("HUAWEI_CLOUD_SECRET_KEY"));
config.setBucket("yudao");
config.setDomain(null); // 如果有自定义域名,则可以设置。
// 默认上海的 endpoint
config.setEndpoint("obs.cn-east-3.myhuaweicloud.com");
// 执行上传
testExecuteUpload(config);
}
private void testExecuteUpload(S3FileClientConfig config) {
// 校验配置
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
// 创建 Client
S3FileClient client = new S3FileClient(0L, config);
client.init();
// 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath);
// 读取文件
if (true) {
byte[] bytes = client.getContent(path);
System.out.println("文件内容:" + bytes.length);
}
// 删除文件
if (false) {
client.delete(path);
}
}
}

View File

@@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.infra.framework.file.core.sftp;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* {@link SftpFileClient} 集成测试
*
* @author 芋道源码
*/
public class SftpFileClientTest {
// docker run -p 2222:22 -d \
// -v $(pwd)/sftp-data:/home/foo/upload \
// atmoz/sftp \
// foo:pass:1001
@Test
@Disabled
public void test() {
// 创建客户端
SftpFileClientConfig config = new SftpFileClientConfig();
config.setDomain("http://127.0.0.1:48080");
config.setBasePath("/upload"); // 注意,这个是相对路径,不是实际 linux 上的路径!!!
config.setHost("127.0.0.1");
config.setPort(2222);
config.setUsername("foo");
config.setPassword("pass");
SftpFileClient client = new SftpFileClient(0L, config);
client.init();
// 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath);
if (false) {
byte[] bytes = client.getContent(path);
System.out.println("文件内容:" + bytes);
}
if (false) {
client.delete(path);
}
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.infra.framework.file.core.utils;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link FileTypeUtils} 的单元测试
*/
public class FileTypeUtilsTest {
@Test
public void testWriteAttachment_contentDispositionEncodeFilename() throws Exception {
// 准备参数
MockHttpServletResponse response = new MockHttpServletResponse();
byte[] content = "test".getBytes(StandardCharsets.UTF_8);
// 调用
FileTypeUtils.writeAttachment(response, "中文 100%+文件.txt", content);
// 断言
assertEquals("attachment;filename=\"__ 100%+__.txt\";"
+ "filename*=UTF-8''%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.txt",
response.getHeader("Content-Disposition"));
assertArrayEquals(content, response.getContentAsByteArray());
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.infra.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.generator.query.DefaultQuery;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import java.util.List;
public class DefaultDatabaseQueryTest {
public static void main(String[] args) {
// DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:oracle:thin:@127.0.0.1:1521:xe",
// "root", "123456").build();
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro",
"root", "123456").build();
// StrategyConfig strategyConfig = new StrategyConfig.Builder().build();
ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, null, null, null, null);
DefaultQuery query = new DefaultQuery(builder);
long time = System.currentTimeMillis();
List<TableInfo> tableInfos = query.queryTables();
for (TableInfo tableInfo : tableInfos) {
if (StrUtil.startWithAny(tableInfo.getName().toLowerCase(), "act_", "flw_", "qrtz_")) {
continue;
}
System.out.println(String.format("CREATE SEQUENCE %s_seq MINVALUE 1;", tableInfo.getName()));
// System.out.println(String.format("DELETE FROM %s WHERE deleted = '1';", tableInfo.getName()));
}
System.out.println(tableInfos.size());
System.out.println(System.currentTimeMillis() - time);
}
}

View File

@@ -0,0 +1,563 @@
package cn.iocoder.yudao.module.infra.service.codegen;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
import cn.iocoder.yudao.module.infra.service.db.DataSourceConfigService;
import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
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.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* {@link CodegenServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(CodegenServiceImpl.class)
public class CodegenServiceImplTest extends BaseDbUnitTest {
@Resource
private CodegenServiceImpl codegenService;
@Resource
private CodegenTableMapper codegenTableMapper;
@Resource
private CodegenColumnMapper codegenColumnMapper;
@MockitoBean
private DatabaseTableService databaseTableService;
@MockitoBean
private DataSourceConfigService dataSourceConfigService;
@MockitoBean
private CodegenBuilder codegenBuilder;
@MockitoBean
private CodegenEngine codegenEngine;
@MockitoBean
private CodegenProperties codegenProperties;
@Test
public void testCreateCodegenList() {
// 准备参数
String author = randomString();
CodegenCreateListReqVO reqVO = randomPojo(CodegenCreateListReqVO.class,
o -> o.setDataSourceConfigId(1L).setTableNames(Collections.singletonList("t_yunai")));
// mock 方法TableInfo
TableInfo tableInfo = mock(TableInfo.class);
when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
.thenReturn(tableInfo);
when(tableInfo.getComment()).thenReturn("芋艿");
// mock 方法TableInfo fields
TableField field01 = mock(TableField.class);
when(field01.getComment()).thenReturn("主键");
TableField field02 = mock(TableField.class);
when(field02.getComment()).thenReturn("名字");
List<TableField> fields = Arrays.asList(field01, field02);
when(tableInfo.getFields()).thenReturn(fields);
// mock 方法CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class);
when(codegenBuilder.buildTable(same(tableInfo))).thenReturn(table);
// mock 方法CodegenColumnDO
List<CodegenColumnDO> columns = randomPojoList(CodegenColumnDO.class);
when(codegenBuilder.buildColumns(eq(table.getId()), same(fields)))
.thenReturn(columns);
// mock 方法CodegenProperties
when(codegenProperties.getFrontType()).thenReturn(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType());
// 调用
List<Long> result = codegenService.createCodegenList(author, reqVO);
// 断言
assertEquals(1, result.size());
// 断言CodegenTableDO
CodegenTableDO dbTable = codegenTableMapper.selectList().get(0);
assertPojoEquals(table, dbTable);
assertEquals(1L, dbTable.getDataSourceConfigId());
assertEquals(CodegenSceneEnum.ADMIN.getScene(), dbTable.getScene());
assertEquals(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), dbTable.getFrontType());
assertEquals(author, dbTable.getAuthor());
// 断言CodegenColumnDO
List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
assertEquals(columns.size(), dbColumns.size());
assertTrue(dbColumns.get(0).getPrimaryKey());
for (int i = 0; i < dbColumns.size(); i++) {
assertPojoEquals(columns.get(i), dbColumns.get(i));
}
}
@Test
public void testValidateTableInfo() {
// 情况一
assertServiceException(() -> codegenService.validateTableInfo(null),
CODEGEN_IMPORT_TABLE_NULL);
// 情况二
TableInfo tableInfo = mock(TableInfo.class);
assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
// 情况三
when(tableInfo.getComment()).thenReturn("芋艿");
assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
CODEGEN_IMPORT_COLUMNS_NULL);
// 情况四
TableField field = mock(TableField.class);
when(field.getName()).thenReturn("name");
when(tableInfo.getFields()).thenReturn(Collections.singletonList(field));
assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
}
@Test
public void testUpdateCodegen_notExists() {
// 准备参数
CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class);
// mock 方法
// 调用,并断言
assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
CODEGEN_TABLE_NOT_EXISTS);
}
@Test
public void testUpdateCodegen_sub_masterNotExists() {
// mock 数据
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table);
// 准备参数
CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
o -> o.getTable().setId(table.getId())
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()));
// 调用,并断言
assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());
}
@Test
public void testUpdateCodegen_sub_columnNotExists() {
// mock 数据
CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(subTable);
// mock 数据master
CodegenTableDO masterTable = randomPojo(CodegenTableDO.class,
o -> o.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType())
.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(masterTable);
// 准备参数
CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
o -> o.getTable().setId(subTable.getId())
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setMasterTableId(masterTable.getId()));
// 调用,并断言
assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());
}
@Test
public void testUpdateCodegen_success() {
// mock 数据
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table);
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
codegenColumnMapper.insert(column01);
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
codegenColumnMapper.insert(column02);
// 准备参数
CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
o -> o.getTable().setId(table.getId())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
.setScene(CodegenSceneEnum.ADMIN.getScene()));
CodegenColumnSaveReqVO columnVO01 = randomPojo(CodegenColumnSaveReqVO.class,
o -> o.setId(column01.getId()).setTableId(table.getId()));
CodegenColumnSaveReqVO columnVO02 = randomPojo(CodegenColumnSaveReqVO.class,
o -> o.setId(column02.getId()).setTableId(table.getId()));
updateReqVO.setColumns(Arrays.asList(columnVO01, columnVO02));
// 调用
codegenService.updateCodegen(updateReqVO);
// 断言
CodegenTableDO dbTable = codegenTableMapper.selectById(table.getId());
assertPojoEquals(updateReqVO.getTable(), dbTable);
List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
assertEquals(2, dbColumns.size());
assertPojoEquals(columnVO01, dbColumns.get(0));
assertPojoEquals(columnVO02, dbColumns.get(1));
}
@Test
public void testSyncCodegenFromDB() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class, o -> o.setTableName("t_yunai")
.setDataSourceConfigId(1L).setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table);
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setColumnName("id").setPrimaryKey(true).setOrdinalPosition(0));
codegenColumnMapper.insert(column01);
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setColumnName("name").setOrdinalPosition(1));
codegenColumnMapper.insert(column02);
// 准备参数
Long tableId = table.getId();
// mock 方法TableInfo
TableInfo tableInfo = mock(TableInfo.class);
when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
.thenReturn(tableInfo);
when(tableInfo.getComment()).thenReturn("芋艿");
// mock 方法TableInfo fields
TableField field01 = mock(TableField.class);
when(field01.getComment()).thenReturn("主键");
TableField field03 = mock(TableField.class);
when(field03.getComment()).thenReturn("分类");
List<TableField> fields = Arrays.asList(field01, field03);
when(tableInfo.getFields()).thenReturn(fields);
when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
.thenReturn(tableInfo);
// mock 方法CodegenTableDO
List<CodegenColumnDO> newColumns = randomPojoList(CodegenColumnDO.class, 2);
when(codegenBuilder.buildColumns(eq(table.getId()), argThat(tableFields -> {
assertEquals(2, tableFields.size());
assertSame(tableInfo.getFields(), tableFields);
return true;
}))).thenReturn(newColumns);
// 调用
codegenService.syncCodegenFromDB(tableId);
// 断言
List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
assertEquals(newColumns.size(), dbColumns.size());
assertPojoEquals(newColumns.get(0), dbColumns.get(0));
assertPojoEquals(newColumns.get(1), dbColumns.get(1));
}
@Test
public void testDeleteCodegen_notExists() {
assertServiceException(() -> codegenService.deleteCodegen(randomLongId()),
CODEGEN_TABLE_NOT_EXISTS);
}
@Test
public void testDeleteCodegen_success() {
// mock 数据
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table);
CodegenColumnDO column = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
codegenColumnMapper.insert(column);
// 准备参数
Long tableId = table.getId();
// 调用
codegenService.deleteCodegen(tableId);
// 断言
assertNull(codegenTableMapper.selectById(tableId));
assertEquals(0, codegenColumnMapper.selectList().size());
}
@Test
public void testGetCodegenTableList() {
// mock 数据
CodegenTableDO table01 = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table01);
CodegenTableDO table02 = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(table02);
// 准备参数
Long dataSourceConfigId = table01.getDataSourceConfigId();
// 调用
List<CodegenTableDO> result = codegenService.getCodegenTableList(dataSourceConfigId);
// 断言
assertEquals(1, result.size());
assertPojoEquals(table01, result.get(0));
}
@Test
public void testGetCodegenTablePage() {
// mock 数据
CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> {
o.setTableName("t_yunai");
o.setTableComment("芋艿");
o.setClassName("SystemYunai");
o.setCreateTime(buildTime(2021, 3, 10));
}).setScene(CodegenSceneEnum.ADMIN.getScene());
codegenTableMapper.insert(tableDO);
// 测试 tableName 不匹配
codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableName(randomString())));
// 测试 tableComment 不匹配
codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableComment(randomString())));
// 测试 className 不匹配
codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setClassName(randomString())));
// 测试 createTime 不匹配
codegenTableMapper.insert(cloneIgnoreId(tableDO, logDO -> logDO.setCreateTime(buildTime(2021, 4, 10))));
// 准备参数
CodegenTablePageReqVO reqVO = new CodegenTablePageReqVO();
reqVO.setTableName("yunai");
reqVO.setTableComment("");
reqVO.setClassName("Yunai");
reqVO.setCreateTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
// 调用
PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(tableDO, pageResult.getList().get(0));
}
@Test
public void testGetCodegenTable() {
// mock 数据
CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
codegenTableMapper.insert(tableDO);
// 准备参数
Long id = tableDO.getId();
// 调用
CodegenTableDO result = codegenService.getCodegenTable(id);
// 断言
assertPojoEquals(tableDO, result);
}
@Test
public void testGetCodegenColumnListByTableId() {
// mock 数据
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class);
codegenColumnMapper.insert(column01);
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class);
codegenColumnMapper.insert(column02);
// 准备参数
Long tableId = column01.getTableId();
// 调用
List<CodegenColumnDO> result = codegenService.getCodegenColumnListByTableId(tableId);
// 断言
assertEquals(1, result.size());
assertPojoEquals(column01, result.get(0));
}
@Test
public void testGenerationCodes_tableNotExists() {
assertServiceException(() -> codegenService.generationCodes(randomLongId()),
CODEGEN_TABLE_NOT_EXISTS);
}
@Test
public void testGenerationCodes_columnNotExists() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
codegenTableMapper.insert(table);
// 准备参数
Long tableId = table.getId();
// 调用,并断言
assertServiceException(() -> codegenService.generationCodes(tableId),
CODEGEN_COLUMN_NOT_EXISTS);
}
@Test
public void testGenerationCodes_sub_tableNotExists() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
codegenTableMapper.insert(table);
// mock 数据CodegenColumnDO
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
codegenColumnMapper.insert(column01);
// 准备参数
Long tableId = table.getId();
// 调用,并断言
assertServiceException(() -> codegenService.generationCodes(tableId),
CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
}
@Test
public void testGenerationCodes_sub_columnNotExists() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
codegenTableMapper.insert(table);
// mock 数据CodegenColumnDO
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
codegenColumnMapper.insert(column01);
// mock 数据sub CodegenTableDO
CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setMasterTableId(table.getId()));
codegenTableMapper.insert(subTable);
// 准备参数
Long tableId = table.getId();
// 调用,并断言
assertServiceException(() -> codegenService.generationCodes(tableId),
CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
}
@Test
public void testGenerationCodes_one_success() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType()));
codegenTableMapper.insert(table);
// mock 数据CodegenColumnDO
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setOrdinalPosition(1));
codegenColumnMapper.insert(column01);
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setOrdinalPosition(2));
codegenColumnMapper.insert(column02);
// mock 数据DataSourceConfigDO
when(dataSourceConfigService.getDataSourceConfig(eq(table.getDataSourceConfigId())))
.thenReturn(randomPojo(DataSourceConfigDO.class, o -> o.setUrl("jdbc:mysql://")));
// mock 执行生成
Map<String, String> codes = MapUtil.of(randomString(), randomString());
when(codegenEngine.execute(eq(DbType.MYSQL), eq(table), argThat(columns -> {
assertEquals(2, columns.size());
assertEquals(column01, columns.get(0));
assertEquals(column02, columns.get(1));
return true;
}), isNull(), isNull())).thenReturn(codes);
// 准备参数
Long tableId = table.getId();
// 调用
Map<String, String> result = codegenService.generationCodes(tableId);
// 断言
assertSame(codes, result);
}
@Test
public void testGenerationCodes_master_success() {
// mock 数据CodegenTableDO
CodegenTableDO table = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
codegenTableMapper.insert(table);
// mock 数据CodegenColumnDO
CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setOrdinalPosition(1));
codegenColumnMapper.insert(column01);
CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
.setOrdinalPosition(2));
codegenColumnMapper.insert(column02);
// mock 数据sub CodegenTableDO
CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setMasterTableId(table.getId())
.setSubJoinColumnId(1024L));
codegenTableMapper.insert(subTable);
// mock 数据sub CodegenColumnDO
CodegenColumnDO subColumn01 = randomPojo(CodegenColumnDO.class, o -> o.setId(1024L).setTableId(subTable.getId()));
codegenColumnMapper.insert(subColumn01);
// mock 数据DataSourceConfigDO
when(dataSourceConfigService.getDataSourceConfig(eq(table.getDataSourceConfigId())))
.thenReturn(randomPojo(DataSourceConfigDO.class, o -> o.setUrl("jdbc:mysql://")));
// mock 执行生成
Map<String, String> codes = MapUtil.of(randomString(), randomString());
when(codegenEngine.execute(eq(DbType.MYSQL), eq(table), argThat(columns -> {
assertEquals(2, columns.size());
assertEquals(column01, columns.get(0));
assertEquals(column02, columns.get(1));
return true;
}), argThat(tables -> {
assertEquals(1, tables.size());
assertPojoEquals(subTable, tables.get(0));
return true;
}), argThat(columns -> {
assertEquals(1, columns.size());
assertPojoEquals(subColumn01, columns.size());
return true;
}))).thenReturn(codes);
// 准备参数
Long tableId = table.getId();
// 调用
Map<String, String> result = codegenService.generationCodes(tableId);
// 断言
assertSame(codes, result);
}
@Test
public void testGetDatabaseTableList() {
// 准备参数
Long dataSourceConfigId = randomLongId();
String name = randomString();
String comment = randomString();
// mock 方法
TableInfo tableInfo01 = mock(TableInfo.class);
when(tableInfo01.getName()).thenReturn("t_yunai");
when(tableInfo01.getComment()).thenReturn("芋艿");
TableInfo tableInfo02 = mock(TableInfo.class);
when(tableInfo02.getName()).thenReturn("t_yunai_02");
when(tableInfo02.getComment()).thenReturn("芋艿_02");
when(databaseTableService.getTableList(eq(dataSourceConfigId), eq(name), eq(comment)))
.thenReturn(ListUtil.toList(tableInfo01, tableInfo02));
// mock 数据
CodegenTableDO tableDO = randomPojo(CodegenTableDO.class,
o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
.setTableName("t_yunai_02")
.setDataSourceConfigId(dataSourceConfigId));
codegenTableMapper.insert(tableDO);
// 调用
List<DatabaseTableRespVO> result = codegenService.getDatabaseTableList(dataSourceConfigId, name, comment);
// 断言
assertEquals(1, result.size());
assertEquals("t_yunai", result.get(0).getName());
assertEquals("芋艿", result.get(0).getComment());
}
}

View File

@@ -0,0 +1,102 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import org.apache.ibatis.type.JdbcType;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CodegenBuilderTest extends BaseMockitoUnitTest {
@InjectMocks
private CodegenBuilder codegenBuilder;
@Test
public void testBuildTable() {
// 准备参数
TableInfo tableInfo = mock(TableInfo.class);
// mock 方法
when(tableInfo.getName()).thenReturn("system_user");
when(tableInfo.getComment()).thenReturn("用户");
// 调用
CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
// 断言
assertEquals("system_user", table.getTableName());
assertEquals("用户", table.getTableComment());
assertEquals("system", table.getModuleName());
assertEquals("user", table.getBusinessName());
assertEquals("User", table.getClassName());
assertEquals("用户", table.getClassComment());
}
@Test
public void testBuildColumns() {
// 准备参数
Long tableId = randomLongId();
TableField tableField = mock(TableField.class);
List<TableField> tableFields = Collections.singletonList(tableField);
// mock 方法
TableField.MetaInfo metaInfo = mock(TableField.MetaInfo.class);
when(tableField.getMetaInfo()).thenReturn(metaInfo);
when(metaInfo.getJdbcType()).thenReturn(JdbcType.BIGINT);
when(tableField.getComment()).thenReturn("编号");
when(tableField.isKeyFlag()).thenReturn(true);
IColumnType columnType = mock(IColumnType.class);
when(tableField.getColumnType()).thenReturn(columnType);
when(columnType.getType()).thenReturn("Long");
when(tableField.getName()).thenReturn("id2");
when(tableField.getPropertyName()).thenReturn("id");
// 调用
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
// 断言
assertEquals(1, columns.size());
CodegenColumnDO column = columns.get(0);
assertEquals(tableId, column.getTableId());
assertEquals("id2", column.getColumnName());
assertEquals("BIGINT", column.getDataType());
assertEquals("编号", column.getColumnComment());
assertFalse(column.getNullable());
assertTrue(column.getPrimaryKey());
assertEquals(1, column.getOrdinalPosition());
assertEquals("Long", column.getJavaType());
assertEquals("id", column.getJavaField());
assertNull(column.getDictType());
assertNotNull(column.getExample());
assertFalse(column.getCreateOperation());
assertTrue(column.getUpdateOperation());
assertFalse(column.getListOperation());
assertEquals("=", column.getListOperationCondition());
assertTrue(column.getListOperationResult());
assertEquals("input", column.getHtmlType());
}
@Test
public void testSanitizeComment() {
// 1. null / 空字符串:原样返回
assertNull(codegenBuilder.sanitizeComment(null));
assertEquals("", codegenBuilder.sanitizeComment(""));
// 2. 无英文引号:原样返回
assertEquals("无引号注释", codegenBuilder.sanitizeComment("无引号注释"));
// 3. 含英文双引号:替换为中文左双引号
assertEquals("含“双“引号", codegenBuilder.sanitizeComment("\"\"引号"));
// 4. 含英文单引号:替换为中文左单引号
assertEquals("含‘单‘引号", codegenBuilder.sanitizeComment("含'单'引号"));
// 5. 双 / 单引号混合
assertEquals("“混‘搭“‘", codegenBuilder.sanitizeComment("\"混'搭\"'"));
}
}

View File

@@ -0,0 +1,182 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenVOTypeEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link CodegenEngine} 的单元测试抽象基类
*
* @author 芋道源码
*/
public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
/**
* 测试文件资源目录
*/
private String resourcesPath = "";
@InjectMocks
protected CodegenEngine codegenEngine;
@Spy
protected CodegenProperties codegenProperties = new CodegenProperties()
.setBasePackage("cn.iocoder.yudao")
.setVoType(CodegenVOTypeEnum.VO.getType())
.setDeleteBatchEnable(true)
.setUnitTestEnable(true)
.setImportEnable(false);
@BeforeEach
public void setUp() {
codegenEngine.setJakartaEnable(true); // 强制使用 jakarta保证单测可以基于 jakarta 断言
codegenEngine.initGlobalBindingMap();
// 获取测试文件 resources 路径writeResult 调试用
String absolutePath = FileUtil.getAbsolutePath("application-unit-test.yaml");
resourcesPath = absolutePath.split("/target")[0] + "/src/test/resources/codegen/";
}
protected static CodegenTableDO getTable(String name) {
String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
return JsonUtils.parseObject(content, "table", CodegenTableDO.class);
}
protected static List<CodegenColumnDO> getColumnList(String name) {
String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
List<CodegenColumnDO> list = JsonUtils.parseArray(content, "columns", CodegenColumnDO.class);
list.forEach(column -> {
if (column.getNullable() == null) {
column.setNullable(false);
}
if (column.getCreateOperation() == null) {
column.setCreateOperation(false);
}
if (column.getUpdateOperation() == null) {
column.setUpdateOperation(false);
}
if (column.getListOperation() == null) {
column.setListOperation(false);
}
if (column.getListOperationResult() == null) {
column.setListOperationResult(false);
}
});
return list;
}
/**
* 重新生成断言数据的开关,命令行加 {@code -Dcodegen.regenerate=true} 启用
*/
private static final boolean REGENERATE = Boolean.parseBoolean(System.getProperty("codegen.regenerate", "false"));
@SuppressWarnings("rawtypes")
protected void assertResult(Map<String, String> result, String path) {
if (REGENERATE) {
writeResult(result, resourcesPath + path);
return;
}
String assertContent = ResourceUtil.readUtf8Str("codegen/" + path + "/assert.json");
List<HashMap> asserts = JsonUtils.parseArray(assertContent, HashMap.class);
Set<String> expectedFiles = asserts.stream()
.map(m -> (String) m.get("filePath"))
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
assertEquals(expectedFiles, result.keySet(), "生成文件集合不匹配");
// 校验每个文件;归一化 \r\n 为 \n让断言不依赖文件落盘的换行风格
asserts.forEach(assertMap -> {
String contentPath = (String) assertMap.get("contentPath");
String filePath = (String) assertMap.get("filePath");
String expected = normalizeLineEndings(ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath));
String actual = result.get(filePath);
assertEquals(expected, normalizeLineEndings(actual), filePath + ":不匹配");
});
}
/**
* 统一换行符并忽略文件末尾换行,避免 Windows/Unix 落盘差异导致快照断言失败
*/
private static String normalizeLineEndings(String content) {
if (content == null) {
return null;
}
content = content.replace("\r\n", "\n").replace('\r', '\n');
return content.replaceAll("\\n+\\z", "");
}
// ==================== 调试专用 ====================
/**
* 【调试使用】将生成的代码,写入到文件
*
* @param result 生成的代码
* @param path 写入文件的路径
*/
protected void writeFile(Map<String, String> result, String path) {
// 生成压缩包
String[] paths = result.keySet().toArray(new String[0]);
ByteArrayInputStream[] ins = result.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipUtil.zip(outputStream, paths, ins);
// 写入文件
FileUtil.writeBytes(outputStream.toByteArray(), path);
}
/**
* 【调试使用】将生成的结果,写入到文件
*
* @param result 生成的代码
* @param basePath 写入文件的路径(绝对路径)
*/
protected void writeResult(Map<String, String> result, String basePath) {
// 写入文件内容
List<Map<String, String>> asserts = new ArrayList<>();
Set<String> usedContentPaths = new LinkedHashSet<>();
result.forEach((filePath, fileContent) -> {
String lastFilePath = StrUtil.subAfter(filePath, '/', true);
String ext = StrUtil.subAfter(lastFilePath, '.', true);
String name = StrUtil.subBefore(lastFilePath, '.', true);
String parentPath = StrUtil.subBefore(filePath, '/' + lastFilePath, true);
String parentDir = StrUtil.subAfter(parentPath, '/', true);
// index.vue 按页面语义稳定归档,避免结果迭代顺序导致列表页和表单页的快照路径互换
String contentPath = "index".equals(name) && ("form".equals(parentDir) || "detail".equals(parentDir))
? ext + '/' + parentDir + '/' + name : ext + '/' + name;
// ERP 多个子表的 form/index.vue 仍可能撞名,继续补充祖父目录区分
if (usedContentPaths.contains(contentPath)) {
String grandParentDir = StrUtil.subAfter(StrUtil.subBefore(parentPath, '/' + parentDir, true), '/', true);
contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name;
for (int i = 2; usedContentPaths.contains(contentPath); i++) {
contentPath = ext + '/' + grandParentDir + '/' + parentDir + '/' + name + '-' + i;
}
}
usedContentPaths.add(contentPath);
asserts.add(MapUtil.<String, String>builder().put("filePath", filePath)
.put("contentPath", contentPath).build());
FileUtil.writeUtf8String(fileContent, basePath + "/" + contentPath);
});
// 写入 assert.json 文件
FileUtil.writeUtf8String(JsonUtils.toJsonPrettyString(asserts), basePath + "/assert.json");
}
}

View File

@@ -0,0 +1,378 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* {@link CodegenEngine} 的 Vue3 Admin Uniapp + Wot UI 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
@Test
public void testExecute_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vue3_admin_uniapp_one");
}
@Test
public void testExecute_dictSearch() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用并断言数字、布尔字典
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
String searchForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue");
assertFalse(searchForm.contains("<wd-radio"));
assertTrue(searchForm.contains("all-option"));
assertTrue(searchForm.contains(":columns=\"getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\""));
assertTrue(searchForm.contains(":columns=\"getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)\""));
// 调用并断言字符串字典
columns.stream().filter(column -> "sex".equals(column.getJavaField())).findFirst().orElseThrow()
.setJavaType("String");
result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
searchForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue");
assertTrue(searchForm.contains(":columns=\"getStrDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\""));
assertTrue(searchForm.contains("sex: undefined as string | undefined,"));
}
@Test
public void testExecute_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vue3_admin_uniapp_tree");
}
@Test
public void testExecute_semantics() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
.setJavaType("Float").setHtmlType("input");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
assertTrue(api.contains("birthday: number"));
assertTrue(api.contains("createTime?: number"));
assertTrue(api.contains("memo: number"));
assertFalse(api.contains("createTime?: Date"));
String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
assertTrue(form.contains("v-model=\"formData.memo\""));
assertTrue(form.contains(":precision=\"2\""));
String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue");
assertTrue(detail.contains("type=\"danger\""));
assertFalse(detail.contains("type=\"error\""));
}
@Test
public void testExecute_stringPrimaryKey() {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
List<CodegenColumnDO> columns = getColumnList("student");
columns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String");
// 准备子表
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
contactColumns.stream().filter(CodegenColumnDO::getPrimaryKey).findFirst().orElseThrow().setJavaType("String");
contactColumns.stream().filter(column -> Objects.equals(column.getId(), 100L))
.findFirst().orElseThrow().setJavaType("String");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
List.of(contactTable), List.of(contactColumns));
// 断言
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
assertTrue(api.contains("return http.post<string>('/infra/student/create', data)"));
assertTrue(api.contains("studentId: string"));
assertTrue(api.contains("getStudentContact(id: string)"));
assertTrue(api.contains("deleteStudentContact(id: string)"));
String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue");
assertFalse(detail.contains("Number(props.id)"));
String subForm = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue");
assertTrue(subForm.contains("id?: string"));
assertTrue(subForm.contains("studentId: string"));
}
@Test
public void testExecute_treeSearch() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
String index = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue");
assertTrue(index.contains("currentParentId.value === 0\n ? list.value"));
assertFalse(index.contains("list.value.filter"));
String breadcrumb = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/components/breadcrumb.vue");
assertTrue(breadcrumb.contains("modelValue: number"));
assertFalse(breadcrumb.contains("TreeValue"));
}
@Test
public void testExecute_duplicateErpSymbol() {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备两个归一化名称相同的子表
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
// 调用
ServiceException exception = assertThrows(ServiceException.class,
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
// 断言
assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
}
@Test
public void testExecute_duplicateMasterSymbol() {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备两个规范化名称相同的子表
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
// 调用并断言
ServiceException exception = assertThrows(ServiceException.class,
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
}
@Test
public void testExecute_duplicateMasterFieldNormal() {
testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_NORMAL);
}
@Test
public void testExecute_duplicateMasterFieldInner() {
testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_INNER);
}
@Test
public void testExecute_duplicateMasterFieldWithMainNormal() {
testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_NORMAL);
}
@Test
public void testExecute_duplicateMasterFieldWithMainInner() {
testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_INNER);
}
@Test
public void testExecute_duplicateMasterFieldErp() {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备属性名同为 items 的子表Item 一对多、Items 一对一
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(crmItemTable, erpItemsTable),
Arrays.asList(getColumnList("contact"), getColumnList("contact")));
// 断言
assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/item/form/index.vue"));
assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/items/form/index.vue"));
}
@Test
public void testRegisterGeneratedSource_duplicatePath() {
// 准备参数
Map<String, String> generatedSources = new HashMap<>();
Map<String, Object> bindingMap = new HashMap<>();
bindingMap.put("subIndex", 0);
CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap);
bindingMap.put("subIndex", 1);
// 调用
IllegalStateException exception = assertThrows(IllegalStateException.class,
() -> CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap));
// 断言
assertTrue(exception.getMessage().contains("生成文件路径重复same.vue"));
assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=0]"));
assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=1]"));
}
@Test
public void testExecute_masterNormal() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vue3_admin_uniapp_master_normal");
}
@Test
public void testExecute_masterInner() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vue3_admin_uniapp_master_inner");
}
@Test
public void testExecute_masterErp() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue3_admin_uniapp_master_erp");
}
private void testExecute_duplicateMasterField(CodegenTemplateTypeEnum templateType) {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备属性名同为 items 的子表Item 一对多、Items 一对一
CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
List<CodegenColumnDO> erpItemsColumns = getColumnList("contact");
// 调用并断言
ServiceException exception = assertThrows(ServiceException.class,
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(crmItemTable, erpItemsTable), Arrays.asList(crmItemColumns, erpItemsColumns)));
assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
}
private void testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum templateType) {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
.setJavaField("items");
// 准备一对多子表 Item生成属性名 items
CodegenTableDO itemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
// 调用并断言
ServiceException exception = assertThrows(ServiceException.class,
() -> codegenEngine.execute(DbType.MYSQL, table, columns,
List.of(itemTable), List.of(getColumnList("contact"))));
assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
}
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
// 准备主表
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备一对多子表
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备一对一子表
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
if (templateType != CodegenTemplateTypeEnum.MASTER_ERP) {
String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
assertTrue(api.contains("studentTeacher?: StudentTeacher | null"));
String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
assertTrue(form.contains("ref<Partial<StudentTeacher> | null>(null)"));
assertTrue(form.contains("studentTeacher.value = studentTeacherData || null"));
assertTrue(form.contains(":model-value=\"item.birthday || Date.now()\""));
assertTrue(form.contains("v-model=\"item.sex\""));
assertTrue(form.contains("<yd-upload-img v-model=\"item.avatar\""));
assertTrue(form.contains("<yd-upload-file v-model=\"item.video\""));
assertTrue(form.contains("<yd-upload-file v-model=\"item.video\" :limit=\"1\""));
assertTrue(form.contains("v-model=\"item.description\""));
assertTrue(form.contains("v-model=\"item.memo\""));
assertTrue(form.contains("birthday: undefined"));
assertTrue(form.contains("function validateSubTables()"));
} else {
String contactForm = result.get(
"yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue");
assertTrue(contactForm.contains(":model-value=\"formData.birthday || Date.now()\""));
assertTrue(contactForm.contains("v-model=\"formData.sex\""));
assertTrue(contactForm.contains("<yd-upload-img v-model=\"formData.avatar\""));
assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\""));
assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\" :limit=\"1\""));
assertTrue(contactForm.contains("v-model=\"formData.description\""));
assertTrue(contactForm.contains("v-model=\"formData.memo\""));
assertTrue(contactForm.contains("birthday: undefined"));
assertTrue(contactForm.contains(":columns=\"getBoolDictOptions"));
}
}
}

View File

@@ -0,0 +1,109 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue3 + Vben5 + AntD + General 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVben5AntdGeneralTest extends CodegenEngineAbstractTest {
private static final Integer FRONT_TYPE = CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType();
@Test
public void testExecute_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_general_one");
}
@Test
public void testExecute_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_general_one_importEnable");
}
@Test
public void testExecute_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_general_tree");
}
@Test
public void testExecute_master_normal() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vben5_antd_general_master_normal");
}
@Test
public void testExecute_master_erp() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vben5_antd_general_master_erp");
}
@Test
public void testExecute_master_inner() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vben5_antd_general_master_inner");
}
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
}
}

View File

@@ -0,0 +1,109 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue3 + Vben5 + AntD + Schema 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVben5AntdSchemaTest extends CodegenEngineAbstractTest {
private static final Integer FRONT_TYPE = CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType();
@Test
public void testExecute_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_schema_one");
}
@Test
public void testExecute_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_schema_one_importEnable");
}
@Test
public void testExecute_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_antd_schema_tree");
}
@Test
public void testExecute_master_normal() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vben5_antd_schema_master_normal");
}
@Test
public void testExecute_master_erp() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vben5_antd_schema_master_erp");
}
@Test
public void testExecute_master_inner() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vben5_antd_schema_master_inner");
}
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
}
}

View File

@@ -0,0 +1,422 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* {@link CodegenEngine} 的 Vue3 + Vben5 + Antdv Next 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVben5AntdvNextTest extends CodegenEngineAbstractTest {
private static final Pattern ANTDV_NEXT_VALUE_IMPORT_PATTERN = Pattern.compile(
"import\\s*\\{([^}]*)}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
private static final Pattern RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile("<RangePicker\\b");
private static final Pattern RANGE_PICKER_HELPER_BINDING_PATTERN = Pattern.compile(
"\\bv-bind\\s*=\\s*([\"'])\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)\\s*\\1");
private static final Pattern RANGE_PICKER_HELPER_CALL_PATTERN = Pattern.compile(
"\\bgetRangePickerDefaultProps\\s*\\(\\s*\\)");
private static final Pattern RANGE_PICKER_HELPER_IMPORT_PATTERN = Pattern.compile(
"import\\s*\\{[^}]*\\bgetRangePickerDefaultProps\\b[^}]*}"
+ "\\s*from\\s*'#/utils(?:/rangePickerProps)?';?", Pattern.DOTALL);
private static final Pattern SCHEMA_RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile(
"\\bcomponent\\s*:\\s*([\"'])RangePicker\\1");
private static final Pattern SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN = Pattern.compile(
"\\.\\.\\.\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)");
private static final String IMPORT_FORM_PATH =
"yudao-ui-admin-vben/src/views/infra/demo/modules/import-form.vue";
private static final String SCHEMA_DATA_PATH =
"yudao-ui-admin-vben/src/views/infra/demo/data.ts";
private static final String STUDENT_CONTACT_FORM_PATH =
"yudao-ui-admin-vben/src/views/infra/demo/modules/student-contact-form.vue";
private static final String STUDENT_TEACHER_FORM_PATH =
"yudao-ui-admin-vben/src/views/infra/demo/modules/student-teacher-form.vue";
private static final List<String> INNER_TEMPLATE_PATHS = Arrays.asList(
"codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm",
"codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm",
"codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm",
"codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm");
@Test
public void testExecute_schemaMasterNormal() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
CodegenTemplateTypeEnum.MASTER_NORMAL);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertSchemaRangePickerGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_schemaMasterErp() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
CodegenTemplateTypeEnum.MASTER_ERP);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertSchemaRangePickerGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_schemaMasterInner() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
CodegenTemplateTypeEnum.MASTER_INNER);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertSchemaRangePickerGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_generalTree() {
// 调用
Map<String, String> result = executeTree(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType());
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertRuleGenerated(result);
assertComponentGenerated(result, "FormItem");
assertComponentGenerated(result, "TreeSelect");
}
@Test
public void testExecute_generalMasterNormal() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
CodegenTemplateTypeEnum.MASTER_NORMAL);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertRuleGenerated(result);
assertGeneralCrudComponentsGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_generalMasterNormal_oneToOneJoinColumnOnlySelect() {
// 调用
Map<String, String> result = executeGeneralMasterNormalWithOneToOneJoinSelect();
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
String content = result.get(STUDENT_TEACHER_FORM_PATH);
assertNotNull(content, STUDENT_TEACHER_FORM_PATH + ":未生成一对一子表表单");
assertTrue(content.contains("<Select v-model:value=\"formData.studentId\""),
STUDENT_TEACHER_FORM_PATH + ":应渲染 join 列的 Select 组件");
assertTrue(content.contains("<SelectOption"), STUDENT_TEACHER_FORM_PATH + ":应渲染 SelectOption 组件");
assertRootValueImport(STUDENT_TEACHER_FORM_PATH, content, "\\bSelectOption\\b");
}
@Test
public void testExecute_generalMasterNormal_oneToManyJoinColumnOnlySelect() {
// 调用
Map<String, String> result = executeGeneralMasterNormalWithOneToManyJoinSelect();
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
String content = result.get(STUDENT_CONTACT_FORM_PATH);
assertNotNull(content, STUDENT_CONTACT_FORM_PATH + ":未生成一对多子表表单");
assertTrue(content.contains("<VxeTable"), STUDENT_CONTACT_FORM_PATH + ":应生成一对多子表表格");
assertFalse(content.contains("<VxeColumn field=\"studentId\""),
STUDENT_CONTACT_FORM_PATH + ":不应生成 join 列");
assertFalse(content.contains("<Select v-model:value=\"row.studentId\""),
STUDENT_CONTACT_FORM_PATH + ":不应渲染 join 列的 Select 组件");
assertFalse(content.contains("<SelectOption"), STUDENT_CONTACT_FORM_PATH + ":不应渲染 SelectOption 组件");
assertFalse(createRootImportPattern("import\\s*\\{", "\\bSelectOption\\b").matcher(content).find(),
STUDENT_CONTACT_FORM_PATH + ":不应导入 SelectOption 组件");
}
@Test
public void testExecute_generalMasterInner() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
CodegenTemplateTypeEnum.MASTER_INNER);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertRuleGenerated(result);
assertGeneralCrudComponentsGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_generalMasterErp() {
// 调用
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
CodegenTemplateTypeEnum.MASTER_ERP);
// 断言
assertGeneratedCodeCompatibleWithAntdvNext(result);
assertRuleGenerated(result);
assertGeneralCrudComponentsGenerated(result);
assertTabsGenerated(result);
}
@Test
public void testExecute_oneImportEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 调用并断言
assertImportFormCompatibleWithAntdvNext(
executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType()));
assertImportFormCompatibleWithAntdvNext(
executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()));
}
@Test
public void testInnerTemplatesParseAntdvNext() {
INNER_TEMPLATE_PATHS.forEach(path -> {
String template = ResourceUtil.readUtf8Str(path);
assertFalse(template.contains("codegen/vue3_vben5_antd/"), path + ":不应引用 Ant Design Vue 模板");
assertTrue(template.contains("codegen/vue3_vben5_antdv_next/"), path + ":应引用 Antdv Next 模板");
});
}
private Map<String, String> executeOne(Integer frontType) {
CodegenTableDO table = getTable("student")
.setFrontType(frontType)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
}
private Map<String, String> executeTree(Integer frontType) {
CodegenTableDO table = getTable("category")
.setFrontType(frontType)
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("category"), null, null);
}
private Map<String, String> executeGeneralMasterNormalWithOneToOneJoinSelect() {
return executeGeneralMasterNormalWithJoinSelect("teacher", 200L, false);
}
private Map<String, String> executeGeneralMasterNormalWithOneToManyJoinSelect() {
return executeGeneralMasterNormalWithJoinSelect("contact", 100L, true);
}
private Map<String, String> executeGeneralMasterNormalWithJoinSelect(String subTableName,
Long subJoinColumnId,
boolean subJoinMany) {
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
CodegenTableDO subTable = getTable(subTableName)
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
.setSubJoinColumnId(subJoinColumnId).setSubJoinMany(subJoinMany);
List<CodegenColumnDO> subColumns = getColumnList(subTableName);
// 稀疏场景join 列是唯一的 select避免其它 select 掩盖 join 列对导入判定的影响。
subColumns.forEach(column -> {
if (subJoinColumnId.equals(column.getId())) {
column.setHtmlType("select");
column.setDictType("");
} else if ("select".equals(column.getHtmlType())) {
column.setHtmlType("input");
}
});
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"),
List.of(subTable), List.of(subColumns));
}
private Map<String, String> executeMaster(Integer frontType, CodegenTemplateTypeEnum templateType) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(frontType)
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(frontType)
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(frontType)
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
return codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
}
private static void assertGeneratedCodeCompatibleWithAntdvNext(Map<String, String> result) {
assertTrue(result.values().stream().anyMatch(content -> content.contains("from 'antdv-next")),
"生成结果应使用 antdv-next");
result.forEach((filePath, content) -> {
assertFalse(content.contains("ant-design-vue"), filePath + ":不应使用 ant-design-vue");
assertFalse(content.contains("antdv-next/es/"), filePath + ":不应使用未导出的 antdv-next 深层路径");
assertFalse(content.contains("<Form.Item"), filePath + ":不应使用 Form.Item");
assertFalse(content.contains("</Form.Item>"), filePath + ":不应使用 Form.Item");
assertRootValueImportsSorted(filePath, content);
assertRootValueImportMatchesUsage(filePath, content,
"\\bDateRangePicker\\s+as\\s+RangePicker\\b", "<RangePicker\\b", "RangePicker");
assertRootValueImportMatchesUsage(filePath, content,
"\\bFormItem\\b", "<FormItem\\b", "FormItem");
assertRootValueImportMatchesUsage(filePath, content,
"\\bSelectOption\\b", "<SelectOption\\b", "SelectOption");
assertRootValueImportMatchesUsage(filePath, content,
"\\bTabPane\\b", "<TabPane\\b", "TabPane");
assertRootValueImportMatchesUsage(filePath, content,
"\\bTabs\\b", "<Tabs\\b", "Tabs");
assertRootTypeImportMatchesUsage(filePath, content,
"\\bRule\\b", "\\bRule\\b", "Rule");
assertRangePickerHelperImportMatchesUsage(filePath, content);
if (filePath.endsWith(".vue")) {
assertRangePickerHelperBindingMatchesComponent(filePath, content);
} else if (filePath.endsWith(".ts")) {
assertSchemaRangePickerHelperMatchesComponent(filePath, content);
}
});
}
private static void assertImportFormCompatibleWithAntdvNext(Map<String, String> result) {
assertGeneratedCodeCompatibleWithAntdvNext(result);
String content = result.get(IMPORT_FORM_PATH);
assertNotNull(content, IMPORT_FORM_PATH + ":未生成导入表单");
assertTrue(content.contains("<Upload"), IMPORT_FORM_PATH + ":应生成 Upload 组件");
assertRootValueImport(IMPORT_FORM_PATH, content, "\\bUpload\\b");
assertTrue(content.contains("beforeUpload(file: File)"), IMPORT_FORM_PATH + ":上传参数应使用原生 File 类型");
assertFalse(content.contains("UploadFile"), IMPORT_FORM_PATH + ":不应使用上传列表项类型 UploadFile");
assertFalse(content.contains("as unknown as File"), IMPORT_FORM_PATH + ":不应通过双重断言转换上传文件");
assertFalse(content.contains("FileType"), IMPORT_FORM_PATH + ":不应继续使用旧 FileType");
}
private static void assertTabsGenerated(Map<String, String> result) {
assertComponentGenerated(result, "Tabs");
assertComponentGenerated(result, "TabPane");
}
private static void assertGeneralCrudComponentsGenerated(Map<String, String> result) {
assertComponentGenerated(result, "FormItem");
assertComponentGenerated(result, "RangePicker");
assertComponentGenerated(result, "SelectOption");
}
private static void assertRuleGenerated(Map<String, String> result) {
assertTrue(result.values().stream().map(CodegenEngineVben5AntdvNextTest::removeAntdvNextRootImports)
.anyMatch(content -> Pattern.compile("\\bRule\\b").matcher(content).find()),
"生成结果应包含 Rule 类型校验规则");
}
private static void assertSchemaRangePickerGenerated(Map<String, String> result) {
String content = result.get(SCHEMA_DATA_PATH);
assertNotNull(content, SCHEMA_DATA_PATH + ":未生成 Schema 配置");
long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
assertTrue(componentCount > 0, SCHEMA_DATA_PATH + ":应生成 RangePicker 组件配置");
}
private static void assertComponentGenerated(Map<String, String> result, String component) {
assertTrue(result.values().stream().anyMatch(content -> content.contains("<" + component)),
"生成结果应包含 " + component + " 组件");
}
private static void assertRootValueImportMatchesUsage(String filePath, String content,
String expectedImportRegex, String usageRegex,
String bindingName) {
assertRootImportMatchesUsage(filePath, content, "import\\s*\\{", expectedImportRegex,
usageRegex, bindingName);
}
private static void assertRootTypeImportMatchesUsage(String filePath, String content,
String expectedImportRegex, String usageRegex,
String bindingName) {
assertRootImportMatchesUsage(filePath, content, "import\\s+type\\s*\\{", expectedImportRegex,
usageRegex, bindingName);
}
private static void assertRootImportMatchesUsage(String filePath, String content, String importPrefix,
String expectedImportRegex, String usageRegex,
String bindingName) {
boolean imported = createRootImportPattern(importPrefix, expectedImportRegex).matcher(content).find();
boolean used = Pattern.compile(usageRegex).matcher(removeAntdvNextRootImports(content)).find();
assertEquals(used, imported, filePath + "" + bindingName + (used ? " 缺少合法根导入" : " 存在孤立根导入"));
}
private static String removeAntdvNextRootImports(String content) {
Pattern pattern = Pattern.compile("import\\s+(?:type\\s+)?\\{[^}]*}\\s*from\\s*'antdv-next';?",
Pattern.DOTALL);
return pattern.matcher(content).replaceAll("");
}
private static void assertRootValueImport(String filePath, String content, String expectedRegex) {
assertRootImport(filePath, content, "import\\s*\\{", expectedRegex);
}
private static void assertRootValueImportsSorted(String filePath, String content) {
var matcher = ANTDV_NEXT_VALUE_IMPORT_PATTERN.matcher(content);
while (matcher.find()) {
List<String> actual = Arrays.stream(matcher.group(1).split(","))
.map(String::trim)
.filter(item -> !item.isEmpty())
.map(item -> item.replaceFirst("^.*\\s+as\\s+", ""))
.toList();
List<String> expected = actual.stream().sorted(String.CASE_INSENSITIVE_ORDER).toList();
assertEquals(expected, actual, filePath + "antdv-next 命名导入未按 natural 顺序排列");
}
}
private static void assertRangePickerHelperImportMatchesUsage(String filePath, String content) {
boolean imported = RANGE_PICKER_HELPER_IMPORT_PATTERN.matcher(content).find();
boolean called = RANGE_PICKER_HELPER_CALL_PATTERN.matcher(content).find();
assertEquals(called, imported,
filePath + "getRangePickerDefaultProps() 调用与导入不一致");
}
private static void assertRangePickerHelperBindingMatchesComponent(String filePath, String content) {
long componentCount = countMatches(RANGE_PICKER_COMPONENT_PATTERN, content);
long bindingCount = countMatches(RANGE_PICKER_HELPER_BINDING_PATTERN, content);
assertEquals(componentCount, bindingCount,
filePath + "RangePicker 与 getRangePickerDefaultProps() 绑定数量不一致");
}
private static void assertSchemaRangePickerHelperMatchesComponent(String filePath, String content) {
long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
long helperSpreadCount = countMatches(SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN, content);
assertEquals(componentCount, helperSpreadCount,
filePath + "Schema RangePicker 与 getRangePickerDefaultProps() 展开数量不一致");
}
private static long countMatches(Pattern pattern, String content) {
return pattern.matcher(content).results().count();
}
private static void assertRootImport(String filePath, String content, String importPrefix, String expectedRegex) {
Pattern pattern = createRootImportPattern(importPrefix, expectedRegex);
assertTrue(pattern.matcher(content).find(), filePath + ":缺少合法的 antdv-next 根导入 " + expectedRegex);
}
private static Pattern createRootImportPattern(String importPrefix, String expectedRegex) {
return Pattern.compile(importPrefix + "[^}]*" + expectedRegex
+ "[^}]*}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
}
}

View File

@@ -0,0 +1,109 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue3 + Vben5 + Element Plus + General 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVben5EleGeneralTest extends CodegenEngineAbstractTest {
private static final Integer FRONT_TYPE = CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType();
@Test
public void testExecute_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_general_one");
}
@Test
public void testExecute_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_general_one_importEnable");
}
@Test
public void testExecute_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_general_tree");
}
@Test
public void testExecute_master_normal() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vben5_ele_general_master_normal");
}
@Test
public void testExecute_master_erp() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vben5_ele_general_master_erp");
}
@Test
public void testExecute_master_inner() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vben5_ele_general_master_inner");
}
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
}
}

View File

@@ -0,0 +1,109 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue3 + Vben5 + Element Plus + Schema 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVben5EleSchemaTest extends CodegenEngineAbstractTest {
private static final Integer FRONT_TYPE = CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType();
@Test
public void testExecute_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_schema_one");
}
@Test
public void testExecute_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_schema_one_importEnable");
}
@Test
public void testExecute_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(FRONT_TYPE)
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vben5_ele_schema_tree");
}
@Test
public void testExecute_master_normal() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vben5_ele_schema_master_normal");
}
@Test
public void testExecute_master_erp() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vben5_ele_schema_master_erp");
}
@Test
public void testExecute_master_inner() {
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vben5_ele_schema_master_inner");
}
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(FRONT_TYPE)
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(FRONT_TYPE)
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
}
}

View File

@@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue2 + Element UI 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVue2Test extends CodegenEngineAbstractTest {
@Test
public void testExecute_vue2_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 生成测试文件
//writeResult(result, resourcesPath + "/vue2_one");
// 断言
assertResult(result, "/vue2_one");
}
@Test
public void testExecute_vue2_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vue2_one_importEnable");
}
@Test
public void testExecute_vue2_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 生成测试文件
//writeResult(result, resourcesPath + "/vue2_tree");
// 断言
assertResult(result, "/vue2_tree");
// writeFile(result, "/Users/yunai/test/demo66.zip");
}
@Test
public void testExecute_vue2_master_normal() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vue2_master_normal");
}
@Test
public void testExecute_vue2_master_erp() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue2_master_erp");
}
@Test
public void testExecute_vue2_master_inner() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vue2_master_inner");
}
private void testExecute_vue2_master(CodegenTemplateTypeEnum templateType,
String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE2_ELEMENT_UI.getType())
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 生成测试文件
//writeResult(result, resourcesPath + path);
// 断言
assertResult(result, path);
// writeFile(result, "/Users/yunai/test/demo11.zip");
}
}

View File

@@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.DbType;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* {@link CodegenEngine} 的 Vue2 + Element Plus 单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVue3Test extends CodegenEngineAbstractTest {
@Test
public void testExecute_vue3_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 生成测试文件
//writeResult(result, resourcesPath + "/vue3_one");
// 断言
assertResult(result, "/vue3_one");
}
@Test
public void testExecute_vue3_one_importEnable() {
// 开启 import 开关
codegenProperties.setImportEnable(true);
codegenEngine.initGlobalBindingMap();
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 断言
assertResult(result, "/vue3_one_importEnable");
}
@Test
public void testExecute_vue3_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
// 生成测试文件
//writeResult(result, resourcesPath + "/vue3_tree");
// 断言
assertResult(result, "/vue3_tree");
// writeFile(result, "/Users/yunai/test/demo66.zip");
}
@Test
public void testExecute_vue3_master_normal() {
testExecute_vue3_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vue3_master_normal");
}
@Test
public void testExecute_vue3_master_erp() {
testExecute_vue3_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue3_master_erp");
}
@Test
public void testExecute_vue3_master_inner() {
testExecute_vue3_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vue3_master_inner");
}
private void testExecute_vue3_master(CodegenTemplateTypeEnum templateType,
String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数(子表)
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数(班主任)
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 生成测试文件
//writeResult(result, resourcesPath + path);
// 断言
assertResult(result, path);
// writeFile(result, "/Users/yunai/test/demo11.zip");
}
}

View File

@@ -0,0 +1,219 @@
package cn.iocoder.yudao.module.infra.service.config;
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.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.infra.controller.admin.config.vo.ConfigPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.config.vo.ConfigSaveReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.config.ConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.config.ConfigMapper;
import cn.iocoder.yudao.module.infra.enums.config.ConfigTypeEnum;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
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.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
@Import(ConfigServiceImpl.class)
public class ConfigServiceImplTest extends BaseDbUnitTest {
@Resource
private ConfigServiceImpl configService;
@Resource
private ConfigMapper configMapper;
@Test
public void testCreateConfig_success() {
// 准备参数
ConfigSaveReqVO reqVO = randomPojo(ConfigSaveReqVO.class)
.setId(null); // 防止 id 被赋值,导致唯一性校验失败
// 调用
Long configId = configService.createConfig(reqVO);
// 断言
assertNotNull(configId);
// 校验记录的属性是否正确
ConfigDO config = configMapper.selectById(configId);
assertPojoEquals(reqVO, config, "id");
assertEquals(ConfigTypeEnum.CUSTOM.getType(), config.getType());
}
@Test
public void testUpdateConfig_success() {
// mock 数据
ConfigDO dbConfig = randomConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
ConfigSaveReqVO reqVO = randomPojo(ConfigSaveReqVO.class, o -> {
o.setId(dbConfig.getId()); // 设置更新的 ID
});
// 调用
configService.updateConfig(reqVO);
// 校验是否更新正确
ConfigDO config = configMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, config);
}
@Test
public void testDeleteConfig_success() {
// mock 数据
ConfigDO dbConfig = randomConfigDO(o -> {
o.setType(ConfigTypeEnum.CUSTOM.getType()); // 只能删除 CUSTOM 类型
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用
configService.deleteConfig(id);
// 校验数据不存在了
assertNull(configMapper.selectById(id));
}
@Test
public void testDeleteConfig_canNotDeleteSystemType() {
// mock 数据
ConfigDO dbConfig = randomConfigDO(o -> {
o.setType(ConfigTypeEnum.SYSTEM.getType()); // SYSTEM 不允许删除
});
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用, 并断言异常
assertServiceException(() -> configService.deleteConfig(id), CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
}
@Test
public void testValidateConfigExists_success() {
// mock 数据
ConfigDO dbConfigDO = randomConfigDO();
configMapper.insert(dbConfigDO);// @Sql: 先插入出一条存在的数据
// 调用成功
configService.validateConfigExists(dbConfigDO.getId());
}
@Test
public void testValidateConfigExist_notExists() {
assertServiceException(() -> configService.validateConfigExists(randomLongId()), CONFIG_NOT_EXISTS);
}
@Test
public void testValidateConfigKeyUnique_success() {
// 调用,成功
configService.validateConfigKeyUnique(randomLongId(), randomString());
}
@Test
public void testValidateConfigKeyUnique_keyDuplicateForCreate() {
// 准备参数
String key = randomString();
// mock 数据
configMapper.insert(randomConfigDO(o -> o.setConfigKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.validateConfigKeyUnique(null, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testValidateConfigKeyUnique_keyDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String key = randomString();
// mock 数据
configMapper.insert(randomConfigDO(o -> o.setConfigKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.validateConfigKeyUnique(id, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testGetConfigPage() {
// mock 数据
ConfigDO dbConfig = randomConfigDO(o -> { // 等会查询到
o.setName("芋艿");
o.setConfigKey("yunai");
o.setType(ConfigTypeEnum.SYSTEM.getType());
o.setCreateTime(buildTime(2021, 2, 1));
});
configMapper.insert(dbConfig);
// 测试 name 不匹配
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setName("土豆")));
// 测试 key 不匹配
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setConfigKey("tudou")));
// 测试 type 不匹配
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setType(ConfigTypeEnum.CUSTOM.getType())));
// 测试 createTime 不匹配
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
ConfigPageReqVO reqVO = new ConfigPageReqVO();
reqVO.setName("");
reqVO.setKey("nai");
reqVO.setType(ConfigTypeEnum.SYSTEM.getType());
reqVO.setCreateTime(buildBetweenTime(2021, 1, 15, 2021, 2, 15));
// 调用
PageResult<ConfigDO> pageResult = configService.getConfigPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbConfig, pageResult.getList().get(0));
}
@Test
public void testGetConfig() {
// mock 数据
ConfigDO dbConfig = randomConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用
ConfigDO config = configService.getConfig(id);
// 断言
assertNotNull(config);
assertPojoEquals(dbConfig, config);
}
@Test
public void testGetConfigByKey() {
// mock 数据
ConfigDO dbConfig = randomConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
String key = dbConfig.getConfigKey();
// 调用
ConfigDO config = configService.getConfigByKey(key);
// 断言
assertNotNull(config);
assertPojoEquals(dbConfig, config);
}
// ========== 随机对象 ==========
@SafeVarargs
private static ConfigDO randomConfigDO(Consumer<ConfigDO>... consumers) {
Consumer<ConfigDO> consumer = (o) -> {
o.setType(randomEle(ConfigTypeEnum.values()).getType()); // 保证 key 的范围
};
return RandomUtils.randomPojo(ConfigDO.class, ArrayUtils.append(consumer, consumers));
}
}

View File

@@ -0,0 +1,208 @@
package cn.iocoder.yudao.module.infra.service.db;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.stubbing.Answer;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.util.List;
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.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* {@link DataSourceConfigServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(DataSourceConfigServiceImpl.class)
public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
@Resource
private DataSourceConfigServiceImpl dataSourceConfigService;
@Resource
private DataSourceConfigMapper dataSourceConfigMapper;
@MockitoBean
private AES aes;
@MockitoBean
private DynamicDataSourceProperties dynamicDataSourceProperties;
@BeforeEach
public void setUp() {
// mock 一个空实现的 StringEncryptor避免 EncryptTypeHandler 报错
ReflectUtil.setFieldValue(EncryptTypeHandler.class, "aes", aes);
when(aes.encryptBase64(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
when(aes.decryptStr(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
// mock DynamicDataSourceProperties
when(dynamicDataSourceProperties.getPrimary()).thenReturn("primary");
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty.setUrl("http://localhost:3306");
dataSourceProperty.setUsername("yunai");
dataSourceProperty.setPassword("tudou");
when(dynamicDataSourceProperties.getDatasource()).thenReturn(MapUtil.of("primary", dataSourceProperty));
}
@Test
public void testCreateDataSourceConfig_success() {
try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
// 准备参数
DataSourceConfigSaveReqVO reqVO = randomPojo(DataSourceConfigSaveReqVO.class)
.setId(null); // 避免 id 被设置
// mock 方法
databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
// 调用
Long dataSourceConfigId = dataSourceConfigService.createDataSourceConfig(reqVO);
// 断言
assertNotNull(dataSourceConfigId);
// 校验记录的属性是否正确
DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(dataSourceConfigId);
assertPojoEquals(reqVO, dataSourceConfig, "id");
}
}
@Test
public void testUpdateDataSourceConfig_success() {
try (MockedStatic<JdbcUtils> databaseUtilsMock = mockStatic(JdbcUtils.class)) {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
DataSourceConfigSaveReqVO reqVO = randomPojo(DataSourceConfigSaveReqVO.class, o -> {
o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
});
// mock 方法
databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
// 调用
dataSourceConfigService.updateDataSourceConfig(reqVO);
// 校验是否更新正确
DataSourceConfigDO dataSourceConfig = dataSourceConfigMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, dataSourceConfig);
}
}
@Test
public void testUpdateDataSourceConfig_notExists() {
// 准备参数
DataSourceConfigSaveReqVO reqVO = randomPojo(DataSourceConfigSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> dataSourceConfigService.updateDataSourceConfig(reqVO), DATA_SOURCE_CONFIG_NOT_EXISTS);
}
@Test
public void testDeleteDataSourceConfig_success() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbDataSourceConfig.getId();
// 调用
dataSourceConfigService.deleteDataSourceConfig(id);
// 校验数据不存在了
assertNull(dataSourceConfigMapper.selectById(id));
}
@Test
public void testDeleteDataSourceConfig_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> dataSourceConfigService.deleteDataSourceConfig(id), DATA_SOURCE_CONFIG_NOT_EXISTS);
}
@Test // 测试使用 password 查询,可以查询到数据
public void testSelectPassword() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 调用
DataSourceConfigDO result = dataSourceConfigMapper.selectOne(DataSourceConfigDO::getPassword,
EncryptTypeHandler.encrypt(dbDataSourceConfig.getPassword()));
assertPojoEquals(dbDataSourceConfig, result);
}
@Test
public void testGetDataSourceConfig_master() {
// 准备参数
Long id = 0L;
// mock 方法
// 调用
DataSourceConfigDO dataSourceConfig = dataSourceConfigService.getDataSourceConfig(id);
// 断言
assertEquals(id, dataSourceConfig.getId());
assertEquals("primary", dataSourceConfig.getName());
assertEquals("http://localhost:3306", dataSourceConfig.getUrl());
assertEquals("yunai", dataSourceConfig.getUsername());
assertEquals("tudou", dataSourceConfig.getPassword());
}
@Test
public void testGetDataSourceConfig_normal() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbDataSourceConfig.getId();
// 调用
DataSourceConfigDO dataSourceConfig = dataSourceConfigService.getDataSourceConfig(id);
// 断言
assertPojoEquals(dbDataSourceConfig, dataSourceConfig);
}
@Test
public void testGetDataSourceConfigList() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
// 调用
List<DataSourceConfigDO> dataSourceConfigList = dataSourceConfigService.getDataSourceConfigList();
// 断言
assertEquals(2, dataSourceConfigList.size());
// master
assertEquals(0L, dataSourceConfigList.get(0).getId());
assertEquals("primary", dataSourceConfigList.get(0).getName());
assertEquals("http://localhost:3306", dataSourceConfigList.get(0).getUrl());
assertEquals("yunai", dataSourceConfigList.get(0).getUsername());
assertEquals("tudou", dataSourceConfigList.get(0).getPassword());
// normal
assertPojoEquals(dbDataSourceConfig, dataSourceConfigList.get(1));
}
}

View File

@@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.infra.service.db;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import jakarta.annotation.Resource;
import org.apache.ibatis.type.JdbcType;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Import(DatabaseTableServiceImpl.class)
public class DatabaseTableServiceImplTest extends BaseDbUnitTest {
@Resource
private DatabaseTableServiceImpl databaseTableService;
@MockitoBean
private DataSourceConfigService dataSourceConfigService;
@Test
public void testGetTableList() {
// 准备参数
Long dataSourceConfigId = randomLongId();
// mock 方法
DataSourceConfigDO dataSourceConfig = new DataSourceConfigDO().setUsername("sa").setPassword("")
.setUrl("jdbc:h2:mem:testdb");
when(dataSourceConfigService.getDataSourceConfig(eq(dataSourceConfigId)))
.thenReturn(dataSourceConfig);
// 调用
List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId,
"config", "参数");
// 断言
assertEquals(1, tables.size());
assertTableInfo(tables.get(0));
}
@Test
public void testGetTable() {
// 准备参数
Long dataSourceConfigId = randomLongId();
// mock 方法
DataSourceConfigDO dataSourceConfig = new DataSourceConfigDO().setUsername("sa").setPassword("")
.setUrl("jdbc:h2:mem:testdb");
when(dataSourceConfigService.getDataSourceConfig(eq(dataSourceConfigId)))
.thenReturn(dataSourceConfig);
// 调用
TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, "infra_config");
// 断言
assertTableInfo(tableInfo);
}
private void assertTableInfo(TableInfo tableInfo) {
assertEquals("infra_config", tableInfo.getName());
assertEquals("参数配置表", tableInfo.getComment());
assertEquals(13, tableInfo.getFields().size());
// id 字段
TableField idField = tableInfo.getFields().get(0);
assertEquals("id", idField.getName());
assertEquals(JdbcType.BIGINT, idField.getMetaInfo().getJdbcType());
assertEquals("编号", idField.getComment());
assertFalse(idField.getMetaInfo().isNullable());
assertTrue(idField.isKeyFlag());
assertTrue(idField.isKeyIdentityFlag());
assertEquals(DbColumnType.LONG, idField.getColumnType());
assertEquals("id", idField.getPropertyName());
// name 字段
TableField nameField = tableInfo.getFields().get(3);
assertEquals("name", nameField.getName());
assertEquals(JdbcType.VARCHAR, nameField.getMetaInfo().getJdbcType());
assertEquals("名字", nameField.getComment());
assertFalse(nameField.getMetaInfo().isNullable());
assertFalse(nameField.isKeyFlag());
assertFalse(nameField.isKeyIdentityFlag());
assertEquals(DbColumnType.STRING, nameField.getColumnType());
assertEquals("name", nameField.getPropertyName());
}
}

View File

@@ -0,0 +1,281 @@
package cn.iocoder.yudao.module.infra.service.file;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientFactory;
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClient;
import cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig;
import cn.iocoder.yudao.module.infra.framework.file.core.enums.FileStorageEnum;
import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomEle;
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.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link FileConfigServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(FileConfigServiceImpl.class)
public class FileConfigServiceImplTest extends BaseDbUnitTest {
@Resource
private FileConfigServiceImpl fileConfigService;
@Resource
private FileConfigMapper fileConfigMapper;
@MockitoBean
private Validator validator;
@MockitoBean
private FileClientFactory fileClientFactory;
@Test
public void testCreateFileConfig_success() {
// 准备参数
Map<String, Object> config = MapUtil.<String, Object>builder().put("basePath", "/yunai")
.put("domain", "https://www.iocoder.cn").build();
FileConfigSaveReqVO reqVO = randomPojo(FileConfigSaveReqVO.class,
o -> o.setStorage(FileStorageEnum.LOCAL.getStorage()).setConfig(config))
.setId(null); // 避免 id 被赋值
// 调用
Long fileConfigId = fileConfigService.createFileConfig(reqVO);
// 断言
assertNotNull(fileConfigId);
// 校验记录的属性是否正确
FileConfigDO fileConfig = fileConfigMapper.selectById(fileConfigId);
assertPojoEquals(reqVO, fileConfig, "id", "config");
assertFalse(fileConfig.getMaster());
assertEquals("/yunai", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
assertEquals("https://www.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
// 验证 cache
assertNull(fileConfigService.getClientCache().getIfPresent(fileConfigId));
}
@Test
public void testUpdateFileConfig_success() {
// mock 数据
FileConfigDO dbFileConfig = randomPojo(FileConfigDO.class, o -> o.setStorage(FileStorageEnum.LOCAL.getStorage())
.setConfig(new LocalFileClientConfig().setBasePath("/yunai").setDomain("https://www.iocoder.cn")));
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
FileConfigSaveReqVO reqVO = randomPojo(FileConfigSaveReqVO.class, o -> {
o.setId(dbFileConfig.getId()); // 设置更新的 ID
o.setStorage(FileStorageEnum.LOCAL.getStorage());
Map<String, Object> config = MapUtil.<String, Object>builder().put("basePath", "/yunai2")
.put("domain", "https://doc.iocoder.cn").build();
o.setConfig(config);
});
// 调用
fileConfigService.updateFileConfig(reqVO);
// 校验是否更新正确
FileConfigDO fileConfig = fileConfigMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, fileConfig, "config");
assertEquals("/yunai2", ((LocalFileClientConfig) fileConfig.getConfig()).getBasePath());
assertEquals("https://doc.iocoder.cn", ((LocalFileClientConfig) fileConfig.getConfig()).getDomain());
// 验证 cache
assertNull(fileConfigService.getClientCache().getIfPresent(fileConfig.getId()));
}
@Test
public void testUpdateFileConfig_notExists() {
// 准备参数
FileConfigSaveReqVO reqVO = randomPojo(FileConfigSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> fileConfigService.updateFileConfig(reqVO), FILE_CONFIG_NOT_EXISTS);
}
@Test
public void testUpdateFileConfigMaster_success() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
FileConfigDO masterFileConfig = randomFileConfigDO().setMaster(true);
fileConfigMapper.insert(masterFileConfig);// @Sql: 先插入出一条存在的数据
// 调用
fileConfigService.updateFileConfigMaster(dbFileConfig.getId());
// 断言数据
assertTrue(fileConfigMapper.selectById(dbFileConfig.getId()).getMaster());
assertFalse(fileConfigMapper.selectById(masterFileConfig.getId()).getMaster());
// 验证 cache
assertNull(fileConfigService.getClientCache().getIfPresent(0L));
}
@Test
public void testUpdateFileConfigMaster_notExists() {
// 调用, 并断言异常
assertServiceException(() -> fileConfigService.updateFileConfigMaster(randomLongId()), FILE_CONFIG_NOT_EXISTS);
}
@Test
public void testDeleteFileConfig_success() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbFileConfig.getId();
// 调用
fileConfigService.deleteFileConfig(id);
// 校验数据不存在了
assertNull(fileConfigMapper.selectById(id));
// 验证 cache
assertNull(fileConfigService.getClientCache().getIfPresent(id));
}
@Test
public void testDeleteFileConfig_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> fileConfigService.deleteFileConfig(id), FILE_CONFIG_NOT_EXISTS);
}
@Test
public void testDeleteFileConfig_master() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(true);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbFileConfig.getId();
// 调用, 并断言异常
assertServiceException(() -> fileConfigService.deleteFileConfig(id), FILE_CONFIG_DELETE_FAIL_MASTER);
}
@Test
public void testGetFileConfigPage() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码")
.setStorage(FileStorageEnum.LOCAL.getStorage());
dbFileConfig.setCreateTime(LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN));// 等会查询到
fileConfigMapper.insert(dbFileConfig);
// 测试 name 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码")));
// 测试 storage 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage())));
// 测试 createTime 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(LocalDateTimeUtil.parse("2020-11-23", DatePattern.NORM_DATE_PATTERN))));
// 准备参数
FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
reqVO.setName("芋道");
reqVO.setStorage(FileStorageEnum.LOCAL.getStorage());
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2020, 1, 1),
buildTime(2020, 1, 24)}));
// 调用
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
}
@Test
public void testFileConfig() throws Exception {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbFileConfig.getId();
// mock 获得 Client
FileClient fileClient = mock(FileClient.class);
when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
when(fileClient.upload(any(), any(), any())).thenReturn("https://www.iocoder.cn");
// 调用,并断言
assertEquals("https://www.iocoder.cn", fileConfigService.testFileConfig(id));
}
@Test
public void testGetFileConfig() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbFileConfig.getId();
// 调用,并断言
assertPojoEquals(dbFileConfig, fileConfigService.getFileConfig(id));
}
@Test
public void testGetFileClient() {
// mock 数据
FileConfigDO fileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(fileConfig);
// 准备参数
Long id = fileConfig.getId();
// mock 获得 Client
FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
// 调用,并断言
assertSame(fileClient, fileConfigService.getFileClient(id));
// 断言缓存
verify(fileClientFactory).createOrUpdateFileClient(eq(id), eq(fileConfig.getStorage()),
eq(fileConfig.getConfig()));
}
@Test
public void testGetMasterFileClient() {
// mock 数据
FileConfigDO fileConfig = randomFileConfigDO().setMaster(true);
fileConfigMapper.insert(fileConfig);
// 准备参数
Long id = fileConfig.getId();
// mock 获得 Client
FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
when(fileClientFactory.getFileClient(eq(fileConfig.getId()))).thenReturn(fileClient);
// 调用,并断言
assertSame(fileClient, fileConfigService.getMasterFileClient());
// 断言缓存
verify(fileClientFactory).createOrUpdateFileClient(eq(fileConfig.getId()), eq(fileConfig.getStorage()),
eq(fileConfig.getConfig()));
}
private FileConfigDO randomFileConfigDO() {
return randomPojo(FileConfigDO.class).setStorage(randomEle(FileStorageEnum.values()).getStorage())
.setConfig(new EmptyFileClientConfig());
}
@Data
public static class EmptyFileClientConfig implements FileClientConfig, Serializable {
}
}

View File

@@ -0,0 +1,465 @@
package cn.iocoder.yudao.module.infra.service.file;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicReference;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_PATH_INVALID;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.*;
@Import({FileServiceImpl.class})
public class FileServiceImplTest extends BaseDbUnitTest {
@Resource
private FileServiceImpl fileService;
@Resource
private FileMapper fileMapper;
@MockitoBean
private FileConfigService fileConfigService;
@BeforeEach
public void setUp() {
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_AS_DIRECTORY = true;
}
@Test
public void testGetFilePage() {
// mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到
o.setPath("yunai");
o.setType("image/jpg");
o.setCreateTime(buildTime(2021, 1, 15));
});
fileMapper.insert(dbFile);
// 测试 path 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou")));
// 测试 type 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
o.setType("image/png");
}));
// 测试 createTime 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
o.setCreateTime(buildTime(2020, 1, 15));
}));
// 准备参数
FilePageReqVO reqVO = new FilePageReqVO();
reqVO.setPath("yunai");
reqVO.setType("jp");
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 1, 10), buildTime(2021, 1, 20)}));
// 调用
PageResult<FileDO> pageResult = fileService.getFilePage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0));
}
/**
* content、name、directory、type 都非空
*/
@Test
public void testCreateFile_success_01() throws Exception {
// 准备参数
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
String name = "单测文件名";
String directory = randomString();
String type = "image/jpeg";
// mock Master 文件客户端
FileClient client = mock(FileClient.class);
when(fileConfigService.getMasterFileClient()).thenReturn(client);
String url = randomString();
AtomicReference<String> pathRef = new AtomicReference<>();
when(client.upload(same(content), argThat(path -> {
assertTrue(path.matches(directory + "/\\d{8}/\\d+/" + name + ".jpg"));
pathRef.set(path);
return true;
}), eq(type))).thenReturn(url);
when(client.getId()).thenReturn(10L);
// 调用
String result = fileService.createFile(content, name, directory, type);
// 断言
assertEquals(result, url);
// 校验数据
FileDO file = fileMapper.selectOne(FileDO::getUrl, url);
assertEquals(10L, file.getConfigId());
assertEquals(pathRef.get(), file.getPath());
assertEquals(url, file.getUrl());
assertEquals(type, file.getType());
assertEquals(content.length, file.getSize());
}
/**
* content 非空,其它都空
*/
@Test
public void testCreateFile_success_02() throws Exception {
// 准备参数
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
// mock Master 文件客户端
String type = "image/jpeg";
FileClient client = mock(FileClient.class);
when(fileConfigService.getMasterFileClient()).thenReturn(client);
String url = randomString();
AtomicReference<String> pathRef = new AtomicReference<>();
when(client.upload(same(content), argThat(path -> {
assertTrue(path.matches("\\d{8}/\\d+/6318848e882d8a7e7e82789d87608f684ee52d41966bfc8cad3ce15aad2b970e\\.jpg"));
pathRef.set(path);
return true;
}), eq(type))).thenReturn(url);
when(client.getId()).thenReturn(10L);
// 调用
String result = fileService.createFile(content, null, null, null);
// 断言
assertEquals(result, url);
// 校验数据
FileDO file = fileMapper.selectOne(FileDO::getUrl, url);
assertEquals(10L, file.getConfigId());
assertEquals(pathRef.get(), file.getPath());
assertEquals(url, file.getUrl());
assertEquals(type, file.getType());
assertEquals(content.length, file.getSize());
}
@Test
public void testDeleteFile_success() throws Exception {
// mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("tudou.jpg"));
fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据
// mock Master 文件客户端
FileClient client = mock(FileClient.class);
when(fileConfigService.getFileClient(eq(10L))).thenReturn(client);
// 准备参数
Long id = dbFile.getId();
// 调用
fileService.deleteFile(id);
// 校验数据不存在了
assertNull(fileMapper.selectById(id));
// 校验调用
verify(client).delete(eq("tudou.jpg"));
}
@Test
public void testDeleteFile_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS);
}
@Test
public void testDeleteFile_pathInvalid() {
// mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("../tudou.jpg"));
fileMapper.insert(dbFile);
// 调用,并断言异常
assertServiceException(() -> fileService.deleteFile(dbFile.getId()), FILE_PATH_INVALID);
}
@Test
public void testGetFileContent() throws Exception {
// 准备参数
Long configId = 10L;
String path = "tudou.jpg";
// mock 方法
FileClient client = mock(FileClient.class);
when(fileConfigService.getFileClient(eq(10L))).thenReturn(client);
byte[] content = new byte[]{};
when(client.getContent(eq("tudou.jpg"))).thenReturn(content);
// 调用
byte[] result = fileService.getFileContent(configId, path);
// 断言
assertSame(result, content);
}
@Test
public void testGetFileContent_pathInvalid() {
// 准备参数
Long configId = 10L;
String path = "../tudou.jpg";
// 调用,并断言异常
assertServiceException(() -> fileService.getFileContent(configId, path), FILE_PATH_INVALID);
}
@Test
public void testGetFileByConfigIdAndPath() {
// mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("avatar/中文 100%+文件.jpg"));
fileMapper.insert(dbFile);
FileDO latestFile = ObjectUtils.cloneIgnoreId(dbFile, o -> o.setName("最新文件名.jpg"));
fileMapper.insert(latestFile);
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("avatar/other.jpg")));
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setConfigId(20L)));
// 调用
FileDO result = fileService.getFileByConfigIdAndPath(10L, "avatar/中文 100%+文件.jpg");
// 断言
AssertUtils.assertPojoEquals(latestFile, result);
}
@Test
public void testCreateFileByPresignedPath_success() {
// 准备参数
FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> {
o.setPath("avatar/test.jpg");
o.setName("test.jpg");
o.setUrl("https://www.iocoder.cn/test.jpg?token=123");
});
// 调用
Long fileId = fileService.createFile(reqVO);
// 断言
FileDO file = fileMapper.selectById(fileId);
assertEquals("avatar/test.jpg", file.getPath());
assertEquals("test.jpg", file.getName());
assertEquals("https://www.iocoder.cn/test.jpg", file.getUrl());
}
@Test
public void testCreateFileByPresignedPath_nameInvalid() {
// 准备参数
FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> {
o.setPath("avatar/test.jpg");
o.setName("../test.jpg");
});
// 调用,并断言异常
assertServiceException(() -> fileService.createFile(reqVO), FILE_PATH_INVALID);
}
@Test
public void testCreateFileByPresignedPath_pathInvalid() {
// 准备参数
FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> {
o.setPath("../test.jpg");
o.setName("test.jpg");
});
// 调用,并断言异常
assertServiceException(() -> fileService.createFile(reqVO), FILE_PATH_INVALID);
}
@Test
public void testGenerateUploadPath_AllEnabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/yyyyMMdd/{时间戳+随机数}/test.jpg
assertTrue(path.startsWith(directory + "/"));
// 包含日期格式8 位数字,如 20240517
assertTrue(path.matches(directory + "/\\d{8}/\\d+/test\\.jpg"));
}
@Test
public void testGenerateUploadPath_PrefixEnabled_SuffixDisabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = false;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/yyyyMMdd/test.jpg
assertTrue(path.startsWith(directory + "/"));
// 包含日期格式8 位数字,如 20240517
assertTrue(path.matches(directory + "/\\d{8}/test\\.jpg"));
}
@Test
public void testGenerateUploadPath_PrefixDisabled_SuffixEnabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/{时间戳+随机数}/test.jpg
assertTrue(path.startsWith(directory + "/"));
assertTrue(path.matches(directory + "/\\d+/test\\.jpg"));
}
@Test
public void testGenerateUploadPath_AllDisabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = false;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/test.jpg
assertEquals(directory + "/" + name, path);
}
@Test
public void testGenerateUploadPath_NoExtension() {
// 准备参数
String name = "test";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/yyyyMMdd/{时间戳+随机数}/test
assertTrue(path.startsWith(directory + "/"));
assertTrue(path.matches(directory + "/\\d{8}/\\d+/test"));
}
@Test
public void testGenerateUploadPath_DirectoryNull() {
// 准备参数
String name = "test.jpg";
String directory = null;
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为yyyyMMdd/{时间戳+随机数}/test.jpg
assertTrue(path.matches("\\d{8}/\\d+/test\\.jpg"));
}
@Test
public void testGenerateUploadPath_SuffixAsName_AllEnabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_AS_DIRECTORY = false;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/yyyyMMdd/test_{时间戳+随机数}.jpg
assertTrue(path.matches(directory + "/\\d{8}/test_\\d+\\.jpg"));
}
@Test
public void testGenerateUploadPath_SuffixAsName_PrefixDisabled() {
// 准备参数
String name = "test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_AS_DIRECTORY = false;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/test_{时间戳+随机数}.jpg
assertTrue(path.matches(directory + "/test_\\d+\\.jpg"));
}
@Test
public void testGenerateUploadPath_SuffixAsName_NoExtension() {
// 准备参数
String name = "test";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_AS_DIRECTORY = false;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为avatar/yyyyMMdd/test_{时间戳+随机数}
assertTrue(path.matches(directory + "/\\d{8}/test_\\d+"));
}
@Test
public void testGenerateUploadPath_FileNameInvalid() {
// 准备参数
String name = "../test.jpg";
String directory = "avatar";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = false;
// 调用,并断言异常
assertServiceException(() -> fileService.generateUploadPath(name, directory), FILE_PATH_INVALID);
}
@Test
public void testGenerateUploadPath_DirectoryInvalid() {
// 准备参数
String name = "test.jpg";
String directory = "../avatar";
// 调用,并断言异常
assertServiceException(() -> fileService.generateUploadPath(name, directory), FILE_PATH_INVALID);
}
@Test
public void testGenerateUploadPath_DirectoryEmpty() {
// 准备参数
String name = "test.jpg";
String directory = "";
FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
// 调用
String path = fileService.generateUploadPath(name, directory);
// 断言
// 格式为yyyyMMdd/{时间戳+随机数}/test.jpg
assertTrue(path.matches("\\d{8}/\\d+/test\\.jpg"));
}
}

View File

@@ -0,0 +1,172 @@
package cn.iocoder.yudao.module.infra.service.job;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.job.vo.log.JobLogPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobLogDO;
import cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper;
import cn.iocoder.yudao.module.infra.enums.job.JobLogStatusEnum;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
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.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@Import(JobLogServiceImpl.class)
public class JobLogServiceImplTest extends BaseDbUnitTest {
@Resource
private JobLogServiceImpl jobLogService;
@Resource
private JobLogMapper jobLogMapper;
@Test
public void testCreateJobLog() {
// 准备参数
JobLogDO reqVO = randomPojo(JobLogDO.class, o -> o.setExecuteIndex(1));
// 调用
Long id = jobLogService.createJobLog(reqVO.getJobId(), reqVO.getBeginTime(),
reqVO.getHandlerName(), reqVO.getHandlerParam(), reqVO.getExecuteIndex());
// 断言
assertNotNull(id);
// 校验记录的属性是否正确
JobLogDO job = jobLogMapper.selectById(id);
assertEquals(JobLogStatusEnum.RUNNING.getStatus(), job.getStatus());
}
@Test
public void testUpdateJobLogResultAsync_success() {
// mock 数据
JobLogDO log = randomPojo(JobLogDO.class, o -> {
o.setExecuteIndex(1);
o.setStatus(JobLogStatusEnum.RUNNING.getStatus());
});
jobLogMapper.insert(log);
// 准备参数
Long logId = log.getId();
LocalDateTime endTime = randomLocalDateTime();
Integer duration = randomInteger();
boolean success = true;
String result = randomString();
// 调用
jobLogService.updateJobLogResultAsync(logId, endTime, duration, success, result);
// 校验记录的属性是否正确
JobLogDO dbLog = jobLogMapper.selectById(log.getId());
assertEquals(endTime, dbLog.getEndTime());
assertEquals(duration, dbLog.getDuration());
assertEquals(JobLogStatusEnum.SUCCESS.getStatus(), dbLog.getStatus());
assertEquals(result, dbLog.getResult());
}
@Test
public void testUpdateJobLogResultAsync_failure() {
// mock 数据
JobLogDO log = randomPojo(JobLogDO.class, o -> {
o.setExecuteIndex(1);
o.setStatus(JobLogStatusEnum.RUNNING.getStatus());
});
jobLogMapper.insert(log);
// 准备参数
Long logId = log.getId();
LocalDateTime endTime = randomLocalDateTime();
Integer duration = randomInteger();
boolean success = false;
String result = randomString();
// 调用
jobLogService.updateJobLogResultAsync(logId, endTime, duration, success, result);
// 校验记录的属性是否正确
JobLogDO dbLog = jobLogMapper.selectById(log.getId());
assertEquals(endTime, dbLog.getEndTime());
assertEquals(duration, dbLog.getDuration());
assertEquals(JobLogStatusEnum.FAILURE.getStatus(), dbLog.getStatus());
assertEquals(result, dbLog.getResult());
}
@Test
public void testCleanJobLog() {
// mock 数据
JobLogDO log01 = randomPojo(JobLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))))
.setExecuteIndex(1);
jobLogMapper.insert(log01);
JobLogDO log02 = randomPojo(JobLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))))
.setExecuteIndex(1);
jobLogMapper.insert(log02);
// 准备参数
Integer exceedDay = 2;
Integer deleteLimit = 1;
// 调用
Integer count = jobLogService.cleanJobLog(exceedDay, deleteLimit);
// 断言
assertEquals(1, count);
List<JobLogDO> logs = jobLogMapper.selectList();
assertEquals(1, logs.size());
// TODO @芋艿createTime updateTime 被屏蔽,仅 win11 会复现,建议后续修复。
assertPojoEquals(log02, logs.get(0), "createTime", "updateTime");
}
@Test
public void testGetJobLog() {
// mock 数据
JobLogDO dbJobLog = randomPojo(JobLogDO.class, o -> o.setExecuteIndex(1));
jobLogMapper.insert(dbJobLog);
// 准备参数
Long id = dbJobLog.getId();
// 调用
JobLogDO jobLog = jobLogService.getJobLog(id);
// 断言
assertPojoEquals(dbJobLog, jobLog);
}
@Test
public void testGetJobPage() {
// mock 数据
JobLogDO dbJobLog = randomPojo(JobLogDO.class, o -> {
o.setExecuteIndex(1);
o.setHandlerName("handlerName 单元测试");
o.setStatus(JobLogStatusEnum.SUCCESS.getStatus());
o.setBeginTime(buildTime(2021, 1, 8));
o.setEndTime(buildTime(2021, 1, 8));
});
jobLogMapper.insert(dbJobLog);
// 测试 jobId 不匹配
jobLogMapper.insert(cloneIgnoreId(dbJobLog, o -> o.setJobId(randomLongId())));
// 测试 handlerName 不匹配
jobLogMapper.insert(cloneIgnoreId(dbJobLog, o -> o.setHandlerName(randomString())));
// 测试 beginTime 不匹配
jobLogMapper.insert(cloneIgnoreId(dbJobLog, o -> o.setBeginTime(buildTime(2021, 1, 7))));
// 测试 endTime 不匹配
jobLogMapper.insert(cloneIgnoreId(dbJobLog, o -> o.setEndTime(buildTime(2021, 1, 9))));
// 测试 status 不匹配
jobLogMapper.insert(cloneIgnoreId(dbJobLog, o -> o.setStatus(JobLogStatusEnum.FAILURE.getStatus())));
// 准备参数
JobLogPageReqVO reqVo = new JobLogPageReqVO();
reqVo.setJobId(dbJobLog.getJobId());
reqVo.setHandlerName("单元");
reqVo.setBeginTime(dbJobLog.getBeginTime());
reqVo.setEndTime(dbJobLog.getEndTime());
reqVo.setStatus(JobLogStatusEnum.SUCCESS.getStatus());
// 调用
PageResult<JobLogDO> pageResult = jobLogService.getJobLogPage(reqVo);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbJobLog, pageResult.getList().get(0));
}
}

View File

@@ -0,0 +1,256 @@
package cn.iocoder.yudao.module.infra.service.job;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.quartz.core.scheduler.SchedulerManager;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.job.vo.job.JobPageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.job.vo.job.JobSaveReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.job.JobDO;
import cn.iocoder.yudao.module.infra.dal.mysql.job.JobMapper;
import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
import cn.iocoder.yudao.module.infra.job.job.JobLogCleanJob;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.quartz.SchedulerException;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
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.framework.test.core.util.RandomUtils.randomString;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
@Import(JobServiceImpl.class)
public class JobServiceImplTest extends BaseDbUnitTest {
@Resource
private JobServiceImpl jobService;
@Resource
private JobMapper jobMapper;
@MockitoBean
private SchedulerManager schedulerManager;
@MockitoBean
private JobLogCleanJob jobLogCleanJob;
@Test
public void testCreateJob_cronExpressionValid() {
// 准备参数。Cron 表达式为 String 类型,默认随机字符串。
JobSaveReqVO reqVO = randomPojo(JobSaveReqVO.class);
// 调用,并断言异常
assertServiceException(() -> jobService.createJob(reqVO), JOB_CRON_EXPRESSION_VALID);
}
@Test
public void testCreateJob_jobHandlerExists() throws SchedulerException {
// 准备参数 指定 Cron 表达式
JobSaveReqVO reqVO = randomPojo(JobSaveReqVO.class, o -> o.setCronExpression("0 0/1 * * * ? *"));
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(reqVO.getHandlerName())))
.thenReturn(jobLogCleanJob);
// 调用
jobService.createJob(reqVO);
// 调用,并断言异常
assertServiceException(() -> jobService.createJob(reqVO), JOB_HANDLER_EXISTS);
}
}
@Test
public void testCreateJob_success() throws SchedulerException {
// 准备参数 指定 Cron 表达式
JobSaveReqVO reqVO = randomPojo(JobSaveReqVO.class, o -> o.setCronExpression("0 0/1 * * * ? *"))
.setId(null);
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(reqVO.getHandlerName())))
.thenReturn(jobLogCleanJob);
// 调用
Long jobId = jobService.createJob(reqVO);
// 断言
assertNotNull(jobId);
// 校验记录的属性是否正确
JobDO job = jobMapper.selectById(jobId);
assertPojoEquals(reqVO, job, "id");
assertEquals(JobStatusEnum.NORMAL.getStatus(), job.getStatus());
// 校验调用
verify(schedulerManager).addJob(eq(job.getId()), eq(job.getHandlerName()), eq(job.getHandlerParam()),
eq(job.getCronExpression()), eq(reqVO.getRetryCount()), eq(reqVO.getRetryInterval()));
}
}
@Test
public void testUpdateJob_jobNotExists(){
// 准备参数
JobSaveReqVO reqVO = randomPojo(JobSaveReqVO.class, o -> o.setCronExpression("0 0/1 * * * ? *"));
// 调用,并断言异常
assertServiceException(() -> jobService.updateJob(reqVO), JOB_NOT_EXISTS);
}
@Test
public void testUpdateJob_onlyNormalStatus(){
// mock 数据
JobDO job = randomPojo(JobDO.class, o -> o.setStatus(JobStatusEnum.INIT.getStatus()));
jobMapper.insert(job);
// 准备参数
JobSaveReqVO updateReqVO = randomPojo(JobSaveReqVO.class, o -> {
o.setId(job.getId());
o.setCronExpression("0 0/1 * * * ? *");
});
// 调用,并断言异常
assertServiceException(() -> jobService.updateJob(updateReqVO),
JOB_UPDATE_ONLY_NORMAL_STATUS);
}
@Test
public void testUpdateJob_success() throws SchedulerException {
// mock 数据
JobDO job = randomPojo(JobDO.class, o -> o.setStatus(JobStatusEnum.NORMAL.getStatus()));
jobMapper.insert(job);
// 准备参数
JobSaveReqVO updateReqVO = randomPojo(JobSaveReqVO.class, o -> {
o.setId(job.getId());
o.setCronExpression("0 0/1 * * * ? *");
});
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(updateReqVO.getHandlerName())))
.thenReturn(jobLogCleanJob);
// 调用
jobService.updateJob(updateReqVO);
// 校验记录的属性是否正确
JobDO updateJob = jobMapper.selectById(updateReqVO.getId());
assertPojoEquals(updateReqVO, updateJob);
// 校验调用
verify(schedulerManager).updateJob(eq(job.getHandlerName()), eq(updateReqVO.getHandlerParam()),
eq(updateReqVO.getCronExpression()), eq(updateReqVO.getRetryCount()), eq(updateReqVO.getRetryInterval()));
}
}
@Test
public void testUpdateJobStatus_changeStatusInvalid() {
// 调用,并断言异常
assertServiceException(() -> jobService.updateJobStatus(1L, JobStatusEnum.INIT.getStatus()),
JOB_CHANGE_STATUS_INVALID);
}
@Test
public void testUpdateJobStatus_changeStatusEquals() {
// mock 数据
JobDO job = randomPojo(JobDO.class, o -> o.setStatus(JobStatusEnum.NORMAL.getStatus()));
jobMapper.insert(job);
// 调用,并断言异常
assertServiceException(() -> jobService.updateJobStatus(job.getId(), job.getStatus()),
JOB_CHANGE_STATUS_EQUALS);
}
@Test
public void testUpdateJobStatus_stopSuccess() throws SchedulerException {
// mock 数据
JobDO job = randomPojo(JobDO.class, o -> o.setStatus(JobStatusEnum.NORMAL.getStatus()));
jobMapper.insert(job);
// 调用
jobService.updateJobStatus(job.getId(), JobStatusEnum.STOP.getStatus());
// 校验记录的属性是否正确
JobDO dbJob = jobMapper.selectById(job.getId());
assertEquals(JobStatusEnum.STOP.getStatus(), dbJob.getStatus());
// 校验调用
verify(schedulerManager).pauseJob(eq(job.getHandlerName()));
}
@Test
public void testUpdateJobStatus_normalSuccess() throws SchedulerException {
// mock 数据
JobDO job = randomPojo(JobDO.class, o -> o.setStatus(JobStatusEnum.STOP.getStatus()));
jobMapper.insert(job);
// 调用
jobService.updateJobStatus(job.getId(), JobStatusEnum.NORMAL.getStatus());
// 校验记录的属性是否正确
JobDO dbJob = jobMapper.selectById(job.getId());
assertEquals(JobStatusEnum.NORMAL.getStatus(), dbJob.getStatus());
// 校验调用
verify(schedulerManager).resumeJob(eq(job.getHandlerName()));
}
@Test
public void testTriggerJob_success() throws SchedulerException {
// mock 数据
JobDO job = randomPojo(JobDO.class);
jobMapper.insert(job);
// 调用
jobService.triggerJob(job.getId());
// 校验调用
verify(schedulerManager).triggerJob(eq(job.getId()),
eq(job.getHandlerName()), eq(job.getHandlerParam()));
}
@Test
public void testDeleteJob_success() throws SchedulerException {
// mock 数据
JobDO job = randomPojo(JobDO.class);
jobMapper.insert(job);
// 调用
jobService.deleteJob(job.getId());
// 校验不存在
assertNull(jobMapper.selectById(job.getId()));
// 校验调用
verify(schedulerManager).deleteJob(eq(job.getHandlerName()));
}
@Test
public void testGetJobPage() {
// mock 数据
JobDO dbJob = randomPojo(JobDO.class, o -> {
o.setName("定时任务测试");
o.setHandlerName("handlerName 单元测试");
o.setStatus(JobStatusEnum.INIT.getStatus());
});
jobMapper.insert(dbJob);
// 测试 name 不匹配
jobMapper.insert(cloneIgnoreId(dbJob, o -> o.setName("土豆")));
// 测试 status 不匹配
jobMapper.insert(cloneIgnoreId(dbJob, o -> o.setStatus(JobStatusEnum.NORMAL.getStatus())));
// 测试 handlerName 不匹配
jobMapper.insert(cloneIgnoreId(dbJob, o -> o.setHandlerName(randomString())));
// 准备参数
JobPageReqVO reqVo = new JobPageReqVO();
reqVo.setName("定时");
reqVo.setStatus(JobStatusEnum.INIT.getStatus());
reqVo.setHandlerName("单元");
// 调用
PageResult<JobDO> pageResult = jobService.getJobPage(reqVo);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbJob, pageResult.getList().get(0));
}
@Test
public void testGetJob() {
// mock 数据
JobDO dbJob = randomPojo(JobDO.class);
jobMapper.insert(dbJob);
// 调用
JobDO job = jobService.getJob(dbJob.getId());
// 断言
assertPojoEquals(dbJob, job);
}
}

View File

@@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.infra.service.logger;
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiAccessLogCreateReqDTO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiAccessLogDO;
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiAccessLogMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import java.time.Duration;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
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.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Import(ApiAccessLogServiceImpl.class)
public class ApiAccessLogServiceImplTest extends BaseDbUnitTest {
@Resource
private ApiAccessLogServiceImpl apiAccessLogService;
@Resource
private ApiAccessLogMapper apiAccessLogMapper;
@Test
public void testGetApiAccessLogPage() {
ApiAccessLogDO apiAccessLogDO = randomPojo(ApiAccessLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setBeginTime(buildTime(2021, 3, 13));
o.setDuration(1000);
o.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
});
apiAccessLogMapper.insert(apiAccessLogDO);
// 测试 userId 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setRequestUrl("bar")));
// 测试 beginTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setBeginTime(buildTime(2021, 2, 6))));
// 测试 duration 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setDuration(100)));
// 测试 resultCode 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setResultCode(2)));
// 准备参数
ApiAccessLogPageReqVO reqVO = new ApiAccessLogPageReqVO();
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setBeginTime(buildBetweenTime(2021, 3, 13, 2021, 3, 13));
reqVO.setDuration(1000);
reqVO.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
// 调用
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getApiAccessLogPage(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(apiAccessLogDO, pageResult.getList().get(0));
}
@Test
public void testCleanJobLog() {
// mock 数据
ApiAccessLogDO log01 = randomPojo(ApiAccessLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))));
apiAccessLogMapper.insert(log01);
ApiAccessLogDO log02 = randomPojo(ApiAccessLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))));
apiAccessLogMapper.insert(log02);
// 准备参数
Integer exceedDay = 2;
Integer deleteLimit = 1;
// 调用
Integer count = apiAccessLogService.cleanAccessLog(exceedDay, deleteLimit);
// 断言
assertEquals(1, count);
List<ApiAccessLogDO> logs = apiAccessLogMapper.selectList();
assertEquals(1, logs.size());
// TODO @芋艿createTime updateTime 被屏蔽,仅 win11 会复现,建议后续修复。
assertPojoEquals(log02, logs.get(0), "createTime", "updateTime");
}
@Test
public void testCreateApiAccessLog() {
// 准备参数
ApiAccessLogCreateReqDTO createDTO = randomPojo(ApiAccessLogCreateReqDTO.class);
// 调用
apiAccessLogService.createApiAccessLog(createDTO);
// 断言
ApiAccessLogDO apiAccessLogDO = apiAccessLogMapper.selectOne(null);
assertPojoEquals(createDTO, apiAccessLogDO);
}
}

View File

@@ -0,0 +1,164 @@
package cn.iocoder.yudao.module.infra.service.logger;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO;
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper;
import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.util.List;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
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.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@Import(ApiErrorLogServiceImpl.class)
public class ApiErrorLogServiceImplTest extends BaseDbUnitTest {
@Resource
private ApiErrorLogServiceImpl apiErrorLogService;
@Resource
private ApiErrorLogMapper apiErrorLogMapper;
@Test
public void testGetApiErrorLogPage() {
// mock 数据
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setExceptionTime(buildTime(2021, 3, 13));
o.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
});
apiErrorLogMapper.insert(apiErrorLogDO);
// 测试 userId 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setRequestUrl("bar")));
// 测试 exceptionTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setExceptionTime(buildTime(2021, 2, 6))));
// 测试 progressStatus 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
// 准备参数
ApiErrorLogPageReqVO reqVO = new ApiErrorLogPageReqVO();
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setExceptionTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
reqVO.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
// 调用
PageResult<ApiErrorLogDO> pageResult = apiErrorLogService.getApiErrorLogPage(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(apiErrorLogDO, pageResult.getList().get(0));
}
@Test
public void testCreateApiErrorLog() {
// 准备参数
ApiErrorLogCreateReqDTO createDTO = randomPojo(ApiErrorLogCreateReqDTO.class);
// 调用
apiErrorLogService.createApiErrorLog(createDTO);
// 断言
ApiErrorLogDO apiErrorLogDO = apiErrorLogMapper.selectOne(null);
assertPojoEquals(createDTO, apiErrorLogDO);
assertEquals(ApiErrorLogProcessStatusEnum.INIT.getStatus(), apiErrorLogDO.getProcessStatus());
}
@Test
public void testUpdateApiErrorLogProcess_success() {
// 准备参数
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class,
o -> o.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus()));
apiErrorLogMapper.insert(apiErrorLogDO);
// 准备参数
Long id = apiErrorLogDO.getId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId);
// 断言
ApiErrorLogDO dbApiErrorLogDO = apiErrorLogMapper.selectById(apiErrorLogDO.getId());
assertEquals(processStatus, dbApiErrorLogDO.getProcessStatus());
assertEquals(processUserId, dbApiErrorLogDO.getProcessUserId());
assertNotNull(dbApiErrorLogDO.getProcessTime());
}
@Test
public void testUpdateApiErrorLogProcess_processed() {
// 准备参数
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class,
o -> o.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus()));
apiErrorLogMapper.insert(apiErrorLogDO);
// 准备参数
Long id = apiErrorLogDO.getId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用,并断言异常
assertServiceException(() ->
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId),
API_ERROR_LOG_PROCESSED);
}
@Test
public void testUpdateApiErrorLogProcess_notFound() {
// 准备参数
Long id = randomLongId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用,并断言异常
assertServiceException(() ->
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId),
API_ERROR_LOG_NOT_FOUND);
}
@Test
public void testCleanJobLog() {
// mock 数据
ApiErrorLogDO log01 = randomPojo(ApiErrorLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-3))));
apiErrorLogMapper.insert(log01);
ApiErrorLogDO log02 = randomPojo(ApiErrorLogDO.class, o -> o.setCreateTime(addTime(Duration.ofDays(-1))));
apiErrorLogMapper.insert(log02);
// 准备参数
Integer exceedDay = 2;
Integer deleteLimit = 1;
// 调用
Integer count = apiErrorLogService.cleanErrorLog(exceedDay, deleteLimit);
// 断言
assertEquals(1, count);
List<ApiErrorLogDO> logs = apiErrorLogMapper.selectList();
assertEquals(1, logs.size());
// TODO @芋艿createTime updateTime 被屏蔽,仅 win11 会复现,建议后续修复。
assertPojoEquals(log02, logs.get(0), "createTime", "updateTime");
}
}