Folders: Add max depth check with descendant to /apis (#115305)

This commit is contained in:
Stephanie Hingtgen
2025-12-19 20:17:39 +00:00
committed by GitHub
parent 4164239f56
commit 2fbe2f77e3
8 changed files with 275 additions and 51 deletions
+108 -5
View File
@@ -282,6 +282,7 @@ func TestValidateUpdate(t *testing.T) {
old *folders.Folder
parents *folders.FolderInfoList
parentsError error
allFolders []folders.Folder
expectedErr string
maxDepth int // defaults to 5 unless set
}{
@@ -454,6 +455,74 @@ func TestValidateUpdate(t *testing.T) {
},
expectedErr: "cannot move folder under its own descendant",
},
{
name: "error when moving folder from root to level2 with children exceeds max depth",
folder: &folders.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "folderWithChildren",
Annotations: map[string]string{
utils.AnnoKeyFolder: "level2",
},
},
Spec: folders.FolderSpec{
Title: "folder with children",
},
},
old: &folders.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "folderWithChildren",
},
Spec: folders.FolderSpec{
Title: "folder with children",
},
},
parents: &folders.FolderInfoList{
Items: []folders.FolderInfo{
{Name: "level2", Parent: "level1"},
{Name: "level1", Parent: folder.GeneralFolderUID},
{Name: folder.GeneralFolderUID},
},
},
allFolders: []folders.Folder{
{ObjectMeta: metav1.ObjectMeta{Name: "child1", Annotations: map[string]string{utils.AnnoKeyFolder: "folderWithChildren"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "grandchild1", Annotations: map[string]string{utils.AnnoKeyFolder: "child1"}}},
},
maxDepth: 4,
expectedErr: "[folder.maximum-depth-reached]",
},
{
name: "can move folder from root level to level1 with children when within max depth",
folder: &folders.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "folderWithChildren",
Annotations: map[string]string{
utils.AnnoKeyFolder: "level1",
},
},
Spec: folders.FolderSpec{
Title: "folder with children",
},
},
old: &folders.Folder{
ObjectMeta: metav1.ObjectMeta{
Name: "folderWithChildren",
},
Spec: folders.FolderSpec{
Title: "folder with children",
},
},
parents: &folders.FolderInfoList{
Items: []folders.FolderInfo{
{Name: "level1", Parent: folder.GeneralFolderUID},
{Name: folder.GeneralFolderUID},
},
},
allFolders: []folders.Folder{
{ObjectMeta: metav1.ObjectMeta{Name: "child1", Annotations: map[string]string{utils.AnnoKeyFolder: "folderWithChildren"}}},
{ObjectMeta: metav1.ObjectMeta{Name: "grandchild1", Annotations: map[string]string{utils.AnnoKeyFolder: "child1"}}},
},
maxDepth: 4,
},
}
for _, tt := range tests {
@@ -474,11 +543,17 @@ func TestValidateUpdate(t *testing.T) {
}, nil).Maybe()
}
}
for i := range tt.allFolders {
f := tt.allFolders[i]
m.On("Get", context.Background(), f.Name, &metav1.GetOptions{}).Return(&f, nil).Maybe()
}
err := validateOnUpdate(context.Background(), tt.folder, tt.old, m,
func(ctx context.Context, folder *folders.Folder) (*folders.FolderInfoList, error) {
return tt.parents, tt.parentsError
}, maxDepth)
},
&mockSearchClient{folders: tt.allFolders},
maxDepth)
if tt.expectedErr == "" {
require.NoError(t, err)
@@ -693,8 +768,7 @@ type mockSearchClient struct {
stats *resourcepb.ResourceStatsResponse
statsErr error
search *resourcepb.ResourceSearchResponse
searchErr error
folders []folders.Folder
}
// GetStats implements resourcepb.ResourceIndexClient.
@@ -703,8 +777,37 @@ func (m *mockSearchClient) GetStats(ctx context.Context, in *resourcepb.Resource
}
// Search implements resourcepb.ResourceIndexClient.
func (m *mockSearchClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
return m.search, m.searchErr
func (m *mockSearchClient) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) {
// get the list of parents from the search request
parentSet := make(map[string]bool)
if req.Options != nil && req.Options.Fields != nil {
for _, field := range req.Options.Fields {
if field.Key == "folder" && field.Operator == "in" {
for _, v := range field.Values {
parentSet[v] = true
}
}
}
}
// find children that match the parent filter
var rows []*resourcepb.ResourceTableRow
for i := range m.folders {
meta, err := utils.MetaAccessor(&m.folders[i])
if err != nil {
continue
}
parentUID := meta.GetFolder()
if parentSet[parentUID] {
rows = append(rows, &resourcepb.ResourceTableRow{
Key: &resourcepb.ResourceKey{Name: m.folders[i].Name},
})
}
}
return &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{Rows: rows},
}, nil
}
// RebuildIndexes implements resourcepb.ResourceIndexClient.