refactor(folders): use set to detect circular references (#113665)

This commit is contained in:
Jean-Philippe Quéméner
2025-11-10 20:37:02 +01:00
committed by GitHub
parent c49caead25
commit 142340e0ff
2 changed files with 139 additions and 5 deletions
@@ -568,14 +568,13 @@ func buildFolderFullPaths(f *folder.Folder, relations map[string]string, folderM
titles = append(titles, f.Title)
uids = append(uids, f.UID)
i := 0
seen := make(map[string]bool)
currentUID := f.UID
for currentUID != "" {
// This is just a circuit breaker to prevent infinite loops. We should never reach this limit.
if i > 1000 {
return fmt.Errorf("folder depth exceeds the maximum allowed depth, You might have a circular reference")
if seen[currentUID] {
return folder.ErrCircularReference.Errorf("circular reference detected for folder %s", currentUID)
}
i++
seen[currentUID] = true
parentUID, exists := relations[currentUID]
if !exists {
break
@@ -891,6 +891,141 @@ func TestBuildFolderFullPaths(t *testing.T) {
}
}
func TestBuildFolderFullPaths_CircularReference(t *testing.T) {
type args struct {
f *folder.Folder
relations map[string]string
folderMap map[string]*folder.Folder
}
tests := []struct {
name string
args args
expectedErr string
}{
{
name: "should detect direct circular reference (A -> B -> A)",
args: args{
f: &folder.Folder{
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-b",
},
relations: map[string]string{
"folder-a": "folder-b",
"folder-b": "folder-a", // circular: B points back to A
},
folderMap: map[string]*folder.Folder{
"folder-a": {
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-b",
},
"folder-b": {
Title: "FolderB",
UID: "folder-b",
ParentUID: "folder-a",
},
},
},
expectedErr: "circular reference detected",
},
{
name: "should detect self-reference (A -> A)",
args: args{
f: &folder.Folder{
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-a", // points to itself
},
relations: map[string]string{
"folder-a": "folder-a",
},
folderMap: map[string]*folder.Folder{
"folder-a": {
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-a",
},
},
},
expectedErr: "circular reference detected",
},
{
name: "should detect longer circular reference (A -> B -> C -> A)",
args: args{
f: &folder.Folder{
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-b",
},
relations: map[string]string{
"folder-a": "folder-b",
"folder-b": "folder-c",
"folder-c": "folder-a", // circular: C points back to A
},
folderMap: map[string]*folder.Folder{
"folder-a": {
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-b",
},
"folder-b": {
Title: "FolderB",
UID: "folder-b",
ParentUID: "folder-c",
},
"folder-c": {
Title: "FolderC",
UID: "folder-c",
ParentUID: "folder-a",
},
},
},
expectedErr: "circular reference detected",
},
{
name: "should detect circular reference starting from middle (B in A -> B -> C -> A)",
args: args{
f: &folder.Folder{
Title: "FolderB",
UID: "folder-b",
ParentUID: "folder-c",
},
relations: map[string]string{
"folder-a": "folder-b",
"folder-b": "folder-c",
"folder-c": "folder-a",
},
folderMap: map[string]*folder.Folder{
"folder-a": {
Title: "FolderA",
UID: "folder-a",
ParentUID: "folder-b",
},
"folder-b": {
Title: "FolderB",
UID: "folder-b",
ParentUID: "folder-c",
},
"folder-c": {
Title: "FolderC",
UID: "folder-c",
ParentUID: "folder-a",
},
},
},
expectedErr: "circular reference detected",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := buildFolderFullPaths(tt.args.f, tt.args.relations, tt.args.folderMap)
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedErr)
})
}
}
func TestList(t *testing.T) {
type args struct {
ctx context.Context