Skip to content

Commit dfea4b7

Browse files
committed
test: add tests for circular reference handling
1 parent 076eaf9 commit dfea4b7

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

core/conf/config_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,78 @@ func Test_buildFieldsInfo(t *testing.T) {
13391339
}
13401340
}
13411341

1342+
func Test_buildFieldsInfo_CircularReference(t *testing.T) {
1343+
type MySQLConfig struct {
1344+
Alias string `json:",optional"`
1345+
DSN string `json:",optional"`
1346+
Type string `json:",optional"`
1347+
MaxOpenConns int `json:",optional"`
1348+
MaxIdleConns int `json:",optional"`
1349+
Slave []*MySQLConfig `json:",optional"` // Self-reference slice
1350+
}
1351+
1352+
type CountryConfig struct {
1353+
MySQL MySQLConfig `json:",optional"`
1354+
}
1355+
1356+
type GlobalConfig struct {
1357+
CN CountryConfig `json:",optional"`
1358+
}
1359+
1360+
tests := []struct {
1361+
name string
1362+
t reflect.Type
1363+
}{
1364+
{
1365+
name: "direct circular reference",
1366+
t: reflect.TypeOf(MySQLConfig{}),
1367+
},
1368+
{
1369+
name: "nested circular reference",
1370+
t: reflect.TypeOf(GlobalConfig{}),
1371+
},
1372+
{
1373+
name: "pointer circular reference",
1374+
t: reflect.TypeOf(&MySQLConfig{}),
1375+
},
1376+
{
1377+
name: "slice of circular reference",
1378+
t: reflect.TypeOf([]MySQLConfig{}),
1379+
},
1380+
}
1381+
1382+
for _, tt := range tests {
1383+
t.Run(tt.name, func(t *testing.T) {
1384+
info, err := buildFieldsInfo(tt.t, "test")
1385+
assert.NoError(t, err)
1386+
assert.NotNil(t, info)
1387+
assert.NotNil(t, info.children)
1388+
})
1389+
}
1390+
}
1391+
1392+
func Test_buildFieldsInfoWithVisited_CircularDetection(t *testing.T) {
1393+
type CircularStruct struct {
1394+
Name string `json:",optional"`
1395+
Children []*CircularStruct `json:",optional"`
1396+
}
1397+
1398+
visited := make(map[reflect.Type]bool)
1399+
tp := reflect.TypeOf(CircularStruct{})
1400+
1401+
info1, err1 := buildFieldsInfoWithVisited(tp, "test1", visited)
1402+
assert.NoError(t, err1)
1403+
assert.NotNil(t, info1)
1404+
1405+
visited[tp] = true
1406+
1407+
info2, err2 := buildFieldsInfoWithVisited(tp, "test2", visited)
1408+
assert.NoError(t, err2)
1409+
assert.NotNil(t, info2)
1410+
assert.NotNil(t, info2.children)
1411+
assert.Equal(t, 0, len(info2.children))
1412+
}
1413+
13421414
func createTempFile(t *testing.T, ext, text string) (string, error) {
13431415
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
13441416
if err != nil {

core/mapping/utils_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,89 @@ func TestValidateValueRange(t *testing.T) {
334334
func TestSetMatchedPrimitiveValue(t *testing.T) {
335335
assert.Error(t, setMatchedPrimitiveValue(reflect.Func, reflect.ValueOf(2), "1"))
336336
}
337+
338+
func TestImplicitValueRequiredStruct_CircularReference(t *testing.T) {
339+
type MySQLConfig struct {
340+
Alias string `json:",optional"`
341+
DSN string `json:",optional"`
342+
Type string `json:",optional"`
343+
MaxOpenConns int `json:",optional"`
344+
MaxIdleConns int `json:",optional"`
345+
Slave []*MySQLConfig `json:",optional"` // Self-reference slice
346+
}
347+
348+
type CountryConfig struct {
349+
MySQL MySQLConfig `json:",optional"`
350+
}
351+
352+
type GlobalConfig struct {
353+
CN CountryConfig `json:",optional"`
354+
}
355+
356+
tests := []struct {
357+
name string
358+
tag string
359+
tp reflect.Type
360+
expected bool
361+
}{
362+
{
363+
name: "direct circular reference - all optional",
364+
tag: "json",
365+
tp: reflect.TypeOf(MySQLConfig{}),
366+
expected: false,
367+
},
368+
{
369+
name: "nested circular reference - all optional",
370+
tag: "json",
371+
tp: reflect.TypeOf(GlobalConfig{}),
372+
expected: false,
373+
},
374+
{
375+
name: "pointer circular reference",
376+
tag: "json",
377+
tp: reflect.TypeOf(&MySQLConfig{}),
378+
expected: false,
379+
},
380+
}
381+
382+
for _, tt := range tests {
383+
t.Run(tt.name, func(t *testing.T) {
384+
result, err := implicitValueRequiredStruct(tt.tag, tt.tp)
385+
assert.NoError(t, err)
386+
assert.Equal(t, tt.expected, result)
387+
})
388+
}
389+
}
390+
391+
func TestImplicitValueRequiredStructWithDepth_MaxDepth(t *testing.T) {
392+
type DeepStruct struct {
393+
Child *DeepStruct `json:",optional"`
394+
}
395+
396+
visited := make(map[reflect.Type]bool)
397+
tp := reflect.TypeOf(DeepStruct{})
398+
399+
result, err := implicitValueRequiredStructWithDepth("json", tp, 150, visited)
400+
assert.NoError(t, err)
401+
assert.False(t, result)
402+
}
403+
404+
func TestImplicitValueRequiredStructWithDepth_CircularDetection(t *testing.T) {
405+
type CircularStruct struct {
406+
Name string `json:",optional"`
407+
Children []*CircularStruct `json:",optional"`
408+
}
409+
410+
visited := make(map[reflect.Type]bool)
411+
tp := reflect.TypeOf(CircularStruct{})
412+
413+
result1, err1 := implicitValueRequiredStructWithDepth("json", tp, 0, visited)
414+
assert.NoError(t, err1)
415+
assert.False(t, result1)
416+
417+
visited[Deref(tp)] = true
418+
419+
result2, err2 := implicitValueRequiredStructWithDepth("json", tp, 0, visited)
420+
assert.NoError(t, err2)
421+
assert.False(t, result2)
422+
}

0 commit comments

Comments
 (0)