diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index c7242b31340..881ea3c1430 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -56,8 +56,8 @@ type TestContext struct { OrgID int64 } -// TestIntegrationValidation tests the dashboard K8s API -func TestIntegrationValidation(t *testing.T) { +// TestIntegrationDashboardAPIValidation tests the dashboard K8s API with validation checks +func TestIntegrationDashboardAPIValidation(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } @@ -79,7 +79,19 @@ func TestIntegrationValidation(t *testing.T) { }, }}) - testIntegrationValidationForServer(t, helper, dualWriterMode) + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + + t.Run("Dashboard validation tests", func(t *testing.T) { + runDashboardValidationTests(t, org1Ctx) + }) + + t.Run("Dashboard quota tests", func(t *testing.T) { + runQuotaTests(t, org1Ctx) + }) }) } @@ -114,45 +126,110 @@ func TestIntegrationValidation(t *testing.T) { } } -func testIntegrationValidationForServer(t *testing.T, helper *apis.K8sTestHelper, dualWriterMode rest.DualWriterMode) { - t.Cleanup(func() { - helper.Shutdown() - }) +// TestIntegrationDashboardAPIAuthorization tests the dashboard K8s API with authorization checks +func TestIntegrationDashboardAPIAuthorization(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } - // Create test contexts organization - org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) - org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, dualWriterMode := range dualWriterModes { + t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableAnonymous: true, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature + featuremgmt.FlagUnifiedStorageSearch, + }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: dualWriterMode, + }, + "folders.folder.grafana.app": { + DualWriterMode: dualWriterMode, + }, + }}) - t.Run("Organization 1 tests", func(t *testing.T) { - t.Run("Dashboard validation tests", func(t *testing.T) { - runDashboardValidationTests(t, org1Ctx) + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx, false) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) + }) + + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) }) + } +} - t.Run("Dashboard quota tests", func(t *testing.T) { - runQuotaTests(t, org1Ctx) +// TestIntegrationDashboardAPI tests the dashboard K8s API +func TestIntegrationDashboardAPI(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + dualWriterModes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, dualWriterMode := range dualWriterModes { + t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { + // Create a K8sTestHelper which will set up a real API server + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableAnonymous: true, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesClientDashboardsFolders, // Enable dashboard feature + featuremgmt.FlagUnifiedStorageSearch, + featuremgmt.FlagKubernetesDashboards, + }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": { + DualWriterMode: dualWriterMode, + }, + "folders.folder.grafana.app": { + DualWriterMode: dualWriterMode, + }, + }}) + + t.Cleanup(func() { + helper.Shutdown() + }) + + org1Ctx := createTestContext(t, helper, helper.Org1, dualWriterMode) + org2Ctx := createTestContext(t, helper, helper.OrgB, dualWriterMode) + + t.Run("Dashboard LIST API test", func(t *testing.T) { + runDashboardListTests(t, org1Ctx) + }) + + t.Run("Authorization tests for all identity types", func(t *testing.T) { + runAuthorizationTests(t, org1Ctx) + }) + + t.Run("Dashboard permission tests", func(t *testing.T) { + runDashboardPermissionTests(t, org1Ctx, true) + }) + + t.Run("Dashboard HTTP API test", func(t *testing.T) { + runDashboardHttpTest(t, org1Ctx, org2Ctx) + }) + + t.Run("Cross-organization tests", func(t *testing.T) { + runCrossOrgTests(t, org1Ctx, org2Ctx) + }) }) - - t.Run("Authorization tests for all identity types", func(t *testing.T) { - runAuthorizationTests(t, org1Ctx) - }) - - t.Run("Dashboard permission tests", func(t *testing.T) { - runDashboardPermissionTests(t, org1Ctx, true) - }) - - t.Run("Dashboard LIST API test", func(t *testing.T) { - t.Skip("Skip LIST") - runDashboardListTest(t, org1Ctx) - }) - }) - - t.Run("Dashboard HTTP API test", func(t *testing.T) { - runDashboardHttpTest(t, org1Ctx, org2Ctx) - }) - - t.Run("Cross-organization tests", func(t *testing.T) { - runCrossOrgTests(t, org1Ctx, org2Ctx) - }) + } } // Auth identity types (user or token) with resource client @@ -169,7 +246,11 @@ type Identity struct { func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Helper() - adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + // Get a new resource client for admin user + // TODO: we need to figure out why reusing the same client results in slower tests + adminClient := func() *apis.K8sResourceClient { + return getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) + } editorClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) t.Run("Dashboard UID validations", func(t *testing.T) { @@ -177,15 +258,15 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with existing UID", func(t *testing.T) { // Create a dashboard with a specific UID specificUID := "existing-uid-dash" - createdDash, err := createDashboard(t, adminClient, "Dashboard with Specific UID", nil, &specificUID) + createdDash, err := createDashboard(t, adminClient(), "Dashboard with Specific UID", nil, &specificUID) require.NoError(t, err) // Try to create another dashboard with the same UID - _, err = createDashboard(t, adminClient, "Another Dashboard with Same UID", nil, &specificUID) + _, err = createDashboard(t, adminClient(), "Another Dashboard with Same UID", nil, &specificUID) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), createdDash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), createdDash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) @@ -193,14 +274,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("reject dashboard with too long UID", func(t *testing.T) { // Create a dashboard with a long UID (over 40 chars) longUID := "this-uid-is-way-too-long-for-a-dashboard-uid-12345678901234567890" - _, err := createDashboard(t, adminClient, "Dashboard with Long UID", nil, &longUID) + _, err := createDashboard(t, adminClient(), "Dashboard with Long UID", nil, &longUID) require.Error(t, err) }) // Test creating dashboard with invalid UID characters t.Run("reject dashboard with invalid UID characters", func(t *testing.T) { invalidUID := "invalid/uid/with/slashes" - _, err := createDashboard(t, adminClient, "Dashboard with Invalid UID", nil, &invalidUID) + _, err := createDashboard(t, adminClient(), "Dashboard with Invalid UID", nil, &invalidUID) require.Error(t, err) }) }) @@ -209,47 +290,47 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard title validations", func(t *testing.T) { // Test empty title t.Run("reject dashboard with empty title", func(t *testing.T) { - _, err := createDashboard(t, adminClient, "", nil, nil) + _, err := createDashboard(t, adminClient(), "", nil, nil) require.Error(t, err) }) // Test long title t.Run("reject dashboard with excessively long title", func(t *testing.T) { veryLongTitle := strings.Repeat("a", 10000) - _, err := createDashboard(t, adminClient, veryLongTitle, nil, nil) + _, err := createDashboard(t, adminClient(), veryLongTitle, nil, nil) require.Error(t, err) }) // Test updating dashboard with empty title t.Run("reject dashboard update with empty title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient(), "Valid Dashboard Title", nil, nil) require.NoError(t, err) require.NotNil(t, dash) // Try to update with empty title - _, err = updateDashboard(t, adminClient, dash, "", nil) + _, err = updateDashboard(t, adminClient(), dash, "", nil) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) // Test updating dashboard with excessively long title t.Run("reject dashboard update with excessively long title", func(t *testing.T) { // First create a valid dashboard - dash, err := createDashboard(t, adminClient, "Valid Dashboard Title", nil, nil) + dash, err := createDashboard(t, adminClient(), "Valid Dashboard Title", nil, nil) require.NoError(t, err) require.NotNil(t, dash) // Try to update with excessively long title veryLongTitle := strings.Repeat("a", 10000) - _, err = updateDashboard(t, adminClient, dash, veryLongTitle, nil) + _, err = updateDashboard(t, adminClient(), dash, veryLongTitle, nil) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -257,15 +338,15 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { t.Run("Dashboard message validations", func(t *testing.T) { // Test long message t.Run("reject dashboard with excessively long update message", func(t *testing.T) { - dash, err := createDashboard(t, adminClient, "Regular dashboard", nil, nil) + dash, err := createDashboard(t, adminClient(), "Regular dashboard", nil, nil) require.NoError(t, err) veryLongMessage := strings.Repeat("a", 600) - _, err = updateDashboard(t, adminClient, dash, "Dashboard updated with a long message", &veryLongMessage) + _, err = updateDashboard(t, adminClient(), dash, "Dashboard updated with a long message", &veryLongMessage) require.Error(t, err) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -274,21 +355,21 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test non-existent folder UID t.Run("reject dashboard with non-existent folder UID", func(t *testing.T) { nonExistentFolderUID := "non-existent-folder-uid" - _, err := createDashboard(t, adminClient, "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) + _, err := createDashboard(t, adminClient(), "Dashboard in Non-existent Folder", &nonExistentFolderUID, nil) ctx.Helper.EnsureStatusError(err, http.StatusNotFound, "folders.folder.grafana.app \"non-existent-folder-uid\" not found") }) t.Run("allow moving folder to general folder", func(t *testing.T) { folder1 := createFolderObject(t, "folder1", "default", "") folder1UID := folder1.GetName() - dash, err := createDashboard(t, adminClient, "Dashboard in a Folder", &folder1UID, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard in a Folder", &folder1UID, nil) require.NoError(t, err) generalFolderUID := "" - _, err = updateDashboard(t, adminClient, dash, "Move dashboard into the General Folder", &generalFolderUID) + _, err = updateDashboard(t, adminClient(), dash, "Move dashboard into the General Folder", &generalFolderUID) require.NoError(t, err) - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -403,7 +484,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Test version increment on update t.Run("version increments on dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Version Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for version test") dashUID := dash.GetName() @@ -413,7 +494,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { initialRV := meta.GetResourceVersion() // Update the dashboard - updatedDash, err := updateDashboard(t, adminClient, dash, "Updated Dashboard for Version Test", nil) + updatedDash, err := updateDashboard(t, adminClient(), dash, "Updated Dashboard for Version Test", nil) require.NoError(t, err) require.NotNil(t, updatedDash) @@ -423,25 +504,25 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.NotEqual(t, meta.GetResourceVersion(), initialRV, "Resource version should be changed after update") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) // Test generation conflict when updating concurrently t.Run("reject update with version conflict", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Version Conflict Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Version Conflict Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for version conflict test") dashUID := dash.GetName() // Get the dashboard twice (simulating two users getting it) - dash1, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + dash1, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) dash2, err := editorClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) // Update with the first copy - updatedDash1, err := updateDashboard(t, adminClient, dash1, "Updated by first user", nil) + updatedDash1, err := updateDashboard(t, adminClient(), dash1, "Updated by first user", nil) require.NoError(t, err) require.NotNil(t, updatedDash1) @@ -451,7 +532,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.Contains(t, err.Error(), "the object has been modified", "Should fail with version conflict error") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) @@ -464,12 +545,12 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { meta.SetGeneration(5) // Create the dashboard - createdDash, err := adminClient.Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) + createdDash, err := adminClient().Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) require.NoError(t, err) dashUID := createdDash.GetName() // Fetch the created dashboard - fetchedDash, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + fetchedDash, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) // Verify the generation was handled properly @@ -477,22 +558,22 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.Equal(t, 5, meta.GetGeneration(), "Generation should be 5") // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) t.Run("dashboard version history available, even for UIDs ending in hyphen", func(t *testing.T) { dashboardUID := "test-dashboard-" - dash, err := createDashboard(t, adminClient, "Dashboard with uid ending in hyphen", nil, &dashboardUID) + dash, err := createDashboard(t, adminClient(), "Dashboard with uid ending in hyphen", nil, &dashboardUID) require.NoError(t, err) - updatedDash, err := updateDashboard(t, adminClient, dash, "Updated dashboard with uid ending in hyphen", nil) + updatedDash, err := updateDashboard(t, adminClient(), dash, "Updated dashboard with uid ending in hyphen", nil) require.NoError(t, err) require.NotNil(t, updatedDash) labelSelector := utils.LabelKeyGetHistory + "=true" fieldSelector := "metadata.name=" + dashboardUID - versions, err := adminClient.Resource.List(context.Background(), v1.ListOptions{ + versions, err := adminClient().Resource.List(context.Background(), v1.ListOptions{ LabelSelector: labelSelector, FieldSelector: fieldSelector, Limit: 10, @@ -502,7 +583,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // one from initial save, one from update require.Equal(t, len(versions.Items), 2) - err = adminClient.Resource.Delete(context.Background(), dashboardUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashboardUID, v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -530,12 +611,12 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for Provisioning Test", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for Provisioning Test", nil, nil) require.NoError(t, err, "Failed to create dashboard for provisioning test") dashUID := dash.GetName() // Fetch the created dashboard - fetchedDash, err := adminClient.Resource.Get(context.Background(), dashUID, v1.GetOptions{}) + fetchedDash, err := adminClient().Resource.Get(context.Background(), dashUID, v1.GetOptions{}) require.NoError(t, err) require.NotNil(t, fetchedDash) @@ -543,7 +624,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { provisionedDash := markDashboardObjectAsProvisioned(t, fetchedDash, "test-provider", "test-external-id", "test-checksum", tc.allowsEdits) // Update the dashboard to apply the provisioning annotations - updatedDash, err := adminClient.Resource.Update(context.Background(), provisionedDash, v1.UpdateOptions{}) + updatedDash, err := adminClient().Resource.Update(context.Background(), provisionedDash, v1.UpdateOptions{}) require.NoError(t, err) require.NotNil(t, updatedDash) @@ -570,16 +651,13 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dashUID, v1.DeleteOptions{}) require.NoError(t, err) }) } }) t.Run("Dashboard refresh interval validations", func(t *testing.T) { - // Create test client - adminClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) - // Store original settings to restore after test origCfg := ctx.Helper.GetEnv().Cfg origMinRefreshInterval := origCfg.MinRefreshInterval @@ -639,14 +717,14 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { _ = meta.SetSpec(specMap) - dash, err := adminClient.Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) + dash, err := adminClient().Resource.Create(context.Background(), dashObj, v1.CreateOptions{}) if tc.shouldSucceed { require.NoError(t, err) require.NotNil(t, dash) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } else { require.Error(t, err) @@ -664,7 +742,7 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { // Create a dashboard with a specific UID to make it easier to manage specificUID := "size-limit-test-dash" - dash, err := createDashboard(t, adminClient, "Dashboard Exceeding Size Limit", nil, &specificUID) + dash, err := createDashboard(t, adminClient(), "Dashboard Exceeding Size Limit", nil, &specificUID) require.NoError(t, err) meta, _ := utils.MetaAccessor(dash) @@ -704,44 +782,17 @@ func runDashboardValidationTests(t *testing.T, ctx TestContext) { require.NoError(t, err, "Failed to set spec") // Try to update with too many panels - _, err = adminClient.Resource.Update(context.Background(), dash, v1.UpdateOptions{}) + _, err = adminClient().Resource.Update(context.Background(), dash, v1.UpdateOptions{}) require.Error(t, err) require.Contains(t, err.Error(), "exceeds", "Error should mention size or limit exceeded") // Clean up - err = adminClient.Resource.Delete(context.Background(), specificUID, v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), specificUID, v1.DeleteOptions{}) require.NoError(t, err) }) }) } -// skipIfMode skips the current test if running in any of the specified modes -// Usage: skipIfMode(t, rest.Mode1, rest.Mode4) -// or with a message: skipIfMode(t, "Known issue with conflict detection", rest.Mode1, rest.Mode4) -// nolint:unused -func (c *TestContext) skipIfMode(t *testing.T, args ...interface{}) { - t.Helper() - - message := "Test not supported in this dual writer mode" - modes := []rest.DualWriterMode{} - - // Parse args - first string is considered a message, all rest.DualWriterMode values are modes to skip - for _, arg := range args { - if msg, ok := arg.(string); ok { - message = msg - } else if mode, ok := arg.(rest.DualWriterMode); ok { - modes = append(modes, mode) - } - } - - // Check if current mode is in the list of modes to skip - for _, mode := range modes { - if c.DualWriterMode == mode { - t.Skipf("%s (mode %d)", message, c.DualWriterMode) - } - } -} - // Run tests for quota validation func runQuotaTests(t *testing.T, ctx TestContext) { t.Helper() @@ -1090,6 +1141,13 @@ func updateDashboard(t *testing.T, client *apis.K8sResourceClient, dashboard *un func runAuthorizationTests(t *testing.T, ctx TestContext) { t.Helper() + // Get a new resource client for admin user + // TODO: we need to figure out why reusing the same client results in slower tests + adminClient := func() *apis.K8sResourceClient { + // admin token + return getServiceAccountResourceClient(t, ctx.Helper, ctx.AdminServiceAccountToken, ctx.OrgID, getDashboardGVR()) + } + // Get clients for each identity type and role adminUserClient := getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()) editorUserClient := getResourceClient(t, ctx.Helper, ctx.EditorUser, getDashboardGVR()) @@ -1112,11 +1170,13 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { {Name: "Viewer token", DashboardClient: viewerTokenClient, Type: "token"}, } + // TODO: re-enable admin cleanup clients when we have figured out why reusing the same client results in slower tests + // TODO: This is currently disabled to avoid issues with reusing the same client in tests. // Get admin clients for cleanup based on identity type - adminCleanupClients := map[string]*apis.K8sResourceClient{ - "user": adminUserClient, - "token": adminTokenClient, - } + // adminCleanupClients := map[string]*apis.K8sResourceClient{ + // "user": adminUserClient, + // "token": adminTokenClient, + // } // Define test cases for different roles type roleTest struct { @@ -1162,8 +1222,9 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { for _, identity := range identities { identity := identity // Capture range variable t.Run(identity.Name, func(t *testing.T) { + // TODO: This is currently disabled to avoid issues with reusing the same client in tests. // Get admin client for cleanup based on identity type - adminClient := adminCleanupClients[identity.Type] + // adminClient := adminCleanupClients[identity.Type] // Get role capabilities for this identity roleCapabilities := authTests[identity.DashboardClient] @@ -1195,7 +1256,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } else { // Test cannot create dashboard @@ -1209,7 +1270,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard updates t.Run("dashboard update", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard to Update by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard to Update by "+identity.Name, nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1229,14 +1290,14 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) // Test dashboard deletion permissions t.Run("dashboard deletion", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for deletion test by "+identity.Name, nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for deletion test by "+identity.Name, nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1247,7 +1308,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { } else { require.Error(t, err, "Should not be able to delete dashboard") // Clean up with admin if the test identity couldn't delete - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) } }) @@ -1256,7 +1317,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { // Test dashboard viewing for all roles t.Run("dashboard viewing", func(t *testing.T) { // Create a dashboard with admin - dash, err := createDashboard(t, adminClient, "Dashboard for "+identity.Name+" to view", nil, nil) + dash, err := createDashboard(t, adminClient(), "Dashboard for "+identity.Name+" to view", nil, nil) require.NoError(t, err) require.NotNil(t, dash) @@ -1266,7 +1327,7 @@ func runAuthorizationTests(t *testing.T, ctx TestContext) { require.NotNil(t, viewedDash) // Clean up - err = adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + err = adminClient().Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) require.NoError(t, err) }) }) @@ -2097,6 +2158,8 @@ func runDashboardHttpTest(t *testing.T, ctx TestContext, foreignOrgCtx TestConte // Helper function to retrieve a dashboard via HTTP func getDashboardViaHTTP(t *testing.T, ctx *TestContext, dashboardPath string, user apis.User) (map[string]interface{}, error) { + t.Helper() + getResp := apis.DoRequest(ctx.Helper, apis.RequestParams{ User: user, Method: http.MethodGet, @@ -2194,7 +2257,7 @@ func testDashboardHttpUpdateMethods(t *testing.T, ctx TestContext, dashboardPath } // Test dashboard list API with complex permission scenarios -func runDashboardListTest(t *testing.T, ctx TestContext) { +func runDashboardListTests(t *testing.T, ctx TestContext) { t.Helper() // Make sure no dashboards exist before we start @@ -2223,8 +2286,8 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { // Define a map of user types to their clients clients := map[string]struct { userClient *apis.K8sResourceClient - tokenClient *apis.K8sResourceClient folderClient *apis.K8sResourceClient + tokenClient *apis.K8sResourceClient }{ "Admin": { userClient: getResourceClient(t, ctx.Helper, ctx.AdminUser, getDashboardGVR()), @@ -2323,6 +2386,24 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { folders := make([]*folder.Folder, len(folderConfigs)) folderDashboards := make([]*unstructured.Unstructured, len(folderConfigs)) + // Clean up + t.Cleanup(func() { + // Delete all root dashboards + for _, dash := range rootDashboards { + err := adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + } + + // Delete all folder dashboards and folders + for i, folder := range folders { + err := adminClient.Resource.Delete(context.Background(), folderDashboards[i].GetName(), v1.DeleteOptions{}) + require.NoError(t, err) + + err = adminFolderClient.Resource.Delete(context.Background(), folder.UID, v1.DeleteOptions{}) + require.NoError(t, err) + } + }) + // Create all test resources (folders, dashboards) in one loop for i, fc := range folderConfigs { // Create root dashboard @@ -2415,71 +2496,55 @@ func runDashboardListTest(t *testing.T, ctx TestContext) { // Test LIST operation for each identity for _, identity := range identities { - t.Run(fmt.Sprintf("LIST operation for %s", identity.Name), func(t *testing.T) { - // Get dashboards visible to this identity - clients := []apis.K8sResourceClient{ - *identity.DashboardClient, - *identity.FolderClient, - } + // Get dashboards visible to this identity + clients := []apis.K8sResourceClient{ + *identity.DashboardClient, + *identity.FolderClient, + } + for _, client := range clients { + t.Run(fmt.Sprintf("LIST operation for %s and %s", identity.Name, client.Args.GVR), func(t *testing.T) { + // Use the client to list all resources + dashList, err := client.Resource.List(context.Background(), v1.ListOptions{}) + require.NoError(t, err) - for _, client := range clients { - t.Run(fmt.Sprintf("LIST operation for %s", client.Args.GVR), func(t *testing.T) { - listOpts := v1.ListOptions{} - dashList, err := client.Resource.List(context.Background(), listOpts) + if len(dashList.Items) == 0 { + t.Logf("WARNING: Got empty dashboard list for %s", identity.Name) + } + + require.NotEmpty(t, dashList.Items) + + // Extract dashboard titles + dashTitles := make([]string, 0, len(dashList.Items)) + for _, dash := range dashList.Items { + meta, err := utils.MetaAccessor(&dash) require.NoError(t, err) - require.NotEmpty(t, dashList.Items) - // Extract dashboard titles - dashTitles := make([]string, 0, len(dashList.Items)) - for _, dash := range dashList.Items { - meta, err := utils.MetaAccessor(&dash) - require.NoError(t, err) + dashTitles = append(dashTitles, meta.FindTitle("")) + } - dashTitles = append(dashTitles, meta.FindTitle("")) - } + // Verify expectations + var expectedTitles []string + if client.Args.GVR == getDashboardGVR() { + expectedTitles = expectations[identity.Name] + } else { + expectedTitles = folderPermissions[identity.Name] + } + require.ElementsMatch(t, expectedTitles, dashTitles) - // Verify expectations - var expectedTitles []string - if client.Args.GVR == getDashboardGVR() { - expectedTitles = expectations[identity.Name] - } else { - expectedTitles = folderPermissions[identity.Name] - } - require.ElementsMatch(t, expectedTitles, dashTitles) - - // Verify all expected items are found - for _, expected := range expectedTitles { - found := false - for _, title := range dashTitles { - if title == expected { - found = true - break - } + // Verify all expected items are found + for _, expected := range expectedTitles { + found := false + for _, title := range dashTitles { + if title == expected { + found = true + break } - require.True(t, found, "%s should see dashboard '%s' but didn't", identity.Name, expected) } - }) - } - }) + require.True(t, found, "%s should see dashboard '%s' but didn't", identity.Name, expected) + } + }) + } } - - // Clean up - t.Run("Cleanup dashboards and folders", func(t *testing.T) { - // Delete all root dashboards - for _, dash := range rootDashboards { - err := adminClient.Resource.Delete(context.Background(), dash.GetName(), v1.DeleteOptions{}) - require.NoError(t, err) - } - - // Delete all folder dashboards and folders - for i, folder := range folders { - err := adminClient.Resource.Delete(context.Background(), folderDashboards[i].GetName(), v1.DeleteOptions{}) - require.NoError(t, err) - - err = adminFolderClient.Resource.Delete(context.Background(), folder.UID, v1.DeleteOptions{}) - require.NoError(t, err) - } - }) } func postHelper(t *testing.T, ctx *TestContext, path string, body interface{}, user apis.User) (map[string]interface{}, error) {