2022-02-16 14:15:44 +01:00
package service
2018-02-19 11:12:56 +01:00
import (
2021-11-03 14:10:39 +01:00
"context"
2025-01-13 07:14:12 -07:00
"fmt"
2024-12-13 15:55:43 -07:00
"reflect"
2020-06-29 14:08:32 +02:00
"testing"
2024-12-17 09:35:46 -07:00
"time"
2020-06-29 14:08:32 +02:00
2025-01-15 10:23:05 -06:00
"github.com/grafana/grafana/pkg/storage/unified/search"
2024-12-13 15:55:43 -07:00
"github.com/stretchr/testify/assert"
2022-03-10 12:19:50 -05:00
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
2025-01-02 08:39:45 -07:00
"google.golang.org/grpc"
2024-12-13 15:55:43 -07:00
"k8s.io/client-go/dynamic"
2022-03-10 12:19:50 -05:00
2024-07-26 16:39:23 +03:00
"github.com/grafana/grafana/pkg/apimachinery/identity"
2024-12-17 09:35:46 -07:00
"github.com/grafana/grafana/pkg/apimachinery/utils"
2022-02-16 14:15:44 +01:00
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
2022-06-30 09:31:54 -04:00
"github.com/grafana/grafana/pkg/services/dashboards"
2024-05-16 14:36:26 -03:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-11-11 14:28:24 +01:00
"github.com/grafana/grafana/pkg/services/folder"
2023-02-01 15:43:21 +02:00
"github.com/grafana/grafana/pkg/services/folder/foldertest"
2019-04-10 13:29:10 +02:00
"github.com/grafana/grafana/pkg/services/guardian"
2025-01-09 22:21:21 -07:00
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/quota"
2025-01-02 08:39:45 -07:00
"github.com/grafana/grafana/pkg/services/search/model"
2022-08-10 11:56:48 +02:00
"github.com/grafana/grafana/pkg/services/user"
2024-12-17 09:35:46 -07:00
"github.com/grafana/grafana/pkg/services/user/usertest"
2022-02-16 14:15:44 +01:00
"github.com/grafana/grafana/pkg/setting"
2025-01-02 08:39:45 -07:00
"github.com/grafana/grafana/pkg/storage/unified/resource"
2024-12-13 15:55:43 -07:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-02-19 11:12:56 +01:00
)
2022-07-15 18:06:44 +02:00
func TestDashboardService ( t * testing . T ) {
2021-10-27 15:17:50 +02:00
t . Run ( "Dashboard service tests" , func ( t * testing . T ) {
2022-06-30 09:31:54 -04:00
fakeStore := dashboards . FakeDashboardStore { }
2022-02-16 14:15:44 +01:00
defer fakeStore . AssertExpectations ( t )
2022-07-13 12:33:28 +03:00
2023-03-02 08:09:57 -05:00
folderSvc := foldertest . NewFakeService ( )
2022-02-16 14:15:44 +01:00
service := & DashboardServiceImpl {
2024-03-14 15:36:35 +01:00
cfg : setting . NewCfg ( ) ,
log : log . New ( "test.logger" ) ,
dashboardStore : & fakeStore ,
folderService : folderSvc ,
2024-05-16 14:36:26 -03:00
features : featuremgmt . WithFeatures ( ) ,
2020-02-28 14:32:01 +01:00
}
2025-01-14 22:15:58 -07:00
folderStore := foldertest . FakeFolderStore { }
folderStore . On ( "GetFolderByUID" , mock . Anything , mock . AnythingOfType ( "int64" ) , mock . AnythingOfType ( "string" ) ) . Return ( nil , dashboards . ErrFolderNotFound ) . Once ( )
service . folderStore = & folderStore
2018-02-19 11:12:56 +01:00
origNewDashboardGuardian := guardian . New
2021-10-27 15:17:50 +02:00
defer func ( ) { guardian . New = origNewDashboardGuardian } ( )
2018-02-20 18:08:19 +01:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
2018-02-19 11:12:56 +01:00
2021-10-27 15:17:50 +02:00
t . Run ( "Save dashboard validation" , func ( t * testing . T ) {
2022-06-30 09:31:54 -04:00
dto := & dashboards . SaveDashboardDTO { }
2018-02-19 11:12:56 +01:00
2021-10-27 15:17:50 +02:00
t . Run ( "When saving a dashboard with empty title it should return error" , func ( t * testing . T ) {
2018-02-19 11:12:56 +01:00
titles := [ ] string { "" , " " , " \t " }
for _ , title := range titles {
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( title )
2021-11-03 14:10:39 +01:00
_ , err := service . SaveDashboard ( context . Background ( ) , dto , false )
2022-06-30 09:31:54 -04:00
require . Equal ( t , err , dashboards . ErrDashboardTitleEmpty )
2018-02-19 11:12:56 +01:00
}
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Should return validation error if folder is named General" , func ( t * testing . T ) {
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboardFolder ( "General" )
2021-11-03 14:10:39 +01:00
_ , err := service . SaveDashboard ( context . Background ( ) , dto , false )
2022-06-30 09:31:54 -04:00
require . Equal ( t , err , dashboards . ErrDashboardFolderNameExists )
2018-02-19 11:12:56 +01:00
} )
2021-10-27 15:17:50 +02:00
t . Run ( "When saving a dashboard should validate uid" , func ( t * testing . T ) {
2018-02-19 11:12:56 +01:00
testCases := [ ] struct {
Uid string
Error error
} {
{ Uid : "" , Error : nil } ,
{ Uid : " " , Error : nil } ,
{ Uid : " \t " , Error : nil } ,
{ Uid : "asdf90_-" , Error : nil } ,
2022-06-30 09:31:54 -04:00
{ Uid : "asdf/90" , Error : dashboards . ErrDashboardInvalidUid } ,
2018-02-19 11:12:56 +01:00
{ Uid : " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer " , Error : nil } ,
2022-06-30 09:31:54 -04:00
{ Uid : "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm" , Error : dashboards . ErrDashboardUidTooLong } ,
2018-02-19 11:12:56 +01:00
}
for _ , tc := range testCases {
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "title" )
dto . Dashboard . SetUID ( tc . Uid )
2022-08-10 11:56:48 +02:00
dto . User = & user . SignedInUser { }
2018-02-19 11:12:56 +01:00
2022-02-16 14:15:44 +01:00
if tc . Error == nil {
2025-01-09 21:28:26 -07:00
fakeStore . On ( "GetDashboard" , mock . Anything , mock . Anything ) . Return ( & dashboards . Dashboard { } , nil ) . Once ( )
2022-02-16 14:15:44 +01:00
}
2024-03-14 15:36:35 +01:00
_ , err := service . BuildSaveDashboardCommand ( context . Background ( ) , dto , false )
2021-10-27 15:17:50 +02:00
require . Equal ( t , err , tc . Error )
2018-02-19 11:12:56 +01:00
}
} )
2023-11-16 11:11:35 +00:00
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"
2025-01-14 22:15:58 -07:00
folderSvc := foldertest . FakeService { ExpectedError : dashboards . ErrFolderNotFound }
service . folderService = & folderSvc
2023-11-16 11:11:35 +00:00
_ , err := service . SaveDashboard ( context . Background ( ) , dto , false )
require . Equal ( t , err , dashboards . ErrFolderNotFound )
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Should return validation error if dashboard is provisioned" , func ( t * testing . T ) {
2025-01-09 21:28:26 -07:00
fakeStore . On ( "GetDashboard" , mock . Anything , mock . Anything ) . Return ( & dashboards . Dashboard { } , nil ) . Once ( )
2023-01-18 13:52:41 +01:00
fakeStore . On ( "GetProvisionedDataByDashboardID" , mock . Anything , mock . AnythingOfType ( "int64" ) ) . Return ( & dashboards . DashboardProvisioning { } , nil ) . Once ( )
2018-03-27 15:12:47 +02:00
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "Dash" )
dto . Dashboard . SetID ( 3 )
2022-08-11 13:28:55 +02:00
dto . User = & user . SignedInUser { UserID : 1 }
2021-11-03 14:10:39 +01:00
_ , err := service . SaveDashboard ( context . Background ( ) , dto , false )
2022-06-30 09:31:54 -04:00
require . Equal ( t , err , dashboards . ErrDashboardCannotSaveProvisionedDashboard )
2018-03-27 15:12:47 +02:00
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Should not return validation error if dashboard is provisioned but UI updates allowed" , func ( t * testing . T ) {
2025-01-09 21:28:26 -07:00
fakeStore . On ( "GetDashboard" , mock . Anything , mock . Anything ) . Return ( & dashboards . Dashboard { } , nil ) . Once ( )
2023-01-16 16:33:55 +01:00
fakeStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & dashboards . Dashboard { Data : simplejson . New ( ) } , nil ) . Once ( )
2019-10-31 14:27:31 +01:00
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "Dash" )
dto . Dashboard . SetID ( 3 )
2022-08-11 13:28:55 +02:00
dto . User = & user . SignedInUser { UserID : 1 }
2021-11-03 14:10:39 +01:00
_ , err := service . SaveDashboard ( context . Background ( ) , dto , true )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
2019-10-31 14:27:31 +01:00
} )
2018-02-19 11:12:56 +01:00
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Save provisioned dashboard validation" , func ( t * testing . T ) {
2022-06-30 09:31:54 -04:00
dto := & dashboards . SaveDashboardDTO { }
2018-04-13 17:45:32 +02:00
2021-10-27 15:17:50 +02:00
t . Run ( "Should not return validation error if dashboard is provisioned" , func ( t * testing . T ) {
2025-01-13 07:14:12 -07:00
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 ( )
2018-04-13 17:45:32 +02:00
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "Dash" )
dto . Dashboard . SetID ( 3 )
2022-08-11 13:28:55 +02:00
dto . User = & user . SignedInUser { UserID : 1 }
2021-11-03 14:10:39 +01:00
_ , err := service . SaveProvisionedDashboard ( context . Background ( ) , dto , nil )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
2018-04-13 17:45:32 +02:00
} )
2020-02-28 14:32:01 +01:00
2021-10-27 15:17:50 +02:00
t . Run ( "Should override invalid refresh interval if dashboard is provisioned" , func ( t * testing . T ) {
2025-01-13 07:14:12 -07:00
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 ( )
2022-02-16 14:15:44 +01:00
2024-01-23 12:36:22 +01:00
oldRefreshInterval := service . cfg . MinRefreshInterval
service . cfg . MinRefreshInterval = "5m"
defer func ( ) { service . cfg . MinRefreshInterval = oldRefreshInterval } ( )
2020-02-28 14:32:01 +01:00
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "Dash" )
dto . Dashboard . SetID ( 3 )
2022-08-11 13:28:55 +02:00
dto . User = & user . SignedInUser { UserID : 1 }
2020-02-28 14:32:01 +01:00
dto . Dashboard . Data . Set ( "refresh" , "1s" )
2021-11-03 14:10:39 +01:00
_ , err := service . SaveProvisionedDashboard ( context . Background ( ) , dto , nil )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
require . Equal ( t , dto . Dashboard . Data . Get ( "refresh" ) . MustString ( ) , "5m" )
2020-02-28 14:32:01 +01:00
} )
2018-04-13 17:45:32 +02:00
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Import dashboard validation" , func ( t * testing . T ) {
2022-06-30 09:31:54 -04:00
dto := & dashboards . SaveDashboardDTO { }
2018-04-13 17:54:41 +02:00
2021-10-27 15:17:50 +02:00
t . Run ( "Should return validation error if dashboard is provisioned" , func ( t * testing . T ) {
2023-01-18 13:52:41 +01:00
fakeStore . On ( "GetProvisionedDataByDashboardID" , mock . Anything , mock . AnythingOfType ( "int64" ) ) . Return ( & dashboards . DashboardProvisioning { } , nil ) . Once ( )
2018-04-13 17:54:41 +02:00
2023-01-16 16:33:55 +01:00
dto . Dashboard = dashboards . NewDashboard ( "Dash" )
dto . Dashboard . SetID ( 3 )
2022-08-11 13:28:55 +02:00
dto . User = & user . SignedInUser { UserID : 1 }
2021-11-12 14:35:38 +01:00
_ , err := service . ImportDashboard ( context . Background ( ) , dto )
2022-06-30 09:31:54 -04:00
require . Equal ( t , err , dashboards . ErrDashboardCannotSaveProvisionedDashboard )
2018-04-13 17:54:41 +02:00
} )
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Given provisioned dashboard" , func ( t * testing . T ) {
t . Run ( "DeleteProvisionedDashboard should delete it" , func ( t * testing . T ) {
2023-01-18 13:52:41 +01:00
args := & dashboards . DeleteDashboardCommand { OrgID : 1 , ID : 1 }
2022-03-22 14:36:50 +01:00
fakeStore . On ( "DeleteDashboard" , mock . Anything , args ) . Return ( nil ) . Once ( )
2021-11-19 14:32:14 +01:00
err := service . DeleteProvisionedDashboard ( context . Background ( ) , 1 , 1 )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
2019-04-10 13:29:10 +02:00
} )
2022-03-22 14:36:50 +01:00
t . Run ( "DeleteDashboard should fail to delete it when provisioning information is missing" , func ( t * testing . T ) {
2023-01-18 13:52:41 +01:00
fakeStore . On ( "GetProvisionedDataByDashboardID" , mock . Anything , mock . AnythingOfType ( "int64" ) ) . Return ( & dashboards . DashboardProvisioning { } , nil ) . Once ( )
2024-12-17 09:35:46 -07:00
err := service . DeleteDashboard ( context . Background ( ) , 1 , "" , 1 )
2022-06-30 09:31:54 -04:00
require . Equal ( t , err , dashboards . ErrDashboardCannotDeleteProvisionedDashboard )
2019-04-10 13:29:10 +02:00
} )
} )
2021-10-27 15:17:50 +02:00
t . Run ( "Given non provisioned dashboard" , func ( t * testing . T ) {
2022-03-22 14:36:50 +01:00
t . Run ( "DeleteProvisionedDashboard should delete the dashboard" , func ( t * testing . T ) {
2023-12-04 10:34:38 +01:00
args := & dashboards . DeleteDashboardCommand { OrgID : 1 , ID : 1 }
2022-03-22 14:36:50 +01:00
fakeStore . On ( "DeleteDashboard" , mock . Anything , args ) . Return ( nil ) . Once ( )
2021-11-19 14:32:14 +01:00
err := service . DeleteProvisionedDashboard ( context . Background ( ) , 1 , 1 )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
2019-04-10 13:29:10 +02:00
} )
2021-10-27 15:17:50 +02:00
t . Run ( "DeleteDashboard should delete it" , func ( t * testing . T ) {
2023-01-18 13:52:41 +01:00
args := & dashboards . DeleteDashboardCommand { OrgID : 1 , ID : 1 }
2022-03-22 14:36:50 +01:00
fakeStore . On ( "DeleteDashboard" , mock . Anything , args ) . Return ( nil ) . Once ( )
2022-11-02 09:15:50 -04:00
fakeStore . On ( "GetProvisionedDataByDashboardID" , mock . Anything , mock . AnythingOfType ( "int64" ) ) . Return ( nil , nil ) . Once ( )
2024-12-17 09:35:46 -07:00
err := service . DeleteDashboard ( context . Background ( ) , 1 , "" , 1 )
2021-10-27 15:17:50 +02:00
require . NoError ( t , err )
2019-04-10 13:29:10 +02:00
} )
} )
2022-11-02 09:15:50 -04:00
t . Run ( "Count dashboards in folder" , func ( t * testing . T ) {
2024-01-30 18:26:34 +02:00
fakeStore . On ( "CountDashboardsInFolders" , mock . Anything , mock . AnythingOfType ( "*dashboards.CountDashboardsInFolderRequest" ) ) . Return ( int64 ( 3 ) , nil )
2024-01-12 16:43:39 +01:00
folderSvc . ExpectedFolder = & folder . Folder { UID : "i am a folder" }
2022-11-02 09:15:50 -04:00
// set up a ctx with signed in user
usr := & user . SignedInUser { UserID : 1 }
2024-07-26 16:39:23 +03:00
ctx := identity . WithRequester ( context . Background ( ) , usr )
2022-11-02 09:15:50 -04:00
2024-01-30 18:26:34 +02:00
count , err := service . CountInFolders ( ctx , 1 , [ ] string { "i am a folder" } , usr )
2022-11-02 09:15:50 -04:00
require . NoError ( t , err )
require . Equal ( t , int64 ( 3 ) , count )
} )
2023-04-14 11:17:23 +02:00
t . Run ( "Delete dashboards in folder" , func ( t * testing . T ) {
2024-01-30 18:26:34 +02:00
args := & dashboards . DeleteDashboardsInFolderRequest { OrgID : 1 , FolderUIDs : [ ] string { "uid" } }
fakeStore . On ( "DeleteDashboardsInFolders" , mock . Anything , args ) . Return ( nil ) . Once ( )
err := service . DeleteInFolders ( context . Background ( ) , 1 , [ ] string { "uid" } , nil )
2023-04-14 11:17:23 +02:00
require . NoError ( t , err )
} )
2024-05-16 14:36:26 -03:00
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 )
} )
2018-02-19 11:12:56 +01:00
} )
}
2024-12-13 15:55:43 -07:00
type mockDashK8sCli struct {
mock . Mock
2025-01-02 08:39:45 -07:00
searcher * mockResourceIndexClient
2024-12-13 15:55:43 -07:00
}
func ( m * mockDashK8sCli ) getClient ( ctx context . Context , orgID int64 ) ( dynamic . ResourceInterface , bool ) {
args := m . Called ( ctx , orgID )
return args . Get ( 0 ) . ( dynamic . ResourceInterface ) , args . Bool ( 1 )
}
2024-12-17 09:35:46 -07:00
func ( m * mockDashK8sCli ) getNamespace ( orgID int64 ) string {
2025-01-13 07:14:12 -07:00
if orgID == 1 {
return "default"
}
return fmt . Sprintf ( "orgs-%d" , orgID )
2024-12-17 09:35:46 -07:00
}
2025-01-02 08:39:45 -07:00
func ( m * mockDashK8sCli ) getSearcher ( ) resource . ResourceIndexClient {
return m . searcher
}
type mockResourceIndexClient struct {
mock . Mock
resource . ResourceIndexClient
}
func ( m * mockResourceIndexClient ) Search ( ctx context . Context , req * resource . ResourceSearchRequest , opts ... grpc . CallOption ) ( * resource . ResourceSearchResponse , error ) {
args := m . Called ( req )
return args . Get ( 0 ) . ( * resource . ResourceSearchResponse ) , args . Error ( 1 )
}
2025-01-16 22:19:56 -07:00
func ( m * mockResourceIndexClient ) GetStats ( ctx context . Context , in * resource . ResourceStatsRequest , opts ... grpc . CallOption ) ( * resource . ResourceStatsResponse , error ) {
args := m . Called ( in )
return args . Get ( 0 ) . ( * resource . ResourceStatsResponse ) , args . Error ( 1 )
}
2024-12-13 15:55:43 -07:00
type mockResourceInterface struct {
mock . Mock
dynamic . ResourceInterface
}
func ( m * mockResourceInterface ) Get ( ctx context . Context , name string , options metav1 . GetOptions , subresources ... string ) ( * unstructured . Unstructured , error ) {
args := m . Called ( ctx , name , options , subresources )
if args . Get ( 0 ) == nil {
return nil , args . Error ( 1 )
}
return args . Get ( 0 ) . ( * unstructured . Unstructured ) , args . Error ( 1 )
}
2024-12-17 09:35:46 -07:00
func ( m * mockResourceInterface ) List ( ctx context . Context , opts metav1 . ListOptions ) ( * unstructured . UnstructuredList , error ) {
args := m . Called ( ctx , opts )
if args . Get ( 0 ) == nil {
return nil , args . Error ( 1 )
}
return args . Get ( 0 ) . ( * unstructured . UnstructuredList ) , args . Error ( 1 )
}
func ( m * mockResourceInterface ) Create ( ctx context . Context , obj * unstructured . Unstructured , options metav1 . CreateOptions , subresources ... string ) ( * unstructured . Unstructured , error ) {
args := m . Called ( ctx , obj , options , subresources )
if args . Get ( 0 ) == nil {
return nil , args . Error ( 1 )
}
return args . Get ( 0 ) . ( * unstructured . Unstructured ) , args . Error ( 1 )
}
func ( m * mockResourceInterface ) Update ( ctx context . Context , obj * unstructured . Unstructured , options metav1 . UpdateOptions , subresources ... string ) ( * unstructured . Unstructured , error ) {
args := m . Called ( ctx , obj , options , subresources )
if args . Get ( 0 ) == nil {
return nil , args . Error ( 1 )
}
return args . Get ( 0 ) . ( * unstructured . Unstructured ) , args . Error ( 1 )
}
func ( m * mockResourceInterface ) Delete ( ctx context . Context , name string , options metav1 . DeleteOptions , subresources ... string ) error {
args := m . Called ( ctx , name , options , subresources )
return args . Error ( 0 )
}
2025-01-06 10:05:22 -07:00
func ( m * mockResourceInterface ) DeleteCollection ( ctx context . Context , options metav1 . DeleteOptions , listOptions metav1 . ListOptions ) error {
args := m . Called ( ctx , options , listOptions )
return args . Error ( 0 )
}
2024-12-17 09:35:46 -07:00
func setupK8sDashboardTests ( service * DashboardServiceImpl ) ( context . Context , * mockDashK8sCli , * mockResourceInterface ) {
k8sClientMock := new ( mockDashK8sCli )
k8sResourceMock := new ( mockResourceInterface )
2025-01-02 08:39:45 -07:00
k8sClientMock . searcher = new ( mockResourceIndexClient )
2024-12-17 09:35:46 -07:00
service . k8sclient = k8sClientMock
service . features = featuremgmt . WithFeatures ( featuremgmt . FlagKubernetesCliDashboards )
ctx := context . Background ( )
userCtx := & user . SignedInUser { UserID : 1 , OrgID : 1 }
ctx = identity . WithRequester ( ctx , userCtx )
return ctx , k8sClientMock , k8sResourceMock
}
2024-12-13 15:55:43 -07:00
func TestGetDashboard ( t * testing . T ) {
fakeStore := dashboards . FakeDashboardStore { }
defer fakeStore . AssertExpectations ( t )
service := & DashboardServiceImpl {
cfg : setting . NewCfg ( ) ,
dashboardStore : & fakeStore ,
}
2024-12-17 09:35:46 -07:00
query := & dashboards . GetDashboardQuery {
UID : "test-uid" ,
OrgID : 1 ,
}
2024-12-13 15:55:43 -07:00
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 ) {
2024-12-17 09:35:46 -07:00
ctx , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
2024-12-13 15:55:43 -07:00
dashboardUnstructured := unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
} ,
"spec" : map [ string ] any {
2024-12-17 09:35:46 -07:00
"test" : "test" ,
"version" : int64 ( 1 ) ,
"title" : "testing slugify" ,
2024-12-13 15:55:43 -07:00
} ,
} }
dashboardExpected := dashboards . Dashboard {
2024-12-17 09:35:46 -07:00
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 ) } ) ,
2024-12-13 15:55:43 -07:00
}
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "Get" , mock . Anything , query . UID , mock . Anything , mock . Anything ) . Return ( & dashboardUnstructured , nil ) . Once ( )
dashboard , err := service . GetDashboard ( ctx , query )
require . NoError ( t , err )
require . NotNil ( t , dashboard )
k8sClientMock . AssertExpectations ( t )
// make sure the conversion is working
require . True ( t , reflect . DeepEqual ( dashboard , & dashboardExpected ) )
} )
2025-01-02 08:39:45 -07:00
t . Run ( "Should get uid if not passed in at first" , func ( t * testing . T ) {
query := & dashboards . GetDashboardQuery {
ID : 1 ,
UID : "" ,
OrgID : 1 ,
}
ctx , k8sClientMock , k8sResourceMock := 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 ) } ) ,
}
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & dashboardUnstructured , nil ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
} ,
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 )
k8sClientMock . AssertExpectations ( t )
k8sClientMock . searcher . AssertExpectations ( t )
// make sure the conversion is working
require . True ( t , reflect . DeepEqual ( dashboard , & dashboardExpected ) )
} )
2024-12-13 15:55:43 -07:00
t . Run ( "Should return error when Kubernetes client fails" , func ( t * testing . T ) {
2024-12-17 09:35:46 -07:00
ctx , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
2024-12-13 15:55:43 -07:00
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "Get" , mock . Anything , query . UID , mock . Anything , mock . Anything ) . Return ( nil , assert . AnError ) . Once ( )
dashboard , err := service . GetDashboard ( ctx , query )
require . Error ( t , err )
require . Nil ( t , dashboard )
k8sClientMock . AssertExpectations ( t )
} )
t . Run ( "Should return dashboard not found if Kubernetes client returns nil" , func ( t * testing . T ) {
2024-12-17 09:35:46 -07:00
ctx , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
2024-12-13 15:55:43 -07:00
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "Get" , mock . Anything , query . UID , 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 )
k8sClientMock . AssertExpectations ( t )
} )
}
2024-12-17 09:35:46 -07:00
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 , k8sClientMock , k8sResourceMock := 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 ) } ) ,
}
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "List" , 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 )
k8sClientMock . AssertExpectations ( t )
// make sure the conversion is working
require . True ( t , reflect . DeepEqual ( dashes , [ ] * dashboards . Dashboard { & dashboardExpected } ) )
} )
}
2025-01-07 14:33:45 -07:00
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 ( )
2025-01-07 15:20:35 -07:00
fakeStore . On ( "GetAllDashboardsByOrgId" , mock . Anything , int64 ( 1 ) ) . Return ( [ ] * dashboards . Dashboard { } , nil ) . Once ( )
2025-01-07 14:33:45 -07:00
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 , k8sClientMock , k8sResourceMock := 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 ) } ) ,
}
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "List" , 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 )
k8sClientMock . AssertExpectations ( t )
// make sure the conversion is working
require . True ( t , reflect . DeepEqual ( dashes , [ ] * dashboards . Dashboard { & dashboardExpected } ) )
} )
}
2025-01-13 07:14:12 -07:00
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , mock . Anything ) . Return ( k8sResourceMock , true )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
"labels" : map [ string ] any {
utils . LabelKeyDeprecatedInternalID : "1" , // nolint:staticcheck
} ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "test" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any {
"test" : "test" ,
"version" : int64 ( 1 ) ,
"title" : "testing slugify" ,
} ,
} } , nil ) . Once ( )
repo := "test"
k8sClientMock . searcher . On ( "Search" ,
mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
// ensure the prefix is added to the query
return req . Options . Key . Namespace == "default" && req . Options . Fields [ 0 ] . Values [ 0 ] == provisionedFileNameWithPrefix ( repo )
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition { } ,
Rows : [ ] * resource . ResourceTableRow { } ,
} ,
TotalHits : 0 ,
} , nil ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
// ensure the prefix is added to the query
return req . Options . Key . Namespace == "orgs-2" && req . Options . Fields [ 0 ] . Values [ 0 ] == provisionedFileNameWithPrefix ( repo )
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 : 1735689600 ,
} )
k8sClientMock . 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , mock . Anything ) . Return ( k8sResourceMock , true )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
"labels" : map [ string ] any {
utils . LabelKeyDeprecatedInternalID : "1" , // nolint:staticcheck
} ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "test" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any {
"test" : "test" ,
"version" : int64 ( 1 ) ,
"title" : "testing slugify" ,
} ,
} } , nil )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
return req . Options . Key . Namespace == "default"
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition { } ,
Rows : [ ] * resource . ResourceTableRow { } ,
} ,
TotalHits : 0 ,
} , nil )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
return req . Options . Key . Namespace == "orgs-2"
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 : 1735689600 ,
} )
k8sClientMock . 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , mock . Anything ) . Return ( k8sResourceMock , true )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
"labels" : map [ string ] any {
utils . LabelKeyDeprecatedInternalID : "1" , // nolint:staticcheck
} ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "test" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any {
"test" : "test" ,
"version" : int64 ( 1 ) ,
"title" : "testing slugify" ,
} ,
} } , nil ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 : 1735689600 ,
} )
k8sClientMock . AssertExpectations ( t )
} )
}
func TestDeleteOrphanedProvisionedDashboards ( 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 ( "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 ) {
_ , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , mock . Anything ) . Return ( k8sResourceMock , true )
k8sResourceMock . 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 ( )
k8sResourceMock . On ( "Get" , mock . Anything , "uid" , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "orphaned" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any { } ,
} } , nil ) . Once ( )
// should not delete this one, because it does not start with "file:"
k8sResourceMock . On ( "Get" , mock . Anything , "uid2" , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid2" ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : "plugin" ,
utils . AnnoKeyRepoHash : "app" ,
} ,
} ,
"spec" : map [ string ] any { } ,
} } , nil ) . Once ( )
k8sResourceMock . On ( "Get" , mock . Anything , "uid3" , mock . Anything , mock . Anything ) . Return ( & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid3" ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "orphaned" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any { } ,
} } , nil ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
return req . Options . Key . Namespace == "default" && req . Options . Fields [ 0 ] . Key == "repo.name" && req . Options . Fields [ 0 ] . Values [ 0 ] == provisionedFileNameWithPrefix ( "test" ) &&
req . Options . Fields [ 0 ] . Operator == "notin"
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
Rows : [ ] * resource . ResourceTableRow {
{
Key : & resource . ResourceKey {
Name : "uid" ,
Resource : "dashboard" ,
} ,
Cells : [ ] [ ] byte {
[ ] byte ( "Dashboard 1" ) ,
[ ] byte ( "folder 1" ) ,
} ,
} ,
} ,
} ,
TotalHits : 1 ,
} , nil ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
return req . Options . Key . Namespace == "orgs-2" && req . Options . Fields [ 0 ] . Key == "repo.name" && req . Options . Fields [ 0 ] . Values [ 0 ] == provisionedFileNameWithPrefix ( "test" ) &&
req . Options . Fields [ 0 ] . Operator == "notin"
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 )
k8sClientMock . 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , mock . Anything ) . Return ( k8sResourceMock , true )
dash := & unstructured . Unstructured { Object : map [ string ] any {
"metadata" : map [ string ] any {
"name" : "uid" ,
"annotations" : map [ string ] any {
utils . AnnoKeyRepoName : fileProvisionedRepoPrefix + "test" ,
utils . AnnoKeyRepoHash : "hash" ,
utils . AnnoKeyRepoPath : "path/to/file" ,
utils . AnnoKeyRepoTimestamp : "2025-01-01T00:00:00Z" ,
} ,
} ,
"spec" : map [ string ] any { } ,
} }
k8sResourceMock . On ( "Get" , 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
k8sResourceMock . On ( "Update" , mock . Anything , dashWithoutAnnotations , mock . Anything , mock . Anything ) . Return ( dashWithoutAnnotations , nil )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 )
k8sClientMock . 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 ,
}
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 , k8sClientMock , _ := setupK8sDashboardTests ( service )
k8sClientMock . searcher . On ( "Search" , mock . MatchedBy ( func ( req * resource . ResourceSearchRequest ) bool {
return req . Options . Fields [ 0 ] . Key == "repo.name" && req . Options . Fields [ 0 ] . Values [ 0 ] == "plugin" &&
req . Options . Fields [ 1 ] . Key == "repo.path" && req . Options . Fields [ 1 ] . Values [ 0 ] == "testing"
} ) ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-13 07:14:12 -07:00
} ,
} ,
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 )
k8sClientMock . AssertExpectations ( t )
} )
}
func TestSaveProvisionedDashboard ( t * testing . T ) {
fakeStore := dashboards . FakeDashboardStore { }
defer fakeStore . AssertExpectations ( t )
service := & DashboardServiceImpl {
cfg : setting . NewCfg ( ) ,
dashboardStore : & fakeStore ,
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
fakeStore . On ( "SaveProvisionedDashboard" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil , nil )
k8sResourceMock . On ( "Create" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & dashboardUnstructured , nil )
dashboard , err := service . SaveProvisionedDashboard ( ctx , query , & dashboards . DashboardProvisioning { } )
require . NoError ( t , err )
require . NotNil ( t , dashboard )
k8sClientMock . AssertExpectations ( t )
// ensure the provisioning data is still saved to the db
fakeStore . AssertExpectations ( t )
} )
}
2024-12-17 09:35:46 -07:00
func TestSaveDashboard ( t * testing . T ) {
fakeStore := dashboards . FakeDashboardStore { }
defer fakeStore . AssertExpectations ( t )
service := & DashboardServiceImpl {
cfg : setting . NewCfg ( ) ,
dashboardStore : & fakeStore ,
}
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 )
2025-01-09 21:28:26 -07:00
fakeStore . On ( "GetDashboard" , mock . Anything , mock . Anything ) . Return ( & dashboards . Dashboard { } , nil )
2024-12-17 09:35:46 -07:00
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true )
2025-01-09 21:28:26 -07:00
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil , nil )
2024-12-17 09:35:46 -07:00
k8sResourceMock . 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true )
2025-01-09 21:28:26 -07:00
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( & dashboardUnstructured , nil )
2024-12-17 09:35:46 -07:00
k8sResourceMock . 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 )
} )
2025-01-09 21:28:26 -07:00
t . Run ( "Should return an error if uid is invalid" , func ( t * testing . T ) {
ctx , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true )
k8sResourceMock . On ( "Get" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil , nil )
k8sResourceMock . 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 )
} )
2024-12-17 09:35:46 -07:00
}
func TestDeleteDashboard ( 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 ( "DeleteDashboard" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
fakeStore . On ( "GetProvisionedDataByDashboardID" , mock . Anything , mock . Anything ) . Return ( nil , 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
2025-01-13 07:14:12 -07:00
k8sResourceMock . On ( "Delete" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
fakeStore . On ( "CleanupAfterDelete" , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
2024-12-17 09:35:46 -07:00
err := service . DeleteDashboard ( ctx , 1 , "uid" , 1 )
require . NoError ( t , err )
k8sClientMock . AssertExpectations ( t )
} )
2025-01-02 08:39:45 -07:00
2025-01-13 07:14:12 -07:00
t . Run ( "If UID is not passed in, it should retrieve that first" , func ( t * testing . T ) {
2025-01-02 08:39:45 -07:00
ctx , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
2025-01-13 07:14:12 -07:00
k8sResourceMock . On ( "Delete" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
fakeStore . On ( "CleanupAfterDelete" , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
2025-01-02 08:39:45 -07:00
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
} ,
Rows : [ ] * resource . ResourceTableRow {
{
Key : & resource . ResourceKey {
Name : "uid" ,
Resource : "dashboard" ,
} ,
Cells : [ ] [ ] byte {
[ ] byte ( "Dashboard 1" ) ,
[ ] byte ( "folder1" ) ,
} ,
} ,
} ,
} ,
TotalHits : 1 ,
} , nil )
2025-01-13 07:14:12 -07:00
err := service . DeleteDashboard ( ctx , 1 , "" , 1 )
2025-01-02 08:39:45 -07:00
require . NoError ( t , err )
k8sClientMock . AssertExpectations ( t )
k8sClientMock . searcher . AssertExpectations ( t )
} )
}
2025-01-06 10:05:22 -07:00
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sResourceMock . On ( "DeleteCollection" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
err := service . DeleteAllDashboards ( ctx , 1 )
require . NoError ( t , err )
k8sClientMock . AssertExpectations ( t )
} )
}
2025-01-02 08:39:45 -07:00
func TestSearchDashboards ( t * testing . T ) {
fakeStore := dashboards . FakeDashboardStore { }
defer fakeStore . AssertExpectations ( t )
service := & DashboardServiceImpl {
cfg : setting . NewCfg ( ) ,
dashboardStore : & fakeStore ,
}
expectedResult := model . HitList {
{
UID : "uid1" ,
OrgID : 1 ,
Title : "Dashboard 1" ,
Type : "dash-db" ,
URI : "db/dashboard-1" ,
URL : "/d/uid1/dashboard-1" ,
2025-01-02 10:38:23 -07:00
Tags : [ ] string {
"tag1" ,
"tag2" ,
} ,
2025-01-02 08:39:45 -07:00
} ,
{
UID : "uid2" ,
OrgID : 1 ,
Title : "Dashboard 2" ,
Type : "dash-db" ,
URI : "db/dashboard-2" ,
URL : "/d/uid2/dashboard-2" ,
Tags : [ ] string { } ,
} ,
}
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" ,
2025-01-02 10:38:23 -07:00
Tags : [ ] string { "tag1" , "tag2" } ,
2025-01-02 08:39:45 -07:00
} ,
{
UID : "uid2" ,
Slug : "dashboard-2" ,
OrgID : 1 ,
Title : "Dashboard 2" ,
} ,
} , 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
2025-01-02 10:38:23 -07:00
{
Name : "tags" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 10:38:23 -07:00
} ,
2025-01-02 08:39:45 -07:00
} ,
Rows : [ ] * resource . ResourceTableRow {
{
Key : & resource . ResourceKey {
Name : "uid1" ,
Resource : "dashboard" ,
} ,
Cells : [ ] [ ] byte {
[ ] byte ( "Dashboard 1" ) ,
[ ] byte ( "" ) ,
2025-01-02 10:38:23 -07:00
[ ] byte ( "[\"tag1\", \"tag2\"]" ) ,
2025-01-02 08:39:45 -07:00
} ,
} ,
{
Key : & resource . ResourceKey {
Name : "uid2" ,
Resource : "dashboard" ,
} ,
Cells : [ ] [ ] byte {
[ ] byte ( "Dashboard 2" ) ,
[ ] byte ( "" ) ,
2025-01-02 10:38:23 -07:00
[ ] byte ( "" ) ,
2025-01-02 08:39:45 -07:00
} ,
} ,
} ,
} ,
TotalHits : 1 ,
} , nil )
result , err := service . SearchDashboards ( ctx , & query )
require . NoError ( t , err )
require . Equal ( t , expectedResult , result )
k8sClientMock . searcher . 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" ,
} ,
{
UID : "uid2" ,
Slug : "dashboard-2" ,
OrgID : 1 ,
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
} ,
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 )
k8sClientMock . searcher . AssertExpectations ( t )
// by uids
result , err = service . GetDashboards ( ctx , queryByUIDs )
require . NoError ( t , err )
require . Equal ( t , expectedResult , result )
k8sClientMock . searcher . 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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sClientMock . searcher . On ( "Search" , mock . Anything ) . Return ( & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
{
Name : "folder" ,
2025-01-15 10:23:05 -06:00
Type : resource . ResourceTableColumnDefinition_STRING ,
2025-01-02 08:39:45 -07:00
} ,
} ,
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 )
k8sClientMock . searcher . AssertExpectations ( t )
} )
2024-12-17 09:35:46 -07:00
}
func TestUnstructuredToLegacyDashboard ( t * testing . T ) {
fake := usertest . NewUserServiceFake ( )
fake . ExpectedUser = & user . User { ID : 10 , UID : "useruid" }
dr := & DashboardServiceImpl {
userService : fake ,
}
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" )
2025-01-03 08:28:45 -07:00
obj . SetDeprecatedInternalID ( 1 ) // nolint:staticcheck
2024-12-17 09:35:46 -07:00
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 ( ) )
} )
}
2025-01-02 10:38:23 -07:00
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 , k8sClientMock , k8sResourceMock := setupK8sDashboardTests ( service )
k8sClientMock . On ( "getClient" , mock . Anything , int64 ( 1 ) ) . Return ( k8sResourceMock , true ) . Once ( )
k8sClientMock . searcher . On ( "Search" , 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 )
} )
}
2025-01-09 22:21:21 -07:00
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 ,
} ,
}
2025-01-16 22:19:56 -07:00
countOrg1 := resource . ResourceStatsResponse {
Stats : [ ] * resource . ResourceStatsResponse_Stats {
{
Count : 1 ,
2025-01-09 22:21:21 -07:00
} ,
} ,
2025-01-16 22:19:56 -07:00
}
countOrg2 := resource . ResourceStatsResponse {
Stats : [ ] * resource . ResourceStatsResponse_Stats {
2025-01-09 22:21:21 -07:00
{
2025-01-16 22:19:56 -07:00
Count : 2 ,
2025-01-09 22:21:21 -07:00
} ,
} ,
}
query := & quota . 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 ) {
2025-01-16 22:19:56 -07:00
ctx , k8sClientMock , _ := setupK8sDashboardTests ( service )
2025-01-09 22:21:21 -07:00
orgSvc := orgtest . FakeOrgService { ExpectedOrgs : orgs }
service . orgService = & orgSvc
2025-01-16 22:19:56 -07:00
k8sClientMock . searcher . On ( "GetStats" , mock . Anything ) . Return ( & countOrg2 , nil ) . Once ( )
k8sClientMock . searcher . On ( "GetStats" , mock . Anything ) . Return ( & countOrg1 , nil ) . Once ( )
2025-01-09 22:21:21 -07:00
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 )
} )
}
2025-01-16 22:19:56 -07:00
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 , k8sClientMock , _ := setupK8sDashboardTests ( service )
k8sClientMock . searcher . On ( "GetStats" , mock . Anything ) . Return ( & count , nil ) . Once ( )
result , err := service . CountDashboardsInOrg ( ctx , 1 )
require . NoError ( t , err )
require . Equal ( t , result , int64 ( 3 ) )
} )
}
2024-12-17 09:35:46 -07:00
func TestLegacySaveCommandToUnstructured ( t * testing . T ) {
namespace := "test-namespace"
t . Run ( "successfully converts save command to unstructured" , func ( t * testing . T ) {
cmd := & dashboards . SaveDashboardCommand {
2025-01-14 22:15:58 -07:00
FolderUID : "folder-uid" ,
2024-12-17 09:35:46 -07:00
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 )
2025-01-14 22:15:58 -07:00
assert . Equal ( t , result . GetAnnotations ( ) , map [ string ] string { utils . AnnoKeyFolder : "folder-uid" } )
2024-12-17 09:35:46 -07:00
} )
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 ) )
2025-01-14 22:15:58 -07:00
// folder annotation should not be set if not inside a folder
assert . Equal ( t , result . GetAnnotations ( ) , map [ string ] string ( nil ) )
2024-12-17 09:35:46 -07:00
} )
}
func TestToUID ( t * testing . T ) {
t . Run ( "parses valid UID" , func ( t * testing . T ) {
rawIdentifier := "user:uid-value"
result := toUID ( rawIdentifier )
assert . Equal ( t , "uid-value" , result )
} )
t . Run ( "returns empty string for invalid identifier" , func ( t * testing . T ) {
rawIdentifier := "invalid-uid"
result := toUID ( rawIdentifier )
assert . Equal ( t , "" , result )
} )
}
2025-01-15 10:23:05 -06:00
// regression test - parsing int32 values from search results was causing a panic
func TestParseResults ( t * testing . T ) {
resSearchResp := & resource . ResourceSearchResponse {
Results : & resource . ResourceTable {
Columns : [ ] * resource . ResourceTableColumnDefinition {
{
Name : "title" ,
Type : resource . ResourceTableColumnDefinition_STRING ,
} ,
{
Name : "folder" ,
Type : resource . ResourceTableColumnDefinition_STRING ,
} ,
{
Name : search . DASHBOARD_ERRORS_LAST_1_DAYS ,
Type : resource . ResourceTableColumnDefinition_INT64 ,
} ,
{
Name : search . DASHBOARD_LINK_COUNT ,
Type : resource . ResourceTableColumnDefinition_INT32 ,
} ,
} ,
Rows : [ ] * resource . ResourceTableRow {
{
Key : & resource . ResourceKey {
Name : "uid" ,
Resource : "dashboard" ,
} ,
Cells : [ ] [ ] byte {
[ ] byte ( "Dashboard 1" ) ,
[ ] byte ( "folder1" ) ,
[ ] byte ( "100" ) ,
[ ] byte ( "25" ) ,
} ,
} ,
} ,
} ,
TotalHits : 1 ,
}
_ , err := ParseResults ( resSearchResp , 0 )
require . NoError ( t , err )
}