fab7931d3c
- 更新应用程序处理程序中的 URL 赋值逻辑,确保静态文件使用原始路径 - 修改缓存数据库的 JSON 解析方法,使用 JsonToObj 函数替代 json.Unmarshal,提升代码可读性和性能 - 在 Map 和 Slice 类型中新增获取四舍五入浮点数的方法,增强数据处理能力 - 在 Obj 类型中添加四舍五入功能,支持精度控制 - 改进数据库查询结果的处理逻辑,确保数据类型的准确性和一致性 - 优化日志格式设置,增强日志信息的可读性
918 lines
27 KiB
Go
918 lines
27 KiB
Go
package common
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ─────────────────────────────────────────────
|
|
// 辅助:检查 Error 是否携带错误
|
|
// ─────────────────────────────────────────────
|
|
func hasErr(e *Error) bool {
|
|
return e.GetError() != nil
|
|
}
|
|
|
|
func newErr() *Error {
|
|
return &Error{}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToMap
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToMap(t *testing.T) {
|
|
t.Run("nil returns nil with error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToMap(nil, e)
|
|
if v != nil || !hasErr(e) {
|
|
t.Errorf("nil: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("Map passthrough", func(t *testing.T) {
|
|
input := Map{"a": 1}
|
|
v := ObjToMap(input)
|
|
if v == nil || v["a"] != 1 {
|
|
t.Errorf("Map passthrough failed: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("map[string]interface{} cast", func(t *testing.T) {
|
|
input := map[string]interface{}{"b": 2}
|
|
v := ObjToMap(input)
|
|
if v == nil || v["b"] != 2 {
|
|
t.Errorf("map[string]interface{} cast failed: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("valid JSON string - integer preserved as int64", func(t *testing.T) {
|
|
v := ObjToMap(`{"x":42}`)
|
|
if v == nil || v["x"] != int64(42) {
|
|
t.Errorf("JSON string: got %v (type %T), want int64(42)", v["x"], v["x"])
|
|
}
|
|
})
|
|
|
|
t.Run("invalid JSON string returns nil with error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToMap(`not json`, e)
|
|
if v != nil || !hasErr(e) {
|
|
t.Errorf("invalid JSON: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("struct default branch marshal+unmarshal", func(t *testing.T) {
|
|
type S struct{ Name string }
|
|
v := ObjToMap(S{Name: "hello"})
|
|
if v == nil || v["Name"] != "hello" {
|
|
t.Errorf("struct default: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("marshal fail (channel) returns nil with error", func(t *testing.T) {
|
|
e := newErr()
|
|
ch := make(chan int)
|
|
v := ObjToMap(ch, e)
|
|
if v != nil || !hasErr(e) {
|
|
t.Errorf("channel marshal: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("default marshal OK but unmarshal to map fails (int)", func(t *testing.T) {
|
|
// json.Marshal(123) = "123", which can't unmarshal to Map
|
|
e := newErr()
|
|
v := ObjToMap(123, e)
|
|
if v != nil || !hasErr(e) {
|
|
t.Errorf("int default: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToMapArray
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToMapArray(t *testing.T) {
|
|
t.Run("nil returns empty", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToMapArray(nil, e)
|
|
if len(v) != 0 || !hasErr(e) {
|
|
t.Errorf("nil: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("JSON array of maps", func(t *testing.T) {
|
|
v := ObjToMapArray(`[{"a":1},{"b":2}]`)
|
|
if len(v) != 2 {
|
|
t.Errorf("expected 2 elements, got %d", len(v))
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToSlice
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToSlice(t *testing.T) {
|
|
t.Run("nil returns nil with error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToSlice(nil, e)
|
|
if v != nil || !hasErr(e) {
|
|
t.Errorf("nil: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("Slice passthrough", func(t *testing.T) {
|
|
input := Slice{1, 2, 3}
|
|
v := ObjToSlice(input)
|
|
if len(v) != 3 {
|
|
t.Errorf("Slice passthrough: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("[]interface{} cast", func(t *testing.T) {
|
|
input := []interface{}{4, 5}
|
|
v := ObjToSlice(input)
|
|
if len(v) != 2 || v[0] != 4 {
|
|
t.Errorf("[]interface{} cast: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("[]string conversion", func(t *testing.T) {
|
|
input := []string{"a", "b", "c"}
|
|
v := ObjToSlice(input)
|
|
if len(v) != 3 || v[0] != "a" {
|
|
t.Errorf("[]string: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("valid JSON array string", func(t *testing.T) {
|
|
v := ObjToSlice(`[1,2,3]`)
|
|
if len(v) != 3 {
|
|
t.Errorf("JSON array: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid JSON string returns error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToSlice(`not json`, e)
|
|
if !hasErr(e) {
|
|
t.Errorf("invalid JSON: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("default: []int marshals to JSON array", func(t *testing.T) {
|
|
v := ObjToSlice([]int{10, 20, 30})
|
|
if len(v) != 3 {
|
|
t.Errorf("[]int default: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("default: marshal fail (channel) returns error", func(t *testing.T) {
|
|
e := newErr()
|
|
ch := make(chan int)
|
|
ObjToSlice(ch, e)
|
|
if !hasErr(e) {
|
|
t.Errorf("channel marshal: expected error")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToTime
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToTime(t *testing.T) {
|
|
mustParse := func(layout, s string) time.Time {
|
|
tt, err := time.Parse(layout, s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return tt
|
|
}
|
|
|
|
t.Run("full datetime string", func(t *testing.T) {
|
|
ts := ObjToTime("2006-01-02 15:04:05")
|
|
want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("full datetime: %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("single digit day+time (bug fix)", func(t *testing.T) {
|
|
ts := ObjToTime("2006-1-2 15:04:05")
|
|
want := mustParse("2006-01-02 15:04:05", "2006-01-02 15:04:05")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("single digit day+time: got %v, want %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("datetime without seconds", func(t *testing.T) {
|
|
ts := ObjToTime("2006-01-02 15:04")
|
|
want := mustParse("2006-01-02 15:04", "2006-01-02 15:04")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("datetime without seconds: %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("date+hour only", func(t *testing.T) {
|
|
ts := ObjToTime("2006-01-02 15")
|
|
want := mustParse("2006-01-02 15", "2006-01-02 15")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("date+hour: %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("date only", func(t *testing.T) {
|
|
ts := ObjToTime("2006-01-02")
|
|
want := mustParse("2006-01-02", "2006-01-02")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("date only: %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("month only", func(t *testing.T) {
|
|
ts := ObjToTime("2006-01")
|
|
want := mustParse("2006-01", "2006-01")
|
|
if ts == nil || !ts.Equal(want) {
|
|
t.Errorf("month only: %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid string returns nil", func(t *testing.T) {
|
|
ts := ObjToTime("not-a-date")
|
|
if ts != nil {
|
|
t.Errorf("invalid string: expected nil, got %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("unix second timestamp (10 digits)", func(t *testing.T) {
|
|
ts := ObjToTime(int64(1700000000)) // 10 digits
|
|
if ts == nil {
|
|
t.Error("second timestamp: got nil")
|
|
}
|
|
want := time.Unix(1700000000, 0)
|
|
if !ts.Equal(want) {
|
|
t.Errorf("second: %v vs %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("unix millisecond timestamp (13 digits)", func(t *testing.T) {
|
|
ts := ObjToTime(int64(1700000000000)) // 13 digits
|
|
if ts == nil {
|
|
t.Error("millisecond timestamp: got nil")
|
|
}
|
|
want := time.UnixMilli(1700000000000)
|
|
if !ts.Equal(want) {
|
|
t.Errorf("millisecond: %v vs %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("unix microsecond timestamp (16 digits)", func(t *testing.T) {
|
|
ts := ObjToTime(int64(1700000000000000)) // 16 digits
|
|
if ts == nil {
|
|
t.Error("microsecond timestamp: got nil")
|
|
}
|
|
want := time.UnixMicro(1700000000000000)
|
|
if !ts.Equal(want) {
|
|
t.Errorf("microsecond: %v vs %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("nanosecond timestamp (19 digits)", func(t *testing.T) {
|
|
ts := ObjToTime(int64(1700000000000000000)) // 19 digits -> nano branch
|
|
if ts == nil {
|
|
t.Error("nanosecond timestamp: got nil")
|
|
}
|
|
want := time.UnixMicro(1700000000000000000 / 1000)
|
|
if !ts.Equal(want) {
|
|
t.Errorf("nanosecond: %v vs %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("year int (4 digits)", func(t *testing.T) {
|
|
ts := ObjToTime(int64(2024))
|
|
if ts == nil {
|
|
t.Error("year int: got nil")
|
|
}
|
|
want := mustParse("2006", "2024")
|
|
if !ts.Equal(want) {
|
|
t.Errorf("year: %v vs %v", ts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("small int returns nil", func(t *testing.T) {
|
|
ts := ObjToTime(int64(1))
|
|
if ts != nil {
|
|
t.Errorf("small int: expected nil, got %v", ts)
|
|
}
|
|
})
|
|
|
|
t.Run("nil returns nil", func(t *testing.T) {
|
|
ts := ObjToTime(nil)
|
|
if ts != nil {
|
|
t.Errorf("nil: expected nil, got %v", ts)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToFloat64
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToFloat64(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input interface{}
|
|
want float64
|
|
wantErr bool
|
|
}{
|
|
{"nil", nil, 0, true},
|
|
{"int", 42, 42.0, false},
|
|
{"int64", int64(100), 100.0, false},
|
|
{"string valid", "3.14", 3.14, false},
|
|
{"string invalid", "abc", 0, true},
|
|
{"float64", float64(1.5), 1.5, false},
|
|
// Fix4: float32 通过 strconv 转换,消除精度膨胀噪声
|
|
{"float32 no precision inflation", float32(1.2), 1.2, false},
|
|
{"[]byte valid", []byte("2.71"), 2.71, false},
|
|
{"[]byte invalid", []byte("xyz"), 0, true},
|
|
{"unknown type", struct{}{}, 0, true},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
e := newErr()
|
|
got := ObjToFloat64(c.input, e)
|
|
if math.Abs(got-c.want) > 1e-9 {
|
|
t.Errorf("value: got %v, want %v", got, c.want)
|
|
}
|
|
if hasErr(e) != c.wantErr {
|
|
t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError())
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("NaN is caught and zeroed", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToFloat64(math.NaN(), e)
|
|
if v != 0 || !hasErr(e) {
|
|
t.Errorf("NaN: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("Inf is caught and zeroed", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToFloat64(math.Inf(1), e)
|
|
if v != 0 || !hasErr(e) {
|
|
t.Errorf("Inf: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("-Inf is caught and zeroed", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToFloat64(math.Inf(-1), e)
|
|
if v != 0 || !hasErr(e) {
|
|
t.Errorf("-Inf: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToRoundFloat64
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToRoundFloat64(t *testing.T) {
|
|
t.Run("precision 2 rounds correctly", func(t *testing.T) {
|
|
v := ObjToRoundFloat64(3.5000001, 2)
|
|
if v != 3.5 {
|
|
t.Errorf("got %v, want 3.5", v)
|
|
}
|
|
})
|
|
|
|
t.Run("precision 0 rounds to integer", func(t *testing.T) {
|
|
v := ObjToRoundFloat64(2.7, 0)
|
|
if v != 3.0 {
|
|
t.Errorf("got %v, want 3.0", v)
|
|
}
|
|
})
|
|
|
|
t.Run("precision -1 returns raw float", func(t *testing.T) {
|
|
v := ObjToRoundFloat64(1.23456789, -1)
|
|
if math.Abs(v-1.23456789) > 1e-9 {
|
|
t.Errorf("got %v, want ~1.23456789", v)
|
|
}
|
|
})
|
|
|
|
t.Run("string input with float noise", func(t *testing.T) {
|
|
v := ObjToRoundFloat64("3.15000001", 2)
|
|
if v != 3.15 {
|
|
t.Errorf("got %v, want 3.15", v)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToCeil*
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToCeil(t *testing.T) {
|
|
t.Run("ObjToCeilInt64 rounds up", func(t *testing.T) {
|
|
v := ObjToCeilInt64(1.1)
|
|
if v != 2 {
|
|
t.Errorf("got %d, want 2", v)
|
|
}
|
|
})
|
|
|
|
t.Run("ObjToCeilInt64 already integer", func(t *testing.T) {
|
|
v := ObjToCeilInt64(float64(3))
|
|
if v != 3 {
|
|
t.Errorf("got %d, want 3", v)
|
|
}
|
|
})
|
|
|
|
t.Run("ObjToCeilInt64 no double ceil (bug fix)", func(t *testing.T) {
|
|
// 3.0 -> Ceil(3.0)=3.0 -> int64(3), NOT Ceil(3.0) again
|
|
v := ObjToCeilInt64(float64(3))
|
|
if v != 3 {
|
|
t.Errorf("double ceil: got %d, want 3", v)
|
|
}
|
|
})
|
|
|
|
t.Run("ObjToCeilFloat64", func(t *testing.T) {
|
|
v := ObjToCeilFloat64(1.1)
|
|
if v != 2.0 {
|
|
t.Errorf("got %v, want 2.0", v)
|
|
}
|
|
})
|
|
|
|
t.Run("ObjToCeilInt", func(t *testing.T) {
|
|
v := ObjToCeilInt(2.3)
|
|
if v != 3 {
|
|
t.Errorf("got %d, want 3", v)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToInt64
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToInt64(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input interface{}
|
|
want int64
|
|
wantErr bool
|
|
}{
|
|
{"nil", nil, 0, true},
|
|
{"int", 7, 7, false},
|
|
{"int64", int64(99), 99, false},
|
|
{"string valid int", "42", 42, false},
|
|
// Fix3 fallback: "1.20" 原来报错,现在截断得 1
|
|
{"string float-like", "1.20", 1, false},
|
|
{"string invalid", "abc", 0, true},
|
|
{"[]byte valid int", []byte("123"), 123, false},
|
|
// Fix3 fallback: []byte "2.70" 截断得 2
|
|
{"[]byte float-like", []byte("2.70"), 2, false},
|
|
{"[]byte invalid", []byte("xyz"), 0, true},
|
|
{"float64 truncate", float64(5.9), 5, false},
|
|
{"float32 truncate", float32(3.1), 3, false},
|
|
{"unknown type", struct{}{}, 0, true},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
e := newErr()
|
|
got := ObjToInt64(c.input, e)
|
|
if got != c.want {
|
|
t.Errorf("value: got %d, want %d", got, c.want)
|
|
}
|
|
if hasErr(e) != c.wantErr {
|
|
t.Errorf("error: got hasErr=%v, wantErr=%v (err=%v)", hasErr(e), c.wantErr, e.GetError())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToInt
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToInt(t *testing.T) {
|
|
v := ObjToInt(10)
|
|
if v != 10 {
|
|
t.Errorf("got %d, want 10", v)
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToBool
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToBool(t *testing.T) {
|
|
t.Run("nil returns false with error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(nil, e)
|
|
if v || !hasErr(e) {
|
|
t.Errorf("nil: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("bool true", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(true, e)
|
|
if !v || hasErr(e) {
|
|
t.Errorf("bool true: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("bool false", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(false, e)
|
|
if v || hasErr(e) {
|
|
t.Errorf("bool false: got %v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("int 1 -> true, no error (bug fix)", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(1, e)
|
|
if !v || hasErr(e) {
|
|
t.Errorf("int 1: got v=%v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("int 0 -> false, no error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(0, e)
|
|
if v || hasErr(e) {
|
|
t.Errorf("int 0: got v=%v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
|
|
t.Run("unknown type returns false with error", func(t *testing.T) {
|
|
e := newErr()
|
|
v := ObjToBool(struct{}{}, e)
|
|
if v || !hasErr(e) {
|
|
t.Errorf("struct: got v=%v err=%v", v, e.GetError())
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToStr
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToStr(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input interface{}
|
|
want string
|
|
}{
|
|
{"nil", nil, ""},
|
|
{"int", 42, "42"},
|
|
{"uint8", uint8(65), "65"},
|
|
{"int64", int64(1234567890), "1234567890"},
|
|
{"[]byte", []byte("hello"), "hello"},
|
|
{"string", "world", "world"},
|
|
{"float64", float64(3.14), "3.14"},
|
|
{"float32", float32(2.5), "2.5"},
|
|
{"bool true", true, "true"},
|
|
{"bool false", false, "false"},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got := ObjToStr(c.input)
|
|
if got != c.want {
|
|
t.Errorf("got %q, want %q", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("default (struct) marshals to JSON", func(t *testing.T) {
|
|
type S struct{ X int }
|
|
got := ObjToStr(S{X: 1})
|
|
if got == "" {
|
|
t.Error("struct: expected non-empty JSON string")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// StrToMap
|
|
// ─────────────────────────────────────────────
|
|
func TestStrToMap(t *testing.T) {
|
|
t.Run("valid JSON", func(t *testing.T) {
|
|
v := StrToMap(`{"k":"v"}`)
|
|
if v["k"] != "v" {
|
|
t.Errorf("StrToMap: %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid JSON returns empty map", func(t *testing.T) {
|
|
v := StrToMap(`not json`)
|
|
if v == nil {
|
|
t.Error("StrToMap invalid: expected empty map, got nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// StrToSlice
|
|
// ─────────────────────────────────────────────
|
|
func TestStrToSlice(t *testing.T) {
|
|
v := StrToSlice(`[1,2,3]`)
|
|
if len(v) != 3 {
|
|
t.Errorf("StrToSlice: expected 3 elements, got %d", len(v))
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// StrArrayToJsonStr
|
|
// ─────────────────────────────────────────────
|
|
func TestStrArrayToJsonStr(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input string
|
|
want string
|
|
}{
|
|
{"empty returns []", "", "[]"},
|
|
{"short (<=2) returns []", "a", "[]"},
|
|
{"no commas", `"a","b"`, `["a","b"]`},
|
|
{"leading comma stripped", `,"a","b"`, `["a","b"]`},
|
|
{"trailing comma stripped", `"a","b",`, `["a","b"]`},
|
|
{"both commas stripped", `,"a","b",`, `["a","b"]`},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got := StrArrayToJsonStr(c.input)
|
|
if got != c.want {
|
|
t.Errorf("got %q, want %q", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// JsonStrToStrArray
|
|
// ─────────────────────────────────────────────
|
|
func TestJsonStrToStrArray(t *testing.T) {
|
|
t.Run("non-empty strips brackets", func(t *testing.T) {
|
|
got := JsonStrToStrArray(`["a","b"]`)
|
|
if got != `,"a","b",` {
|
|
t.Errorf("got %q", got)
|
|
}
|
|
})
|
|
|
|
t.Run("empty string", func(t *testing.T) {
|
|
got := JsonStrToStrArray("")
|
|
if got != ",," {
|
|
t.Errorf("got %q", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// StrToInt
|
|
// ─────────────────────────────────────────────
|
|
func TestStrToInt(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
v, err := StrToInt("123")
|
|
if v != 123 || err != nil {
|
|
t.Errorf("got %d %v", v, err)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid", func(t *testing.T) {
|
|
_, err := StrToInt("abc")
|
|
if err == nil {
|
|
t.Error("expected error")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// JsonToObj
|
|
// ─────────────────────────────────────────────
|
|
func TestJsonToObj(t *testing.T) {
|
|
t.Run("integer preserved as int64", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"id":2}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected map, got %T", v)
|
|
}
|
|
if m["id"] != int64(2) {
|
|
t.Errorf("got %v (type %T), want int64(2)", m["id"], m["id"])
|
|
}
|
|
})
|
|
|
|
t.Run("decimal preserved as float64 with correct precision", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"price":1.20}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m := v.(map[string]interface{})
|
|
price, ok := m["price"].(float64)
|
|
if !ok {
|
|
t.Fatalf("expected float64, got %T", m["price"])
|
|
}
|
|
if price != 1.2 {
|
|
t.Errorf("got %v, want 1.2", price)
|
|
}
|
|
})
|
|
|
|
t.Run("large integer preserved as int64", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"num":9999999}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m := v.(map[string]interface{})
|
|
if m["num"] != int64(9999999) {
|
|
t.Errorf("got %v (type %T), want int64(9999999)", m["num"], m["num"])
|
|
}
|
|
})
|
|
|
|
t.Run("array with mixed types", func(t *testing.T) {
|
|
v, err := JsonToObj(`[1, 2.5, "hello", true]`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
arr := v.([]interface{})
|
|
if len(arr) != 4 {
|
|
t.Fatalf("expected 4 elements, got %d", len(arr))
|
|
}
|
|
if arr[0] != int64(1) {
|
|
t.Errorf("arr[0]: got %v (%T), want int64(1)", arr[0], arr[0])
|
|
}
|
|
if arr[1] != float64(2.5) {
|
|
t.Errorf("arr[1]: got %v (%T), want float64(2.5)", arr[1], arr[1])
|
|
}
|
|
if arr[2] != "hello" {
|
|
t.Errorf("arr[2]: got %v, want hello", arr[2])
|
|
}
|
|
if arr[3] != true {
|
|
t.Errorf("arr[3]: got %v, want true", arr[3])
|
|
}
|
|
})
|
|
|
|
t.Run("nested object integers preserved", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"order":{"id":5,"num":3,"price":1.20}}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m := v.(map[string]interface{})
|
|
order := m["order"].(map[string]interface{})
|
|
if order["id"] != int64(5) {
|
|
t.Errorf("nested id: got %v (%T), want int64(5)", order["id"], order["id"])
|
|
}
|
|
if order["num"] != int64(3) {
|
|
t.Errorf("nested num: got %v (%T), want int64(3)", order["num"], order["num"])
|
|
}
|
|
if order["price"] != float64(1.2) {
|
|
t.Errorf("nested price: got %v, want 1.2", order["price"])
|
|
}
|
|
})
|
|
|
|
t.Run("invalid JSON returns error", func(t *testing.T) {
|
|
_, err := JsonToObj(`not json`)
|
|
if err == nil {
|
|
t.Error("expected error for invalid JSON")
|
|
}
|
|
})
|
|
|
|
t.Run("empty object", func(t *testing.T) {
|
|
v, err := JsonToObj(`{}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m, ok := v.(map[string]interface{})
|
|
if !ok || len(m) != 0 {
|
|
t.Errorf("expected empty map, got %v", v)
|
|
}
|
|
})
|
|
|
|
t.Run("null value", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"x":null}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m := v.(map[string]interface{})
|
|
if m["x"] != nil {
|
|
t.Errorf("expected nil, got %v", m["x"])
|
|
}
|
|
})
|
|
|
|
t.Run("zero integer", func(t *testing.T) {
|
|
v, err := JsonToObj(`{"count":0}`)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
m := v.(map[string]interface{})
|
|
if m["count"] != int64(0) {
|
|
t.Errorf("got %v (%T), want int64(0)", m["count"], m["count"])
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToMap 数字类型保留(新行为)
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToMapNumberPreservation(t *testing.T) {
|
|
t.Run("JSON integer -> int64", func(t *testing.T) {
|
|
v := ObjToMap(`{"id":2,"num":0}`)
|
|
if v["id"] != int64(2) {
|
|
t.Errorf("id: got %v (%T), want int64(2)", v["id"], v["id"])
|
|
}
|
|
if v["num"] != int64(0) {
|
|
t.Errorf("num: got %v (%T), want int64(0)", v["num"], v["num"])
|
|
}
|
|
})
|
|
|
|
t.Run("JSON decimal -> float64", func(t *testing.T) {
|
|
v := ObjToMap(`{"price":1.20,"rate":0.5}`)
|
|
if v["price"] != float64(1.2) {
|
|
t.Errorf("price: got %v (%T), want float64(1.2)", v["price"], v["price"])
|
|
}
|
|
if v["rate"] != float64(0.5) {
|
|
t.Errorf("rate: got %v (%T), want float64(0.5)", v["rate"], v["rate"])
|
|
}
|
|
})
|
|
|
|
t.Run("struct with int field -> int64", func(t *testing.T) {
|
|
type S struct {
|
|
Count int `json:"count"`
|
|
Name string `json:"name"`
|
|
}
|
|
v := ObjToMap(S{Count: 3, Name: "test"})
|
|
if v == nil {
|
|
t.Fatal("got nil")
|
|
}
|
|
if v["count"] != int64(3) {
|
|
t.Errorf("count: got %v (%T), want int64(3)", v["count"], v["count"])
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// ObjToSlice 数字类型保留(新行为)
|
|
// ─────────────────────────────────────────────
|
|
func TestObjToSliceNumberPreservation(t *testing.T) {
|
|
t.Run("JSON integer array -> int64 elements", func(t *testing.T) {
|
|
v := ObjToSlice(`[1, 2, 3]`)
|
|
if len(v) != 3 {
|
|
t.Fatalf("expected 3, got %d", len(v))
|
|
}
|
|
if v[0] != int64(1) {
|
|
t.Errorf("v[0]: got %v (%T), want int64(1)", v[0], v[0])
|
|
}
|
|
})
|
|
|
|
t.Run("JSON mixed array - integer and float", func(t *testing.T) {
|
|
v := ObjToSlice(`[2, 1.5, "hello"]`)
|
|
if v[0] != int64(2) {
|
|
t.Errorf("v[0]: got %v (%T), want int64(2)", v[0], v[0])
|
|
}
|
|
if v[1] != float64(1.5) {
|
|
t.Errorf("v[1]: got %v (%T), want float64(1.5)", v[1], v[1])
|
|
}
|
|
})
|
|
}
|
|
|
|
// ─────────────────────────────────────────────
|
|
// Map.JsonToMap 数字类型保留(新行为)
|
|
// ─────────────────────────────────────────────
|
|
func TestMapJsonToMapNumberPreservation(t *testing.T) {
|
|
t.Run("integer -> int64", func(t *testing.T) {
|
|
m := Map{}
|
|
m.JsonToMap(`{"id":5,"count":0}`)
|
|
if m["id"] != int64(5) {
|
|
t.Errorf("id: got %v (%T), want int64(5)", m["id"], m["id"])
|
|
}
|
|
if m["count"] != int64(0) {
|
|
t.Errorf("count: got %v (%T), want int64(0)", m["count"], m["count"])
|
|
}
|
|
})
|
|
|
|
t.Run("decimal -> float64", func(t *testing.T) {
|
|
m := Map{}
|
|
m.JsonToMap(`{"price":1.20}`)
|
|
if m["price"] != float64(1.2) {
|
|
t.Errorf("price: got %v (%T), want float64(1.2)", m["price"], m["price"])
|
|
}
|
|
})
|
|
|
|
t.Run("StrToMap integer -> int64", func(t *testing.T) {
|
|
m := StrToMap(`{"total":100,"amount":1.50}`)
|
|
if m["total"] != int64(100) {
|
|
t.Errorf("total: got %v (%T), want int64(100)", m["total"], m["total"])
|
|
}
|
|
if m["amount"] != float64(1.5) {
|
|
t.Errorf("amount: got %v (%T), want float64(1.5)", m["amount"], m["amount"])
|
|
}
|
|
})
|
|
|
|
t.Run("invalid JSON sets error", func(t *testing.T) {
|
|
e := newErr()
|
|
m := Map{}
|
|
m.JsonToMap(`not json`, e)
|
|
if !hasErr(e) {
|
|
t.Error("expected error for invalid JSON")
|
|
}
|
|
})
|
|
}
|