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", "", "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") } }