Files
streamsql/utils/fieldpath/fieldpath_test.go
T
2025-06-16 12:02:06 +08:00

412 lines
9.3 KiB
Go

package fieldpath
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseFieldPath(t *testing.T) {
tests := []struct {
name string
path string
expected []FieldPart
hasError bool
}{
{
name: "简单字段",
path: "name",
expected: []FieldPart{
{Type: "field", Name: "name"},
},
},
{
name: "嵌套字段",
path: "user.profile.name",
expected: []FieldPart{
{Type: "field", Name: "user"},
{Type: "field", Name: "profile"},
{Type: "field", Name: "name"},
},
},
{
name: "数组索引",
path: "data[0]",
expected: []FieldPart{
{Type: "field", Name: "data"},
{Type: "array_index", Index: 0, Key: "0", KeyType: "number"},
},
},
{
name: "数组索引与字段",
path: "users[1].name",
expected: []FieldPart{
{Type: "field", Name: "users"},
{Type: "array_index", Index: 1, Key: "1", KeyType: "number"},
{Type: "field", Name: "name"},
},
},
{
name: "字符串键",
path: "config['database']",
expected: []FieldPart{
{Type: "field", Name: "config"},
{Type: "map_key", Key: "database", KeyType: "string"},
},
},
{
name: "双引号字符串键",
path: "settings[\"timeout\"]",
expected: []FieldPart{
{Type: "field", Name: "settings"},
{Type: "map_key", Key: "timeout", KeyType: "string"},
},
},
{
name: "负数索引",
path: "items[-1]",
expected: []FieldPart{
{Type: "field", Name: "items"},
{Type: "array_index", Index: -1, Key: "-1", KeyType: "number"},
},
},
{
name: "混合复杂访问",
path: "users[0].profile['name']",
expected: []FieldPart{
{Type: "field", Name: "users"},
{Type: "array_index", Index: 0, Key: "0", KeyType: "number"},
{Type: "field", Name: "profile"},
{Type: "map_key", Key: "name", KeyType: "string"},
},
},
{
name: "多维数组",
path: "matrix[1][2]",
expected: []FieldPart{
{Type: "field", Name: "matrix"},
{Type: "array_index", Index: 1, Key: "1", KeyType: "number"},
{Type: "array_index", Index: 2, Key: "2", KeyType: "number"},
},
},
{
name: "无效括号",
path: "data[abc",
hasError: true,
},
{
name: "无效键格式",
path: "data[abc]",
hasError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
accessor, err := ParseFieldPath(tt.path)
if tt.hasError {
assert.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, accessor)
assert.Equal(t, tt.expected, accessor.Parts)
})
}
}
func TestGetNestedFieldComplex(t *testing.T) {
// 创建复杂的测试数据
testData := map[string]interface{}{
"users": []interface{}{
map[string]interface{}{
"id": 1,
"profile": map[string]interface{}{
"name": "Alice",
"email": "alice@example.com",
"preferences": map[string]interface{}{
"theme": "dark",
"lang": "en",
},
},
"scores": []interface{}{95, 87, 92},
},
map[string]interface{}{
"id": 2,
"profile": map[string]interface{}{
"name": "Bob",
"email": "bob@example.com",
},
"scores": []interface{}{88, 94, 89},
},
},
"config": map[string]interface{}{
"database": "mysql://localhost:3306",
"redis": "redis://localhost:6379",
"settings": map[string]interface{}{
"timeout": 5000,
"retries": 3,
},
},
"matrix": []interface{}{
[]interface{}{1, 2, 3},
[]interface{}{4, 5, 6},
[]interface{}{7, 8, 9},
},
}
tests := []struct {
name string
path string
expected interface{}
found bool
}{
{
name: "数组索引访问",
path: "users[0]",
expected: testData["users"].([]interface{})[0],
found: true,
},
{
name: "数组元素字段",
path: "users[1].profile.name",
expected: "Bob",
found: true,
},
{
name: "嵌套Map键访问",
path: "users[0].profile['name']",
expected: "Alice",
found: true,
},
{
name: "Map键访问",
path: "config['database']",
expected: "mysql://localhost:3306",
found: true,
},
{
name: "嵌套配置访问",
path: "config.settings['timeout']",
expected: 5000,
found: true,
},
{
name: "数组中的数组",
path: "users[0].scores[2]",
expected: 92,
found: true,
},
{
name: "二维数组访问",
path: "matrix[1][2]",
expected: 6,
found: true,
},
{
name: "负数索引",
path: "users[-1].profile.name",
expected: "Bob",
found: true,
},
{
name: "负数索引访问数组",
path: "users[0].scores[-1]",
expected: 92,
found: true,
},
{
name: "不存在的字段",
path: "users[0].profile.nonexistent",
found: false,
},
{
name: "超出索引范围",
path: "users[10].name",
found: false,
},
{
name: "不存在的键",
path: "config['nonexistent']",
found: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, found := GetNestedField(testData, tt.path)
assert.Equal(t, tt.found, found, "found状态应该匹配")
if tt.found {
assert.Equal(t, tt.expected, result, "结果值应该匹配")
}
})
}
}
func TestSetNestedFieldComplex(t *testing.T) {
t.Run("设置简单嵌套字段", func(t *testing.T) {
data := make(map[string]interface{})
err := SetNestedField(data, "user.profile.name", "Alice")
assert.NoError(t, err)
result, found := GetNestedField(data, "user.profile.name")
assert.True(t, found)
assert.Equal(t, "Alice", result)
})
t.Run("设置到现有数据", func(t *testing.T) {
data := map[string]interface{}{
"user": map[string]interface{}{
"id": 1,
},
}
err := SetNestedField(data, "user.profile.name", "Bob")
assert.NoError(t, err)
result, found := GetNestedField(data, "user.profile.name")
assert.True(t, found)
assert.Equal(t, "Bob", result)
// 确保原有数据仍然存在
id, found := GetNestedField(data, "user.id")
assert.True(t, found)
assert.Equal(t, 1, id)
})
t.Run("覆盖非Map类型", func(t *testing.T) {
data := map[string]interface{}{
"user": "string_value",
}
err := SetNestedField(data, "user.profile.name", "Charlie")
assert.NoError(t, err)
result, found := GetNestedField(data, "user.profile.name")
assert.True(t, found)
assert.Equal(t, "Charlie", result)
})
}
func TestIsNestedFieldComplex(t *testing.T) {
tests := []struct {
field string
expected bool
}{
{"name", false},
{"user.name", true},
{"data[0]", true},
{"users[1].name", true},
{"config['key']", true},
{"matrix[0][1]", true},
{"a.b.c.d", true},
}
for _, tt := range tests {
t.Run(tt.field, func(t *testing.T) {
result := IsNestedField(tt.field)
assert.Equal(t, tt.expected, result)
})
}
}
func TestExtractTopLevelFieldComplex(t *testing.T) {
tests := []struct {
path string
expected string
}{
{"name", "name"},
{"user.profile.name", "user"},
{"data[0]", "data"},
{"users[1].name", "users"},
{"config['database']", "config"},
{"matrix[0][1]", "matrix"},
{"a.b[0].c['key']", "a"},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
result := ExtractTopLevelField(tt.path)
assert.Equal(t, tt.expected, result)
})
}
}
func TestValidateFieldPath(t *testing.T) {
tests := []struct {
path string
hasError bool
}{
{"name", false},
{"user.profile.name", false},
{"data[0]", false},
{"users[1].name", false},
{"config['database']", false},
{"matrix[0][1]", false},
{"data[abc]", true}, // 无效括号内容
{"data[", true}, // 未闭合括号
{"data]", false}, // 仅右括号不算错误(当作普通字段名)
{"", true}, // 空路径
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
err := ValidateFieldPath(tt.path)
if tt.hasError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestNormalizeFieldPath(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"name", "name"},
{"user.profile.name", "user.profile.name"},
{"data[0]", "data[0]"},
{"users[1].name", "users[1].name"},
{"config['database']", "config['database']"},
{"matrix[0][1]", "matrix[0][1]"},
{"a.b[0].c['key']", "a.b[0].c['key']"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := NormalizeFieldPath(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetFieldPathDepth(t *testing.T) {
tests := []struct {
path string
expected int
}{
{"", 0},
{"name", 1},
{"user.name", 2},
{"data[0]", 2},
{"users[1].name", 3},
{"config['database']", 2},
{"matrix[0][1]", 3},
{"a.b.c.d", 4},
{"users[0].profile['name']", 4},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
result := GetFieldPathDepth(tt.path)
assert.Equal(t, tt.expected, result)
})
}
}