package server import ( "context" "fmt" "math/rand" "testing" "time" authzv1 "github.com/grafana/authlib/authz/proto/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" "github.com/grafana/grafana/pkg/services/authz/zanzana/common" "github.com/grafana/grafana/pkg/services/authz/zanzana/store" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" ) const ( benchNamespace = "default" // Folder tree parameters foldersPerLevel = 3 folderDepth = 7 // Other data generation parameters numResources = 50000 numUsers = 1000 numTeams = 100 // Timeout for List operations listTimeout = 30 * time.Second // Resource type constants for benchmarks benchDashboardGroup = "dashboard.grafana.app" benchDashboardResource = "dashboards" benchFolderGroup = "folder.grafana.app" benchFolderResource = "folders" // BenchmarkBatchCheck measures the performance of BatchCheck requests with 50 items per batch. batchCheckSize = 50 ) // benchmarkData holds all the generated test data for benchmarks type benchmarkData struct { folders []string // folder UIDs folderDepths map[string]int // folder UID -> depth level folderParents map[string]string // folder UID -> parent UID folderDescendants map[string]int // folder UID -> number of descendants (including self) foldersByDepth [][]string // folders grouped by depth level resources []string // resource names resourceFolders map[string]string // resource name -> folder UID users []string // user identifiers (e.g., "user:1") teams []string // team identifiers (e.g., "team:1") // Pre-computed test scenarios deepestFolder string // folder at max depth for worst-case tests midDepthFolder string // folder at depth/2 shallowFolder string // folder at depth 1 rootFolder string // root level folder (depth 0) largestRootFolder string // root folder with most descendants largestRootDescCount int // number of descendants in largestRootFolder maxDepth int // maximum depth in the tree } // generateFolderHierarchy creates a balanced tree of folders. // Each folder has `childrenPerFolder` children, up to `depth` levels deep. func generateFolderHierarchy(childrenPerFolder, depth int) ([]*openfgav1.TupleKey, *benchmarkData) { // Calculate total folders: childrenPerFolder + childrenPerFolder^2 + ... + childrenPerFolder^(depth+1) totalFolders := 0 levelSize := childrenPerFolder for d := 0; d <= depth; d++ { totalFolders += levelSize levelSize *= childrenPerFolder } data := &benchmarkData{ folders: make([]string, 0, totalFolders), folderDepths: make(map[string]int), folderParents: make(map[string]string), folderDescendants: make(map[string]int), } tuples := make([]*openfgav1.TupleKey, 0, totalFolders) folderIdx := 0 // Track folders at each level for parent assignment levelFolders := make([][]string, depth+1) for i := range levelFolders { levelFolders[i] = make([]string, 0) } // Create root level folders (depth 0) for i := 0; i < childrenPerFolder; i++ { folderUID := fmt.Sprintf("folder-%d", folderIdx) data.folders = append(data.folders, folderUID) data.folderDepths[folderUID] = 0 levelFolders[0] = append(levelFolders[0], folderUID) folderIdx++ } // Create folders at each subsequent depth level for d := 1; d <= depth; d++ { parentFolders := levelFolders[d-1] // Each parent gets exactly childrenPerFolder children for _, parentUID := range parentFolders { for j := 0; j < childrenPerFolder; j++ { folderUID := fmt.Sprintf("folder-%d", folderIdx) data.folders = append(data.folders, folderUID) data.folderDepths[folderUID] = d data.folderParents[folderUID] = parentUID levelFolders[d] = append(levelFolders[d], folderUID) // Create parent relationship tuple tuples = append(tuples, common.NewFolderParentTuple(folderUID, parentUID)) folderIdx++ } } } // Set reference folders for different depth scenarios data.rootFolder = levelFolders[0][0] data.shallowFolder = levelFolders[0][0] if len(levelFolders[1]) > 0 { data.shallowFolder = levelFolders[1][0] } midDepth := depth / 2 if len(levelFolders[midDepth]) > 0 { data.midDepthFolder = levelFolders[midDepth][0] } // Deepest folder if len(levelFolders[depth]) > 0 { data.deepestFolder = levelFolders[depth][0] } // Calculate descendant counts for each folder (bottom-up) // Initialize all folders with count of 1 (self) for _, folder := range data.folders { data.folderDescendants[folder] = 1 } // Process folders from deepest to shallowest, accumulating descendant counts for d := depth; d >= 0; d-- { for _, folder := range levelFolders[d] { if parent, hasParent := data.folderParents[folder]; hasParent { data.folderDescendants[parent] += data.folderDescendants[folder] } } } // Find root folder with most descendants for _, rootFolder := range levelFolders[0] { count := data.folderDescendants[rootFolder] if count > data.largestRootDescCount { data.largestRootDescCount = count data.largestRootFolder = rootFolder } } // Store folders by depth for depth-based testing data.foldersByDepth = levelFolders data.maxDepth = depth return tuples, data } // generateResources creates resources distributed across folders func generateResources(data *benchmarkData, numResources int) []*openfgav1.TupleKey { data.resources = make([]string, numResources) data.resourceFolders = make(map[string]string, numResources) // Distribute resources across folders for i := 0; i < numResources; i++ { resourceName := fmt.Sprintf("resource-%d", i) folderIdx := i % len(data.folders) folderUID := data.folders[folderIdx] data.resources[i] = resourceName data.resourceFolders[resourceName] = folderUID } // Note: We don't create tuples for resources themselves, // permissions are assigned to users/teams on folders or directly on resources return nil } // generateUsers creates user identifiers func generateUsers(data *benchmarkData, numUsers int) { data.users = make([]string, numUsers) for i := 0; i < numUsers; i++ { data.users[i] = fmt.Sprintf("user:%d", i) } } // generateTeams creates team identifiers func generateTeams(data *benchmarkData, numTeams int) { data.teams = make([]string, numTeams) for i := 0; i < numTeams; i++ { data.teams[i] = fmt.Sprintf("team:%d", i) } } // generatePermissionTuples creates various permission assignments for benchmarking. // Users are distributed across 7 patterns: global, root folder, mid-depth folder, // folder-scoped resource, direct resource, team-based, and no permissions. const numPermissionPatterns = 7 func generatePermissionTuples(data *benchmarkData) []*openfgav1.TupleKey { tuples := make([]*openfgav1.TupleKey, 0) // Distribute users across different permission patterns usersPerPattern := len(data.users) / numPermissionPatterns // Pattern 1: Users with GroupResource permission (all access) // Users 0 to usersPerPattern-1 for i := 0; i < usersPerPattern; i++ { tuples = append(tuples, common.NewGroupResourceTuple( data.users[i], common.RelationGet, benchDashboardGroup, benchDashboardResource, "", )) } // Pattern 2: Users with folder-level permission on root folders // Users usersPerPattern to 2*usersPerPattern-1 for i := usersPerPattern; i < 2*usersPerPattern; i++ { folderIdx := (i - usersPerPattern) % len(data.folders) // Only assign to root-level folders for this pattern for j := folderIdx; j < len(data.folders); j++ { if data.folderDepths[data.folders[j]] == 0 { tuples = append(tuples, common.NewFolderTuple( data.users[i], common.RelationSetView, data.folders[j], )) break } } } // Pattern 3: Users with folder-level permission on mid-depth folders // Use relative depth range: 1/3 to 2/3 of max depth // Use "view" relation which grants get through the optimized schema minMidDepth := data.maxDepth / 3 maxMidDepth := 2 * data.maxDepth / 3 if maxMidDepth < minMidDepth { maxMidDepth = minMidDepth } // Collect folders in the mid-depth range var midDepthFolders []string for d := minMidDepth; d <= maxMidDepth; d++ { if d < len(data.foldersByDepth) { midDepthFolders = append(midDepthFolders, data.foldersByDepth[d]...) } } // Fall back to root folders if no mid-depth folders exist if len(midDepthFolders) == 0 { midDepthFolders = data.foldersByDepth[0] } for i := 2 * usersPerPattern; i < 3*usersPerPattern; i++ { folderIdx := (i - 2*usersPerPattern) % len(midDepthFolders) tuples = append(tuples, common.NewFolderTuple( data.users[i], common.RelationSetView, midDepthFolders[folderIdx], )) } // Pattern 4: Users with folder-scoped resource permission for i := 3 * usersPerPattern; i < 4*usersPerPattern; i++ { folderIdx := (i - 3*usersPerPattern) % len(data.folders) tuples = append(tuples, common.NewFolderResourceTuple( data.users[i], common.RelationGet, benchDashboardGroup, benchDashboardResource, "", data.folders[folderIdx], )) } // Pattern 5: Users with direct resource permission for i := 4 * usersPerPattern; i < 5*usersPerPattern; i++ { resourceIdx := (i - 4*usersPerPattern) % len(data.resources) tuples = append(tuples, common.NewResourceTuple( data.users[i], common.RelationGet, benchDashboardGroup, benchDashboardResource, "", data.resources[resourceIdx], )) } // Pattern 6: Team memberships and team permissions // First, add users to teams for i := 5 * usersPerPattern; i < 6*usersPerPattern && i < len(data.users); i++ { teamIdx := (i - 5*usersPerPattern) % len(data.teams) tuples = append(tuples, common.NewTypedTuple( common.TypeTeam, data.users[i], common.RelationTeamMember, fmt.Sprintf("%d", teamIdx), )) } // Then, give teams folder permissions // Use "view" relation which grants get through the optimized schema for i := 0; i < len(data.teams); i++ { folderIdx := i % len(data.folders) teamMember := fmt.Sprintf("team:%d#member", i) tuples = append(tuples, common.NewFolderTuple( teamMember, common.RelationSetView, data.folders[folderIdx], )) } // Pattern 7: Users with no permissions (remaining users) // These users don't get any tuples - they're for testing denial cases return tuples } // setupBenchmarkServer creates a server with the benchmark data loaded func setupBenchmarkServer(b *testing.B) (*Server, *benchmarkData) { b.Helper() if testing.Short() { b.Skip("skipping benchmark in short mode") } cfg := setting.NewCfg() testStore := sqlstore.NewTestStore(b, sqlstore.WithCfg(cfg)) openFGAStore, err := store.NewEmbeddedStore(cfg, testStore, log.NewNopLogger()) require.NoError(b, err) openfga, err := NewOpenFGAServer(cfg.ZanzanaServer, openFGAStore) require.NoError(b, err) srv, err := NewServer(cfg.ZanzanaServer, openfga, log.NewNopLogger(), tracing.NewNoopTracerService(), prometheus.NewRegistry()) require.NoError(b, err) // Generate test data b.Log("Generating folder hierarchy...") folderTuples, data := generateFolderHierarchy(foldersPerLevel, folderDepth) b.Log("Generating resources...") generateResources(data, numResources) b.Log("Generating users...") generateUsers(data, numUsers) b.Log("Generating teams...") generateTeams(data, numTeams) b.Log("Generating permission tuples...") permTuples := generatePermissionTuples(data) // Add special user with permission on largest root folder (for >1000 folder test) // Use "view" relation which grants get through the optimized schema largeRootUserTuple := common.NewFolderTuple( "user:large-root-access", common.RelationSetView, data.largestRootFolder, ) permTuples = append(permTuples, largeRootUserTuple) // Add users with permissions at each depth level for depth-based testing // Use "view" relation which grants get through the optimized schema for depth := 0; depth <= data.maxDepth; depth++ { if len(data.foldersByDepth[depth]) == 0 { continue } folder := data.foldersByDepth[depth][0] user := fmt.Sprintf("user:depth-%d-access", depth) permTuples = append(permTuples, common.NewFolderTuple(user, common.RelationSetView, folder)) } // Combine all tuples allTuples := append(folderTuples, permTuples...) b.Logf("Total tuples to write: %d", len(allTuples)) // Get store info ctx := newContextWithZanzanaUpdatePermission() storeInf, err := srv.getStoreInfo(ctx, benchNamespace) require.NoError(b, err) // Write tuples in batches (OpenFGA limits to 100 per write) batchSize := 100 for i := 0; i < len(allTuples); i += batchSize { end := i + batchSize if end > len(allTuples) { end = len(allTuples) } batch := allTuples[i:end] _, err = srv.openfga.Write(ctx, &openfgav1.WriteRequest{ StoreId: storeInf.ID, AuthorizationModelId: storeInf.ModelID, Writes: &openfgav1.WriteRequestWrites{ TupleKeys: batch, OnDuplicate: "ignore", }, }) require.NoError(b, err) if (i/batchSize)%100 == 0 { b.Logf("Written %d/%d tuples", end, len(allTuples)) } } b.Logf("Benchmark data setup complete: %d folders, %d resources, %d users, %d teams", len(data.folders), len(data.resources), len(data.users), len(data.teams)) b.Logf("Largest root folder: %s with %d descendants", data.largestRootFolder, data.largestRootDescCount) return srv, data } // BenchmarkCheck measures the performance of Check requests func BenchmarkCheck(b *testing.B) { srv, data := setupBenchmarkServer(b) ctx := newContextWithNamespace() // Helper to create check requests newCheckReq := func(subject, verb, group, resource, folder, name string) *authzv1.CheckRequest { return &authzv1.CheckRequest{ Namespace: benchNamespace, Subject: subject, Verb: verb, Group: group, Resource: resource, Folder: folder, Name: name, } } usersPerPattern := len(data.users) / 7 b.Run("GroupResourceDirect", func(b *testing.B) { // User with group_resource permission - should have access to everything user := data.users[0] // First user has GroupResource permission resource := data.resources[rand.Intn(len(data.resources))] folder := data.resourceFolders[resource] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } if !res.GetAllowed() { b.Fatal("expected access to be allowed") } } }) // Test folder inheritance at each depth level (0 to maxDepth) // User has permission on ROOT folder (depth 0), we check access at each deeper level rootUser := "user:depth-0-access" // has view permission on root folder for depth := 0; depth <= data.maxDepth; depth++ { depth := depth // capture for closure if len(data.foldersByDepth[depth]) == 0 { continue } b.Run(fmt.Sprintf("FolderInheritance/Depth%d", depth), func(b *testing.B) { resource := data.resources[0] folder := data.foldersByDepth[depth][0] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(rootUser, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } _ = res.GetAllowed() } }) } b.Run("FolderResourceScoped", func(b *testing.B) { // User with folder-scoped resource permission user := data.users[3*usersPerPattern] folderIdx := 0 folder := data.folders[folderIdx] resource := data.resources[folderIdx] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } _ = res.GetAllowed() } }) b.Run("DirectResource", func(b *testing.B) { // User with direct resource permission user := data.users[4*usersPerPattern] resourceIdx := 0 resource := data.resources[resourceIdx] folder := data.resourceFolders[resource] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } _ = res.GetAllowed() } }) b.Run("TeamMembership", func(b *testing.B) { // User who is a team member, team has folder permission user := data.users[5*usersPerPattern] teamIdx := 0 folderIdx := teamIdx % len(data.folders) folder := data.folders[folderIdx] resource := data.resources[folderIdx%len(data.resources)] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } _ = res.GetAllowed() } }) b.Run("NoAccess", func(b *testing.B) { // User with no permissions - tests denial path user := data.users[len(data.users)-1] // Last user has no permissions resource := data.resources[0] folder := data.resourceFolders[resource] b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource, folder, resource)) if err != nil { b.Fatal(err) } if res.GetAllowed() { b.Fatal("expected access to be denied") } } }) b.Run("FolderCheck", func(b *testing.B) { // Direct folder access check user := data.users[usersPerPattern] folder := data.rootFolder b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.Check(ctx, newCheckReq(user, utils.VerbGet, benchFolderGroup, benchFolderResource, "", folder)) if err != nil { b.Fatal(err) } _ = res.GetAllowed() } }) } func BenchmarkBatchCheck(b *testing.B) { srv, data := setupBenchmarkServer(b) ctx := newContextWithNamespace() // Helper to create batch check requests newBatchCheckReq := func(subject string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest { return &authzextv1.BatchCheckRequest{ Namespace: benchNamespace, Subject: subject, Items: items, } } // Helper to create batch items for resources in folders createBatchItems := func(resources []string, resourceFolders map[string]string) []*authzextv1.BatchCheckItem { items := make([]*authzextv1.BatchCheckItem, 0, batchCheckSize) for i := 0; i < batchCheckSize && i < len(resources); i++ { resource := resources[i] items = append(items, &authzextv1.BatchCheckItem{ Verb: utils.VerbGet, Group: benchDashboardGroup, Resource: benchDashboardResource, Name: resource, Folder: resourceFolders[resource], }) } return items } // Helper to create batch items for folders at a specific depth createFolderBatchItems := func(folders []string, depth int, folderDepths map[string]int) []*authzextv1.BatchCheckItem { items := make([]*authzextv1.BatchCheckItem, 0, batchCheckSize) for _, folder := range folders { if folderDepths[folder] == depth && len(items) < batchCheckSize { items = append(items, &authzextv1.BatchCheckItem{ Verb: utils.VerbGet, Group: benchDashboardGroup, Resource: benchDashboardResource, Name: fmt.Sprintf("resource-in-%s", folder), Folder: folder, }) } } // Fill remaining slots if needed for len(items) < batchCheckSize && len(folders) > 0 { folder := folders[len(items)%len(folders)] items = append(items, &authzextv1.BatchCheckItem{ Verb: utils.VerbGet, Group: benchDashboardGroup, Resource: benchDashboardResource, Name: fmt.Sprintf("resource-%d", len(items)), Folder: folder, }) } return items } usersPerPattern := len(data.users) / numPermissionPatterns b.Run("GroupResourceDirect", func(b *testing.B) { // User with group_resource permission - should have access to everything user := data.users[0] items := createBatchItems(data.resources, data.resourceFolders) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("FolderInheritance/Depth1", func(b *testing.B) { // User with folder permission on shallow folder user := data.users[usersPerPattern] items := createFolderBatchItems(data.folders, 1, data.folderDepths) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("FolderInheritance/Depth4", func(b *testing.B) { // User with folder permission on mid-depth folder user := data.users[2*usersPerPattern] items := createFolderBatchItems(data.folders, 4, data.folderDepths) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("FolderInheritance/Depth7", func(b *testing.B) { // Check access on deepest folders (worst case for inheritance traversal) user := data.users[usersPerPattern] items := createFolderBatchItems(data.folders, data.maxDepth, data.folderDepths) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("DirectResource", func(b *testing.B) { // User with direct resource permission user := data.users[4*usersPerPattern] items := createBatchItems(data.resources, data.resourceFolders) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("TeamMembership", func(b *testing.B) { // User who is a team member, team has folder permission user := data.users[5*usersPerPattern] items := createBatchItems(data.resources, data.resourceFolders) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("NoAccess", func(b *testing.B) { // User with no permissions - tests denial path user := data.users[len(data.users)-1] items := createBatchItems(data.resources, data.resourceFolders) b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) b.Run("MixedFolders", func(b *testing.B) { // Batch of items across different folder depths user := data.users[usersPerPattern] items := make([]*authzextv1.BatchCheckItem, 0, batchCheckSize) for i := 0; i < batchCheckSize; i++ { folder := data.folders[i%len(data.folders)] items = append(items, &authzextv1.BatchCheckItem{ Verb: utils.VerbGet, Group: benchDashboardGroup, Resource: benchDashboardResource, Name: fmt.Sprintf("resource-%d", i), Folder: folder, }) } b.ResetTimer() for i := 0; i < b.N; i++ { res, err := srv.BatchCheck(ctx, newBatchCheckReq(user, items)) if err != nil { b.Fatal(err) } _ = res.Groups } }) } // BenchmarkList measures the performance of List requests (Compile equivalent) func BenchmarkList(b *testing.B) { srv, data := setupBenchmarkServer(b) baseCtx := newContextWithNamespace() // Helper to create list requests newListReq := func(subject, verb, group, resource string) *authzv1.ListRequest { return &authzv1.ListRequest{ Namespace: benchNamespace, Subject: subject, Verb: verb, Group: group, Resource: resource, } } // Helper to create context with timeout ctxWithTimeout := func() (context.Context, context.CancelFunc) { return context.WithTimeout(baseCtx, listTimeout) } usersPerPattern := len(data.users) / 7 b.Run("AllAccess", func(b *testing.B) { // User with group_resource permission - should return All=true quickly user := data.users[0] b.Logf("Test: User with group_resource permission (access to ALL dashboards)") b.Logf("Expected: All=true returned immediately without ListObjects call") b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource)) cancel() if err != nil { b.Fatalf("Error: %v", err) } if !res.GetAll() { b.Fatal("expected All=true for user with group_resource permission") } } }) b.Run("FolderScoped", func(b *testing.B) { // User with folder permissions - should return folder list user := data.users[usersPerPattern] b.Logf("Test: User with direct folder permission on a single folder") b.Logf("Expected: Returns list of folders user has access to") b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource)) cancel() if err != nil { b.Fatalf("Error: %v", err) } if i == 0 { b.Logf("Result: %d folders, %d items, All=%v", len(res.GetFolders()), len(res.GetItems()), res.GetAll()) } } }) b.Run("DirectResources", func(b *testing.B) { // User with direct resource permissions - should return items list user := data.users[4*usersPerPattern] b.Logf("Test: User with direct permission on specific resources") b.Logf("Expected: Returns list of specific resources user has access to") b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource)) cancel() if err != nil { b.Fatalf("Error: %v", err) } if i == 0 { b.Logf("Result: %d folders, %d items, All=%v", len(res.GetFolders()), len(res.GetItems()), res.GetAll()) } } }) b.Run("NoAccess", func(b *testing.B) { // User with no permissions - should return empty results user := data.users[len(data.users)-1] b.Logf("Test: User with NO permissions (denial case)") b.Logf("Expected: Empty results") b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchDashboardGroup, benchDashboardResource)) cancel() if err != nil { b.Fatalf("Error: %v", err) } if i == 0 { b.Logf("Result: %d folders, %d items, All=%v", len(res.GetFolders()), len(res.GetItems()), res.GetAll()) } } }) b.Run("LargeRootFolder", func(b *testing.B) { // User with access to root folder that has many descendants user := "user:large-root-access" b.Logf("Test: User with permission on ROOT folder (folder-0)") b.Logf("Root folder %s has %d total descendants", data.largestRootFolder, data.largestRootDescCount) b.Logf("Expected: ListObjects should return folders through inheritance") b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() start := time.Now() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchFolderGroup, benchFolderResource)) elapsed := time.Since(start) cancel() if err != nil { b.Fatalf("Error after %v: %v", elapsed, err) } if i == 0 { b.Logf("Result: %d folders returned in %v (descendants: %d)", len(res.GetItems()), elapsed, data.largestRootDescCount) } } }) // Test List at various folder depths to find breaking point b.Run("ByDepth", func(b *testing.B) { b.Logf("Testing List performance at various folder depths (timeout: %v)", listTimeout) b.Logf("Tree structure: %d folders per level, %d max depth", foldersPerLevel, data.maxDepth) for depth := 0; depth <= data.maxDepth; depth++ { if len(data.foldersByDepth[depth]) == 0 { continue } folder := data.foldersByDepth[depth][0] descendants := data.folderDescendants[folder] user := fmt.Sprintf("user:depth-%d-access", depth) b.Run(fmt.Sprintf("Depth%d_%dDescendants", depth, descendants), func(b *testing.B) { b.Logf("Test: User with permission on folder at depth %d", depth) b.Logf("Folder: %s, Descendants: %d", folder, descendants) // First, do a single timed run to report ctx, cancel := ctxWithTimeout() start := time.Now() res, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchFolderGroup, benchFolderResource)) elapsed := time.Since(start) cancel() if err != nil { b.Logf("FAILED after %v: %v", elapsed, err) if elapsed >= listTimeout { b.Logf("TIMEOUT: List took longer than %v", listTimeout) } b.Skip("Skipping benchmark iterations due to error") return } b.Logf("Result: %d folders in %v", len(res.GetItems()), elapsed) if elapsed > 5*time.Second { b.Logf("WARNING: Single List took %v, skipping benchmark iterations", elapsed) b.Skip("Too slow for benchmark iterations") return } b.ResetTimer() for i := 0; i < b.N; i++ { ctx, cancel := ctxWithTimeout() _, err := srv.List(ctx, newListReq(user, utils.VerbGet, benchFolderGroup, benchFolderResource)) cancel() if err != nil { b.Fatalf("Error: %v", err) } } }) } }) }