873 lines
19 KiB
Go
873 lines
19 KiB
Go
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))
|
|
}
|
|
} |