cast/convert_compat_test.go

201 lines
5.4 KiB
Go
Raw Permalink Normal View History

package cast_test
import (
"reflect"
"testing"
"apigo.cc/go/cast"
)
// Matrix Test Entry
func TestConvertMatrix(t *testing.T) {
type testCase struct {
name string
from any
to any // 传入指针的指针,用于接收结果
expected any
}
// 1. 基础类型全互转矩阵
cases := []testCase{
{name: "int to string", from: 123, to: new(string), expected: "123"},
{name: "string to int", from: "456", to: new(int), expected: 456},
{name: "float to int", from: 123.45, to: new(int), expected: 123},
{name: "bool to string", from: true, to: new(string), expected: "true"},
{name: "string to bool", from: "1", to: new(bool), expected: true},
{name: "string to bool (text)", from: "true", to: new(bool), expected: true},
// 2. 容器与单值的自动包装/解包 (去摩擦)
{name: "single to slice", from: 100, to: new([]int), expected: []int{100}},
{name: "slice to single (len 1)", from: []int{200}, to: new(int), expected: 200},
{name: "slice to single (len >1, take first)", from: []int{300, 400}, to: new(int), expected: 300},
// 3. 字符串与切片的智能转换
{name: "csv string to slice", from: "1, 2, 3", to: new([]int), expected: []int{1, 2, 3}},
{name: "json string to slice", from: `[4, 5, 6]`, to: new([]int), expected: []int{4, 5, 6}},
// 4. 指针转换 (深度穿透)
{name: "deep pointer to val", from: ptr(ptr(789)), to: new(int), expected: 789},
{name: "val to pointer", from: 999, to: new(*int), expected: ptr(999)},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cast.Convert(tc.to, tc.from)
// 获取 to 指针指向的实际值
actual := reflect.ValueOf(tc.to).Elem().Interface()
if !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("[%s] Failed: from(%v) expected(%v) but got(%v)", tc.name, tc.from, tc.expected, actual)
}
})
}
}
func TestConvert_SelfAssignment(t *testing.T) {
type User struct {
Name string
}
u := &User{Name: "Tom"}
// 应命中自我转换保护,不报错且不改变状态
cast.Convert(u, u)
if u.Name != "Tom" {
t.Error("Self-assignment guard failed")
}
}
// 5. 复杂映射与去摩擦 Key 匹配测试
func TestComplexFrictionlessMapping(t *testing.T) {
// 场景Map 键名非常混乱Struct 嵌套,目标存在旧数据
type Sub struct {
Age int
}
type User struct {
UserID int
UserName string
SubInfo Sub
}
from := map[string]any{
"user_id": 1001, // 下划线
"USER-NAME": "Andy", // 中划线+大写
"subinfo": map[string]any{ // 嵌套+全小写
"age": "18", // 类型不一致 (string -> int)
},
}
var to User
to.UserID = 999 // 预设旧数据,验证是否被正确覆盖
cast.Convert(&to, from)
if to.UserID != 1001 {
t.Errorf("UserID match failed: %d", to.UserID)
}
if to.UserName != "Andy" {
t.Errorf("UserName match failed: %s", to.UserName)
}
if to.SubInfo.Age != 18 {
t.Errorf("SubInfo.Age match failed: %d", to.SubInfo.Age)
}
}
// 6. 函数转换测试 (Func to Func)
func TestFuncConversion(t *testing.T) {
// 源函数:接收 int, string返回 int, string
f1 := func(a int, b string) (int, string) {
return a + 1, b + "!"
}
// 目标函数:意图是接收 string, any返回 string, any
var f2 func(string, any) (string, any)
cast.Convert(&f2, f1)
if f2 == nil {
t.Fatal("Converted function is nil")
}
r1, r2 := f2("10", "hello")
if r1 != "11" || r2 != "hello!" {
t.Errorf("Func conversion failed: r1=%v, r2=%v", r1, r2)
}
}
// 8. 命名风格模糊匹配测试
func TestFuzzyNamingMapping(t *testing.T) {
type TestStruct struct {
JSONTag string
UserID int
UserName string
IsActiveFlag bool
}
// 测试用例:各种不规范输入到标准结构体的映射
from := map[string]any{
"jsontag": "val1", // 小写
"JSONTag": "val2", // 原名
"JSON_TAG": "val3", // 下划线大写
"user_id": 123, // 下划线小写
"USERID": 456, // 全大写
"userName": "Andy", // 首字母小写
"user-name": "Bob", // 中划线
"is_active_flag": "true", // 模糊匹配 bool
}
var to TestStruct
cast.Convert(&to, from)
if to.UserID == 0 {
t.Errorf("UserID should be matched and set")
}
if to.UserName == "" {
t.Errorf("UserName should be matched and set")
}
if !to.IsActiveFlag {
t.Errorf("IsActiveFlag should be set to true")
}
t.Logf("Conversion result: %+v", to)
}
// 9. 异常防御测试 (Panic Prevention)
func TestPanicPrevention(t *testing.T) {
t.Run("nil destination", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Panic occurred with nil destination: %v", r)
}
}()
cast.Convert(nil, "data")
})
t.Run("nil pointer destination", func(t *testing.T) {
// nil 指针目标应该是可以接受的,库应当忽略或优雅处理,不应 panic
var p *int = nil
cast.Convert(&p, 123) // 这里传入的是指针的指针
if p == nil || *p != 123 {
t.Errorf("Expected p to be initialized and set to 123, got %v", p)
}
})
t.Run("read-only destination", func(t *testing.T) {
const i = 1
cast.Convert(i, 123)
})
}
// 辅助函数
func ptr[T any](v T) *T { return &v }
// --- 性能测试 ---
func BenchmarkCastMatrixConvert(b *testing.B) {
from := map[string]any{"id": "123", "name": "test"}
type Target struct {
ID int
Name string
}
var to Target
for i := 0; i < b.N; i++ {
cast.Convert(&to, from)
}
}