149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
package convert_test
|
||
|
||
import (
|
||
"reflect"
|
||
"testing"
|
||
|
||
"apigo.cc/go/convert"
|
||
)
|
||
|
||
// 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) {
|
||
convert.To(tc.from, tc.to)
|
||
// 获取 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)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 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 // 预设旧数据,验证是否被正确覆盖
|
||
convert.To(from, &to)
|
||
|
||
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)
|
||
convert.To(f1, &f2)
|
||
|
||
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)
|
||
}
|
||
}
|
||
|
||
// 7. Parse 钩子测试
|
||
type HookStruct struct {
|
||
Status int
|
||
}
|
||
|
||
func (h *HookStruct) ParseStatus(v any) int {
|
||
s := reflect.ValueOf(v).String()
|
||
if s == "active" {
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func TestParseHook(t *testing.T) {
|
||
from := map[string]any{"status": "active"}
|
||
var to HookStruct
|
||
convert.To(from, &to)
|
||
|
||
if to.Status != 1 {
|
||
t.Errorf("Parse hook failed, got %d", to.Status)
|
||
}
|
||
}
|
||
|
||
// 辅助函数
|
||
func ptr[T any](v T) *T { return &v }
|
||
|
||
// --- 性能测试 ---
|
||
|
||
func BenchmarkMatrixConvert(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++ {
|
||
convert.To(from, &to)
|
||
}
|
||
}
|