nested folders: support creation of nested folders in folder service when feature flag is set (#58364)

* nested folders: support creation of nested folders in folder service when feature flag is set
This commit is contained in:
Kristin Laemmert
2022-11-08 08:59:55 -05:00
committed by GitHub
parent 94573d3e61
commit a255c32e1a
9 changed files with 184 additions and 43 deletions
+122 -20
View File
@@ -2,6 +2,7 @@ package folderimpl
import (
"context"
"errors"
"math/rand"
"testing"
@@ -10,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
@@ -19,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -34,7 +37,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, nil, nil, nil)
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{}, nil, nil)
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
})
@@ -45,20 +48,24 @@ func TestIntegrationFolderService(t *testing.T) {
t.Skip("skipping integration test")
}
t.Run("Folder service tests", func(t *testing.T) {
store := &dashboards.FakeDashboardStore{}
dashStore := &dashboards.FakeDashboardStore{}
db := sqlstore.InitTestDB(t)
store := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"}))
cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardService := dashboardsvc.ProvideDashboardService(cfg, store, nil, features, folderPermissions, dashboardPermissions, acmock.New())
dashboardService := dashboardsvc.ProvideDashboardService(cfg, dashStore, nil, features, folderPermissions, dashboardPermissions, acmock.New())
service := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardService: dashboardService,
dashboardStore: store,
dashboardStore: dashStore,
store: store,
searchService: nil,
features: features,
permissions: folderPermissions,
@@ -76,8 +83,8 @@ func TestIntegrationFolderService(t *testing.T) {
folder.Id = folderId
folder.Uid = folderUID
store.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
store.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
@@ -96,13 +103,13 @@ func TestIntegrationFolderService(t *testing.T) {
})
t.Run("When creating folder should return access denied error", func(t *testing.T) {
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
_, err := service.CreateFolder(context.Background(), usr, orgID, folder.Title, folderUID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
})
t.Run("When updating folder should return access denied error", func(t *testing.T) {
store.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
dashStore.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
folder := args.Get(1).(*models.GetDashboardQuery)
folder.Result = models.NewDashboard("dashboard-test")
folder.Result.IsFolder = true
@@ -134,11 +141,11 @@ func TestIntegrationFolderService(t *testing.T) {
dash.Id = rand.Int63()
f := models.DashboardToFolder(dash)
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
dashStore.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "")
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "someuid")
require.NoError(t, err)
require.Equal(t, f, actualFolder)
})
@@ -157,9 +164,9 @@ func TestIntegrationFolderService(t *testing.T) {
dashboardFolder.Uid = util.GenerateShortUID()
f := models.DashboardToFolder(dashboardFolder)
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
store.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil)
dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil)
req := &models.UpdateFolderCommand{
Uid: dashboardFolder.Uid,
@@ -175,10 +182,10 @@ func TestIntegrationFolderService(t *testing.T) {
f := models.NewFolder(util.GenerateShortUID())
f.Id = rand.Int63()
f.Uid = util.GenerateShortUID()
store.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
var actualCmd *models.DeleteDashboardCommand
store.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).Return(nil).Once()
@@ -204,7 +211,7 @@ func TestIntegrationFolderService(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID())
expected.Id = rand.Int63()
store.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil)
dashStore.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)
@@ -215,7 +222,7 @@ func TestIntegrationFolderService(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID())
expected.Uid = util.GenerateShortUID()
store.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil)
dashStore.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)
@@ -225,7 +232,7 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When get folder by title should return folder", func(t *testing.T) {
expected := models.NewFolder("TEST-" + util.GenerateShortUID())
store.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil)
dashStore.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)
@@ -326,3 +333,98 @@ func TestFolderService(t *testing.T) {
require.NoError(t, err)
})
}
func TestCreate_NestedFolders(t *testing.T) {
t.Run("with feature flag unset", func(t *testing.T) {
ctx := appcontext.WithUser(context.Background(), usr)
store := &FakeStore{}
dashStore := dashboards.FakeDashboardStore{}
dashboardsvc := dashboards.FakeDashboardService{}
// nothing enabled yet
cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled
foldersvc := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardService: &dashboardsvc,
dashboardStore: &dashStore,
store: store,
features: features,
}
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.NoError(t, err)
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
require.False(t, store.CreateCalled)
})
t.Run("with nested folder feature flag on", func(t *testing.T) {
ctx := appcontext.WithUser(context.Background(), usr)
store := &FakeStore{}
dashStore := &dashboards.FakeDashboardStore{}
dashboardsvc := &dashboards.FakeDashboardService{}
// nothing enabled yet
cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures("nestedFolders")
cfg.IsFeatureToggleEnabled = features.IsEnabled
foldersvc := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardService: dashboardsvc,
dashboardStore: dashStore,
store: store,
features: features,
}
t.Run("create, no error", func(t *testing.T) {
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.NoError(t, err)
// CreateFolder should also call the folder store's create method.
require.True(t, store.CreateCalled)
})
t.Run("create returns error from nested folder service", func(t *testing.T) {
// This test creates and deletes the dashboard, so needs some extra setup.
g := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{})
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
// return an error from the folder store
store.ExpectedError = errors.New("FAILED")
// the service return success as long as the legacy create succeeds
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.Error(t, err, "FAILED")
// CreateFolder should also call the folder store's create method.
require.True(t, store.CreateCalled)
t.Cleanup(func() {
guardian.New = g
})
})
})
}