package structx_test import ( "fmt" "strings" "testing" "time" "code.yun.ink/pkg/structx" "github.com/shopspring/decimal" ) func TestAttactToStructMap(t *testing.T) { m := make(map[string]interface{}) m["name"] = "Tom" m["age"] = 18 m["is_man"] = true fmt.Println(m) d := Data{} r, err := structx.AttactToStructAny(&d, m) fmt.Printf("%+v %+v %+v", d, r, err) } type Data struct { Name string `json:"name"` Age int `json:"age"` IsMan bool `json:"is_man"` Addr string `json:"addr"` } // 基础测试结构体 type BasicStruct struct { Name string `json:"name"` Age int `json:"age"` Salary float64 `json:"salary"` IsActive bool `json:"is_active"` Count uint `json:"count"` } // 嵌套结构体 type NestedStruct struct { Basic BasicStruct `json:"basic"` Comment string `json:"comment"` Amount decimal.Decimal `json:"amount"` Amount2 *decimal.Decimal `json:"amount2"` Timestamp time.Time `json:"timestamp"` } // 指针嵌套结构体 type PointerStruct struct { Basic *BasicStruct `json:"basic"` Enabled bool `json:"enabled"` } // 多层嵌套结构体 type MultiLevelStruct struct { Nested NestedStruct `json:"nested"` Pointer *PointerStruct `json:"pointer"` Tags []string `json:"tags"` } // 自定义类型 type CustomString string type CustomInt int type CustomTypeStruct struct { ID CustomString `json:"id"` Version CustomInt `json:"version"` Email string `json:"email"` } // 实现 TextUnmarshaler 接口的类型 type CustomUnmarshaler string func (c *CustomUnmarshaler) UnmarshalText(text []byte) error { *c = CustomUnmarshaler("custom_" + string(text)) return nil } type UnmarshalerStruct struct { Data CustomUnmarshaler `json:"data"` Name string `json:"name"` } // 复杂嵌套结构体 type ComplexStruct struct { Basic BasicStruct `json:"basic"` Nested *NestedStruct `json:"nested"` Custom CustomTypeStruct `json:"custom"` Timestamp time.Time `json:"timestamp"` Metadata map[string]string `json:"metadata"` Unmarshaler CustomUnmarshaler `json:"unmarshaler"` } // 基础类型测试 func TestAttactToStruct_BasicTypes(t *testing.T) { tests := []struct { name string input map[string]string expected BasicStruct wantErr bool }{ { name: "所有基础类型", input: map[string]string{ "name": "John Doe", "age": "30", "salary": "50000.50", "is_active": "true", "count": "100", }, expected: BasicStruct{ Name: "John Doe", Age: 30, Salary: 50000.50, IsActive: true, Count: 100, }, wantErr: false, }, { name: "部分字段", input: map[string]string{ "name": "Alice", "age": "25", }, expected: BasicStruct{ Name: "Alice", Age: 25, }, wantErr: false, }, { name: "无效布尔值", input: map[string]string{ "is_active": "invalid", }, wantErr: true, }, { name: "无效数字", input: map[string]string{ "age": "not_a_number", }, wantErr: true, }, { name: "空字符串处理", input: map[string]string{ "name": "", "age": "0", }, expected: BasicStruct{ Name: "", Age: 0, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s BasicStruct changes, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if s != tt.expected { t.Errorf("期望 %+v, 得到 %+v", tt.expected, s) } // 验证变更记录 if len(changes) != len(tt.input) { t.Errorf("期望 %d 个变更记录, 得到 %d", len(tt.input), len(changes)) } }) } } // 测试嵌套结构体 func TestAttactToStruct_NestedStruct(t *testing.T) { tests := []struct { name string input map[string]string expected NestedStruct wantErr bool }{ // { // name: "嵌套结构体赋值", // input: map[string]string{ // "basic.name": "John", // "basic.age": "30", // "basic.salary": "50000.0", // "basic.is_active": "true", // "comment": "test comment", // }, // expected: NestedStruct{ // Basic: BasicStruct{ // Name: "John", // Age: 30, // Salary: 50000.0, // IsActive: true, // }, // Comment: "test comment", // }, // wantErr: false, // }, { name: "部分嵌套字段", input: map[string]string{ "basic.name": "Alice", "comment": "partial", "amount": "123.45", "amount2": "123.45", // 测试指针字段 "timestamp": "2024-01-01T15:04:05Z", }, expected: NestedStruct{ Basic: BasicStruct{ Name: "Alice", }, Comment: "partial", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s NestedStruct changes, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if s.Basic.Name != tt.expected.Basic.Name || s.Basic.Age != tt.expected.Basic.Age || s.Comment != tt.expected.Comment { t.Errorf("期望 %+v, 得到 %+v", tt.expected, s) } // 验证嵌套字段的变更记录 for key := range tt.input { if _, exists := changes[key]; !exists { t.Errorf("缺少变更记录 for key: %s", key) } } }) } } // 测试指针嵌套结构体 func TestAttactToStruct_PointerNested(t *testing.T) { tests := []struct { name string input map[string]string expected PointerStruct wantErr bool }{ // { // name: "指针嵌套结构体", // input: map[string]string{ // "basic.name": "John", // "basic.age": "30", // "basic.is_active": "true", // "enabled": "true", // }, // expected: PointerStruct{ // Basic: &BasicStruct{ // Name: "John", // Age: 30, // IsActive: true, // }, // Enabled: true, // }, // wantErr: false, // }, { name: "空指针初始化", input: map[string]string{ "basic.name": "New User", "enabled": "true", }, expected: PointerStruct{ Basic: &BasicStruct{ Name: "New User", }, Enabled: true, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s PointerStruct changes, err := structx.AttactToStruct(&s, tt.input) _ = changes if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if s.Basic == nil || s.Basic.Name != tt.expected.Basic.Name || s.Enabled != tt.expected.Enabled { t.Errorf("期望 %+v, 得到 %+v", tt.expected, s) } }) } } // 自定义类型测试 func TestAttactToStruct_CustomTypes(t *testing.T) { tests := []struct { name string input map[string]string expected CustomTypeStruct wantErr bool }{ { name: "自定义类型转换", input: map[string]string{ "id": "user_123", "version": "2", "email": "test@example.com", }, expected: CustomTypeStruct{ ID: CustomString("user_123"), Version: CustomInt(2), Email: "test@example.com", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s CustomTypeStruct changes, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if string(s.ID) != string(tt.expected.ID) || int(s.Version) != int(tt.expected.Version) || s.Email != tt.expected.Email { t.Errorf("期望 %+v, 得到 %+v", tt.expected, s) } if len(changes) != len(tt.input) { t.Errorf("期望 %d 变更记录, 得到 %d", len(tt.input), len(changes)) } }) } } // Unmarshaler 接口测试 func TestAttactToStruct_TextUnmarshaler(t *testing.T) { tests := []struct { name string input map[string]string expected UnmarshalerStruct wantErr bool }{ { name: "TextUnmarshaler 接口", input: map[string]string{ "data": "test_data", "name": "John", }, expected: UnmarshalerStruct{ Data: CustomUnmarshaler("custom_test_data"), Name: "John", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s UnmarshalerStruct changes, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if string(s.Data) != string(tt.expected.Data) || s.Name != tt.expected.Name { t.Errorf("期望 Data=%s, Name=%s; 得到 Data=%s, Name=%s", tt.expected.Data, tt.expected.Name, s.Data, s.Name) } if len(changes) != len(tt.input) { t.Errorf("期望 %d 变更记录, 得到 %d", len(tt.input), len(changes)) } }) } } // 错误场景测试 func TestAttactToStruct_ErrorScenarios(t *testing.T) { tests := []struct { name string structPtr interface{} input map[string]string expectedErr string }{ { name: "非指针参数", structPtr: BasicStruct{}, input: map[string]string{"name": "test"}, expectedErr: "structxx 需要是非空指针", }, { name: "空指针", structPtr: (*BasicStruct)(nil), input: map[string]string{"name": "test"}, expectedErr: "需要是非空指针", }, { name: "自定义类型", structPtr: &struct{ Data []string }{}, input: map[string]string{"Data": `["test","test2"]`}, expectedErr: "", }, { name: "无效的嵌套路径", structPtr: &NestedStruct{}, input: map[string]string{ "nonexistent.field": "value", "basic.name": "test", }, expectedErr: "字段 nonexistent.field 不存在", // 应该忽略不存在的字段而不报错 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ch, err := structx.AttactToStruct(tt.structPtr, tt.input) fmt.Printf("变更记录:%+v %+v\n", ch,tt.structPtr) if tt.expectedErr == "" && err != nil { t.Errorf("不期望错误但得到: %v", err) return } if tt.expectedErr != "" { if err == nil { t.Errorf("期望错误包含 '%s', 但得到 nil", tt.expectedErr) return } if !strings.Contains(err.Error(), tt.expectedErr) { t.Errorf("期望错误包含 '%s', 但得到: %v", tt.expectedErr, err) } } }) } } // 变更记录验证测试 func TestAttactToStruct_ChangeInfoValidation(t *testing.T) { tests := []struct { name string input map[string]string expectedFields []string }{ { name: "变更记录完整性", input: map[string]string{ "name": "New Name", "age": "25", "is_active": "true", }, expectedFields: []string{"name", "age", "is_active"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s BasicStruct // 设置初始值 s.Name = "Old Name" s.Age = 30 s.IsActive = false changes, err := structx.AttactToStruct(&s, tt.input) if err != nil { t.Errorf("意外的错误: %v", err) return } // 验证所有期望的字段都有变更记录 for _, field := range tt.expectedFields { change, exists := changes[field] if !exists { t.Errorf("缺少字段 %s 的变更记录", field) continue } // 验证旧值和新值 if change.Old == "" { t.Errorf("字段 %s 的旧值不应为空", field) } if change.New == "" { t.Errorf("字段 %s 的新值不应为空", field) } // 验证值类型正确 if change.Val == nil { t.Errorf("字段 %s 的值不应为 nil", field) } } // 验证变更记录数量 if len(changes) != len(tt.expectedFields) { t.Errorf("期望 %d 个变更记录, 得到 %d", len(tt.expectedFields), len(changes)) } }) } } func BenchmarkAttactToStruct(b *testing.B) { var s BasicStruct input := map[string]string{ "name": "benchmark", "age": "30", "salary": "50000.0", "is_active": "true", "count": "100", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := structx.AttactToStruct(&s, input) if err != nil { b.Fatalf("基准测试失败: %v", err) } } } // 性能测试 func BenchmarkAttactToStruct_Nested(b *testing.B) { var s NestedStruct input := map[string]string{ "basic.name": "benchmark", "basic.age": "30", "basic.salary": "50000.0", "basic.is_active": "true", "comment": "test", "amount": "500.0", "amount2": "250.0", "timestamp": "2024-01-01T12:00:00Z", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := structx.AttactToStruct(&s, input) if err != nil { b.Fatalf("基准测试失败: %v", err) } } } // AttactToStructAny 测试 func TestAttactToStructAny(t *testing.T) { tests := []struct { name string input map[string]interface{} expected BasicStruct wantErr bool }{ { name: "混合类型输入", input: map[string]interface{}{ "name": "John", // string "age": 30, // int "salary": 50000.50, // float64 "is_active": true, // bool "count": uint(100), // uint }, expected: BasicStruct{ Name: "John", Age: 30, Salary: 50000.50, IsActive: true, Count: 100, }, wantErr: false, }, { name: "无法转换的类型", input: map[string]interface{}{ "name": make(chan int), // 无法转换为字符串的类型 }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s BasicStruct _, err := structx.AttactToStructAny(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } if s != tt.expected { t.Errorf("期望 %+v, 得到 %+v", tt.expected, s) } }) } } // 测试指针类型嵌套结构体 type PointerNestedStruct struct { BasicPtr *BasicStruct `json:"basic_ptr"` Direct BasicStruct `json:"direct"` Value string `json:"value"` } func TestAttactToStruct_PointerNested2(t *testing.T) { tests := []struct { name string input map[string]string expected PointerNestedStruct wantErr bool }{ { name: "指针类型嵌套结构体", input: map[string]string{ "basic_ptr.name": "Pointer John", "basic_ptr.age": "35", "direct.name": "Direct John", "direct.age": "25", "value": "test", }, expected: PointerNestedStruct{ BasicPtr: &BasicStruct{ Name: "Pointer John", Age: 35, }, Direct: BasicStruct{ Name: "Direct John", Age: 25, }, Value: "test", }, wantErr: false, }, { name: "空指针初始化", input: map[string]string{ "basic_ptr.name": "New User", }, expected: PointerNestedStruct{ BasicPtr: &BasicStruct{ Name: "New User", }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var s PointerNestedStruct changes, err := structx.AttactToStruct(&s, tt.input) if tt.wantErr { if err == nil { t.Errorf("期望错误,但得到 nil") } return } if err != nil { t.Errorf("意外的错误: %v", err) return } // 验证指针类型字段 if s.BasicPtr == nil { t.Error("BasicPtr 不应该为 nil") } else if s.BasicPtr.Name != tt.expected.BasicPtr.Name { t.Errorf("BasicPtr.Name 期望 %s, 得到 %s", tt.expected.BasicPtr.Name, s.BasicPtr.Name) } // 验证直接嵌套字段 if s.Direct.Name != tt.expected.Direct.Name { t.Errorf("Direct.Name 期望 %s, 得到 %s", tt.expected.Direct.Name, s.Direct.Name) } // 验证变更记录 expectedChangeCount := len(tt.input) if len(changes) != expectedChangeCount { t.Errorf("期望 %d 个变更记录, 得到 %d", expectedChangeCount, len(changes)) } }) } } // 测试多层指针嵌套 type MultiLevelPointerStruct struct { Level1 *Level1Struct `json:"level1"` } type Level1Struct struct { Level2 *Level2Struct `json:"level2"` Name string `json:"name"` } type Level2Struct struct { Level3 *Level3Struct `json:"level3"` Value int `json:"value"` } type Level3Struct struct { FinalValue string `json:"final_value"` } func TestAttactToStruct_MultiLevelPointer(t *testing.T) { input := map[string]string{ "level1.name": "Top Level", "level1.level2.value": "100", "level1.level2.level3.final_value": "end_value", } var s MultiLevelPointerStruct changes, err := structx.AttactToStruct(&s, input) if err != nil { t.Errorf("意外的错误: %v", err) return } // 验证多层指针嵌套 if s.Level1 == nil { t.Error("Level1 不应该为 nil") } else if s.Level1.Name != "Top Level" { t.Errorf("Level1.Name 期望 Top Level, 得到 %s", s.Level1.Name) } if s.Level1.Level2 == nil { t.Error("Level2 不应该为 nil") } else if s.Level1.Level2.Value != 100 { t.Errorf("Level2.Value 期望 100, 得到 %d", s.Level1.Level2.Value) } if s.Level1.Level2.Level3 == nil { t.Error("Level3 不应该为 nil") } else if s.Level1.Level2.Level3.FinalValue != "end_value" { t.Errorf("Level3.FinalValue 期望 end_value, 得到 %s", s.Level1.Level2.Level3.FinalValue) } // 验证变更记录 if len(changes) != 3 { t.Errorf("期望 3 个变更记录, 得到 %d", len(changes)) } } // 测试混合指针和值类型嵌套 type MixedNestedStruct struct { PtrField *BasicStruct `json:"ptr_field"` ValueField BasicStruct `json:"value_field"` Simple string `json:"simple"` } func TestAttactToStruct_MixedNested(t *testing.T) { input := map[string]string{ "ptr_field.name": "Pointer Name", "ptr_field.age": "40", "value_field.name": "Value Name", "value_field.age": "30", "simple": "simple_value", } var s MixedNestedStruct changes, err := structx.AttactToStruct(&s, input) if err != nil { t.Errorf("意外的错误: %v", err) return } // 验证指针字段 if s.PtrField == nil { t.Error("PtrField 不应该为 nil") } else { if s.PtrField.Name != "Pointer Name" { t.Errorf("PtrField.Name 期望 Pointer Name, 得到 %s", s.PtrField.Name) } if s.PtrField.Age != 40 { t.Errorf("PtrField.Age 期望 40, 得到 %d", s.PtrField.Age) } } // 验证值字段 if s.ValueField.Name != "Value Name" { t.Errorf("ValueField.Name 期望 Value Name, 得到 %s", s.ValueField.Name) } if s.ValueField.Age != 30 { t.Errorf("ValueField.Age 期望 30, 得到 %d", s.ValueField.Age) } // 验证简单字段 if s.Simple != "simple_value" { t.Errorf("Simple 期望 simple_value, 得到 %s", s.Simple) } // 验证变更记录 if len(changes) != 5 { // 5个字段的变更 t.Errorf("期望 5 个变更记录, 得到 %d", len(changes)) } }