package service import ( "context" "fmt" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/search/model" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" ) func TestDashboardService(t *testing.T) { t.Run("Dashboard service tests", func(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) defer fakeStore.AssertExpectations(t) folderSvc := foldertest.NewFakeService() service := &DashboardServiceImpl{ cfg: setting.NewCfg(), log: log.New("test.logger"), dashboardStore: &fakeStore, folderService: folderSvc, features: featuremgmt.WithFeatures(), publicDashboardService: fakePublicDashboardService, } folderStore := foldertest.FakeFolderStore{} folderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(nil, dashboards.ErrFolderNotFound).Once() service.folderStore = &folderStore origNewDashboardGuardian := guardian.New defer func() { guardian.New = origNewDashboardGuardian }() guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) t.Run("Save dashboard validation", func(t *testing.T) { dto := &dashboards.SaveDashboardDTO{} t.Run("When saving a dashboard with empty title it should return error", func(t *testing.T) { titles := []string{"", " ", " \t "} for _, title := range titles { dto.Dashboard = dashboards.NewDashboard(title) _, err := service.SaveDashboard(context.Background(), dto, false) require.Equal(t, err, dashboards.ErrDashboardTitleEmpty) } }) t.Run("Should return validation error if message is too long", func(t *testing.T) { dto.Dashboard = dashboards.NewDashboard("Dash") dto.Message = `Here we go, 500+ characters for testing. I'm sorry that you're having to read this. I spent too long trying to come up with something clever to say or a funny joke. Unforuntately, nothing came to mind. So instead, I'm will share this with you, as a form of payment for having to read this: https://youtu.be/dQw4w9WgXcQ?si=KeoTIpn9tUtQnOBk! Enjoy :) Now lets see if this test passes or if the result is more exciting than these 500 characters I wrote. Best of luck to the both of us!` _, err := service.SaveDashboard(context.Background(), dto, false) require.Equal(t, err, dashboards.ErrDashboardMessageTooLong) // set to a shorter message for the rest of the tests dto.Message = `message` }) t.Run("Should return validation error if folder is named General", func(t *testing.T) { dto.Dashboard = dashboards.NewDashboardFolder("General") _, err := service.SaveDashboard(context.Background(), dto, false) require.Equal(t, err, dashboards.ErrDashboardFolderNameExists) }) t.Run("When saving a dashboard should validate uid", func(t *testing.T) { testCases := []struct { Uid string Error error }{ {Uid: "", Error: nil}, {Uid: " ", Error: nil}, {Uid: " \t ", Error: nil}, {Uid: "asdf90_-", Error: nil}, {Uid: "asdf/90", Error: dashboards.ErrDashboardInvalidUid}, {Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil}, {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: dashboards.ErrDashboardUidTooLong}, } for _, tc := range testCases { dto.Dashboard = dashboards.NewDashboard("title") dto.Dashboard.SetUID(tc.Uid) dto.User = &user.SignedInUser{} if tc.Error == nil { fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() } _, err := service.BuildSaveDashboardCommand(context.Background(), dto, false) require.Equal(t, err, tc.Error) } }) t.Run("Should return validation error if a folder that is specified can't be found", func(t *testing.T) { dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.FolderUID = "non-existing-folder" folderSvc := foldertest.FakeService{ExpectedError: dashboards.ErrFolderNotFound} service.folderService = &folderSvc _, err := service.SaveDashboard(context.Background(), dto, false) require.Equal(t, err, dashboards.ErrFolderNotFound) }) t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) dto.User = &user.SignedInUser{UserID: 1} _, err := service.SaveDashboard(context.Background(), dto, false) require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard) }) t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) { fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Once() fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) dto.User = &user.SignedInUser{UserID: 1} _, err := service.SaveDashboard(context.Background(), dto, true) require.NoError(t, err) }) }) t.Run("Save provisioned dashboard validation", func(t *testing.T) { dto := &dashboards.SaveDashboardDTO{} t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(nil).Once() fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) dto.User = &user.SignedInUser{UserID: 1} _, err := service.SaveProvisionedDashboard(context.Background(), dto, nil) require.NoError(t, err) }) t.Run("Should override invalid refresh interval if dashboard is provisioned", func(t *testing.T) { fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.AnythingOfType("*dashboards.DashboardProvisioning")).Return(nil).Once() fakeStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{Data: simplejson.New()}, nil).Once() oldRefreshInterval := service.cfg.MinRefreshInterval service.cfg.MinRefreshInterval = "5m" defer func() { service.cfg.MinRefreshInterval = oldRefreshInterval }() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) dto.User = &user.SignedInUser{UserID: 1} dto.Dashboard.Data.Set("refresh", "1s") _, err := service.SaveProvisionedDashboard(context.Background(), dto, nil) require.NoError(t, err) require.Equal(t, dto.Dashboard.Data.Get("refresh").MustString(), "5m") }) }) t.Run("Import dashboard validation", func(t *testing.T) { dto := &dashboards.SaveDashboardDTO{} t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() dto.Dashboard = dashboards.NewDashboard("Dash") dto.Dashboard.SetID(3) dto.User = &user.SignedInUser{UserID: 1} _, err := service.ImportDashboard(context.Background(), dto) require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard) }) }) t.Run("Given provisioned dashboard", func(t *testing.T) { t.Run("DeleteProvisionedDashboard should delete it", func(t *testing.T) { args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1} fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteProvisionedDashboard(context.Background(), 1, 1) require.NoError(t, err) }) t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) { fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(&dashboards.DashboardProvisioning{}, nil).Once() err := service.DeleteDashboard(context.Background(), 1, "", 1) require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) }) }) t.Run("Given non provisioned dashboard", func(t *testing.T) { t.Run("DeleteProvisionedDashboard should delete the dashboard", func(t *testing.T) { args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1} fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteProvisionedDashboard(context.Background(), 1, 1) require.NoError(t, err) }) t.Run("DeleteDashboard should delete it", func(t *testing.T) { args := &dashboards.DeleteDashboardCommand{OrgID: 1, ID: 1} fakeStore.On("DeleteDashboard", mock.Anything, args).Return(nil).Once() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.AnythingOfType("int64")).Return(nil, nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteDashboard(context.Background(), 1, "", 1) require.NoError(t, err) }) }) t.Run("Count dashboards in folder", func(t *testing.T) { fakeStore.On("CountDashboardsInFolders", mock.Anything, mock.AnythingOfType("*dashboards.CountDashboardsInFolderRequest")).Return(int64(3), nil) folderSvc.ExpectedFolder = &folder.Folder{UID: "i am a folder"} // set up a ctx with signed in user usr := &user.SignedInUser{UserID: 1} ctx := identity.WithRequester(context.Background(), usr) count, err := service.CountInFolders(ctx, 1, []string{"i am a folder"}, usr) require.NoError(t, err) require.Equal(t, int64(3), count) }) t.Run("Delete dashboards in folder", func(t *testing.T) { args := &dashboards.DeleteDashboardsInFolderRequest{OrgID: 1, FolderUIDs: []string{"uid"}} fakeStore.On("DeleteDashboardsInFolders", mock.Anything, args).Return(nil).Once() fakeStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteInFolders(context.Background(), 1, []string{"uid"}, nil) require.NoError(t, err) }) t.Run("Soft Delete dashboards in folder", func(t *testing.T) { service.features = featuremgmt.WithFeatures(featuremgmt.FlagDashboardRestore) fakeStore.On("SoftDeleteDashboardsInFolders", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteInFolders(context.Background(), 1, []string{"uid"}, nil) require.NoError(t, err) }) }) } func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) { mockCli := new(client.MockK8sHandler) service.k8sclient = mockCli service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders) ctx := context.Background() userCtx := &user.SignedInUser{UserID: 1, OrgID: 1} ctx = identity.WithRequester(ctx, userCtx) return ctx, mockCli } func TestGetDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } query := &dashboards.GetDashboardQuery{ UID: "test-uid", OrgID: 1, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetDashboard", mock.Anything, query).Return(&dashboards.Dashboard{}, nil).Once() dashboard, err := service.GetDashboard(context.Background(), query) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} dashboardExpected := dashboards.Dashboard{ UID: "uid", // uid is the name of the k8s object Title: "testing slugify", Slug: "testing-slugify", // slug is taken from title OrgID: 1, // orgID is populated from the query Version: 1, Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}), } k8sCliMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil).Once() k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) dashboard, err := service.GetDashboard(ctx, query) require.NoError(t, err) require.NotNil(t, dashboard) k8sCliMock.AssertExpectations(t) // make sure the conversion is working require.True(t, reflect.DeepEqual(dashboard, &dashboardExpected)) }) t.Run("Should get uid if not passed in at first", func(t *testing.T) { query := &dashboards.GetDashboardQuery{ ID: 1, UID: "", OrgID: 1, } ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} dashboardExpected := dashboards.Dashboard{ UID: "uid", // uid is the name of the k8s object Title: "testing slugify", Slug: "testing-slugify", // slug is taken from title OrgID: 1, // orgID is populated from the query Version: 1, Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}), } k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil).Once() k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder1"), }, }, }, }, TotalHits: 1, }, nil) dashboard, err := service.GetDashboard(ctx, query) require.NoError(t, err) require.NotNil(t, dashboard) k8sCliMock.AssertExpectations(t) k8sCliMock.AssertExpectations(t) // make sure the conversion is working require.True(t, reflect.DeepEqual(dashboard, &dashboardExpected)) }) t.Run("Should return error when Kubernetes client fails", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything, mock.Anything).Return(nil, assert.AnError).Once() dashboard, err := service.GetDashboard(ctx, query) require.Error(t, err) require.Nil(t, dashboard) k8sCliMock.AssertExpectations(t) }) t.Run("Should return dashboard not found if Kubernetes client returns nil", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Get", mock.Anything, query.UID, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once() dashboard, err := service.GetDashboard(ctx, query) require.Error(t, err) require.Equal(t, dashboards.ErrDashboardNotFound, err) require.Nil(t, dashboard) k8sCliMock.AssertExpectations(t) }) } func TestGetAllDashboards(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetAllDashboards", mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once() dashboard, err := service.GetAllDashboards(context.Background()) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} dashboardExpected := dashboards.Dashboard{ UID: "uid", // uid is the name of the k8s object Title: "testing slugify", Slug: "testing-slugify", // slug is taken from title OrgID: 1, // orgID is populated from the query Version: 1, // default to version 1 Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}), } k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("List", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{dashboardUnstructured}}, nil).Once() dashes, err := service.GetAllDashboards(ctx) require.NoError(t, err) require.NotNil(t, dashes) k8sCliMock.AssertExpectations(t) // make sure the conversion is working require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected})) }) } func TestGetAllDashboardsByOrgId(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetAllDashboardsByOrgId", mock.Anything, int64(1)).Return([]*dashboards.Dashboard{}, nil).Once() dashboard, err := service.GetAllDashboardsByOrgId(context.Background(), 1) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} dashboardExpected := dashboards.Dashboard{ UID: "uid", // uid is the name of the k8s object Title: "testing slugify", Slug: "testing-slugify", // slug is taken from title OrgID: 1, // orgID is populated from the query Version: 1, // default to version 1 Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid", "version": int64(1)}), } k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("List", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.UnstructuredList{Items: []unstructured.Unstructured{dashboardUnstructured}}, nil).Once() dashes, err := service.GetAllDashboardsByOrgId(ctx, 1) require.NoError(t, err) require.NotNil(t, dashes) k8sCliMock.AssertExpectations(t) // make sure the conversion is working require.True(t, reflect.DeepEqual(dashes, []*dashboards.Dashboard{&dashboardExpected})) }) } func TestGetProvisionedDashboardData(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, orgService: &orgtest.FakeOrgService{ ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}}, }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetProvisionedDashboardData", mock.Anything, "test").Return([]*dashboards.DashboardProvisioning{}, nil).Once() dashboard, err := service.GetProvisionedDashboardData(context.Background(), "test") require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled and get from relevant org", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(), "kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind, "metadata": map[string]interface{}{ "name": "uid", "labels": map[string]interface{}{ utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck }, "annotations": map[string]interface{}{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "test", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), }, }, "spec": map[string]interface{}{ "test": "test", "version": int64(1), "title": "testing slugify", }, }, }, nil).Once() repo := "test" k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { // make sure the kind is added to the query return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck req.Options.Fields[1].Values[0] == repo })).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{}, Rows: []*resource.ResourceTableRow{}, }, TotalHits: 0, }, nil).Once() k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { // make sure the kind is added to the query return req.Options.Fields[0].Values[0] == string(utils.ManagerKindClassicFP) && // nolint:staticcheck req.Options.Fields[1].Values[0] == repo })).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil).Once() dashes, err := service.GetProvisionedDashboardData(ctx, repo) require.NoError(t, err) require.Len(t, dashes, 1) require.Equal(t, dashes[0], &dashboards.DashboardProvisioning{ ID: 0, DashboardID: 1, Name: "test", ExternalID: "path/to/file", CheckSum: "hash", Updated: provisioningTimestamp, }) k8sCliMock.AssertExpectations(t) }) } func TestGetProvisionedDashboardDataByDashboardID(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, orgService: &orgtest.FakeOrgService{ ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}}, }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, int64(1)).Return(&dashboards.DashboardProvisioning{}, nil).Once() dashboard, err := service.GetProvisionedDashboardDataByDashboardID(context.Background(), 1) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled and get from whatever org it is in", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(), "kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind, "metadata": map[string]interface{}{ "name": "uid", "labels": map[string]interface{}{ utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck }, "annotations": map[string]interface{}{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "test", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), }, }, "spec": map[string]interface{}{ "test": "test", "version": int64(1), "title": "testing slugify", }, }}, nil) k8sCliMock.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{}, Rows: []*resource.ResourceTableRow{}, }, TotalHits: 0, }, nil) k8sCliMock.On("Search", mock.Anything, int64(2), mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil) dash, err := service.GetProvisionedDashboardDataByDashboardID(ctx, 1) require.NoError(t, err) require.Equal(t, dash, &dashboards.DashboardProvisioning{ ID: 0, DashboardID: 1, Name: "test", ExternalID: "path/to/file", CheckSum: "hash", Updated: provisioningTimestamp, }) k8sCliMock.AssertExpectations(t) }) } func TestGetProvisionedDashboardDataByDashboardUID(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, orgService: &orgtest.FakeOrgService{ ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}}, }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetProvisionedDataByDashboardUID", mock.Anything, int64(1), "test").Return(&dashboards.DashboardProvisioning{}, nil).Once() dashboard, err := service.GetProvisionedDashboardDataByDashboardUID(context.Background(), 1, "test") require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) provisioningTimestamp := int64(1234567) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]interface{}{ "apiVersion": dashboardv0alpha1.DashboardResourceInfo.GroupVersion().String(), "kind": dashboardv0alpha1.DashboardResourceInfo.GroupVersionKind().Kind, "metadata": map[string]interface{}{ "name": "uid", "labels": map[string]interface{}{ utils.LabelKeyDeprecatedInternalID: "1", // nolint:staticcheck }, "annotations": map[string]interface{}{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "test", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), }, }, "spec": map[string]interface{}{ "test": "test", "version": int64(1), "title": "testing slugify", }, }}, nil).Once() k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil).Once() dash, err := service.GetProvisionedDashboardDataByDashboardUID(ctx, 1, "uid") require.NoError(t, err) require.Equal(t, dash, &dashboards.DashboardProvisioning{ ID: 0, DashboardID: 1, Name: "test", ExternalID: "path/to/file", CheckSum: "hash", Updated: provisioningTimestamp, }) k8sCliMock.AssertExpectations(t) }) } func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, orgService: &orgtest.FakeOrgService{ ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}}, }, publicDashboardService: fakePublicDashboardService, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("DeleteOrphanedProvisionedDashboards", mock.Anything, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ ReaderNames: []string{"test"}, }).Return(nil).Once() err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ ReaderNames: []string{"test"}, }) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled, delete across all orgs, but only delete file based provisioned dashboards", func(t *testing.T) { _, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid", OrgID: 1}).Return(nil).Once() fakeStore.On("CleanupAfterDelete", mock.Anything, &dashboards.DeleteDashboardCommand{UID: "uid3", OrgID: 2}).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", "annotations": map[string]any{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "orphaned", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z", }, }, "spec": map[string]any{}, }}, nil).Once() // should not delete this one, because it does not start with "file:" k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid2", "annotations": map[string]any{ utils.AnnoKeyManagerKind: string(utils.ManagerKindPlugin), utils.AnnoKeyManagerIdentity: "app", }, }, "spec": map[string]any{}, }}, nil).Once() k8sCliMock.On("Get", mock.Anything, "uid3", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid3", "annotations": map[string]any{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "orphaned", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z", }, }, "spec": map[string]any{}, }}, nil).Once() k8sCliMock.On("Search", mock.Anything, int64(1), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { return req.Options.Fields[0].Key == "manager.id" && req.Options.Fields[0].Values[0] == "test" && req.Options.Fields[0].Operator == "notin" })).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil).Once() k8sCliMock.On("Search", mock.Anything, int64(2), mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { return req.Options.Fields[0].Key == "manager.id" && req.Options.Fields[0].Values[0] == "test" && req.Options.Fields[0].Operator == "notin" })).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid2", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 2"), []byte("folder 2"), }, }, { Key: &resource.ResourceKey{ Name: "uid3", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 3"), []byte("folder 3"), }, }, }, }, TotalHits: 2, }, nil).Once() err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ ReaderNames: []string{"test"}, }) require.NoError(t, err) k8sCliMock.AssertExpectations(t) }) } func TestUnprovisionDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, orgService: &orgtest.FakeOrgService{ ExpectedOrgs: []*org.OrgDTO{{ID: 1}, {ID: 2}}, }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("UnprovisionDashboard", mock.Anything, int64(1)).Return(nil).Once() err := service.UnprovisionDashboard(context.Background(), 1) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled - should remove annotations", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) dash := &unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", "annotations": map[string]any{ utils.AnnoKeyManagerKind: utils.ManagerKindClassicFP, // nolint:staticcheck utils.AnnoKeyManagerIdentity: "test", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: "2025-01-01T00:00:00Z", }, }, "spec": map[string]any{}, }} k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(dash, nil) dashWithoutAnnotations := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": "dashboard.grafana.app/v0alpha1", "kind": "Dashboard", "metadata": map[string]any{ "name": "uid", "namespace": "default", "annotations": map[string]any{}, }, "spec": map[string]any{ "uid": "uid", "version": 1, }, }} // should update it to be without annotations k8sCliMock.On("Update", mock.Anything, dashWithoutAnnotations, mock.Anything).Return(dashWithoutAnnotations, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil) err := service.UnprovisionDashboard(ctx, 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) }) } func TestGetDashboardsByPluginID(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } query := &dashboards.GetDashboardsByPluginIDQuery{ PluginID: "testing", OrgID: 1, } uidUnstructured := &unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid1", }, "spec": map[string]any{ "title": "Dashboard 1", }, }} t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetDashboardsByPluginID", mock.Anything, mock.Anything).Return([]*dashboards.Dashboard{}, nil).Once() _, err := service.GetDashboardsByPluginID(context.Background(), query) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(uidUnstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.MatchedBy(func(req *resource.ResourceSearchRequest) bool { return ( // gofmt comment helper req.Options.Fields[0].Key == "manager.kind" && req.Options.Fields[0].Values[0] == string(utils.ManagerKindPlugin) && req.Options.Fields[1].Key == "manager.id" && req.Options.Fields[1].Values[0] == "testing") })).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, }, }, TotalHits: 1, }, nil) dashes, err := service.GetDashboardsByPluginID(ctx, query) require.NoError(t, err) require.Len(t, dashes, 1) k8sCliMock.AssertExpectations(t) }) } func TestSaveProvisionedDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, folderService: &foldertest.FakeService{ ExpectedFolder: &folder.Folder{ ID: 0, UID: "general", }, }, log: log.NewNopLogger(), } origNewDashboardGuardian := guardian.New defer func() { guardian.New = origNewDashboardGuardian }() guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) query := &dashboards.SaveDashboardDTO{ OrgID: 1, User: &user.SignedInUser{UserID: 1}, Dashboard: &dashboards.Dashboard{ UID: "uid", Title: "testing slugify", Slug: "testing-slugify", OrgID: 1, Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid"}), }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil) fakeStore.On("SaveDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) dashboard, err := service.SaveProvisionedDashboard(context.Background(), query, &dashboards.DashboardProvisioning{}) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} t.Run("Should use Kubernetes create if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) fakeStore.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(nil) k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") dashboard, err := service.SaveProvisionedDashboard(ctx, query, &dashboards.DashboardProvisioning{}) require.NoError(t, err) require.NotNil(t, dashboard) k8sCliMock.AssertExpectations(t) // ensure the provisioning data is still saved to the db fakeStore.AssertExpectations(t) }) } func TestSaveDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, folderService: &foldertest.FakeService{ ExpectedFolder: &folder.Folder{}, }, } origNewDashboardGuardian := guardian.New defer func() { guardian.New = origNewDashboardGuardian }() guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) query := &dashboards.SaveDashboardDTO{ OrgID: 1, User: &user.SignedInUser{UserID: 1}, Dashboard: &dashboards.Dashboard{ UID: "uid", // uid is the name of the k8s object Title: "testing slugify", Slug: "testing-slugify", // slug is taken from title OrgID: 1, // orgID is populated from the query Data: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "uid"}), }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil) fakeStore.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) fakeStore.On("SaveDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) dashboard, err := service.SaveDashboard(context.Background(), query, false) require.NoError(t, err) require.NotNil(t, dashboard) fakeStore.AssertExpectations(t) }) dashboardUnstructured := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", }, "spec": map[string]any{ "test": "test", "version": int64(1), "title": "testing slugify", }, }} t.Run("Should use Kubernetes create if feature flags are enabled and dashboard doesn't exist", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) dashboard, err := service.SaveDashboard(ctx, query, false) require.NoError(t, err) require.NotNil(t, dashboard) }) t.Run("Should use Kubernetes update if feature flags are enabled and dashboard exists", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") k8sCliMock.On("Update", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) dashboard, err := service.SaveDashboard(ctx, query, false) require.NoError(t, err) require.NotNil(t, dashboard) }) t.Run("Should return an error if uid is invalid", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) k8sCliMock.On("GetNamespace", mock.Anything).Return("default") k8sCliMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructured, nil) query.Dashboard.UID = "invalid/uid" _, err := service.SaveDashboard(ctx, query, false) require.Error(t, err) }) } func TestDeleteDashboard(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} fakePublicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t) defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, publicDashboardService: fakePublicDashboardService, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("DeleteDashboard", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything, mock.Anything).Return(nil, nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteDashboard(context.Background(), 1, "uid", 1) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteDashboard(ctx, 1, "uid", 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) }) t.Run("If UID is not passed in, it should retrieve that first", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Delete", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() fakeStore.On("CleanupAfterDelete", mock.Anything, mock.Anything).Return(nil).Once() fakePublicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder1"), }, }, }, }, TotalHits: 1, }, nil) err := service.DeleteDashboard(ctx, 1, "", 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) k8sCliMock.AssertExpectations(t) }) } func TestDeleteAllDashboards(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("DeleteAllDashboards", mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteAllDashboards(context.Background(), 1) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("DeleteCollection", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() err := service.DeleteAllDashboards(ctx, 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) }) } func TestSearchDashboards(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} fakeFolders := foldertest.NewFakeService() fakeFolders.ExpectedFolder = &folder.Folder{ Title: "testing-folder-1", UID: "f1", } fakeFolders.ExpectedFolders = []*folder.Folder{fakeFolders.ExpectedFolder} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, folderService: fakeFolders, } expectedResult := model.HitList{ { UID: "uid1", OrgID: 1, Title: "Dashboard 1", Type: "dash-db", URI: "db/dashboard-1", URL: "/d/uid1/dashboard-1", Tags: []string{ "tag1", "tag2", }, FolderTitle: "testing-folder-1", FolderUID: "f1", }, { UID: "uid2", OrgID: 1, Title: "Dashboard 2", Type: "dash-db", URI: "db/dashboard-2", URL: "/d/uid2/dashboard-2", Tags: []string{}, FolderTitle: "testing-folder-1", FolderUID: "f1", }, } query := dashboards.FindPersistedDashboardsQuery{ DashboardUIDs: []string{"uid1", "uid2"}, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{ { UID: "uid1", Slug: "dashboard-1", OrgID: 1, Title: "Dashboard 1", Tags: []string{"tag1", "tag2"}, FolderTitle: "testing-folder-1", FolderUID: "f1", }, { UID: "uid2", Slug: "dashboard-2", OrgID: 1, Title: "Dashboard 2", FolderTitle: "testing-folder-1", FolderUID: "f1", }, }, nil).Once() result, err := service.SearchDashboards(context.Background(), &query) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) expectedFolders := model.HitList{ { UID: "f1", Title: "testing-folder-1", }, } fakeFolders.ExpectedHitList = expectedFolders k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "tags", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid1", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("f1"), []byte("[\"tag1\", \"tag2\"]"), }, }, { Key: &resource.ResourceKey{ Name: "uid2", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 2"), []byte("f1"), []byte(""), }, }, }, }, TotalHits: 1, }, nil) result, err := service.SearchDashboards(ctx, &query) require.NoError(t, err) require.Equal(t, expectedResult, result) k8sCliMock.AssertExpectations(t) }) } func TestGetDashboards(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } expectedResult := []*dashboards.Dashboard{ { UID: "uid1", Slug: "dashboard-1", OrgID: 1, Title: "Dashboard 1", Data: simplejson.NewFromAny(map[string]any{"title": "Dashboard 1", "uid": "uid1"}), }, { UID: "uid2", Slug: "dashboard-2", OrgID: 1, Title: "Dashboard 2", Data: simplejson.NewFromAny(map[string]any{"title": "Dashboard 2", "uid": "uid2"}), }, } uid1Unstructured := &unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid1", }, "spec": map[string]any{ "title": "Dashboard 1", }, }} uid2Unstructured := &unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid2", }, "spec": map[string]any{ "title": "Dashboard 2", }, }} queryByIDs := &dashboards.GetDashboardsQuery{ DashboardIDs: []int64{1, 2}, OrgID: 1, } queryByUIDs := &dashboards.GetDashboardsQuery{ DashboardUIDs: []string{"uid1", "uid2"}, OrgID: 1, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() // by ids fakeStore.On("GetDashboards", mock.Anything, queryByIDs).Return(expectedResult, nil).Once() result, err := service.GetDashboards(context.Background(), queryByIDs) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) // by uids fakeStore.On("GetDashboards", mock.Anything, queryByUIDs).Return(expectedResult, nil).Once() result, err = service.GetDashboards(context.Background(), queryByUIDs) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Get", mock.Anything, "uid1", mock.Anything, mock.Anything, mock.Anything).Return(uid1Unstructured, nil) k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(uid2Unstructured, nil) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{}, nil) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid1", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte(""), }, }, { Key: &resource.ResourceKey{ Name: "uid2", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 2"), []byte(""), }, }, }, }, TotalHits: 1, }, nil) // by ids result, err := service.GetDashboards(ctx, queryByIDs) require.NoError(t, err) require.Equal(t, expectedResult, result) k8sCliMock.AssertExpectations(t) // by uids result, err = service.GetDashboards(ctx, queryByUIDs) require.NoError(t, err) require.Equal(t, expectedResult, result) k8sCliMock.AssertExpectations(t) }) } func TestGetDashboardUIDByID(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } expectedResult := &dashboards.DashboardRef{ UID: "uid1", Slug: "dashboard-1", } query := &dashboards.GetDashboardRefByIDQuery{ ID: 1, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetDashboardUIDByID", mock.Anything, query).Return(expectedResult, nil).Once() result, err := service.GetDashboardUIDByID(context.Background(), query) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid1", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder1"), }, }, }, }, TotalHits: 1, }, nil) result, err := service.GetDashboardUIDByID(ctx, query) require.NoError(t, err) require.Equal(t, expectedResult, result) k8sCliMock.AssertExpectations(t) }) } func TestUnstructuredToLegacyDashboard(t *testing.T) { k8sCliMock := new(client.MockK8sHandler) k8sCliMock.On("GetUserFromMeta", mock.Anything, mock.Anything).Return(&user.User{ID: 10, UID: "useruid"}, nil) dr := &DashboardServiceImpl{ k8sclient: k8sCliMock, } t.Run("successfully converts unstructured to legacy dashboard", func(t *testing.T) { uid := "36b7c825-79cc-435e-acf6-c78bd96a4510" orgID := int64(123) title := "Test Dashboard" now := metav1.Now() item := &unstructured.Unstructured{ Object: map[string]interface{}{ "spec": map[string]interface{}{ "title": title, "version": int64(1), }, }, } obj, err := utils.MetaAccessor(item) require.NoError(t, err) obj.SetCreationTimestamp(now) obj.SetName(uid) obj.SetCreatedBy("user:useruid") obj.SetUpdatedBy("user:useruid") obj.SetDeprecatedInternalID(1) // nolint:staticcheck result, err := dr.UnstructuredToLegacyDashboard(context.Background(), item, orgID) assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, uid, result.UID) assert.Equal(t, title, result.Title) assert.Equal(t, orgID, result.OrgID) assert.Equal(t, "test-dashboard", result.Slug) // should slugify the title assert.Equal(t, false, result.HasACL) assert.Equal(t, false, result.IsFolder) assert.Equal(t, int64(1), result.ID) assert.Equal(t, now.Time.Format(time.RFC3339), result.Created.Format(time.RFC3339)) assert.Equal(t, int64(10), result.CreatedBy) assert.Equal(t, now.Time.Format(time.RFC3339), result.Updated.Format(time.RFC3339)) // updated should default to created assert.Equal(t, int64(10), result.UpdatedBy) }) t.Run("returns error if spec is missing", func(t *testing.T) { item := &unstructured.Unstructured{ Object: map[string]interface{}{}, } _, err := (&DashboardServiceImpl{}).UnstructuredToLegacyDashboard(context.Background(), item, int64(123)) assert.Error(t, err) assert.Equal(t, "error parsing dashboard from k8s response", err.Error()) }) } func TestGetDashboardTags(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } expectedResult := []*dashboards.DashboardTagCloudItem{ { Term: "tag1", Count: 1, }, { Term: "tag2", Count: 3, }, } query := &dashboards.GetDashboardTagsQuery{ OrgID: 1, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("GetDashboardTags", mock.Anything, query).Return(expectedResult, nil).Once() result, err := service.GetDashboardTags(context.Background(), query) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Facet: map[string]*resource.ResourceSearchResponse_Facet{ "tags": { Terms: []*resource.ResourceSearchResponse_TermFacet{ { Term: "tag1", Count: 1, }, { Term: "tag2", Count: 3, }, }, }, }, }, nil) result, err := service.GetDashboardTags(ctx, query) require.NoError(t, err) require.Equal(t, expectedResult, result) fakeStore.AssertExpectations(t) }) } func TestQuotaCount(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } orgs := []*org.OrgDTO{ { ID: 1, }, { ID: 2, }, } countOrg1 := resource.ResourceStatsResponse{ Stats: []*resource.ResourceStatsResponse_Stats{ { Count: 1, }, }, } countOrg2 := resource.ResourceStatsResponse{ Stats: []*resource.ResourceStatsResponse_Stats{ { Count: 2, }, }, } query := "a.ScopeParameters{ OrgID: 1, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("Count", mock.Anything, mock.Anything).Return(nil, nil).Once() _, err := service.Count(context.Background(), query) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) orgSvc := orgtest.FakeOrgService{ExpectedOrgs: orgs} service.orgService = &orgSvc k8sCliMock.On("GetStats", mock.Anything, mock.Anything).Return(&countOrg2, nil).Once() k8sCliMock.On("GetStats", mock.Anything, mock.Anything).Return(&countOrg1, nil).Once() result, err := service.Count(ctx, query) require.NoError(t, err) orgTag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.OrgScope) require.NoError(t, err) c, _ := result.Get(orgTag) require.Equal(t, c, int64(1)) globalTag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.GlobalScope) require.NoError(t, err) c, _ = result.Get(globalTag) require.Equal(t, c, int64(3)) fakeStore.AssertExpectations(t) }) } func TestCountDashboardsInOrg(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } count := resource.ResourceStatsResponse{ Stats: []*resource.ResourceStatsResponse_Stats{ { Count: 3, }, }, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("CountInOrg", mock.Anything, mock.Anything).Return(nil, nil).Once() _, err := service.CountDashboardsInOrg(context.Background(), 1) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetStats", mock.Anything, mock.Anything).Return(&count, nil).Once() result, err := service.CountDashboardsInOrg(ctx, 1) require.NoError(t, err) require.Equal(t, result, int64(3)) }) } func TestCountInFolders(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), dashboardStore: &fakeStore, } dashs := &resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder 1"), }, }, { Key: &resource.ResourceKey{ Name: "uid2", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 2"), []byte("folder 1"), }, }, }, }, TotalHits: 2, } t.Run("Should fallback to dashboard store if Kubernetes feature flags are not enabled", func(t *testing.T) { service.features = featuremgmt.WithFeatures() fakeStore.On("CountDashboardsInFolders", mock.Anything, mock.Anything).Return(int64(1), nil).Once() _, err := service.CountInFolders(context.Background(), 1, []string{"folder1"}, &user.SignedInUser{}) require.NoError(t, err) fakeStore.AssertExpectations(t) }) t.Run("Should use Kubernetes client if feature flags are enabled", func(t *testing.T) { ctx, k8sCliMock := setupK8sDashboardTests(service) k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(dashs, nil).Once() result, err := service.CountInFolders(ctx, 1, []string{"folder1"}, &user.SignedInUser{}) require.NoError(t, err) require.Equal(t, result, int64(2)) }) } func TestSearchDashboardsThroughK8sRaw(t *testing.T) { ctx := context.Background() k8sCliMock := new(client.MockK8sHandler) service := &DashboardServiceImpl{k8sclient: k8sCliMock} query := &dashboards.FindPersistedDashboardsQuery{ OrgId: 1, } k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder1"), }, }, }, }, TotalHits: 1, }, nil) res, err := service.searchDashboardsThroughK8s(ctx, query) require.NoError(t, err) assert.Equal(t, []*dashboards.Dashboard{ { UID: "uid", OrgID: 1, FolderUID: "folder1", Title: "Dashboard 1", Slug: "dashboard-1", // should be slugified }, }, res) assert.Equal(t, "dash-db", query.Type) // query type should be added } func TestSearchProvisionedDashboardsThroughK8sRaw(t *testing.T) { ctx := context.Background() k8sCliMock := new(client.MockK8sHandler) service := &DashboardServiceImpl{k8sclient: k8sCliMock} query := &dashboards.FindPersistedDashboardsQuery{ OrgId: 1, } provisioningTimestamp := int64(1234567) dashboardUnstructuredProvisioned := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid", "annotations": map[string]any{ utils.AnnoKeyManagerKind: string(utils.ManagerKindClassicFP), // nolint:staticcheck utils.AnnoKeyManagerIdentity: "test", utils.AnnoKeySourceChecksum: "hash", utils.AnnoKeySourcePath: "path/to/file", utils.AnnoKeySourceTimestamp: fmt.Sprintf("%d", time.Unix(provisioningTimestamp, 0).UnixMilli()), }, }, "spec": map[string]any{}, }} dashboardUnstructuredNotProvisioned := unstructured.Unstructured{Object: map[string]any{ "metadata": map[string]any{ "name": "uid2", }, "spec": map[string]any{}, }} k8sCliMock.On("GetNamespace", mock.Anything, mock.Anything).Return("default") k8sCliMock.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{ Results: &resource.ResourceTable{ Columns: []*resource.ResourceTableColumnDefinition{ { Name: "title", Type: resource.ResourceTableColumnDefinition_STRING, }, { Name: "folder", Type: resource.ResourceTableColumnDefinition_STRING, }, }, Rows: []*resource.ResourceTableRow{ { Key: &resource.ResourceKey{ Name: "uid", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 1"), []byte("folder1"), }, }, { Key: &resource.ResourceKey{ Name: "uid2", Resource: "dashboard", }, Cells: [][]byte{ []byte("Dashboard 2"), []byte("folder2"), }, }, }, }, TotalHits: 1, }, nil) k8sCliMock.On("Get", mock.Anything, "uid", mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructuredProvisioned, nil).Once() k8sCliMock.On("Get", mock.Anything, "uid2", mock.Anything, mock.Anything, mock.Anything).Return(&dashboardUnstructuredNotProvisioned, nil).Once() res, err := service.searchProvisionedDashboardsThroughK8s(ctx, query) require.NoError(t, err) assert.Equal(t, []*dashboardProvisioningWithUID{ { DashboardUID: "uid", DashboardProvisioning: dashboards.DashboardProvisioning{ Name: "test", ExternalID: "path/to/file", CheckSum: "hash", Updated: provisioningTimestamp, }, }, }, res) // only should return the one provisioned dashboard assert.Equal(t, "dash-db", query.Type) // query type should be added as dashboards only } func TestLegacySaveCommandToUnstructured(t *testing.T) { namespace := "test-namespace" t.Run("successfully converts save command to unstructured", func(t *testing.T) { cmd := &dashboards.SaveDashboardCommand{ FolderUID: "folder-uid", Message: "saving this dashboard", Dashboard: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "test-uid"}), } result, err := LegacySaveCommandToUnstructured(cmd, namespace) assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, "test-uid", result.GetName()) assert.Equal(t, "test-namespace", result.GetNamespace()) spec := result.Object["spec"].(map[string]any) assert.Equal(t, spec["version"], 1) assert.Equal(t, result.GetAnnotations(), map[string]string{utils.AnnoKeyFolder: "folder-uid", utils.AnnoKeyMessage: "saving this dashboard"}) }) t.Run("should increase version when called", func(t *testing.T) { cmd := &dashboards.SaveDashboardCommand{ Dashboard: simplejson.NewFromAny(map[string]any{"test": "test", "title": "testing slugify", "uid": "test-uid", "version": int64(1)}), } result, err := LegacySaveCommandToUnstructured(cmd, namespace) assert.NoError(t, err) assert.NotNil(t, result) spec := result.Object["spec"].(map[string]any) assert.Equal(t, spec["version"], float64(2)) // folder annotation should not be set if not inside a folder assert.Equal(t, result.GetAnnotations(), map[string]string(nil)) }) }