forked from GiteaTest2015/streamsql
573 lines
13 KiB
Go
573 lines
13 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)
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestGetAllReferencedFields 测试GetAllReferencedFields函数
|
||
func TestGetAllReferencedFields(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
fieldPaths []string
|
||
expected []string
|
||
}{
|
||
{
|
||
name: "空列表",
|
||
fieldPaths: []string{},
|
||
expected: []string{},
|
||
},
|
||
{
|
||
name: "单个简单字段",
|
||
fieldPaths: []string{"name"},
|
||
expected: []string{"name"},
|
||
},
|
||
{
|
||
name: "多个不同顶级字段",
|
||
fieldPaths: []string{"device.info.name", "sensor.temperature", "data[0].value"},
|
||
expected: []string{"device", "sensor", "data"},
|
||
},
|
||
{
|
||
name: "重复顶级字段",
|
||
fieldPaths: []string{"user.name", "user.email", "user.profile.age"},
|
||
expected: []string{"user"},
|
||
},
|
||
{
|
||
name: "包含空字符串",
|
||
fieldPaths: []string{"user.name", "", "device.id"},
|
||
expected: []string{"user", "device"},
|
||
},
|
||
{
|
||
name: "数组和map访问",
|
||
fieldPaths: []string{"items[0].name", "config['database']", "matrix[1][2]"},
|
||
expected: []string{"items", "config", "matrix"},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result := GetAllReferencedFields(tt.fieldPaths)
|
||
// 由于返回的是map的keys,顺序可能不同,所以需要排序比较
|
||
assert.ElementsMatch(t, tt.expected, result)
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestFieldAccessError 测试FieldAccessError类型
|
||
func TestFieldAccessError(t *testing.T) {
|
||
err := &FieldAccessError{
|
||
Path: "invalid.path[abc]",
|
||
Message: "invalid bracket content",
|
||
}
|
||
|
||
expected := "field access error for path 'invalid.path[abc]': invalid bracket content"
|
||
assert.Equal(t, expected, err.Error())
|
||
}
|
||
|
||
// TestSetNestedFieldEdgeCases 测试SetNestedField函数的边缘情况
|
||
func TestSetNestedFieldEdgeCases(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
fieldPath string
|
||
value interface{}
|
||
hasError bool
|
||
errorMsg string
|
||
}{
|
||
{
|
||
name: "空字段路径",
|
||
fieldPath: "",
|
||
value: "test",
|
||
hasError: true,
|
||
errorMsg: "empty field path",
|
||
},
|
||
{
|
||
name: "无效字段路径",
|
||
fieldPath: "field[abc]",
|
||
value: "test",
|
||
hasError: false, // 会fallback到简单设置
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
data := make(map[string]interface{})
|
||
err := SetNestedField(data, tt.fieldPath, tt.value)
|
||
|
||
if tt.hasError {
|
||
assert.Error(t, err)
|
||
if tt.errorMsg != "" {
|
||
assert.Contains(t, err.Error(), tt.errorMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestGetNestedFieldEdgeCases 测试GetNestedField函数的边缘情况
|
||
func TestGetNestedFieldEdgeCases(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
data interface{}
|
||
fieldPath string
|
||
expected interface{}
|
||
found bool
|
||
}{
|
||
{
|
||
name: "空字段路径",
|
||
data: map[string]interface{}{"test": "value"},
|
||
fieldPath: "",
|
||
expected: nil,
|
||
found: false,
|
||
},
|
||
{
|
||
name: "nil数据",
|
||
data: nil,
|
||
fieldPath: "test",
|
||
expected: nil,
|
||
found: false,
|
||
},
|
||
{
|
||
name: "数组越界访问",
|
||
data: map[string]interface{}{
|
||
"items": []interface{}{"a", "b"},
|
||
},
|
||
fieldPath: "items[5]",
|
||
expected: nil,
|
||
found: false,
|
||
},
|
||
{
|
||
name: "负数索引访问",
|
||
data: map[string]interface{}{
|
||
"items": []interface{}{"a", "b", "c"},
|
||
},
|
||
fieldPath: "items[-1]",
|
||
expected: "c",
|
||
found: true,
|
||
},
|
||
{
|
||
name: "map中不存在的键",
|
||
data: map[string]interface{}{
|
||
"config": map[string]interface{}{"key1": "value1"},
|
||
},
|
||
fieldPath: "config['nonexistent']",
|
||
expected: nil,
|
||
found: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
result, found := GetNestedField(tt.data, tt.fieldPath)
|
||
assert.Equal(t, tt.expected, result)
|
||
assert.Equal(t, tt.found, found)
|
||
})
|
||
}
|
||
}
|