diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 07696a86041..774940d62e5 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -432,7 +432,7 @@ func setupHTTPServerWithCfgDb( teamPermissionsService: teamPermissionService, searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()), DashboardService: dashboardservice.ProvideDashboardService( - cfg, dashboardsStore, nil, features, + cfg, dashboardsStore, dashboardsStore, nil, features, folderPermissionsService, dashboardPermissionsService, ac, ), preferenceService: preftest.NewPreferenceServiceFake(), diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index cec1451d319..19383034e0b 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -47,7 +47,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { SQLStore: mockSQLStore, Features: features, DashboardService: dashboardservice.ProvideDashboardService( - settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, + settings, dashboardStore, dashboards.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, ), AccessControl: accesscontrolmock.New().WithDisabled(), } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 2b3a7521ba7..a6ae7b5183e 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -177,7 +177,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUp() sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.False(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) @@ -209,7 +209,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUp() sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) @@ -393,7 +393,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUpInner() sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) @@ -456,7 +456,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { require.True(t, setting.ViewersCanEdit) sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.True(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) @@ -494,7 +494,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUpInner() sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) @@ -547,7 +547,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { setUpInner() sc.sqlStore = mockSQLStore - dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, nil, nil, dashboardService, nil) assert.False(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) @@ -952,7 +952,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { return "/tmp/grafana/dashboards" } - dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore, dashboardService) + dash := getDashboardShouldReturn200WithConfig(t, sc, fakeProvisioningService, dashboardStore, dashboardService, nil) assert.Equal(t, "../../../dashboard1.json", dash.Meta.ProvisionedExternalId, mockSQLStore) }, mockSQLStore) @@ -992,7 +992,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { }) } -func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store, dashboardService dashboards.DashboardService) dtos.DashboardFullWithMeta { +func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store, dashboardService dashboards.DashboardService, folderStore dashboards.FolderStore) dtos.DashboardFullWithMeta { t.Helper() if provisioningService == nil { @@ -1017,7 +1017,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr if dashboardService == nil { dashboardService = service.ProvideDashboardService( - cfg, dashboardStore, nil, features, + cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions, ac, ) } @@ -1030,7 +1030,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr ProvisioningService: provisioningService, AccessControl: accesscontrolmock.New(), dashboardProvisioningService: service.ProvideDashboardService( - cfg, dashboardStore, nil, features, + cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions, ac, ), DashboardService: dashboardService, diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 327d74dbc0f..32d6b85bf3b 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -45,7 +45,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { folderPermissionsService: folderPermissions, dashboardPermissionsService: dashboardPermissions, DashboardService: service.ProvideDashboardService( - settings, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, + settings, dashboardStore, dashboards.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, ), AccessControl: accesscontrolmock.New().WithDisabled(), } diff --git a/pkg/cmd/grafana-cli/runner/wire.go b/pkg/cmd/grafana-cli/runner/wire.go index cfd97956c16..ce25050be66 100644 --- a/pkg/cmd/grafana-cli/runner/wire.go +++ b/pkg/cmd/grafana-cli/runner/wire.go @@ -257,6 +257,7 @@ var wireSet = wire.NewSet( wire.Bind(new(dashboards.DashboardProvisioningService), new(*dashboardservice.DashboardServiceImpl)), wire.Bind(new(dashboards.PluginService), new(*dashboardservice.DashboardServiceImpl)), wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)), + wire.Bind(new(dashboards.FolderStore), new(*dashboardstore.DashboardStore)), dashboardimportservice.ProvideService, wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)), plugindashboardsservice.ProvideService, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index cb7f1ce6eaa..4082ca6d624 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -293,6 +293,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(dashboards.DashboardProvisioningService), new(*dashboardservice.DashboardServiceImpl)), wire.Bind(new(dashboards.PluginService), new(*dashboardservice.DashboardServiceImpl)), wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)), + wire.Bind(new(dashboards.FolderStore), new(*dashboardstore.DashboardStore)), dashboardimportservice.ProvideService, wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)), plugindashboardsservice.ProvideService, diff --git a/pkg/services/dashboards/accesscontrol.go b/pkg/services/dashboards/accesscontrol.go index 6350c35188d..cd0bf9333d2 100644 --- a/pkg/services/dashboards/accesscontrol.go +++ b/pkg/services/dashboards/accesscontrol.go @@ -38,7 +38,7 @@ var ( ) // NewFolderNameScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:name:" into an uid based scope. -func NewFolderNameScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { +func NewFolderNameScopeResolver(db Store, folderDB FolderStore) (string, ac.ScopeAttributeResolver) { prefix := ScopeFoldersProvider.GetResourceScopeName("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -48,7 +48,7 @@ func NewFolderNameScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { if len(nsName) == 0 { return nil, ac.ErrInvalidScope } - folder, err := db.GetFolderByTitle(ctx, orgID, nsName) + folder, err := folderDB.GetFolderByTitle(ctx, orgID, nsName) if err != nil { return nil, err } @@ -57,7 +57,7 @@ func NewFolderNameScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { } // NewFolderIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope. -func NewFolderIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { +func NewFolderIDScopeResolver(db Store, folderDB FolderStore) (string, ac.ScopeAttributeResolver) { prefix := ScopeFoldersProvider.GetResourceScope("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -73,7 +73,7 @@ func NewFolderIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { return []string{ScopeFoldersProvider.GetResourceScopeUID(ac.GeneralFolderUID)}, nil } - folder, err := db.GetFolderByID(ctx, orgID, id) + folder, err := folderDB.GetFolderByID(ctx, orgID, id) if err != nil { return nil, err } @@ -84,7 +84,7 @@ func NewFolderIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { // NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:" // into uid based scopes for both dashboard and folder -func NewDashboardIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { +func NewDashboardIDScopeResolver(db Store, folderDB FolderStore) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScope("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -101,13 +101,13 @@ func NewDashboardIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { return nil, err } - return resolveDashboardScope(ctx, db, orgID, dashboard) + return resolveDashboardScope(ctx, db, folderDB, orgID, dashboard) }) } // NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:" // into uid based scopes for both dashboard and folder -func NewDashboardUIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) { +func NewDashboardUIDScopeResolver(db Store, folderDB FolderStore) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScopeUID("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -124,11 +124,11 @@ func NewDashboardUIDScopeResolver(db Store) (string, ac.ScopeAttributeResolver) return nil, err } - return resolveDashboardScope(ctx, db, orgID, dashboard) + return resolveDashboardScope(ctx, db, folderDB, orgID, dashboard) }) } -func resolveDashboardScope(ctx context.Context, db Store, orgID int64, dashboard *Dashboard) ([]string, error) { +func resolveDashboardScope(ctx context.Context, db Store, folderDB FolderStore, orgID int64, dashboard *Dashboard) ([]string, error) { var folderUID string if dashboard.FolderID < 0 { return []string{ScopeDashboardsProvider.GetResourceScopeUID(dashboard.UID)}, nil @@ -137,7 +137,7 @@ func resolveDashboardScope(ctx context.Context, db Store, orgID int64, dashboard if dashboard.FolderID == 0 { folderUID = ac.GeneralFolderUID } else { - folder, err := db.GetFolderByID(ctx, orgID, dashboard.FolderID) + folder, err := folderDB.GetFolderByID(ctx, orgID, dashboard.FolderID) if err != nil { return nil, err } diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index 062994c87c3..ae7f319fc98 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -17,41 +17,42 @@ import ( func TestNewFolderNameScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}) + prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) require.Equal(t, "folders:name:", prefix) }) t.Run("resolver should convert to uid scope", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - - _, resolver := NewFolderNameScopeResolver(dashboardStore) - orgId := rand.Int63() title := "Very complex :title with: and /" + util.GenerateShortUID() db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()} - dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() + + folderStore := NewFakeFolderStore(t) + folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() scope := "folders:name:" + title + _, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore) + resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) require.NoError(t, err) require.Len(t, resolvedScopes, 1) require.Equal(t, fmt.Sprintf("folders:uid:%v", db.UID), resolvedScopes[0]) - dashboardStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title) + folderStore.AssertCalled(t, "GetFolderByTitle", mock.Anything, orgId, title) }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderNameScopeResolver(dashboardStore) + _, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderNameScopeResolver(dashboardStore) + _, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:name:") require.ErrorIs(t, err, ac.ErrInvalidScope) @@ -59,10 +60,11 @@ func TestNewFolderNameScopeResolver(t *testing.T) { t.Run("returns 'not found' if folder does not exist", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderNameScopeResolver(dashboardStore) + folderStore := NewFakeFolderStore(t) + _, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore) orgId := rand.Int63() - dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() + folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() scope := "folders:name:" + util.GenerateShortUID() @@ -74,20 +76,21 @@ func TestNewFolderNameScopeResolver(t *testing.T) { func TestNewFolderIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}) + prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) require.Equal(t, "folders:id:", prefix) }) t.Run("resolver should convert to uid scope", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} + folderStore := NewFakeFolderStore(t) - _, resolver := NewFolderIDScopeResolver(dashboardStore) + _, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore) orgId := rand.Int63() uid := util.GenerateShortUID() db := &folder.Folder{ID: rand.Int63(), UID: uid} - dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() + folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() scope := "folders:id:" + strconv.FormatInt(db.ID, 10) @@ -96,11 +99,11 @@ func TestNewFolderIDScopeResolver(t *testing.T) { require.Len(t, resolvedScopes, 1) require.Equal(t, fmt.Sprintf("folders:uid:%v", db.UID), resolvedScopes[0]) - dashboardStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.ID) + folderStore.AssertCalled(t, "GetFolderByID", mock.Anything, orgId, db.ID) }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderIDScopeResolver(dashboardStore) + _, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) @@ -111,7 +114,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) { dashboardStore = &FakeDashboardStore{} orgId = rand.Int63() scope = "folders:id:0" - _, resolver = NewFolderIDScopeResolver(dashboardStore) + _, resolver = NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t)) ) resolved, err := resolver.Resolve(context.Background(), orgId, scope) @@ -123,18 +126,19 @@ func TestNewFolderIDScopeResolver(t *testing.T) { t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderIDScopeResolver(dashboardStore) + _, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("returns 'not found' if folder does not exist", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} + folderStore := NewFakeFolderStore(t) - _, resolver := NewFolderIDScopeResolver(dashboardStore) + _, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore) orgId := rand.Int63() - dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() + folderStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once() scope := "folders:id:10" resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) @@ -145,21 +149,22 @@ func TestNewFolderIDScopeResolver(t *testing.T) { func TestNewDashboardIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}) + prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) require.Equal(t, "dashboards:id:", prefix) }) t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { store := &FakeDashboardStore{} + folderStore := NewFakeFolderStore(t) - _, resolver := NewDashboardIDScopeResolver(store) + _, resolver := NewDashboardIDScopeResolver(store, folderStore) orgID := rand.Int63() folder := &folder.Folder{ID: 2, UID: "2"} dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - store.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() + folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() scope := ac.Scope("dashboards", "id", strconv.FormatInt(dashboard.ID, 10)) resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) @@ -170,14 +175,14 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}) + _, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - _, resolver := NewDashboardIDScopeResolver(store) + _, resolver := NewDashboardIDScopeResolver(store, NewFakeFolderStore(t)) dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) @@ -192,20 +197,21 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { func TestNewDashboardUIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}) + prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) require.Equal(t, "dashboards:uid:", prefix) }) t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - _, resolver := NewDashboardUIDScopeResolver(store) + folderStore := NewFakeFolderStore(t) + _, resolver := NewDashboardUIDScopeResolver(store, folderStore) orgID := rand.Int63() folder := &folder.Folder{ID: 2, UID: "2"} dashboard := &Dashboard{ID: 1, FolderID: folder.ID, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil).Once() - store.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() + folderStore.On("GetFolderByID", mock.Anything, orgID, folder.ID).Return(folder, nil).Once() scope := ac.Scope("dashboards", "uid", dashboard.UID) resolvedScopes, err := resolver.Resolve(context.Background(), orgID, scope) @@ -216,14 +222,14 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}) + _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t)) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - _, resolver := NewDashboardUIDScopeResolver(store) + _, resolver := NewDashboardUIDScopeResolver(store, NewFakeFolderStore(t)) dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 5614a57be90..0216cb111b9 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -83,8 +83,6 @@ type Store interface { // CountDashboardsInFolder returns the number of dashboards associated with // the given parent folder ID. CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) - - FolderStore } // FolderStore is a folder store. diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index a1e6555f4d4..bcba1197795 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -37,6 +37,7 @@ type DashboardServiceImpl struct { cfg *setting.Cfg log log.Logger dashboardStore dashboards.Store + folderStore dashboards.FolderStore dashAlertExtractor alerting.DashAlertExtractor features featuremgmt.FeatureToggles folderPermissions accesscontrol.FolderPermissionsService @@ -45,17 +46,17 @@ type DashboardServiceImpl struct { } func ProvideDashboardService( - cfg *setting.Cfg, store dashboards.Store, dashAlertExtractor alerting.DashAlertExtractor, + cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore dashboards.FolderStore, dashAlertExtractor alerting.DashAlertExtractor, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl, ) *DashboardServiceImpl { - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(store)) - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(store)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(dashboardStore, folderStore)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashboardStore, folderStore)) return &DashboardServiceImpl{ cfg: cfg, log: log.New("dashboard-service"), - dashboardStore: store, + dashboardStore: dashboardStore, dashAlertExtractor: dashAlertExtractor, features: features, folderPermissions: folderPermissionsService, @@ -623,7 +624,7 @@ func (dr DashboardServiceImpl) CountDashboardsInFolder(ctx context.Context, quer return 0, err } - folder, err := dr.dashboardStore.GetFolderByUID(ctx, u.OrgID, query.FolderUID) + folder, err := dr.folderStore.GetFolderByUID(ctx, u.OrgID, query.FolderUID) if err != nil { return 0, err } diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 51d34a60e4a..3826dd22601 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -826,7 +826,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) service := ProvideDashboardService( - cfg, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -885,7 +885,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) service := ProvideDashboardService( - cfg, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -906,7 +906,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) service := ProvideDashboardService( - cfg, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -945,7 +945,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) service := ProvideDashboardService( - cfg, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -985,7 +985,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) service := ProvideDashboardService( - cfg, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 68d708b7440..05a321cba45 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -25,10 +25,13 @@ func TestDashboardService(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) + folderStore := dashboards.NewFakeFolderStore(t) + service := &DashboardServiceImpl{ cfg: setting.NewCfg(), log: log.New("test.logger"), dashboardStore: &fakeStore, + folderStore: folderStore, dashAlertExtractor: &dummyDashAlertExtractor{}, } @@ -227,7 +230,7 @@ func TestDashboardService(t *testing.T) { }) t.Run("Count dashboards in folder", func(t *testing.T) { - fakeStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) + folderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) fakeStore.On("CountDashboardsInFolder", mock.Anything, mock.AnythingOfType("*dashboards.CountDashboardsInFolderRequest")).Return(int64(3), nil) // set up a ctx with signed in user diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index e8792224678..4dad7c72e67 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -5,10 +5,8 @@ package dashboards import ( context "context" - folder "github.com/grafana/grafana/pkg/services/folder" - mock "github.com/stretchr/testify/mock" - models "github.com/grafana/grafana/pkg/models" + mock "github.com/stretchr/testify/mock" quota "github.com/grafana/grafana/pkg/services/quota" ) @@ -220,75 +218,6 @@ func (_m *FakeDashboardStore) GetDashboardsByPluginID(ctx context.Context, query return r0 } -// GetFolderByID provides a mock function with given fields: ctx, orgID, id -func (_m *FakeDashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) { - ret := _m.Called(ctx, orgID, id) - - var r0 *folder.Folder - if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *folder.Folder); ok { - r0 = rf(ctx, orgID, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*folder.Folder) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { - r1 = rf(ctx, orgID, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetFolderByTitle provides a mock function with given fields: ctx, orgID, title -func (_m *FakeDashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) { - ret := _m.Called(ctx, orgID, title) - - var r0 *folder.Folder - if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok { - r0 = rf(ctx, orgID, title) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*folder.Folder) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { - r1 = rf(ctx, orgID, title) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetFolderByUID provides a mock function with given fields: ctx, orgID, uid -func (_m *FakeDashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) { - ret := _m.Called(ctx, orgID, uid) - - var r0 *folder.Folder - if rf, ok := ret.Get(0).(func(context.Context, int64, string) *folder.Folder); ok { - r0 = rf(ctx, orgID, uid) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*folder.Folder) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, int64, string) error); ok { - r1 = rf(ctx, orgID, uid) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetProvisionedDashboardData provides a mock function with given fields: ctx, name func (_m *FakeDashboardStore) GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) { ret := _m.Called(ctx, name) diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 043f5c348d3..a4939070c20 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -30,13 +30,14 @@ import ( type Service struct { store store - log log.Logger - cfg *setting.Cfg - dashboardStore dashboards.Store - searchService *search.SearchService - features featuremgmt.FeatureToggles - permissions accesscontrol.FolderPermissionsService - accessControl accesscontrol.AccessControl + log log.Logger + cfg *setting.Cfg + dashboardStore dashboards.Store + dashboardFolderStore dashboards.FolderStore + searchService *search.SearchService + features featuremgmt.FeatureToggles + permissions accesscontrol.FolderPermissionsService + accessControl accesscontrol.AccessControl // bus is currently used to publish events that cause scheduler to update rules. bus bus.Bus @@ -47,24 +48,26 @@ func ProvideService( bus bus.Bus, cfg *setting.Cfg, dashboardStore dashboards.Store, + folderStore dashboards.FolderStore, db db.DB, // DB for the (new) nested folder store features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, searchService *search.SearchService, ) folder.Service { - ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore)) - ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore)) + ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore, folderStore)) + ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore, folderStore)) store := ProvideStore(db, cfg, features) svr := &Service{ - cfg: cfg, - log: log.New("folder-service"), - dashboardStore: dashboardStore, - store: store, - searchService: searchService, - features: features, - permissions: folderPermissionsService, - accessControl: ac, - bus: bus, + cfg: cfg, + log: log.New("folder-service"), + dashboardStore: dashboardStore, + dashboardFolderStore: folderStore, + store: store, + searchService: searchService, + features: features, + permissions: folderPermissionsService, + accessControl: ac, + bus: bus, } if features.IsEnabled(featuremgmt.FlagNestedFolders) { svr.DBMigration(db) @@ -166,7 +169,7 @@ func (s *Service) GetChildren(ctx context.Context, cmd *folder.GetChildrenQuery) filtered := make([]*folder.Folder, 0, len(children)) for _, f := range children { // fetch folder from dashboard store - dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, f.OrgID, f.UID) + dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, f.OrgID, f.UID) if err != nil { s.log.Error("failed to fetch folder by UID: %s from dashboard store", f.UID, err) continue @@ -220,7 +223,7 @@ func (s *Service) getFolderByID(ctx context.Context, user *user.SignedInUser, id return &folder.Folder{ID: id, Title: "General"}, nil } - dashFolder, err := s.dashboardStore.GetFolderByID(ctx, orgID, id) + dashFolder, err := s.dashboardFolderStore.GetFolderByID(ctx, orgID, id) if err != nil { return nil, err } @@ -244,7 +247,7 @@ func (s *Service) getFolderByID(ctx context.Context, user *user.SignedInUser, id } func (s *Service) getFolderByUID(ctx context.Context, user *user.SignedInUser, orgID int64, uid string) (*folder.Folder, error) { - dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid) + dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, orgID, uid) if err != nil { return nil, err } @@ -268,7 +271,7 @@ func (s *Service) getFolderByUID(ctx context.Context, user *user.SignedInUser, o } func (s *Service) getFolderByTitle(ctx context.Context, user *user.SignedInUser, orgID int64, title string) (*folder.Folder, error) { - dashFolder, err := s.dashboardStore.GetFolderByTitle(ctx, orgID, title) + dashFolder, err := s.dashboardFolderStore.GetFolderByTitle(ctx, orgID, title) if err != nil { return nil, err } @@ -330,7 +333,7 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( } var createdFolder *folder.Folder - createdFolder, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.ID) + createdFolder, err = s.dashboardFolderStore.GetFolderByID(ctx, cmd.OrgID, dash.ID) if err != nil { return nil, err } @@ -475,7 +478,7 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm } var foldr *folder.Folder - foldr, err = s.dashboardStore.GetFolderByID(ctx, cmd.OrgID, dash.ID) + foldr, err = s.dashboardFolderStore.GetFolderByID(ctx, cmd.OrgID, dash.ID) if err != nil { return nil, err } @@ -534,7 +537,7 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e } } - dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, cmd.OrgID, cmd.UID) + dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, cmd.UID) if err != nil { return err } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 611dfa27d89..c2455f5c448 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -38,7 +38,7 @@ func TestIntegrationProvideFolderService(t *testing.T) { t.Run("should register scope resolvers", func(t *testing.T) { cfg := setting.NewCfg() ac := acmock.New() - ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, &featuremgmt.FeatureManager{}, nil, nil) + ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{}, nil, nil) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2) }) @@ -51,7 +51,9 @@ func TestIntegrationFolderService(t *testing.T) { t.Run("Folder service tests", func(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} db := sqlstore.InitTestDB(t) - store := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"})) + nestedFolderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"})) + + folderStore := dashboards.NewFakeFolderStore(t) cfg := setting.NewCfg() cfg.RBACEnabled = false @@ -59,14 +61,15 @@ func TestIntegrationFolderService(t *testing.T) { folderPermissions := acmock.NewMockedPermissionsService() service := &Service{ - cfg: cfg, - log: log.New("test-folder-service"), - dashboardStore: dashStore, - store: store, - searchService: nil, - features: features, - permissions: folderPermissions, - bus: bus.ProvideBus(tracing.InitializeTracerForTest()), + cfg: cfg, + log: log.New("test-folder-service"), + dashboardStore: dashStore, + dashboardFolderStore: folderStore, + store: nestedFolderStore, + searchService: nil, + features: features, + permissions: folderPermissions, + bus: bus.ProvideBus(tracing.InitializeTracerForTest()), } t.Run("Given user has no permissions", func(t *testing.T) { @@ -80,8 +83,8 @@ func TestIntegrationFolderService(t *testing.T) { f.ID = folderId f.UID = folderUID - dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(f, nil) - dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(f, nil) + folderStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(f, nil) + folderStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(f, nil) t.Run("When get folder by id should return access denied error", func(t *testing.T) { _, err := service.Get(context.Background(), &folder.GetFolderQuery{ @@ -143,8 +146,8 @@ func TestIntegrationFolderService(t *testing.T) { newFolder := models.NewFolder("Folder") newFolder.Uid = folderUID - dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil) - dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil) + folderStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil) + folderStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil) err := service.Delete(context.Background(), &folder.DeleteFolderCommand{ UID: folderUID, @@ -172,7 +175,7 @@ func TestIntegrationFolderService(t *testing.T) { dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(dash, nil).Once() - dashStore.On("GetFolderByID", mock.Anything, orgID, dash.ID).Return(f, nil) + folderStore.On("GetFolderByID", mock.Anything, orgID, dash.ID).Return(f, nil) actualFolder, err := service.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, @@ -205,7 +208,7 @@ func TestIntegrationFolderService(t *testing.T) { dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(dashboardFolder, nil) - dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) + folderStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) title := "TEST-Folder" req := &folder.UpdateFolderCommand{ @@ -224,7 +227,7 @@ func TestIntegrationFolderService(t *testing.T) { f := folder.NewFolder(util.GenerateShortUID(), "") f.ID = rand.Int63() f.UID = util.GenerateShortUID() - dashStore.On("GetFolderByUID", mock.Anything, orgID, f.UID).Return(f, nil) + folderStore.On("GetFolderByUID", mock.Anything, orgID, f.UID).Return(f, nil) var actualCmd *dashboards.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { @@ -258,7 +261,7 @@ func TestIntegrationFolderService(t *testing.T) { expected := folder.NewFolder(util.GenerateShortUID(), "") expected.ID = rand.Int63() - dashStore.On("GetFolderByID", mock.Anything, orgID, expected.ID).Return(expected, nil) + folderStore.On("GetFolderByID", mock.Anything, orgID, expected.ID).Return(expected, nil) actual, err := service.getFolderByID(context.Background(), usr, expected.ID, orgID) require.Equal(t, expected, actual) @@ -269,7 +272,7 @@ func TestIntegrationFolderService(t *testing.T) { expected := folder.NewFolder(util.GenerateShortUID(), "") expected.UID = util.GenerateShortUID() - dashStore.On("GetFolderByUID", mock.Anything, orgID, expected.UID).Return(expected, nil) + folderStore.On("GetFolderByUID", mock.Anything, orgID, expected.UID).Return(expected, nil) actual, err := service.getFolderByUID(context.Background(), usr, orgID, expected.UID) require.Equal(t, expected, actual) @@ -279,7 +282,7 @@ func TestIntegrationFolderService(t *testing.T) { t.Run("When get folder by title should return folder", func(t *testing.T) { expected := folder.NewFolder("TEST-"+util.GenerateShortUID(), "") - dashStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil) + folderStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil) actual, err := service.getFolderByTitle(context.Background(), usr, orgID, expected.Title) require.Equal(t, expected, actual) @@ -322,23 +325,27 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { guardian.New = g }) - folderStore := NewFakeStore() + nestedFolderStore := NewFakeStore() dashStore := dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) + + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) + cfg := setting.NewCfg() cfg.RBACEnabled = false folderService := &Service{ - cfg: cfg, - store: folderStore, - dashboardStore: &dashStore, - features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), - log: log.New("test-folder-service"), + cfg: cfg, + store: nestedFolderStore, + dashboardStore: &dashStore, + dashboardFolderStore: dashboardFolderStore, + features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), + log: log.New("test-folder-service"), } t.Run("create folder", func(t *testing.T) { - folderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()} + nestedFolderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()} res, err := folderService.Create(context.Background(), &folder.CreateFolderCommand{SignedInUser: usr, Title: "my folder"}) require.NoError(t, err) require.NotNil(t, res.UID) @@ -359,11 +366,13 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - folderStore := NewFakeStore() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures(), nil) + nestedFolderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), nil) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", @@ -372,7 +381,7 @@ func TestNestedFolderService(t *testing.T) { }) require.NoError(t, err) // CreateFolder should not call the folder store create if the feature toggle is not enabled. - require.False(t, folderStore.CreateCalled) + require.False(t, nestedFolderStore.CreateCalled) }) t.Run("When delete folder, no delete in folder table done", func(t *testing.T) { @@ -381,16 +390,18 @@ func TestNestedFolderService(t *testing.T) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) - folderStore := NewFakeStore() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) + + nestedFolderStore := NewFakeStore() - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures(), nil) + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), nil) err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) require.NoError(t, err) require.NotNil(t, actualCmd) - require.False(t, folderStore.DeleteCalled) + require.False(t, nestedFolderStore.DeleteCalled) }) }) @@ -406,11 +417,13 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - folderStore := NewFakeStore() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) + + nestedFolderStore := NewFakeStore() - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ @@ -421,7 +434,7 @@ func TestNestedFolderService(t *testing.T) { }) require.NoError(t, err) // CreateFolder should also call the folder store's create method. - require.True(t, folderStore.CreateCalled) + require.True(t, nestedFolderStore.CreateCalled) }) t.Run("create without UID, no error", func(t *testing.T) { @@ -435,11 +448,13 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{UID: "newUID"}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - folderStore := NewFakeStore() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + nestedFolderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ @@ -449,7 +464,7 @@ func TestNestedFolderService(t *testing.T) { }) require.NoError(t, err) // CreateFolder should also call the folder store's create method. - require.True(t, folderStore.CreateCalled) + require.True(t, nestedFolderStore.CreateCalled) require.Equal(t, "newUID", f.UID) }) @@ -469,14 +484,16 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(dashboardFolder, nil) - dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) var actualCmd *dashboards.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - folderStore := NewFakeStore() - folderStore.ExpectedParentFolders = []*folder.Folder{ + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) + + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, {UID: "newFolder3", ParentUID: "newFolder3"}, @@ -491,13 +508,13 @@ func TestNestedFolderService(t *testing.T) { SignedInUser: usr, } - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Create(context.Background(), &cmd) require.Error(t, err, folder.ErrCircularReference) // CreateFolder should not call the folder store's create method. - require.False(t, folderStore.CreateCalled) + require.False(t, nestedFolderStore.CreateCalled) require.NotNil(t, actualCmd) }) @@ -513,19 +530,20 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) var actualCmd *dashboards.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) + // return an error from the folder store - folderStore := NewFakeStore() - folderStore.ExpectedError = errors.New("FAILED") + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedError = errors.New("FAILED") // the service return success as long as the legacy create succeeds - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ @@ -537,7 +555,7 @@ func TestNestedFolderService(t *testing.T) { require.Error(t, err, "FAILED") // CreateFolder should also call the folder store's create method. - require.True(t, folderStore.CreateCalled) + require.True(t, nestedFolderStore.CreateCalled) require.NotNil(t, actualCmd) }) @@ -550,11 +568,12 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) @@ -570,11 +589,12 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) @@ -590,16 +610,17 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderStore.ExpectedParentFolders = []*folder.Folder{ + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, {UID: "newFolder3", ParentUID: "newFolder3"}, } - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) @@ -615,12 +636,13 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderStore.ExpectedError = folder.ErrCircularReference + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore.ExpectedError = folder.ErrCircularReference - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) @@ -636,16 +658,17 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderStore.ExpectedParentFolders = []*folder.Folder{ + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, } - folderStore.ExpectedFolderHeight = 5 + nestedFolderStore.ExpectedFolderHeight = 5 - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) @@ -661,12 +684,13 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderStore.ExpectedParentFolders = []*folder.Folder{{UID: "myFolder", ParentUID: "12345"}, {UID: "12345", ParentUID: ""}} + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore.ExpectedParentFolders = []*folder.Folder{{UID: "myFolder", ParentUID: "12345"}, {UID: "12345", ParentUID: ""}} - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) @@ -686,19 +710,21 @@ func TestNestedFolderService(t *testing.T) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) - folderStore := NewFakeStore() - folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + nestedFolderStore := NewFakeStore() + nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) require.NoError(t, err) require.NotNil(t, actualCmd) - require.True(t, folderStore.DeleteCalled) + require.True(t, nestedFolderStore.DeleteCalled) }) t.Run("create returns error if maximum depth reached", func(t *testing.T) { @@ -713,23 +739,24 @@ func TestNestedFolderService(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) - dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) var actualCmd *dashboards.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() + dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) + parents := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth) for i := 0; i < folder.MaxNestedFolderDepth; i++ { parents = append(parents, &folder.Folder{UID: fmt.Sprintf("folder%d", i)}) } - folderStore := NewFakeStore() - //folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - folderStore.ExpectedParentFolders = parents + nestedFolderStore := NewFakeStore() + //nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + nestedFolderStore.ExpectedParentFolders = parents - folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, }) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ @@ -745,18 +772,19 @@ func TestNestedFolderService(t *testing.T) { }) } -func setup(t *testing.T, dashStore dashboards.Store, folderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service { +func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore dashboards.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service { t.Helper() // nothing enabled yet cfg := setting.NewCfg() cfg.RBACEnabled = false return &Service{ - cfg: cfg, - log: log.New("test-folder-service"), - dashboardStore: dashStore, - store: folderStore, - features: features, - accessControl: ac, + cfg: cfg, + log: log.New("test-folder-service"), + dashboardStore: dashStore, + dashboardFolderStore: dashboardFolderStore, + store: nestedFolderStore, + features: features, + accessControl: ac, } } diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index 1c20b27a217..319ce4d26b7 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -601,7 +601,7 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce }) require.NoError(t, err) ac := accesscontrolmock.New().WithPermissions(permissions) - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashStore)) + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashStore, dashStore)) license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() teamSvc := teamimpl.ProvideService(store, store.Cfg) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index d293d57d6c7..7461a554b6c 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -291,7 +291,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() service := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, dashAlertExtractor, + cfg, dashboardStore, dashboardStore, dashAlertExtractor, features, folderPermissions, dashboardPermissions, ac, ) dashboard, err := service.SaveDashboard(context.Background(), dashItem, true) @@ -314,7 +314,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, nil, features, folderPermissions, nil) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, nil) t.Logf("Creating folder with title and UID %q", title) ctx := appcontext.WithUser(context.Background(), &user) folder, err := s.Create(ctx, &folder.CreateFolderCommand{ @@ -435,14 +435,14 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() dashboardService := dashboardservice.ProvideDashboardService( - sqlStore.Cfg, dashboardStore, nil, + sqlStore.Cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, ) guardian.InitLegacyGuardian(sqlStore, dashboardService, &teamtest.FakeService{}) service := LibraryElementService{ Cfg: sqlStore.Cfg, SQLStore: sqlStore, - folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, nil, features, folderPermissions, nil), + folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, nil), } // deliberate difference between signed in user and user in db to make it crystal clear diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 586a3800dd0..7a16b1aa134 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -704,7 +704,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil) ac := acmock.New() service := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, dashAlertService, + cfg, dashboardStore, dashboardStore, dashAlertService, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), ac, ) dashboard, err := service.SaveDashboard(context.Background(), dashItem, true) @@ -726,7 +726,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user. quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, nil, features, folderPermissions, nil) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, nil) t.Logf("Creating folder with title and UID %q", title) ctx := appcontext.WithUser(context.Background(), user) @@ -835,7 +835,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo ac := acmock.New() folderPermissions := acmock.NewMockedPermissionsService() - folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, nil, features, folderPermissions, nil) + folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, nil) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService) service := LibraryPanelService{ diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 4c8e0207214..a14b0083f60 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -79,13 +79,13 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG, dashboardPermissions := acmock.NewMockedPermissionsService() dashboardService := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, nil, + cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac, ) tracer := tracing.InitializeTracerForTest() bus := bus.ProvideBus(tracer) - folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, nil, features, folderPermissions, nil) + folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, dashboardStore, nil, features, folderPermissions, nil) ng, err := ngalert.ProvideService( cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),