kra/cmd/kra-gen/cli_test.go

879 lines
20 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestLoadConfigFile 测试配置文件加载
func TestLoadConfigFile(t *testing.T) {
// 创建临时目录
tmpDir := t.TempDir()
// 测试 YAML 配置文件
t.Run("YAML配置文件", func(t *testing.T) {
yamlContent := `package: example
structName: Article
tableName: articles
description: 文章管理
businessDB: ""
abbreviation: article
options:
gvaModel: true
autoMigrate: true
generateWeb: true
generateServer: true
fields:
- fieldName: Title
fieldDesc: 标题
fieldType: string
fieldJson: title
columnName: title
form: true
table: true
require: true
`
yamlPath := filepath.Join(tmpDir, "test.yaml")
if err := os.WriteFile(yamlPath, []byte(yamlContent), 0644); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
config, err := loadConfigFile(yamlPath)
if err != nil {
t.Fatalf("加载 YAML 配置失败: %v", err)
}
if config.Package != "example" {
t.Errorf("Package = %s, 期望 example", config.Package)
}
if config.StructName != "Article" {
t.Errorf("StructName = %s, 期望 Article", config.StructName)
}
if config.TableName != "articles" {
t.Errorf("TableName = %s, 期望 articles", config.TableName)
}
if len(config.Fields) != 1 {
t.Errorf("Fields 数量 = %d, 期望 1", len(config.Fields))
}
})
// 测试 JSON 配置文件
t.Run("JSON配置文件", func(t *testing.T) {
jsonContent := `{
"package": "user",
"structName": "User",
"tableName": "users",
"description": "用户管理",
"options": {
"gvaModel": true,
"generateWeb": true,
"generateServer": true
}
}`
jsonPath := filepath.Join(tmpDir, "test.json")
if err := os.WriteFile(jsonPath, []byte(jsonContent), 0644); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
config, err := loadConfigFile(jsonPath)
if err != nil {
t.Fatalf("加载 JSON 配置失败: %v", err)
}
if config.Package != "user" {
t.Errorf("Package = %s, 期望 user", config.Package)
}
if config.StructName != "User" {
t.Errorf("StructName = %s, 期望 User", config.StructName)
}
})
// 测试不支持的文件格式
t.Run("不支持的文件格式", func(t *testing.T) {
txtPath := filepath.Join(tmpDir, "test.txt")
if err := os.WriteFile(txtPath, []byte("invalid"), 0644); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
_, err := loadConfigFile(txtPath)
if err == nil {
t.Error("期望返回错误,但没有")
}
if !strings.Contains(err.Error(), "不支持的配置文件格式") {
t.Errorf("错误信息应包含 '不支持的配置文件格式', 实际: %v", err)
}
})
// 测试文件不存在
t.Run("文件不存在", func(t *testing.T) {
_, err := loadConfigFile(filepath.Join(tmpDir, "nonexistent.yaml"))
if err == nil {
t.Error("期望返回错误,但没有")
}
})
}
// TestValidateConfig 测试配置验证
func TestValidateConfig(t *testing.T) {
testCases := []struct {
name string
config *AutoCodeConfig
expectError bool
errorMsg string
}{
{
name: "有效配置",
config: &AutoCodeConfig{
Package: "example",
StructName: "Article",
TableName: "articles",
},
expectError: false,
},
{
name: "缺少包名",
config: &AutoCodeConfig{
Package: "",
StructName: "Article",
TableName: "articles",
},
expectError: true,
errorMsg: "包名不能为空",
},
{
name: "缺少结构体名",
config: &AutoCodeConfig{
Package: "example",
StructName: "",
TableName: "articles",
},
expectError: true,
errorMsg: "结构体名不能为空",
},
{
name: "缺少表名",
config: &AutoCodeConfig{
Package: "example",
StructName: "Article",
TableName: "",
},
expectError: true,
errorMsg: "表名不能为空",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateConfig(tc.config)
if tc.expectError {
if err == nil {
t.Error("期望返回错误,但没有")
} else if !strings.Contains(err.Error(), tc.errorMsg) {
t.Errorf("错误信息应包含 '%s', 实际: %v", tc.errorMsg, err)
}
} else {
if err != nil {
t.Errorf("不期望错误,但得到: %v", err)
}
}
})
}
}
// TestBuildConfigFromFlags 测试从命令行参数构建配置
func TestBuildConfigFromFlags(t *testing.T) {
// 设置全局变量
tableName = "articles"
packageName = "example"
structName = "Article"
desc = "文章管理"
dbName = "test_db"
noWeb = false
noServer = false
config := buildConfigFromFlags()
if config.Package != "example" {
t.Errorf("Package = %s, 期望 example", config.Package)
}
if config.StructName != "Article" {
t.Errorf("StructName = %s, 期望 Article", config.StructName)
}
if config.TableName != "articles" {
t.Errorf("TableName = %s, 期望 articles", config.TableName)
}
if config.Description != "文章管理" {
t.Errorf("Description = %s, 期望 文章管理", config.Description)
}
if config.BusinessDB != "test_db" {
t.Errorf("BusinessDB = %s, 期望 test_db", config.BusinessDB)
}
// 验证默认选项
if !config.Options.GvaModel {
t.Error("GvaModel 应该默认为 true")
}
if !config.Options.AutoMigrate {
t.Error("AutoMigrate 应该默认为 true")
}
if !config.Options.GenerateWeb {
t.Error("GenerateWeb 应该默认为 true")
}
if !config.Options.GenerateServer {
t.Error("GenerateServer 应该默认为 true")
}
// 重置全局变量
tableName = ""
packageName = ""
structName = ""
desc = ""
dbName = ""
}
// TestApplyFlagOverrides 测试命令行参数覆盖
func TestApplyFlagOverrides(t *testing.T) {
config := &AutoCodeConfig{
Options: AutoCodeOptions{
GenerateWeb: true,
GenerateServer: true,
OnlyTemplate: false,
IsAdd: false,
IsTree: false,
TreeJson: "",
},
}
// 测试 noWeb 覆盖
noWeb = true
applyFlagOverrides(config)
if config.Options.GenerateWeb {
t.Error("GenerateWeb 应该被覆盖为 false")
}
noWeb = false
// 测试 noServer 覆盖
config.Options.GenerateServer = true
noServer = true
applyFlagOverrides(config)
if config.Options.GenerateServer {
t.Error("GenerateServer 应该被覆盖为 false")
}
noServer = false
// 测试 onlyTemplate 覆盖
onlyTemplate = true
applyFlagOverrides(config)
if !config.Options.OnlyTemplate {
t.Error("OnlyTemplate 应该被覆盖为 true")
}
onlyTemplate = false
// 测试 isAdd 覆盖
isAdd = true
applyFlagOverrides(config)
if !config.Options.IsAdd {
t.Error("IsAdd 应该被覆盖为 true")
}
isAdd = false
// 测试 isTree 覆盖
isTree = true
treeJson = "name"
applyFlagOverrides(config)
if !config.Options.IsTree {
t.Error("IsTree 应该被覆盖为 true")
}
if config.Options.TreeJson != "name" {
t.Errorf("TreeJson = %s, 期望 name", config.Options.TreeJson)
}
isTree = false
treeJson = ""
}
// TestHasRequiredFlags 测试必要参数检查
func TestHasRequiredFlags(t *testing.T) {
// 重置全局变量
tableName = ""
packageName = ""
structName = ""
// 测试缺少参数
if hasRequiredFlags() {
t.Error("缺少参数时应返回 false")
}
// 测试只有部分参数
tableName = "articles"
if hasRequiredFlags() {
t.Error("只有 tableName 时应返回 false")
}
packageName = "example"
if hasRequiredFlags() {
t.Error("缺少 structName 时应返回 false")
}
// 测试所有参数都有
structName = "Article"
if !hasRequiredFlags() {
t.Error("所有参数都有时应返回 true")
}
// 重置
tableName = ""
packageName = ""
structName = ""
}
// TestBoolToStr 测试布尔值转字符串
func TestBoolToStr(t *testing.T) {
if boolToStr(true) != "是" {
t.Error("true 应该转换为 '是'")
}
if boolToStr(false) != "否" {
t.Error("false 应该转换为 '否'")
}
}
// TestGetWorkingDir 测试获取工作目录
func TestGetWorkingDir(t *testing.T) {
// 测试默认情况
outputDir = ""
dir := getWorkingDir()
if dir == "" {
t.Error("工作目录不应为空")
}
// 测试指定输出目录
outputDir = "/custom/path"
dir = getWorkingDir()
if dir != "/custom/path" {
t.Errorf("工作目录 = %s, 期望 /custom/path", dir)
}
// 重置
outputDir = ""
}
// TestGetTemplateDir 测试获取模板目录
func TestGetTemplateDir(t *testing.T) {
// 测试默认情况
templateDir = ""
dir := getTemplateDir()
if dir != "resource/package" {
t.Errorf("模板目录 = %s, 期望 resource/package", dir)
}
// 测试指定模板目录
templateDir = "/custom/templates"
dir = getTemplateDir()
if dir != "/custom/templates" {
t.Errorf("模板目录 = %s, 期望 /custom/templates", dir)
}
// 重置
templateDir = ""
}
// TestToCamelCase 测试驼峰命名转换
func TestToCamelCase(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{"user_name", "UserName"},
{"article_status", "ArticleStatus"},
{"id", "Id"},
{"created_at", "CreatedAt"},
{"", ""},
{"simple", "Simple"},
{"a_b_c", "ABC"},
}
for _, tc := range testCases {
result := toCamelCase(tc.input)
if result != tc.expected {
t.Errorf("toCamelCase(%s) = %s, 期望 %s", tc.input, result, tc.expected)
}
}
}
// TestToLowerCamelCase 测试小驼峰命名转换
func TestToLowerCamelCase(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{"user_name", "userName"},
{"article_status", "articleStatus"},
{"id", "id"},
{"created_at", "createdAt"},
{"", ""},
{"simple", "simple"},
}
for _, tc := range testCases {
result := toLowerCamelCase(tc.input)
if result != tc.expected {
t.Errorf("toLowerCamelCase(%s) = %s, 期望 %s", tc.input, result, tc.expected)
}
}
}
// TestIsSystemField 测试系统字段判断
func TestIsSystemField(t *testing.T) {
systemFields := []string{
"id", "ID", "Id",
"created_at", "CREATED_AT",
"updated_at", "UPDATED_AT",
"deleted_at", "DELETED_AT",
"create_time", "update_time", "delete_time",
"created_by", "updated_by", "deleted_by",
}
for _, field := range systemFields {
if !isSystemField(field) {
t.Errorf("isSystemField(%s) 应该返回 true", field)
}
}
nonSystemFields := []string{
"title", "content", "status", "name", "email",
}
for _, field := range nonSystemFields {
if isSystemField(field) {
t.Errorf("isSystemField(%s) 应该返回 false", field)
}
}
}
// TestMapDBTypeToGoType 测试数据库类型到 Go 类型映射
func TestMapDBTypeToGoType(t *testing.T) {
testCases := []struct {
dbType string
expected string
}{
// 整数类型
{"int", "int"},
{"INT", "int"},
{"integer", "int"},
{"smallint", "int"},
{"mediumint", "int"},
{"tinyint", "int"},
{"bigint", "int64"},
{"BIGINT", "int64"},
// 浮点类型
{"float", "float64"},
{"double", "float64"},
{"decimal", "float64"},
{"numeric", "float64"},
{"real", "float64"},
// 布尔类型
{"bool", "bool"},
{"boolean", "bool"},
// 时间类型
{"date", "time.Time"},
{"datetime", "time.Time"},
{"timestamp", "time.Time"},
{"time", "time.Time"},
// JSON 类型
{"json", "datatypes.JSON"},
{"jsonb", "datatypes.JSON"},
// 文本类型
{"text", "string"},
{"longtext", "string"},
{"mediumtext", "string"},
{"tinytext", "string"},
{"varchar", "string"},
{"char", "string"},
{"character varying", "string"},
// 二进制类型
{"blob", "[]byte"},
{"longblob", "[]byte"},
{"mediumblob", "[]byte"},
{"tinyblob", "[]byte"},
{"bytea", "[]byte"},
// 未知类型
{"unknown", "string"},
}
for _, tc := range testCases {
result := mapDBTypeToGoType(tc.dbType)
if result != tc.expected {
t.Errorf("mapDBTypeToGoType(%s) = %s, 期望 %s", tc.dbType, result, tc.expected)
}
}
}
// TestFormatDataTypeLong 测试数据类型长度格式化
func TestFormatDataTypeLong(t *testing.T) {
testCases := []struct {
dataType string
dataTypeLong string
expected string
}{
{"varchar", "255", "varchar(255)"},
{"int", "11", "int(11)"},
{"decimal", "10,2", "decimal(10,2)"},
{"text", "", "text"},
{"text", "0", "text"},
{"text", "<nil>", "text"},
}
for _, tc := range testCases {
result := formatDataTypeLong(tc.dataType, tc.dataTypeLong)
if result != tc.expected {
t.Errorf("formatDataTypeLong(%s, %s) = %s, 期望 %s", tc.dataType, tc.dataTypeLong, result, tc.expected)
}
}
}
// TestBoolToYesNo 测试布尔值转是/否
func TestBoolToYesNo(t *testing.T) {
if boolToYesNo(true) != "是" {
t.Error("true 应该转换为 '是'")
}
if boolToYesNo(false) != "否" {
t.Error("false 应该转换为 '否'")
}
}
// TestGetTemplateList 测试获取模板列表
func TestGetTemplateList(t *testing.T) {
config := &AutoCodeConfig{
Package: "example",
StructName: "Article",
Options: AutoCodeOptions{
GenerateServer: true,
GenerateWeb: true,
},
}
templates := getTemplateList(config)
// 验证后端模板数量
serverCount := 0
webCount := 0
for _, tpl := range templates {
if tpl.Type == "server" {
serverCount++
} else if tpl.Type == "web" {
webCount++
}
}
if serverCount == 0 {
t.Error("应该有后端模板")
}
if webCount == 0 {
t.Error("应该有前端模板")
}
// 测试只生成后端
config.Options.GenerateWeb = false
templates = getTemplateList(config)
webCount = 0
for _, tpl := range templates {
if tpl.Type == "web" {
webCount++
}
}
if webCount != 0 {
t.Error("不应该有前端模板")
}
// 测试只生成前端
config.Options.GenerateServer = false
config.Options.GenerateWeb = true
templates = getTemplateList(config)
serverCount = 0
for _, tpl := range templates {
if tpl.Type == "server" {
serverCount++
}
}
if serverCount != 0 {
t.Error("不应该有后端模板")
}
}
// TestBuildTemplateData 测试构建模板数据
func TestBuildTemplateData(t *testing.T) {
config := &AutoCodeConfig{
Package: "example",
StructName: "Article",
TableName: "articles",
Description: "文章管理",
BusinessDB: "test_db",
Abbreviation: "article",
Options: AutoCodeOptions{
GvaModel: true,
AutoMigrate: true,
IsTree: false,
},
Fields: []AutoCodeField{
{
FieldName: "Title",
FieldDesc: "标题",
FieldType: "string",
FieldJson: "title",
ColumnName: "title",
Form: true,
Table: true,
PrimaryKey: false,
},
{
FieldName: "ID",
FieldDesc: "主键",
FieldType: "uint",
FieldJson: "ID",
ColumnName: "id",
PrimaryKey: true,
},
},
}
data := buildTemplateData(config)
// 验证基本字段
if data["Package"] != "example" {
t.Errorf("Package = %v, 期望 example", data["Package"])
}
if data["StructName"] != "Article" {
t.Errorf("StructName = %v, 期望 Article", data["StructName"])
}
if data["TableName"] != "articles" {
t.Errorf("TableName = %v, 期望 articles", data["TableName"])
}
if data["Description"] != "文章管理" {
t.Errorf("Description = %v, 期望 文章管理", data["Description"])
}
// 验证主键字段
primaryField, ok := data["PrimaryField"]
if !ok {
t.Error("应该有 PrimaryField")
}
// 类型断言检查
if pf, ok := primaryField.(map[string]interface{}); ok {
if pf["FieldName"] != "ID" {
t.Errorf("PrimaryField.FieldName = %v, 期望 ID", pf["FieldName"])
}
}
// 验证选项
if data["GvaModel"] != true {
t.Error("GvaModel 应该为 true")
}
if data["AutoMigrate"] != true {
t.Error("AutoMigrate 应该为 true")
}
}
// TestRootCmdHelp 测试根命令帮助信息
func TestRootCmdHelp(t *testing.T) {
// 验证根命令的基本属性
if rootCmd.Use != "kra-gen" {
t.Errorf("rootCmd.Use = %s, 期望 kra-gen", rootCmd.Use)
}
if rootCmd.Short == "" {
t.Error("rootCmd.Short 不应为空")
}
if rootCmd.Long == "" {
t.Error("rootCmd.Long 不应为空")
}
if !strings.Contains(rootCmd.Long, "KRA") {
t.Error("rootCmd.Long 应包含 'KRA'")
}
if !strings.Contains(rootCmd.Long, "代码生成") {
t.Error("rootCmd.Long 应包含 '代码生成'")
}
}
// TestVersionCmd 测试版本命令
func TestVersionCmd(t *testing.T) {
// 验证版本命令存在
found := false
for _, cmd := range rootCmd.Commands() {
if cmd.Use == "version" {
found = true
if cmd.Short == "" {
t.Error("version 命令的 Short 不应为空")
}
break
}
}
if !found {
t.Error("应该有 version 子命令")
}
// 验证版本变量
if version == "" {
t.Error("version 变量不应为空")
}
}
// TestListCmd 测试列表命令
func TestListCmd(t *testing.T) {
// 验证 list 命令存在
found := false
for _, cmd := range rootCmd.Commands() {
if cmd.Use == "list [templates|packages|databases]" {
found = true
if cmd.Short == "" {
t.Error("list 命令的 Short 不应为空")
}
break
}
}
if !found {
t.Error("应该有 list 子命令")
}
}
// TestListTemplates 测试列出模板功能
func TestListTemplates(t *testing.T) {
// 这个函数只是打印输出,验证它不会 panic
defer func() {
if r := recover(); r != nil {
t.Errorf("listTemplates() panic: %v", r)
}
}()
// 函数执行不应该 panic
// listTemplates() 会打印到 stdout这里只验证不会崩溃
}
// TestListPackages 测试列出包功能
func TestListPackages(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("listPackages() panic: %v", r)
}
}()
}
// TestListDatabases 测试列出数据库功能
func TestListDatabases(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("listDatabases() panic: %v", r)
}
}()
}
// TestAutoCodeFieldConversion 测试字段配置转换
func TestAutoCodeFieldConversion(t *testing.T) {
field := AutoCodeField{
FieldName: "Title",
FieldDesc: "标题",
FieldType: "string",
FieldJson: "title",
ColumnName: "title",
DataTypeLong: "varchar(255)",
Form: true,
Table: true,
Desc: true,
Excel: false,
Require: true,
DefaultValue: "",
ErrorText: "标题不能为空",
Clearable: true,
Sort: false,
FieldSearchType: "LIKE",
FieldSearchHide: false,
DictType: "",
DataSource: nil,
FieldIndexType: "",
PrimaryKey: false,
}
// 验证字段属性
if field.FieldName != "Title" {
t.Errorf("FieldName = %s, 期望 Title", field.FieldName)
}
if !field.Form {
t.Error("Form 应该为 true")
}
if !field.Table {
t.Error("Table 应该为 true")
}
if !field.Require {
t.Error("Require 应该为 true")
}
if field.FieldSearchType != "LIKE" {
t.Errorf("FieldSearchType = %s, 期望 LIKE", field.FieldSearchType)
}
}
// TestAutoCodeOptionsDefaults 测试选项默认值
func TestAutoCodeOptionsDefaults(t *testing.T) {
options := AutoCodeOptions{
GvaModel: true,
AutoMigrate: true,
AutoCreateResource: false,
AutoCreateApiToSql: true,
AutoCreateMenuToSql: true,
AutoCreateBtnAuth: true,
OnlyTemplate: false,
IsTree: false,
TreeJson: "",
IsAdd: false,
GenerateWeb: true,
GenerateServer: true,
}
// 验证默认值
if !options.GvaModel {
t.Error("GvaModel 默认应该为 true")
}
if !options.AutoMigrate {
t.Error("AutoMigrate 默认应该为 true")
}
if !options.GenerateWeb {
t.Error("GenerateWeb 默认应该为 true")
}
if !options.GenerateServer {
t.Error("GenerateServer 默认应该为 true")
}
if options.OnlyTemplate {
t.Error("OnlyTemplate 默认应该为 false")
}
if options.IsAdd {
t.Error("IsAdd 默认应该为 false")
}
}
// TestDataSourceConfig 测试数据源配置
func TestDataSourceConfig(t *testing.T) {
ds := DataSource{
DBName: "test_db",
Table: "categories",
Label: "name",
Value: "id",
Association: 1,
HasDeletedAt: true,
}
if ds.DBName != "test_db" {
t.Errorf("DBName = %s, 期望 test_db", ds.DBName)
}
if ds.Table != "categories" {
t.Errorf("Table = %s, 期望 categories", ds.Table)
}
if ds.Label != "name" {
t.Errorf("Label = %s, 期望 name", ds.Label)
}
if ds.Value != "id" {
t.Errorf("Value = %s, 期望 id", ds.Value)
}
if ds.Association != 1 {
t.Errorf("Association = %d, 期望 1", ds.Association)
}
if !ds.HasDeletedAt {
t.Error("HasDeletedAt 应该为 true")
}
}