package system import ( "bytes" "go/format" "strings" "testing" "text/template" "kra/pkg/utils/autocode" ) // TestTemplateFuncMap 测试模板函数映射是否正确注册 func TestTemplateFuncMap(t *testing.T) { funcMap := autocode.GetTemplateFuncMap() // 验证必要的函数已注册 requiredFuncs := []string{ "title", "toPascalCase", "GenerateField", "GenerateModelField", "GenerateSearchField", "GenerateSearchConditions", "GenerateGormGenSearchConditions", "GenerateSearchFormItem", "GenerateTableColumn", "GenerateFormItem", "GenerateDescriptionItem", "GenerateDefaultFormValue", "GenerateTSType", "GenerateReactSearchFormItem", "GenerateReactTableColumn", "GenerateReactProTableColumn", "GenerateReactFormItem", "GenerateReactDescriptionItem", "GenerateReactDefaultValue", "GenerateCustomMethodInterface", "GenerateCustomMethodImpl", } for _, funcName := range requiredFuncs { if _, ok := funcMap[funcName]; !ok { t.Errorf("模板函数 %s 未注册", funcName) } } } // TestGenerateTSType 测试 Go 类型到 TypeScript 类型的映射 func TestGenerateTSType(t *testing.T) { testCases := []struct { goType string expected string }{ // 整数类型 {"int", "number"}, {"int8", "number"}, {"int16", "number"}, {"int32", "number"}, {"int64", "number"}, // 无符号整数类型 {"uint", "number"}, {"uint8", "number"}, {"uint16", "number"}, {"uint32", "number"}, {"uint64", "number"}, // 浮点类型 {"float32", "number"}, {"float64", "number"}, // 基础类型 {"string", "string"}, {"bool", "boolean"}, // 时间类型 {"time.Time", "string"}, // JSON 类型 {"datatypes.JSON", "Record"}, {"json", "Record"}, // 媒体类型 {"picture", "string"}, {"pictures", "string[]"}, {"video", "string"}, {"file", "Array<{ name: string; url: string; uid: string }>"}, // 其他类型 {"array", "any[]"}, {"richtext", "string"}, {"enum", "string"}, // 指针类型 {"*string", "string"}, {"*int", "number"}, {"*bool", "boolean"}, {"*time.Time", "string"}, {"*int64", "number"}, {"*float64", "number"}, // 未知类型 {"unknown", "any"}, {"customType", "any"}, {"", "any"}, } for _, tc := range testCases { t.Run(tc.goType, func(t *testing.T) { result := autocode.GenerateTSType(tc.goType) if result != tc.expected { t.Errorf("GenerateTSType(%s) = %s, 期望 %s", tc.goType, result, tc.expected) } }) } } // TestGenerateTSTypePointerRecursion 测试指针类型的递归处理 func TestGenerateTSTypePointerRecursion(t *testing.T) { testCases := []struct { name string goType string expected string }{ {"单层指针-string", "*string", "string"}, {"单层指针-int", "*int", "number"}, {"单层指针-bool", "*bool", "boolean"}, {"单层指针-time", "*time.Time", "string"}, {"单层指针-json", "*datatypes.JSON", "Record"}, {"单层指针-未知类型", "*unknownType", "any"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateTSType(tc.goType) if result != tc.expected { t.Errorf("GenerateTSType(%s) = %s, 期望 %s", tc.goType, result, tc.expected) } }) } } // TestGenerateTSTypeAllNumericTypes 测试所有数值类型映射 func TestGenerateTSTypeAllNumericTypes(t *testing.T) { numericTypes := []string{ "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64", } for _, goType := range numericTypes { t.Run(goType, func(t *testing.T) { result := autocode.GenerateTSType(goType) if result != "number" { t.Errorf("GenerateTSType(%s) = %s, 期望 number", goType, result) } }) } } // TestGenerateTSTypeMediaTypes 测试媒体类型映射 func TestGenerateTSTypeMediaTypes(t *testing.T) { testCases := []struct { goType string expected string }{ {"picture", "string"}, {"pictures", "string[]"}, {"video", "string"}, {"file", "Array<{ name: string; url: string; uid: string }>"}, {"richtext", "string"}, } for _, tc := range testCases { t.Run(tc.goType, func(t *testing.T) { result := autocode.GenerateTSType(tc.goType) if result != tc.expected { t.Errorf("GenerateTSType(%s) = %s, 期望 %s", tc.goType, result, tc.expected) } }) } } // TestToPascalCase 测试 PascalCase 转换 func TestToPascalCase(t *testing.T) { testCases := []struct { input string expected string }{ {"article_status", "ArticleStatus"}, {"user-role", "UserRole"}, {"hello world", "HelloWorld"}, {"simple", "Simple"}, {"", ""}, {"already_Pascal_Case", "AlreadyPascalCase"}, } for _, tc := range testCases { result := autocode.ToPascalCase(tc.input) if result != tc.expected { t.Errorf("ToPascalCase(%s) = %s, 期望 %s", tc.input, result, tc.expected) } } } // TestGenerateModelField 测试 GORM Gen 模型字段生成 func TestGenerateModelField(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string // 生成的字段应包含的内容 }{ { name: "字符串字段", field: autocode.AutoCodeField{ FieldName: "Title", FieldDesc: "标题", FieldType: "string", FieldJson: "title", ColumnName: "title", DataTypeLong: "255", }, contains: []string{"Title", "string", "gorm:", "column:title", "json:\"title\"", "标题"}, }, { name: "整数字段", field: autocode.AutoCodeField{ FieldName: "Status", FieldDesc: "状态", FieldType: "int", FieldJson: "status", ColumnName: "status", DataTypeLong: "1", }, contains: []string{"Status", "int8", "gorm:", "column:status", "json:\"status\""}, }, { name: "时间字段", field: autocode.AutoCodeField{ FieldName: "CreatedAt", FieldDesc: "创建时间", FieldType: "time.Time", FieldJson: "createdAt", ColumnName: "created_at", }, contains: []string{"CreatedAt", "time.Time", "gorm:", "column:created_at"}, }, { name: "布尔字段", field: autocode.AutoCodeField{ FieldName: "IsActive", FieldDesc: "是否激活", FieldType: "bool", FieldJson: "isActive", ColumnName: "is_active", }, contains: []string{"IsActive", "bool", "gorm:", "column:is_active"}, }, { name: "JSON字段", field: autocode.AutoCodeField{ FieldName: "Extra", FieldDesc: "扩展信息", FieldType: "json", FieldJson: "extra", ColumnName: "extra", }, contains: []string{"Extra", "datatypes.JSON", "gorm:", "column:extra"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateModelField(tc.field) for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateModelField 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestGenerateGormGenSearchConditions 测试 GORM Gen 搜索条件生成 func TestGenerateGormGenSearchConditions(t *testing.T) { fields := []*autocode.AutoCodeField{ { FieldName: "Title", FieldType: "string", FieldSearchType: "LIKE", }, { FieldName: "Status", FieldType: "int", FieldSearchType: "EQ", }, { FieldName: "CreatedAt", FieldType: "time.Time", FieldSearchType: "BETWEEN", }, } result := autocode.GenerateGormGenSearchConditions(fields, "Article") // 验证 LIKE 条件 if !strings.Contains(result, "t.Title.Like") { t.Error("LIKE 搜索条件应使用 t.Title.Like") } // 验证 EQ 条件 if !strings.Contains(result, "t.Status.Eq") { t.Error("EQ 搜索条件应使用 t.Status.Eq") } // 验证 BETWEEN 条件 if !strings.Contains(result, "t.CreatedAt.Between") { t.Error("BETWEEN 搜索条件应使用 t.CreatedAt.Between") } } // TestGenerateReactFormItem 测试 React 表单项生成 func TestGenerateReactFormItem(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string }{ { name: "字符串输入框", field: autocode.AutoCodeField{ FieldName: "Title", FieldDesc: "标题", FieldType: "string", FieldJson: "title", Require: true, }, contains: []string{"Form.Item", "name=\"title\"", "label=\"标题\"", "Input", "required: true"}, }, { name: "布尔开关", field: autocode.AutoCodeField{ FieldName: "IsActive", FieldDesc: "是否激活", FieldType: "bool", FieldJson: "isActive", }, contains: []string{"Form.Item", "name=\"isActive\"", "Switch", "valuePropName=\"checked\""}, }, { name: "数字输入框", field: autocode.AutoCodeField{ FieldName: "Count", FieldDesc: "数量", FieldType: "int", FieldJson: "count", }, contains: []string{"Form.Item", "name=\"count\"", "InputNumber"}, }, { name: "日期选择器", field: autocode.AutoCodeField{ FieldName: "PublishDate", FieldDesc: "发布日期", FieldType: "time.Time", FieldJson: "publishDate", }, contains: []string{"Form.Item", "name=\"publishDate\"", "DatePicker", "showTime"}, }, { name: "字典选择器", field: autocode.AutoCodeField{ FieldName: "Status", FieldDesc: "状态", FieldType: "string", FieldJson: "status", DictType: "article_status", }, contains: []string{"Form.Item", "name=\"status\"", "Select", "article_statusOptions"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateReactFormItem(tc.field) for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateReactFormItem 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestGenerateReactTableColumn 测试 React 表格列生成 func TestGenerateReactTableColumn(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string }{ { name: "普通字符串列", field: autocode.AutoCodeField{ FieldName: "Title", FieldDesc: "标题", FieldType: "string", FieldJson: "title", }, contains: []string{"title: '标题'", "dataIndex: 'title'", "key: 'title'"}, }, { name: "可排序列", field: autocode.AutoCodeField{ FieldName: "CreatedAt", FieldDesc: "创建时间", FieldType: "time.Time", FieldJson: "createdAt", Sort: true, }, contains: []string{"title: '创建时间'", "sorter: true", "dayjs"}, }, { name: "布尔列", field: autocode.AutoCodeField{ FieldName: "IsActive", FieldDesc: "是否激活", FieldType: "bool", FieldJson: "isActive", }, contains: []string{"title: '是否激活'", "是", "否"}, }, { name: "图片列", field: autocode.AutoCodeField{ FieldName: "Cover", FieldDesc: "封面", FieldType: "picture", FieldJson: "cover", }, contains: []string{"title: '封面'", "Image"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateReactTableColumn(tc.field) for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateReactTableColumn 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestGenerateReactDefaultValue 测试 React 默认值生成 func TestGenerateReactDefaultValue(t *testing.T) { testCases := []struct { fieldType string fieldJson string expected string }{ {"bool", "isActive", "isActive: false,"}, {"string", "title", "title: '',"}, {"int", "count", "count: 0,"}, {"float64", "price", "price: 0,"}, {"time.Time", "createdAt", "createdAt: undefined,"}, {"pictures", "images", "images: [],"}, {"file", "attachment", "attachment: [],"}, {"json", "extra", "extra: {},"}, } for _, tc := range testCases { field := autocode.AutoCodeField{ FieldType: tc.fieldType, FieldJson: tc.fieldJson, } result := autocode.GenerateReactDefaultValue(field) if result != tc.expected { t.Errorf("GenerateReactDefaultValue(%s) = %s, 期望 %s", tc.fieldType, result, tc.expected) } } } // TestTemplateRenderingBasic 测试基本模板渲染功能 func TestTemplateRenderingBasic(t *testing.T) { // 简单模板测试 tplContent := `package {{.Package}} type {{.StructName}} struct { ID int64 } ` data := map[string]interface{}{ "Package": "example", "StructName": "Article", } tmpl, err := template.New("test").Funcs(autocode.GetTemplateFuncMap()).Parse(tplContent) if err != nil { t.Fatalf("模板解析失败: %v", err) } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { t.Fatalf("模板执行失败: %v", err) } result := buf.String() // 验证渲染结果 if !strings.Contains(result, "package example") { t.Error("渲染结果应包含 'package example'") } if !strings.Contains(result, "type Article struct") { t.Error("渲染结果应包含 'type Article struct'") } } // TestTemplateRenderingWithFunctions 测试带模板函数的渲染 func TestTemplateRenderingWithFunctions(t *testing.T) { tplContent := `// {{.Description}}类型定义 export interface {{.StructName}} { {{- range .Fields}} {{.FieldJson}}: {{ GenerateTSType .FieldType }}; // {{.FieldDesc}} {{- end}} } ` data := map[string]interface{}{ "Description": "文章", "StructName": "Article", "Fields": []autocode.AutoCodeField{ {FieldJson: "title", FieldType: "string", FieldDesc: "标题"}, {FieldJson: "content", FieldType: "string", FieldDesc: "内容"}, {FieldJson: "viewCount", FieldType: "int", FieldDesc: "浏览量"}, {FieldJson: "isPublished", FieldType: "bool", FieldDesc: "是否发布"}, }, } tmpl, err := template.New("test").Funcs(autocode.GetTemplateFuncMap()).Parse(tplContent) if err != nil { t.Fatalf("模板解析失败: %v", err) } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { t.Fatalf("模板执行失败: %v", err) } result := buf.String() // 验证 TypeScript 类型映射 if !strings.Contains(result, "title: string;") { t.Error("渲染结果应包含 'title: string;'") } if !strings.Contains(result, "viewCount: number;") { t.Error("渲染结果应包含 'viewCount: number;'") } if !strings.Contains(result, "isPublished: boolean;") { t.Error("渲染结果应包含 'isPublished: boolean;'") } } // TestGoCodeFormatting 测试 Go 代码格式化(Property 4: 模板渲染幂等性) func TestGoCodeFormatting(t *testing.T) { // 生成的 Go 代码 goCode := `package example import "fmt" type Article struct { ID int64 Title string Content string } func (a *Article) String() string { return fmt.Sprintf("Article{ID: %d, Title: %s}", a.ID, a.Title) } ` // 格式化代码 formatted, err := format.Source([]byte(goCode)) if err != nil { t.Fatalf("代码格式化失败: %v", err) } // 再次格式化,验证幂等性 formattedAgain, err := format.Source(formatted) if err != nil { t.Fatalf("二次格式化失败: %v", err) } // 验证两次格式化结果相同 if string(formatted) != string(formattedAgain) { t.Error("格式化应该是幂等的,两次格式化结果应相同") } } // TestGenerateSearchField 测试搜索字段生成 func TestGenerateSearchField(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string isEmpty bool }{ { name: "无搜索类型", field: autocode.AutoCodeField{ FieldName: "Title", FieldType: "string", FieldJson: "title", FieldSearchType: "", }, isEmpty: true, }, { name: "LIKE搜索", field: autocode.AutoCodeField{ FieldName: "Title", FieldType: "string", FieldJson: "title", FieldSearchType: "LIKE", }, contains: []string{"Title", "*string", "json:\"title\""}, }, { name: "BETWEEN搜索-时间", field: autocode.AutoCodeField{ FieldName: "CreatedAt", FieldType: "time.Time", FieldJson: "createdAt", FieldSearchType: "BETWEEN", }, contains: []string{"CreatedAtRange", "[]time.Time"}, }, { name: "BETWEEN搜索-数字", field: autocode.AutoCodeField{ FieldName: "Price", FieldType: "float64", FieldJson: "price", FieldSearchType: "BETWEEN", }, contains: []string{"StartPrice", "EndPrice", "*float64"}, }, { name: "IN搜索", field: autocode.AutoCodeField{ FieldName: "Status", FieldType: "int", FieldJson: "status", FieldSearchType: "IN", }, contains: []string{"Status", "[]int"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateSearchField(tc.field) if tc.isEmpty { if result != "" { t.Errorf("期望空字符串,实际: %s", result) } return } for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateSearchField 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestGenerateReactDescriptionItem 测试 React 描述项生成 func TestGenerateReactDescriptionItem(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string }{ { name: "普通字符串", field: autocode.AutoCodeField{ FieldName: "Title", FieldDesc: "标题", FieldType: "string", FieldJson: "title", }, contains: []string{"Descriptions.Item", "label=\"标题\"", "currentRecord?.title"}, }, { name: "布尔类型", field: autocode.AutoCodeField{ FieldName: "IsActive", FieldDesc: "是否激活", FieldType: "bool", FieldJson: "isActive", }, contains: []string{"Descriptions.Item", "是", "否"}, }, { name: "时间类型", field: autocode.AutoCodeField{ FieldName: "CreatedAt", FieldDesc: "创建时间", FieldType: "time.Time", FieldJson: "createdAt", }, contains: []string{"Descriptions.Item", "dayjs", "format"}, }, { name: "图片类型", field: autocode.AutoCodeField{ FieldName: "Cover", FieldDesc: "封面", FieldType: "picture", FieldJson: "cover", }, contains: []string{"Descriptions.Item", "Image"}, }, { name: "JSON类型", field: autocode.AutoCodeField{ FieldName: "Extra", FieldDesc: "扩展信息", FieldType: "json", FieldJson: "extra", }, contains: []string{"Descriptions.Item", "JSON.stringify"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateReactDescriptionItem(tc.field) for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateReactDescriptionItem 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestGenerateReactProTableColumn 测试 Ant Design Pro Table 列配置生成 func TestGenerateReactProTableColumn(t *testing.T) { testCases := []struct { name string field autocode.AutoCodeField contains []string }{ { name: "普通文本列", field: autocode.AutoCodeField{ FieldName: "Title", FieldDesc: "标题", FieldType: "string", FieldJson: "title", }, contains: []string{"title: '标题'", "dataIndex: 'title'", "ellipsis: true"}, }, { name: "布尔列", field: autocode.AutoCodeField{ FieldName: "IsActive", FieldDesc: "是否激活", FieldType: "bool", FieldJson: "isActive", }, contains: []string{"valueType: 'select'", "valueEnum", "是", "否"}, }, { name: "时间列-范围搜索", field: autocode.AutoCodeField{ FieldName: "CreatedAt", FieldDesc: "创建时间", FieldType: "time.Time", FieldJson: "createdAt", FieldSearchType: "BETWEEN", }, contains: []string{"valueType: 'dateTimeRange'"}, }, { name: "数字列", field: autocode.AutoCodeField{ FieldName: "Count", FieldDesc: "数量", FieldType: "int", FieldJson: "count", FieldSearchType: "EQ", }, contains: []string{"valueType: 'digit'"}, }, { name: "字典列", field: autocode.AutoCodeField{ FieldName: "Status", FieldDesc: "状态", FieldType: "string", FieldJson: "status", DictType: "article_status", }, contains: []string{"valueType: 'select'", "article_statusOptions"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := autocode.GenerateReactProTableColumn(tc.field) for _, expected := range tc.contains { if !strings.Contains(result, expected) { t.Errorf("GenerateReactProTableColumn 结果应包含 %s, 实际结果: %s", expected, result) } } }) } } // TestComplexTemplateRendering 测试复杂模板渲染(模拟实际 biz 层模板) func TestComplexTemplateRendering(t *testing.T) { tplContent := `package {{.Package}} import ( "context" "{{.Module}}/internal/data/model/{{.Package}}" "github.com/go-kratos/kratos/v2/log" ) // {{.StructName}}Repo {{.Description}}仓储接口 type {{.StructName}}Repo interface { Create(ctx context.Context, {{.Abbreviation}} *{{.Package}}.{{.StructName}}) error Delete(ctx context.Context, {{.PrimaryField.FieldJson}} {{.PrimaryField.FieldType}}) error Update(ctx context.Context, {{.Abbreviation}} *{{.Package}}.{{.StructName}}) error FindByID(ctx context.Context, {{.PrimaryField.FieldJson}} {{.PrimaryField.FieldType}}) (*{{.Package}}.{{.StructName}}, error) } // {{.StructName}}Usecase {{.Description}}用例 type {{.StructName}}Usecase struct { repo {{.StructName}}Repo log *log.Helper } ` data := map[string]interface{}{ "Package": "article", "Module": "kra", "StructName": "Article", "Description": "文章", "Abbreviation": "article", "PrimaryField": map[string]interface{}{ "FieldJson": "id", "FieldType": "int64", }, } tmpl, err := template.New("test").Funcs(autocode.GetTemplateFuncMap()).Parse(tplContent) if err != nil { t.Fatalf("模板解析失败: %v", err) } var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { t.Fatalf("模板执行失败: %v", err) } result := buf.String() // 验证关键内容 expectedContents := []string{ "package article", "ArticleRepo interface", "ArticleUsecase struct", "Create(ctx context.Context", "Delete(ctx context.Context", "Update(ctx context.Context", "FindByID(ctx context.Context", "*article.Article", "log.Helper", } for _, expected := range expectedContents { if !strings.Contains(result, expected) { t.Errorf("渲染结果应包含 '%s'", expected) } } }