fix: enable dashboard api k8s list tests (#106508)

* fix: separate dashboard api tests

* fix: typo

* refactor: revert file rename

* fix: compilable

* fix: trial

* fix: remove unused func

* Pass the right value for kubernetesDashboardsEnabled flag

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Enable mode 3 for remaining tests

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Disable integration tests for MySQL and Postgres

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Run go work use

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Update go.mod

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Remove TODO comment

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Revert to go 1.24.5

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* go.mod

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Remove duplicated block

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Experiment without reusing client

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Reenable integration tests on Postgres and MySQL

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Revert "Experiment without reusing client"

This reverts commit 0126e321a0.

* Reapply "Experiment without reusing client"

This reverts commit 5368b4c531.

* Refactor how we get an adminClient

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

* Add TODO comments

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>

---------

Signed-off-by: Maicon Costa <maiconscosta@gmail.com>
Co-authored-by: Maicon Costa <maiconscosta@gmail.com>
This commit is contained in:
Mustafa Sencer Özcan
2025-07-25 09:46:02 +02:00
committed by GitHub
co-authored by Maicon Costa
parent 16339b0788
commit 761150fcb8
@@ -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) {