2022-10-11 03:47:53 +08:00
package folderimpl
2018-02-20 20:55:43 +08:00
import (
2021-09-14 22:08:04 +08:00
"context"
2024-02-26 18:27:22 +08:00
"encoding/json"
2022-11-08 21:59:55 +08:00
"errors"
2022-11-23 22:44:45 +08:00
"fmt"
2024-05-02 15:14:12 +08:00
"log/slog"
2022-03-14 23:21:42 +08:00
"math/rand"
2024-01-25 15:27:13 +08:00
"strings"
2018-02-20 20:55:43 +08:00
"testing"
2023-06-02 22:38:02 +08:00
"time"
2018-02-20 20:55:43 +08:00
2024-03-15 20:05:27 +08:00
"github.com/google/go-cmp/cmp"
2022-03-11 01:19:50 +08:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
2023-07-25 19:05:53 +08:00
"github.com/grafana/grafana/pkg/api/routing"
2022-10-18 21:31:56 +08:00
"github.com/grafana/grafana/pkg/bus"
2023-03-15 16:51:37 +08:00
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
2022-03-14 23:21:42 +08:00
"github.com/grafana/grafana/pkg/infra/log"
2024-05-02 15:14:12 +08:00
"github.com/grafana/grafana/pkg/infra/log/logtest"
2022-10-18 21:31:56 +08:00
"github.com/grafana/grafana/pkg/infra/tracing"
2023-01-18 18:22:23 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2023-03-20 19:04:22 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
2022-11-11 21:28:24 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
2022-03-10 19:58:18 +08:00
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
2025-02-12 03:14:25 +08:00
"github.com/grafana/grafana/pkg/services/apiserver/client"
2022-02-16 21:15:44 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2024-03-15 20:05:27 +08:00
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
2023-03-15 16:51:37 +08:00
"github.com/grafana/grafana/pkg/services/dashboards/database"
2023-12-05 23:13:31 +08:00
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
2022-03-10 19:58:18 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-11-08 18:33:13 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2023-02-01 21:43:21 +08:00
"github.com/grafana/grafana/pkg/services/folder/foldertest"
2022-02-16 21:15:44 +08:00
"github.com/grafana/grafana/pkg/services/guardian"
2023-07-25 19:05:53 +08:00
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/librarypanels"
2023-06-02 22:38:02 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
2024-07-24 00:07:27 +08:00
"github.com/grafana/grafana/pkg/services/org"
2025-01-24 04:23:59 +08:00
"github.com/grafana/grafana/pkg/services/publicdashboards"
2023-03-15 16:51:37 +08:00
"github.com/grafana/grafana/pkg/services/quota/quotatest"
2022-11-08 21:59:55 +08:00
"github.com/grafana/grafana/pkg/services/sqlstore"
2023-07-25 19:05:53 +08:00
"github.com/grafana/grafana/pkg/services/store/entity"
2024-02-26 18:27:22 +08:00
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
2023-03-15 16:51:37 +08:00
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
2022-08-10 17:56:48 +08:00
"github.com/grafana/grafana/pkg/services/user"
2022-03-10 19:58:18 +08:00
"github.com/grafana/grafana/pkg/setting"
2022-03-14 23:21:42 +08:00
"github.com/grafana/grafana/pkg/util"
2018-02-20 20:55:43 +08:00
)
2022-02-16 21:15:44 +08:00
var orgID = int64 ( 1 )
2024-08-01 23:20:38 +08:00
var usr = & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { orgID : { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( folder . GeneralFolderUID ) } } } }
var noPermUsr = & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
2022-02-16 21:15:44 +08:00
2022-05-24 17:04:03 +08:00
func TestIntegrationProvideFolderService ( t * testing . T ) {
2022-06-10 23:46:21 +08:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2022-03-14 23:21:42 +08:00
t . Run ( "should register scope resolvers" , func ( t * testing . T ) {
2022-03-11 01:19:50 +08:00
ac := acmock . New ( )
2025-01-14 05:15:35 +08:00
db , cfg := db . InitTestDBWithCfg ( t )
2024-09-30 16:28:47 +08:00
store := ProvideStore ( db )
2025-01-27 21:29:47 +08:00
ProvideService (
store , ac , bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) ,
2025-02-14 19:34:52 +08:00
nil , nil , nil , db , featuremgmt . WithFeatures ( ) , supportbundlestest . NewFakeBundleService ( ) , nil , cfg , nil , tracing . InitializeTracerForTest ( ) , nil )
2018-02-20 20:55:43 +08:00
2024-11-18 18:47:10 +08:00
require . Len ( t , ac . Calls . RegisterAttributeScopeResolver , 2 )
2022-03-14 23:21:42 +08:00
} )
}
2022-05-24 17:04:03 +08:00
func TestIntegrationFolderService ( t * testing . T ) {
2022-06-10 23:46:21 +08:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2022-03-14 23:21:42 +08:00
t . Run ( "Folder service tests" , func ( t * testing . T ) {
2022-11-08 21:59:55 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2024-04-24 16:38:40 +08:00
db , cfg := sqlstore . InitTestDB ( t )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2023-01-20 00:38:07 +08:00
2023-02-01 21:43:21 +08:00
folderStore := foldertest . NewFakeFolderStore ( t )
2025-01-24 04:23:59 +08:00
publicDashboardService := publicdashboards . NewFakePublicDashboardServiceWrapper ( t )
2022-03-14 23:21:42 +08:00
features := featuremgmt . WithFeatures ( )
2023-12-04 17:34:38 +08:00
alertingStore := ngstore . DBstore {
SQLStore : db ,
Cfg : cfg . UnifiedAlerting ,
Logger : log . New ( "test-alerting-store" ) ,
2024-08-01 23:20:38 +08:00
AccessControl : actest . FakeAccessControl { ExpectedEvaluate : true } ,
2023-12-04 17:34:38 +08:00
}
2022-10-11 03:47:53 +08:00
service := & Service {
2025-01-24 04:23:59 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
publicDashboardService : publicDashboardService ,
features : features ,
bus : bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) ,
db : db ,
accessControl : acimpl . ProvideAccessControl ( features ) ,
metrics : newFoldersMetrics ( nil ) ,
registry : make ( map [ string ] folder . RegistryService ) ,
tracer : tracing . InitializeTracerForTest ( ) ,
2022-03-14 23:21:42 +08:00
}
2022-03-11 01:19:50 +08:00
2023-12-04 17:34:38 +08:00
require . NoError ( t , service . RegisterService ( alertingStore ) )
2021-07-01 16:40:38 +08:00
t . Run ( "Given user has no permissions" , func ( t * testing . T ) {
2018-02-20 20:55:43 +08:00
origNewGuardian := guardian . New
2018-02-21 01:08:19 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { } )
2018-02-20 20:55:43 +08:00
2022-03-14 23:21:42 +08:00
folderUID := util . GenerateShortUID ( )
2022-11-11 21:28:24 +08:00
f := folder . NewFolder ( "Folder" , "" )
f . UID = folderUID
2022-03-14 23:21:42 +08:00
2023-01-20 00:38:07 +08:00
folderStore . On ( "GetFolderByUID" , mock . Anything , orgID , folderUID ) . Return ( f , nil )
2018-02-20 20:55:43 +08:00
2021-07-01 16:40:38 +08:00
t . Run ( "When get folder by id should return access denied error" , func ( t * testing . T ) {
2022-11-23 17:13:47 +08:00
_ , err := service . Get ( context . Background ( ) , & folder . GetFolderQuery {
2024-01-12 23:43:39 +08:00
UID : & folderUID ,
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
2024-08-01 23:20:38 +08:00
SignedInUser : noPermUsr ,
2022-11-23 17:13:47 +08:00
} )
2022-06-30 21:31:54 +08:00
require . Equal ( t , err , dashboards . ErrFolderAccessDenied )
2021-07-01 16:40:38 +08:00
} )
t . Run ( "When get folder by uid should return access denied error" , func ( t * testing . T ) {
2022-11-23 17:13:47 +08:00
_ , err := service . Get ( context . Background ( ) , & folder . GetFolderQuery {
UID : & folderUID ,
OrgID : orgID ,
2024-08-01 23:20:38 +08:00
SignedInUser : noPermUsr ,
2022-11-23 17:13:47 +08:00
} )
2022-06-30 21:31:54 +08:00
require . Equal ( t , err , dashboards . ErrFolderAccessDenied )
2018-02-20 20:55:43 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Run ( "When creating folder should return access denied error" , func ( t * testing . T ) {
2023-01-16 23:33:55 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil ) . Times ( 2 )
2022-11-23 22:44:45 +08:00
_ , err := service . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
Title : f . Title ,
UID : folderUID ,
2024-08-01 23:20:38 +08:00
SignedInUser : noPermUsr ,
2022-11-10 17:41:03 +08:00
} )
2024-08-01 23:20:38 +08:00
require . Error ( t , err )
2018-02-20 20:55:43 +08:00
} )
2022-12-20 21:00:33 +08:00
title := "Folder-TEST"
2021-07-01 16:40:38 +08:00
t . Run ( "When updating folder should return access denied error" , func ( t * testing . T ) {
2023-01-25 17:36:26 +08:00
folderResult := dashboards . NewDashboard ( "dashboard-test" )
folderResult . IsFolder = true
dashStore . On ( "GetDashboard" , mock . Anything , mock . AnythingOfType ( "*dashboards.GetDashboardQuery" ) ) . Return ( folderResult , nil )
2022-12-20 21:00:33 +08:00
_ , err := service . Update ( context . Background ( ) , & folder . UpdateFolderCommand {
UID : folderUID ,
OrgID : orgID ,
NewTitle : & title ,
2024-08-01 23:20:38 +08:00
SignedInUser : noPermUsr ,
2018-02-20 20:55:43 +08:00
} )
2022-06-30 21:31:54 +08:00
require . Equal ( t , err , dashboards . ErrFolderAccessDenied )
2018-02-20 20:55:43 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Run ( "When deleting folder by uid should return access denied error" , func ( t * testing . T ) {
2023-01-25 16:14:32 +08:00
newFolder := folder . NewFolder ( "Folder" , "" )
newFolder . UID = folderUID
2022-11-10 16:42:32 +08:00
2023-01-20 00:38:07 +08:00
folderStore . On ( "GetFolderByUID" , mock . Anything , orgID , folderUID ) . Return ( newFolder , nil )
2022-11-10 16:42:32 +08:00
2022-12-20 23:38:09 +08:00
err := service . Delete ( context . Background ( ) , & folder . DeleteFolderCommand {
2022-11-10 16:42:32 +08:00
UID : folderUID ,
OrgID : orgID ,
ForceDeleteRules : false ,
2024-08-01 23:20:38 +08:00
SignedInUser : noPermUsr ,
2022-11-10 16:42:32 +08:00
} )
2021-07-01 16:40:38 +08:00
require . Error ( t , err )
2022-06-30 21:31:54 +08:00
require . Equal ( t , err , dashboards . ErrFolderAccessDenied )
2018-02-20 20:55:43 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Cleanup ( func ( ) {
2018-02-20 20:55:43 +08:00
guardian . New = origNewGuardian
} )
} )
2021-07-01 16:40:38 +08:00
t . Run ( "Given user has permission to save" , func ( t * testing . T ) {
2018-02-20 20:55:43 +08:00
origNewGuardian := guardian . New
2023-11-10 20:03:00 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true , CanViewValue : true } )
2023-03-20 19:04:22 +08:00
service . features = featuremgmt . WithFeatures ( )
2018-02-20 20:55:43 +08:00
2022-03-14 23:21:42 +08:00
t . Run ( "When creating folder should not return access denied error" , func ( t * testing . T ) {
2023-01-16 23:33:55 +08:00
dash := dashboards . NewDashboardFolder ( "Test-Folder" )
dash . ID = rand . Int63 ( )
2023-11-10 20:03:00 +08:00
dash . UID = util . GenerateShortUID ( )
2023-01-16 23:33:55 +08:00
f := dashboards . FromDashboard ( dash )
2018-02-20 20:55:43 +08:00
2023-01-16 23:33:55 +08:00
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 ( )
2018-02-20 20:55:43 +08:00
2022-11-23 22:44:45 +08:00
actualFolder , err := service . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
Title : dash . Title ,
2023-11-10 20:03:00 +08:00
UID : dash . UID ,
2022-11-23 17:13:47 +08:00
SignedInUser : usr ,
2022-11-10 17:41:03 +08:00
} )
2021-07-01 16:40:38 +08:00
require . NoError ( t , err )
2022-11-11 21:28:24 +08:00
require . Equal ( t , f , actualFolder )
2018-02-20 20:55:43 +08:00
} )
2022-03-30 21:14:26 +08:00
t . Run ( "When creating folder should return error if uid is general" , func ( t * testing . T ) {
2023-01-16 23:33:55 +08:00
dash := dashboards . NewDashboardFolder ( "Test-Folder" )
dash . ID = rand . Int63 ( )
2022-03-30 21:14:26 +08:00
2022-11-23 22:44:45 +08:00
_ , err := service . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
Title : dash . Title ,
UID : "general" ,
SignedInUser : usr ,
2022-11-10 17:41:03 +08:00
} )
2022-06-30 21:31:54 +08:00
require . ErrorIs ( t , err , dashboards . ErrFolderInvalidUID )
2022-03-30 21:14:26 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Run ( "When updating folder should not return access denied error" , func ( t * testing . T ) {
2023-01-16 23:33:55 +08:00
dashboardFolder := dashboards . NewDashboardFolder ( "Folder" )
dashboardFolder . ID = rand . Int63 ( )
dashboardFolder . UID = util . GenerateShortUID ( )
2023-11-22 05:06:20 +08:00
dashboardFolder . OrgID = orgID
2022-03-14 23:21:42 +08:00
2024-01-23 00:03:30 +08:00
f , err := service . store . Create ( context . Background ( ) , folder . CreateFolderCommand {
2023-11-22 05:06:20 +08:00
OrgID : orgID ,
Title : dashboardFolder . Title ,
UID : dashboardFolder . UID ,
SignedInUser : usr ,
} )
require . NoError ( t , err )
2024-01-23 00:03:30 +08:00
assert . Equal ( t , "Folder" , f . Title )
2023-11-22 05:06:20 +08:00
2023-01-16 23:33:55 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2022-12-20 21:00:33 +08:00
title := "TEST-Folder"
2024-01-23 00:03:30 +08:00
updatedDashboardFolder := * dashboardFolder
updatedDashboardFolder . Title = title
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & updatedDashboardFolder , nil )
dashStore . On ( "GetDashboard" , mock . Anything , mock . AnythingOfType ( "*dashboards.GetDashboardQuery" ) ) . Return ( & updatedDashboardFolder , nil )
folderStore . On ( "GetFolderByID" , mock . Anything , orgID , dashboardFolder . ID ) . Return ( & folder . Folder {
OrgID : orgID ,
ID : dashboardFolder . ID ,
UID : dashboardFolder . UID ,
Title : title ,
} , nil )
2022-12-20 21:00:33 +08:00
req := & folder . UpdateFolderCommand {
2023-01-16 23:33:55 +08:00
UID : dashboardFolder . UID ,
2022-12-20 21:00:33 +08:00
OrgID : orgID ,
NewTitle : & title ,
SignedInUser : usr ,
2022-03-14 23:21:42 +08:00
}
2022-12-20 21:00:33 +08:00
reqResult , err := service . Update ( context . Background ( ) , req )
2021-07-01 16:40:38 +08:00
require . NoError ( t , err )
2024-01-23 00:03:30 +08:00
assert . Equal ( t , title , reqResult . Title )
2018-02-20 20:55:43 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Run ( "When deleting folder by uid should not return access denied error" , func ( t * testing . T ) {
2022-11-11 21:28:24 +08:00
f := folder . NewFolder ( util . GenerateShortUID ( ) , "" )
f . UID = util . GenerateShortUID ( )
2023-11-10 20:03:00 +08:00
folderStore . On ( "GetFolderByUID" , mock . Anything , orgID , f . UID ) . Return ( f , nil )
2025-01-24 04:23:59 +08:00
publicDashboardService . On ( "DeleteByDashboardUIDs" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil ) . Once ( )
2022-03-22 21:36:50 +08:00
2023-01-18 20:52:41 +08:00
var actualCmd * dashboards . DeleteDashboardCommand
2022-11-08 21:59:55 +08:00
dashStore . On ( "DeleteDashboard" , mock . Anything , mock . Anything ) . Run ( func ( args mock . Arguments ) {
2023-01-18 20:52:41 +08:00
actualCmd = args . Get ( 1 ) . ( * dashboards . DeleteDashboardCommand )
2022-03-22 21:36:50 +08:00
} ) . Return ( nil ) . Once ( )
2025-01-24 04:23:59 +08:00
dashStore . On ( "FindDashboards" , mock . Anything , mock . Anything ) . Return ( [ ] dashboards . DashboardSearchProjection { } , nil ) . Once ( )
2022-03-14 23:21:42 +08:00
expectedForceDeleteRules := rand . Int63 ( ) % 2 == 0
2022-12-20 23:38:09 +08:00
err := service . Delete ( context . Background ( ) , & folder . DeleteFolderCommand {
2022-11-11 21:28:24 +08:00
UID : f . UID ,
2022-11-10 16:42:32 +08:00
OrgID : orgID ,
ForceDeleteRules : expectedForceDeleteRules ,
2022-11-23 17:13:47 +08:00
SignedInUser : usr ,
2022-11-10 16:42:32 +08:00
} )
2021-07-01 16:40:38 +08:00
require . NoError ( t , err )
2022-03-14 23:21:42 +08:00
require . NotNil ( t , actualCmd )
2023-01-18 20:52:41 +08:00
require . Equal ( t , orgID , actualCmd . OrgID )
2022-03-14 23:21:42 +08:00
require . Equal ( t , expectedForceDeleteRules , actualCmd . ForceDeleteFolderRules )
2018-02-20 20:55:43 +08:00
} )
2024-05-17 01:36:26 +08:00
t . Run ( "When deleting folder by uid, expectedForceDeleteRules as false, and dashboard Restore turned on should not return access denied error" , func ( t * testing . T ) {
f := folder . NewFolder ( util . GenerateShortUID ( ) , "" )
f . UID = util . GenerateShortUID ( )
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 ) {
actualCmd = args . Get ( 1 ) . ( * dashboards . DeleteDashboardCommand )
} ) . Return ( nil ) . Once ( )
service . features = featuremgmt . WithFeatures ( featuremgmt . FlagDashboardRestore )
expectedForceDeleteRules := false
err := service . Delete ( context . Background ( ) , & folder . DeleteFolderCommand {
UID : f . UID ,
OrgID : orgID ,
ForceDeleteRules : expectedForceDeleteRules ,
SignedInUser : usr ,
} )
require . NoError ( t , err )
require . NotNil ( t , actualCmd )
require . Equal ( t , orgID , actualCmd . OrgID )
require . Equal ( t , expectedForceDeleteRules , actualCmd . ForceDeleteFolderRules )
} )
t . Run ( "When deleting folder by uid, expectedForceDeleteRules as true, and dashboard Restore turned on should not return access denied error" , func ( t * testing . T ) {
f := folder . NewFolder ( util . GenerateShortUID ( ) , "" )
f . UID = util . GenerateShortUID ( )
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 ) {
actualCmd = args . Get ( 1 ) . ( * dashboards . DeleteDashboardCommand )
} ) . Return ( nil ) . Once ( )
service . features = featuremgmt . WithFeatures ( featuremgmt . FlagDashboardRestore )
expectedForceDeleteRules := true
err := service . Delete ( context . Background ( ) , & folder . DeleteFolderCommand {
UID : f . UID ,
OrgID : orgID ,
ForceDeleteRules : expectedForceDeleteRules ,
SignedInUser : usr ,
} )
require . NoError ( t , err )
require . NotNil ( t , actualCmd )
require . Equal ( t , orgID , actualCmd . OrgID )
require . Equal ( t , expectedForceDeleteRules , actualCmd . ForceDeleteFolderRules )
} )
2021-07-01 16:40:38 +08:00
t . Cleanup ( func ( ) {
2024-05-17 01:36:26 +08:00
service . features = featuremgmt . WithFeatures ( )
2018-02-20 20:55:43 +08:00
guardian . New = origNewGuardian
} )
} )
2021-07-01 16:40:38 +08:00
t . Run ( "Given user has permission to view" , func ( t * testing . T ) {
2018-02-20 20:55:43 +08:00
origNewGuardian := guardian . New
2018-02-21 01:08:19 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanViewValue : true } )
2018-02-20 20:55:43 +08:00
2022-03-14 23:21:42 +08:00
t . Run ( "When get folder by uid should return folder" , func ( t * testing . T ) {
2022-11-11 21:28:24 +08:00
expected := folder . NewFolder ( util . GenerateShortUID ( ) , "" )
expected . UID = util . GenerateShortUID ( )
2022-03-14 23:21:42 +08:00
2023-01-20 00:38:07 +08:00
folderStore . On ( "GetFolderByUID" , mock . Anything , orgID , expected . UID ) . Return ( expected , nil )
2022-03-14 23:21:42 +08:00
2023-02-09 20:19:08 +08:00
actual , err := service . getFolderByUID ( context . Background ( ) , orgID , expected . UID )
2022-03-14 23:21:42 +08:00
require . Equal ( t , expected , actual )
require . NoError ( t , err )
2018-02-20 20:55:43 +08:00
} )
2024-02-16 00:13:14 +08:00
t . Run ( "When get folder by uid and uid is general should return the root folder object" , func ( t * testing . T ) {
uid := accesscontrol . GeneralFolderUID
query := & folder . GetFolderQuery {
UID : & uid ,
SignedInUser : usr ,
}
actual , err := service . Get ( context . Background ( ) , query )
require . Equal ( t , folder . RootFolder , actual )
require . NoError ( t , err )
} )
2022-03-14 23:21:42 +08:00
t . Run ( "When get folder by title should return folder" , func ( t * testing . T ) {
2022-11-11 21:28:24 +08:00
expected := folder . NewFolder ( "TEST-" + util . GenerateShortUID ( ) , "" )
2022-03-14 23:21:42 +08:00
2024-01-25 17:29:56 +08:00
folderStore . On ( "GetFolderByTitle" , mock . Anything , orgID , expected . Title , mock . Anything ) . Return ( expected , nil )
2022-03-14 23:21:42 +08:00
2024-01-25 17:29:56 +08:00
actual , err := service . getFolderByTitle ( context . Background ( ) , orgID , expected . Title , nil )
2022-03-14 23:21:42 +08:00
require . Equal ( t , expected , actual )
require . NoError ( t , err )
2018-02-20 20:55:43 +08:00
} )
2021-07-01 16:40:38 +08:00
t . Cleanup ( func ( ) {
2018-02-20 20:55:43 +08:00
guardian . New = origNewGuardian
} )
} )
2018-02-21 18:25:21 +08:00
2021-07-01 16:40:38 +08:00
t . Run ( "Should map errors correct" , func ( t * testing . T ) {
2018-02-21 18:25:21 +08:00
testCases := [ ] struct {
ActualError error
ExpectedError error
} {
2022-06-30 21:31:54 +08:00
{ ActualError : dashboards . ErrDashboardTitleEmpty , ExpectedError : dashboards . ErrFolderTitleEmpty } ,
{ ActualError : dashboards . ErrDashboardUpdateAccessDenied , ExpectedError : dashboards . ErrFolderAccessDenied } ,
{ ActualError : dashboards . ErrDashboardWithSameUIDExists , ExpectedError : dashboards . ErrFolderWithSameUIDExists } ,
{ ActualError : dashboards . ErrDashboardVersionMismatch , ExpectedError : dashboards . ErrFolderVersionMismatch } ,
{ ActualError : dashboards . ErrDashboardNotFound , ExpectedError : dashboards . ErrFolderNotFound } ,
{ ActualError : dashboards . ErrDashboardInvalidUid , ExpectedError : dashboards . ErrDashboardInvalidUid } ,
2018-02-21 18:25:21 +08:00
}
for _ , tc := range testCases {
actualError := toFolderError ( tc . ActualError )
2020-11-19 21:47:17 +08:00
assert . EqualErrorf ( t , actualError , tc . ExpectedError . Error ( ) ,
"For error '%s' expected error '%s', actual '%s'" , tc . ActualError , tc . ExpectedError , actualError )
2018-02-21 18:25:21 +08:00
}
} )
2024-06-18 19:21:40 +08:00
t . Run ( "Returns root folder" , func ( t * testing . T ) {
t . Run ( "When the folder UID is blank should return the root folder" , func ( t * testing . T ) {
emptyString := ""
actual , err := service . Get ( context . Background ( ) , & folder . GetFolderQuery {
UID : & emptyString ,
OrgID : 1 ,
SignedInUser : usr ,
} )
assert . NoError ( t , err )
assert . Equal ( t , folder . GeneralFolder . UID , actual . UID )
assert . Equal ( t , folder . GeneralFolder . Title , actual . Title )
} )
} )
2018-02-20 20:55:43 +08:00
} )
}
2022-11-08 18:33:13 +08:00
2023-04-27 23:00:09 +08:00
func TestIntegrationNestedFolderService ( t * testing . T ) {
2023-03-15 16:51:37 +08:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2024-09-26 07:21:39 +08:00
db , cfg := sqlstore . InitTestDB ( t )
2024-09-13 01:20:33 +08:00
cfg . UnifiedAlerting . BaseInterval = time . Second
2023-03-15 16:51:37 +08:00
quotaService := quotatest . New ( false , nil )
folderStore := ProvideDashboardFolderStore ( db )
featuresFlagOn := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOn , tagimpl . ProvideService ( db ) )
2023-03-15 16:51:37 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2025-01-24 04:23:59 +08:00
publicDashboardFakeService := publicdashboards . NewFakePublicDashboardServiceWrapper ( t )
2023-03-15 16:51:37 +08:00
2023-06-02 22:38:02 +08:00
b := bus . ProvideBus ( tracing . InitializeTracerForTest ( ) )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( featuremgmt . WithFeatures ( ) )
2023-06-02 22:38:02 +08:00
2023-03-15 16:51:37 +08:00
serviceWithFlagOn := & Service {
2025-01-24 04:23:59 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : featuresFlagOn ,
bus : b ,
db : db ,
accessControl : ac ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
tracer : tracing . InitializeTracerForTest ( ) ,
publicDashboardService : publicDashboardFakeService ,
2023-03-15 16:51:37 +08:00
}
2023-03-20 19:04:22 +08:00
signedInUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
2023-07-25 19:05:53 +08:00
orgID : {
2023-12-04 17:34:38 +08:00
dashboards . ActionFoldersCreate : { } ,
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersAll } ,
accesscontrol . ActionAlertingRuleDelete : { dashboards . ScopeFoldersAll } ,
} ,
2023-03-20 19:04:22 +08:00
} }
2023-03-15 16:51:37 +08:00
createCmd := folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
SignedInUser : & signedInUser ,
}
2023-07-25 19:05:53 +08:00
libraryElementCmd := model . CreateLibraryElementCommand {
Model : [ ] byte ( `
{
"datasource" : "${DS_GDEV-TESTDATA}" ,
"id" : 1 ,
"title" : "Text - Library Panel" ,
"type" : "text" ,
"description" : "A description"
}
` ) ,
Kind : int64 ( model . PanelElement ) ,
}
routeRegister := routing . NewRouteRegister ( )
2023-06-02 22:38:02 +08:00
folderPermissions := acmock . NewMockedPermissionsService ( )
dashboardPermissions := acmock . NewMockedPermissionsService ( )
2023-04-27 23:00:09 +08:00
t . Run ( "Should get descendant counts" , func ( t * testing . T ) {
depth := 5
t . Run ( "With nested folder feature flag on" , func ( t * testing . T ) {
origNewGuardian := guardian . New
2023-07-25 19:05:53 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
// CanEditValue is required to create library elements
CanEditValue : true ,
} )
2025-01-24 04:23:59 +08:00
publicDashboardFakeService . On ( "DeleteByDashboardUIDs" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil )
2023-03-15 16:51:37 +08:00
2025-02-14 19:34:52 +08:00
dashSrv , err := dashboardservice . ProvideDashboardServiceImpl ( cfg , dashStore , folderStore , featuresFlagOn , folderPermissions , ac , serviceWithFlagOn , nestedFolderStore , nil , client . MockTestRestConfig { } , nil , quotaService , nil , publicDashboardFakeService , nil )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2025-01-22 04:57:43 +08:00
dashSrv . RegisterDashboardPermissions ( dashboardPermissions )
2023-06-02 22:38:02 +08:00
2024-10-12 00:19:52 +08:00
alertStore , err := ngstore . ProvideDBStore ( cfg , featuresFlagOn , db , serviceWithFlagOn , dashSrv , ac , b )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2023-03-15 16:51:37 +08:00
2025-01-27 21:42:57 +08:00
elementService := libraryelements . ProvideService ( cfg , db , routeRegister , serviceWithFlagOn , featuresFlagOn , ac , dashSrv )
2023-07-25 19:05:53 +08:00
lps , err := librarypanels . ProvideService ( cfg , db , routeRegister , elementService , serviceWithFlagOn )
require . NoError ( t , err )
2024-10-10 19:22:57 +08:00
ancestors := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "getDescendantCountsOn" , createCmd , true )
2023-03-15 16:51:37 +08:00
2024-01-25 15:27:13 +08:00
parent , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 0 ] . UID )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2024-01-25 15:27:13 +08:00
subfolder , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 1 ] . UID )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-10-24 15:04:45 +08:00
_ = insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in parent" , orgID , parent . ID , parent . UID , "prod" )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-10-24 15:04:45 +08:00
_ = insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in subfolder" , orgID , subfolder . ID , subfolder . UID , "prod" )
2023-06-02 22:38:02 +08:00
_ = createRule ( t , alertStore , parent . UID , "parent alert" )
_ = createRule ( t , alertStore , subfolder . UID , "sub alert" )
2023-04-27 23:00:09 +08:00
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = parent . ID
2024-04-09 18:27:43 +08:00
libraryElementCmd . FolderUID = & parent . UID
2023-07-25 19:05:53 +08:00
_ , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
require . NoError ( t , err )
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = subfolder . ID
2024-04-09 18:27:43 +08:00
libraryElementCmd . FolderUID = & subfolder . UID
2023-07-25 19:05:53 +08:00
_ , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
require . NoError ( t , err )
2023-04-27 23:00:09 +08:00
countCmd := folder . GetDescendantCountsQuery {
2024-01-25 15:27:13 +08:00
UID : & ancestors [ 0 ] . UID ,
2023-04-27 23:00:09 +08:00
OrgID : orgID ,
SignedInUser : & signedInUser ,
}
m , err := serviceWithFlagOn . GetDescendantCounts ( context . Background ( ) , & countCmd )
require . NoError ( t , err )
2023-07-25 19:05:53 +08:00
require . Equal ( t , int64 ( depth - 1 ) , m [ entity . StandardKindFolder ] )
require . Equal ( t , int64 ( 2 ) , m [ entity . StandardKindDashboard ] )
require . Equal ( t , int64 ( 2 ) , m [ entity . StandardKindAlertRule ] )
require . Equal ( t , int64 ( 2 ) , m [ entity . StandardKindLibraryPanel ] )
2023-04-27 23:00:09 +08:00
t . Cleanup ( func ( ) {
guardian . New = origNewGuardian
2024-01-25 15:27:13 +08:00
for _ , ancestor := range ancestors {
2024-01-31 00:26:34 +08:00
err := serviceWithFlagOn . store . Delete ( context . Background ( ) , [ ] string { ancestor . UID } , orgID )
2023-04-27 23:00:09 +08:00
assert . NoError ( t , err )
}
} )
} )
t . Run ( "With nested folder feature flag off" , func ( t * testing . T ) {
featuresFlagOff := featuremgmt . WithFeatures ( )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOff , tagimpl . ProvideService ( db ) )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2023-04-27 23:00:09 +08:00
serviceWithFlagOff := & Service {
2025-01-24 04:23:59 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : featuresFlagOff ,
bus : b ,
db : db ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
tracer : tracing . InitializeTracerForTest ( ) ,
publicDashboardService : publicDashboardFakeService ,
2023-04-27 23:00:09 +08:00
}
origNewGuardian := guardian . New
2023-07-25 19:05:53 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
// CanEditValue is required to create library elements
CanEditValue : true ,
} )
2023-04-27 23:00:09 +08:00
2025-01-24 04:23:59 +08:00
publicDashboardFakeService . On ( "DeleteByDashboardUIDs" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil )
2024-03-14 22:36:35 +08:00
dashSrv , err := dashboardservice . ProvideDashboardServiceImpl ( cfg , dashStore , folderStore , featuresFlagOff ,
2025-02-14 19:34:52 +08:00
folderPermissions , ac , serviceWithFlagOff , nestedFolderStore , nil , client . MockTestRestConfig { } , nil , quotaService , nil , publicDashboardFakeService , nil )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2025-01-22 04:57:43 +08:00
dashSrv . RegisterDashboardPermissions ( dashboardPermissions )
2024-10-12 00:19:52 +08:00
alertStore , err := ngstore . ProvideDBStore ( cfg , featuresFlagOff , db , serviceWithFlagOff , dashSrv , ac , b )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2025-01-27 21:42:57 +08:00
elementService := libraryelements . ProvideService ( cfg , db , routeRegister , serviceWithFlagOff , featuresFlagOff , ac , dashSrv )
2023-07-25 19:05:53 +08:00
lps , err := librarypanels . ProvideService ( cfg , db , routeRegister , elementService , serviceWithFlagOff )
require . NoError ( t , err )
2024-10-10 19:22:57 +08:00
ancestors := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "getDescendantCountsOff" , createCmd , true )
2023-04-27 23:00:09 +08:00
2024-01-25 15:27:13 +08:00
parent , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 0 ] . UID )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2024-01-25 15:27:13 +08:00
subfolder , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 1 ] . UID )
2023-04-27 23:00:09 +08:00
require . NoError ( t , err )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-10-24 15:04:45 +08:00
_ = insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in parent" , orgID , parent . ID , parent . UID , "prod" )
2023-11-21 04:44:51 +08:00
// nolint:staticcheck
2023-10-24 15:04:45 +08:00
_ = insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in subfolder" , orgID , subfolder . ID , subfolder . UID , "prod" )
2023-06-02 22:38:02 +08:00
_ = createRule ( t , alertStore , parent . UID , "parent alert" )
_ = createRule ( t , alertStore , subfolder . UID , "sub alert" )
2023-04-27 23:00:09 +08:00
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = parent . ID
2024-04-09 18:27:43 +08:00
libraryElementCmd . FolderUID = & parent . UID
2023-07-25 19:05:53 +08:00
_ , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
require . NoError ( t , err )
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = subfolder . ID
2024-04-09 18:27:43 +08:00
libraryElementCmd . FolderUID = & subfolder . UID
2023-07-25 19:05:53 +08:00
_ , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
require . NoError ( t , err )
2023-04-27 23:00:09 +08:00
countCmd := folder . GetDescendantCountsQuery {
2024-01-25 15:27:13 +08:00
UID : & ancestors [ 0 ] . UID ,
2023-04-27 23:00:09 +08:00
OrgID : orgID ,
SignedInUser : & signedInUser ,
}
m , err := serviceWithFlagOff . GetDescendantCounts ( context . Background ( ) , & countCmd )
require . NoError ( t , err )
2023-07-25 19:05:53 +08:00
require . Equal ( t , int64 ( 0 ) , m [ entity . StandardKindFolder ] )
require . Equal ( t , int64 ( 1 ) , m [ entity . StandardKindDashboard ] )
require . Equal ( t , int64 ( 1 ) , m [ entity . StandardKindAlertRule ] )
require . Equal ( t , int64 ( 1 ) , m [ entity . StandardKindLibraryPanel ] )
2023-04-27 23:00:09 +08:00
t . Cleanup ( func ( ) {
guardian . New = origNewGuardian
2024-01-25 15:27:13 +08:00
for _ , ancestor := range ancestors {
2024-01-31 00:26:34 +08:00
err := serviceWithFlagOn . store . Delete ( context . Background ( ) , [ ] string { ancestor . UID } , orgID )
2023-04-27 23:00:09 +08:00
assert . NoError ( t , err )
}
} )
2023-03-15 16:51:37 +08:00
} )
} )
2023-04-27 23:00:09 +08:00
t . Run ( "Should delete folders" , func ( t * testing . T ) {
2023-07-25 19:05:53 +08:00
featuresFlagOff := featuremgmt . WithFeatures ( )
serviceWithFlagOff := & Service {
2025-01-24 04:23:59 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
dashboardFolderStore : folderStore ,
features : featuresFlagOff ,
bus : b ,
db : db ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
tracer : tracing . InitializeTracerForTest ( ) ,
publicDashboardService : publicDashboardFakeService ,
2023-07-25 19:05:53 +08:00
}
2023-04-27 23:00:09 +08:00
2023-07-25 19:05:53 +08:00
testCases := [ ] struct {
service * Service
2024-01-10 02:38:06 +08:00
featuresFlag featuremgmt . FeatureToggles
2023-07-25 19:05:53 +08:00
prefix string
depth int
forceDelete bool
deletionErr error
dashboardErr error
folderErr error
libPanelParentErr error
libPanelSubErr error
desc string
} {
{
service : serviceWithFlagOn ,
featuresFlag : featuresFlagOn ,
prefix : "flagon-force" ,
depth : 3 ,
forceDelete : true ,
dashboardErr : dashboards . ErrFolderNotFound ,
folderErr : folder . ErrFolderNotFound ,
libPanelParentErr : model . ErrLibraryElementNotFound ,
libPanelSubErr : model . ErrLibraryElementNotFound ,
desc : "With nested folder feature flag on and force deletion of rules" ,
} ,
{
service : serviceWithFlagOn ,
featuresFlag : featuresFlagOn ,
prefix : "flagon-noforce" ,
depth : 3 ,
forceDelete : false ,
2023-12-04 17:34:38 +08:00
deletionErr : folder . ErrFolderNotEmpty ,
2023-07-25 19:05:53 +08:00
desc : "With nested folder feature flag on and no force deletion of rules" ,
} ,
{
service : serviceWithFlagOff ,
featuresFlag : featuresFlagOff ,
prefix : "flagoff-force" ,
depth : 1 ,
forceDelete : true ,
dashboardErr : dashboards . ErrFolderNotFound ,
2023-11-10 20:03:00 +08:00
folderErr : folder . ErrFolderNotFound ,
2023-07-25 19:05:53 +08:00
libPanelParentErr : model . ErrLibraryElementNotFound ,
desc : "With nested folder feature flag off and force deletion of rules" ,
} ,
{
service : serviceWithFlagOff ,
featuresFlag : featuresFlagOff ,
prefix : "flagoff-noforce" ,
depth : 1 ,
forceDelete : false ,
2023-12-04 17:34:38 +08:00
deletionErr : folder . ErrFolderNotEmpty ,
2023-07-25 19:05:53 +08:00
desc : "With nested folder feature flag off and no force deletion of rules" ,
} ,
}
2023-03-15 16:51:37 +08:00
2023-07-25 19:05:53 +08:00
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
origNewGuardian := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
// CanEditValue is required to create library elements
CanEditValue : true ,
} )
2023-06-02 22:38:02 +08:00
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , tc . featuresFlag , tagimpl . ProvideService ( db ) )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2023-07-25 19:05:53 +08:00
tc . service . dashboardStore = dashStore
tc . service . store = nestedFolderStore
2025-01-24 04:23:59 +08:00
publicDashboardFakeService . On ( "DeleteByDashboardUIDs" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil )
2023-06-02 22:38:02 +08:00
2025-02-14 19:34:52 +08:00
dashSrv , err := dashboardservice . ProvideDashboardServiceImpl ( cfg , dashStore , folderStore , tc . featuresFlag , folderPermissions , ac , tc . service , tc . service . store , nil , client . MockTestRestConfig { } , nil , quotaService , nil , publicDashboardFakeService , nil )
2023-07-25 19:05:53 +08:00
require . NoError ( t , err )
2025-01-22 04:57:43 +08:00
dashSrv . RegisterDashboardPermissions ( dashboardPermissions )
2025-01-27 21:42:57 +08:00
elementService := libraryelements . ProvideService ( cfg , db , routeRegister , tc . service , tc . featuresFlag , ac , dashSrv )
lps , err := librarypanels . ProvideService ( cfg , db , routeRegister , elementService , tc . service )
require . NoError ( t , err )
2024-10-12 00:19:52 +08:00
alertStore , err := ngstore . ProvideDBStore ( cfg , tc . featuresFlag , db , tc . service , dashSrv , ac , b )
2023-07-25 19:05:53 +08:00
require . NoError ( t , err )
2023-06-02 22:38:02 +08:00
2024-10-10 19:22:57 +08:00
ancestors := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , tc . depth , tc . prefix , createCmd , true )
2023-06-02 22:38:02 +08:00
2024-01-25 15:27:13 +08:00
parent , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 0 ] . UID )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
_ = createRule ( t , alertStore , parent . UID , "parent alert" )
2023-07-25 19:05:53 +08:00
var (
subfolder * folder . Folder
subPanel model . LibraryElementDTO
)
if tc . depth > 1 {
2024-01-25 15:27:13 +08:00
subfolder , err = serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestors [ 1 ] . UID )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2023-07-25 19:05:53 +08:00
_ = createRule ( t , alertStore , subfolder . UID , "sub alert" )
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = subfolder . ID
2025-01-17 19:04:02 +08:00
libraryElementCmd . FolderUID = & subfolder . UID
2023-07-25 19:05:53 +08:00
subPanel , err = lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
}
2023-11-01 01:24:16 +08:00
// nolint:staticcheck
2023-07-25 19:05:53 +08:00
libraryElementCmd . FolderID = parent . ID
2024-04-09 18:27:43 +08:00
libraryElementCmd . FolderUID = & parent . UID
2023-07-25 19:05:53 +08:00
parentPanel , err := lps . LibraryElementService . CreateElement ( context . Background ( ) , & signedInUser , libraryElementCmd )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2023-03-15 16:51:37 +08:00
2023-06-02 22:38:02 +08:00
deleteCmd := folder . DeleteFolderCommand {
2024-01-25 15:27:13 +08:00
UID : ancestors [ 0 ] . UID ,
2023-06-02 22:38:02 +08:00
OrgID : orgID ,
SignedInUser : & signedInUser ,
2023-07-25 19:05:53 +08:00
ForceDeleteRules : tc . forceDelete ,
2023-06-02 22:38:02 +08:00
}
2023-07-25 19:05:53 +08:00
err = tc . service . Delete ( context . Background ( ) , & deleteCmd )
require . ErrorIs ( t , err , tc . deletionErr )
2023-06-02 22:38:02 +08:00
2024-01-25 15:27:13 +08:00
for i , ancestor := range ancestors {
2023-07-25 19:05:53 +08:00
// dashboard table
2024-01-25 15:27:13 +08:00
_ , err := tc . service . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestor . UID )
2023-07-25 19:05:53 +08:00
require . ErrorIs ( t , err , tc . dashboardErr )
// folder table
2024-01-25 15:27:13 +08:00
_ , err = tc . service . store . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & ancestors [ i ] . UID , OrgID : orgID } )
2023-07-25 19:05:53 +08:00
require . ErrorIs ( t , err , tc . folderErr )
2023-04-27 23:00:09 +08:00
}
2023-06-02 22:38:02 +08:00
2023-07-25 19:05:53 +08:00
_ , err = lps . LibraryElementService . GetElement ( context . Background ( ) , & signedInUser , model . GetLibraryElementCommand {
FolderName : parent . Title ,
2023-10-31 23:46:48 +08:00
FolderID : parent . ID , // nolint:staticcheck
2023-07-25 19:05:53 +08:00
UID : parentPanel . UID ,
} )
require . ErrorIs ( t , err , tc . libPanelParentErr )
if tc . depth > 1 {
_ , err = lps . LibraryElementService . GetElement ( context . Background ( ) , & signedInUser , model . GetLibraryElementCommand {
FolderName : subfolder . Title ,
2023-10-31 23:46:48 +08:00
FolderID : subfolder . ID , // nolint:staticcheck
2023-07-25 19:05:53 +08:00
UID : subPanel . UID ,
} )
require . ErrorIs ( t , err , tc . libPanelSubErr )
}
2023-06-02 22:38:02 +08:00
t . Cleanup ( func ( ) {
guardian . New = origNewGuardian
} )
2023-04-27 23:00:09 +08:00
} )
2023-07-25 19:05:53 +08:00
}
2023-03-15 16:51:37 +08:00
} )
}
2022-11-10 17:41:03 +08:00
func TestNestedFolderServiceFeatureToggle ( t * testing . T ) {
2023-01-18 23:47:59 +08:00
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2022-11-10 17:41:03 +08:00
dashStore := dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-01-16 23:33:55 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & dashboards . Dashboard { } , nil )
2023-01-20 00:38:07 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-20 00:38:07 +08:00
2024-05-10 18:56:52 +08:00
db , _ := sqlstore . InitTestDB ( t )
2022-11-08 18:33:13 +08:00
folderService := & Service {
2023-01-20 00:38:07 +08:00
store : nestedFolderStore ,
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2024-04-24 16:38:40 +08:00
db : db ,
2023-01-20 00:38:07 +08:00
dashboardStore : & dashStore ,
dashboardFolderStore : dashboardFolderStore ,
features : featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders ) ,
2025-01-14 17:26:15 +08:00
accessControl : acimpl . ProvideAccessControl ( featuremgmt . WithFeatures ( ) ) ,
2023-12-05 23:13:31 +08:00
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2022-11-08 18:33:13 +08:00
}
t . Run ( "create folder" , func ( t * testing . T ) {
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { ParentUID : util . GenerateShortUID ( ) }
2023-01-18 23:47:59 +08:00
res , err := folderService . Create ( context . Background ( ) , & folder . CreateFolderCommand { SignedInUser : usr , Title : "my folder" } )
2022-11-08 18:33:13 +08:00
require . NoError ( t , err )
require . NotNil ( t , res . UID )
2022-11-24 21:59:47 +08:00
require . NotEmpty ( t , res . ParentUID )
2022-11-08 18:33:13 +08:00
} )
}
2022-11-08 21:59:55 +08:00
2024-01-23 00:03:30 +08:00
func TestFolderServiceDualWrite ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2024-09-26 07:21:39 +08:00
db , _ := sqlstore . InitTestDB ( t )
2024-01-23 00:03:30 +08:00
cfg := setting . NewCfg ( )
features := featuremgmt . WithFeatures ( )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2024-01-23 00:03:30 +08:00
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , features , tagimpl . ProvideService ( db ) )
2024-01-23 00:03:30 +08:00
require . NoError ( t , err )
dashboardFolderStore := ProvideDashboardFolderStore ( db )
folderService := & Service {
store : nestedFolderStore ,
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2024-04-24 16:38:40 +08:00
db : db ,
2024-01-23 00:03:30 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : dashboardFolderStore ,
features : featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders ) ,
2025-01-14 17:26:15 +08:00
accessControl : acimpl . ProvideAccessControl ( featuremgmt . WithFeatures ( ) ) ,
2024-01-23 00:03:30 +08:00
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2024-01-23 00:03:30 +08:00
bus : bus . ProvideBus ( tracing . InitializeTracerForTest ( ) ) ,
}
t . Run ( "When creating a folder it should trim leading and trailing spaces in both dashboard and folder tables" , func ( t * testing . T ) {
f , err := folderService . Create ( context . Background ( ) , & folder . CreateFolderCommand { SignedInUser : usr , OrgID : orgID , Title : " my folder " } )
require . NoError ( t , err )
assert . Equal ( t , "my folder" , f . Title )
dashFolder , err := dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , f . UID )
require . NoError ( t , err )
nestedFolder , err := nestedFolderStore . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & f . UID , OrgID : orgID } )
require . NoError ( t , err )
assert . Equal ( t , dashFolder . Title , nestedFolder . Title )
} )
t . Run ( "When updating a folder it should trim leading and trailing spaces in both dashboard and folder tables" , func ( t * testing . T ) {
f , err := folderService . Create ( context . Background ( ) , & folder . CreateFolderCommand { SignedInUser : usr , OrgID : orgID , Title : "my folder 2" } )
require . NoError ( t , err )
f , err = folderService . Update ( context . Background ( ) , & folder . UpdateFolderCommand { SignedInUser : usr , OrgID : orgID , UID : f . UID , NewTitle : util . Pointer ( " my updated folder 2 " ) , Version : f . Version } )
require . NoError ( t , err )
assert . Equal ( t , "my updated folder 2" , f . Title )
dashFolder , err := dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , f . UID )
require . NoError ( t , err )
nestedFolder , err := nestedFolderStore . Get ( context . Background ( ) , folder . GetFolderQuery { UID : & f . UID , OrgID : orgID } )
require . NoError ( t , err )
assert . Equal ( t , dashFolder . Title , nestedFolder . Title )
} )
}
2022-11-10 16:42:32 +08:00
func TestNestedFolderService ( t * testing . T ) {
2022-11-08 21:59:55 +08:00
t . Run ( "with feature flag unset" , func ( t * testing . T ) {
2023-11-10 20:03:00 +08:00
t . Run ( "Should create a folder in both dashboard and folders tables" , func ( t * testing . T ) {
2023-01-18 23:47:59 +08:00
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
2023-11-22 05:06:20 +08:00
// dash is needed here because folderSvc.Create expects SaveDashboard to return it
dash := dashboards . NewDashboardFolder ( "myFolder" )
dash . ID = rand . Int63 ( )
dash . UID = "some_uid"
2023-01-18 23:47:59 +08:00
// dashboard store & service commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-11-22 05:06:20 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( dash , nil )
2022-11-10 16:42:32 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( )
2023-01-20 00:38:07 +08:00
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , db )
2024-08-01 23:20:38 +08:00
tempUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
tempUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( folder . GeneralFolderUID ) } }
2023-01-18 18:22:23 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
2023-11-22 05:06:20 +08:00
Title : dash . Title ,
UID : dash . UID ,
2024-08-01 23:20:38 +08:00
SignedInUser : tempUser ,
2022-11-10 17:41:03 +08:00
} )
2022-11-10 16:42:32 +08:00
require . NoError ( t , err )
2023-11-10 20:03:00 +08:00
require . True ( t , nestedFolderStore . CreateCalled )
2022-11-10 16:42:32 +08:00
} )
2022-11-08 21:59:55 +08:00
} )
t . Run ( "with nested folder feature flag on" , func ( t * testing . T ) {
2024-08-01 23:20:38 +08:00
t . Run ( "Should be able to create a nested folder under the root with the right permissions" , func ( t * testing . T ) {
2023-01-18 23:47:59 +08:00
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
2023-11-22 05:06:20 +08:00
dash := dashboards . NewDashboardFolder ( "myFolder" )
dash . ID = rand . Int63 ( )
dash . UID = "some_uid"
2023-01-18 23:47:59 +08:00
// dashboard store commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-11-22 05:06:20 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( dash , nil )
2023-01-18 18:22:23 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2023-01-18 18:22:23 +08:00
2024-08-01 23:20:38 +08:00
tempUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
tempUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( folder . GeneralFolderUID ) } }
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , db )
2023-01-18 18:22:23 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
2023-11-22 05:06:20 +08:00
Title : dash . Title ,
UID : dash . UID ,
2024-08-01 23:20:38 +08:00
SignedInUser : tempUser ,
2022-11-10 17:41:03 +08:00
} )
2022-11-08 21:59:55 +08:00
require . NoError ( t , err )
// CreateFolder should also call the folder store's create method.
2023-01-20 00:38:07 +08:00
require . True ( t , nestedFolderStore . CreateCalled )
2022-11-08 21:59:55 +08:00
} )
2024-08-01 23:20:38 +08:00
t . Run ( "Should not be able to create a folder under the root with subfolder creation permissions" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
// dashboard store commands that should be called.
dashStore := & dashboards . FakeDashboardStore { }
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-08-01 23:20:38 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
tempUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
tempUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "subfolder_uid" ) } }
db , _ := sqlstore . InitTestDB ( t )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , db )
2024-08-01 23:20:38 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
Title : "some_folder" ,
UID : "some_uid" ,
SignedInUser : tempUser ,
} )
require . ErrorIs ( t , err , dashboards . ErrFolderCreationAccessDenied )
} )
2023-03-20 19:04:22 +08:00
t . Run ( "Should not be able to create new folder under another folder without the right permissions" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
dash := dashboards . NewDashboardFolder ( "Test-Folder" )
dash . ID = rand . Int63 ( )
dash . UID = "some_uid"
tempUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
tempUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "wrong_uid" ) } }
// dashboard store commands that should be called.
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 )
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , nil , nil , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2023-03-20 19:04:22 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
Title : dash . Title ,
UID : dash . UID ,
SignedInUser : tempUser ,
ParentUID : "some_parent" ,
} )
2024-08-01 23:20:38 +08:00
require . ErrorIs ( t , err , dashboards . ErrFolderCreationAccessDenied )
2023-03-20 19:04:22 +08:00
} )
t . Run ( "Should be able to create new folder under another folder with the right permissions" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
dash := dashboards . NewDashboardFolder ( "Test-Folder" )
dash . ID = rand . Int63 ( )
dash . UID = "some_uid"
// dashboard store commands that should be called.
dashStore := & dashboards . FakeDashboardStore { }
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-11-22 05:06:20 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( dash , nil )
2023-03-20 19:04:22 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-11-16 19:11:35 +08:00
dashboardFolderStore . On ( "GetFolderByUID" , mock . Anything , mock . AnythingOfType ( "int64" ) , mock . AnythingOfType ( "string" ) ) . Return ( & folder . Folder { } , nil )
2023-03-20 19:04:22 +08:00
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "some_parent" ) } }
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , db )
2023-03-20 19:04:22 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
Title : dash . Title ,
UID : dash . UID ,
SignedInUser : nestedFolderUser ,
ParentUID : "some_parent" ,
} )
require . NoError ( t , err )
require . True ( t , nestedFolderStore . CreateCalled )
2024-08-01 23:20:38 +08:00
// Parent write access check will eventually be replaced with scoped folder creation check
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "some_parent" ) } }
_ , err = folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
Title : dash . Title + "2" ,
UID : dash . UID + "2" ,
SignedInUser : nestedFolderUser ,
ParentUID : "some_parent" ,
} )
require . NoError ( t , err )
require . True ( t , nestedFolderStore . CreateCalled )
2023-03-20 19:04:22 +08:00
} )
2022-12-15 00:07:55 +08:00
t . Run ( "create without UID, no error" , func ( t * testing . T ) {
2023-01-18 23:47:59 +08:00
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
2023-01-18 23:47:59 +08:00
// dashboard store commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-01-16 23:33:55 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & dashboards . Dashboard { UID : "newUID" } , nil )
2023-01-18 18:22:23 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2024-04-24 16:38:40 +08:00
} , db )
2023-01-18 18:22:23 +08:00
f , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-12-15 00:07:55 +08:00
OrgID : orgID ,
Title : "myFolder" ,
SignedInUser : usr ,
} )
require . NoError ( t , err )
// CreateFolder should also call the folder store's create method.
2023-01-20 00:38:07 +08:00
require . True ( t , nestedFolderStore . CreateCalled )
2022-12-15 00:07:55 +08:00
require . Equal ( t , "newUID" , f . UID )
} )
t . Run ( "create failed because of circular reference" , func ( t * testing . T ) {
2023-01-18 23:47:59 +08:00
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-16 23:33:55 +08:00
dashboardFolder := dashboards . NewDashboardFolder ( "myFolder" )
dashboardFolder . ID = rand . Int63 ( )
dashboardFolder . UID = "myFolder"
f := dashboards . FromDashboard ( dashboardFolder )
2022-12-15 00:07:55 +08:00
2023-01-18 23:47:59 +08:00
// dashboard store commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-16 23:33:55 +08:00
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 )
2022-12-15 00:07:55 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-11-16 19:11:35 +08:00
dashboardFolderStore . On ( "GetFolderByUID" , mock . Anything , orgID , dashboardFolder . UID ) . Return ( f , nil )
2023-01-20 00:38:07 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedParentFolders = [ ] * folder . Folder {
2022-12-15 00:07:55 +08:00
{ UID : "newFolder" , ParentUID : "newFolder" } ,
{ UID : "newFolder2" , ParentUID : "newFolder2" } ,
{ UID : "newFolder3" , ParentUID : "newFolder3" } ,
{ UID : "myFolder" , ParentUID : "newFolder" } ,
}
cmd := folder . CreateFolderCommand {
2023-11-16 19:11:35 +08:00
ParentUID : dashboardFolder . UID ,
2022-12-15 00:07:55 +08:00
OrgID : orgID ,
2023-11-16 19:11:35 +08:00
Title : "myFolder1" ,
UID : "myFolder1" ,
2022-12-15 00:07:55 +08:00
SignedInUser : usr ,
}
2023-01-18 18:22:23 +08:00
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2024-04-24 16:38:40 +08:00
} , db )
2023-01-18 18:22:23 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & cmd )
2022-12-15 00:07:55 +08:00
require . Error ( t , err , folder . ErrCircularReference )
2023-01-18 18:22:23 +08:00
// CreateFolder should not call the folder store's create method.
2023-01-20 00:38:07 +08:00
require . False ( t , nestedFolderStore . CreateCalled )
2022-12-15 00:07:55 +08:00
} )
2022-11-08 21:59:55 +08:00
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
2023-01-18 23:47:59 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
2023-01-18 23:47:59 +08:00
// dashboard store commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil )
2023-01-16 23:33:55 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & dashboards . Dashboard { } , nil )
2022-11-08 21:59:55 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-20 00:38:07 +08:00
2022-11-08 21:59:55 +08:00
// return an error from the folder store
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedError = errors . New ( "FAILED" )
2022-11-08 21:59:55 +08:00
// the service return success as long as the legacy create succeeds
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2024-04-24 16:38:40 +08:00
} , db )
2023-01-18 18:22:23 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 17:13:47 +08:00
OrgID : orgID ,
Title : "myFolder" ,
UID : "myFolder" ,
SignedInUser : usr ,
2022-11-10 17:41:03 +08:00
} )
2022-11-08 21:59:55 +08:00
require . Error ( t , err , "FAILED" )
// CreateFolder should also call the folder store's create method.
2023-01-20 00:38:07 +08:00
require . True ( t , nestedFolderStore . CreateCalled )
2022-11-08 21:59:55 +08:00
} )
2022-11-10 16:42:32 +08:00
2023-03-20 19:04:22 +08:00
t . Run ( "move without the right permissions should fail" , func ( t * testing . T ) {
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-06-10 22:17:51 +08:00
//dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
2023-01-18 18:22:23 +08:00
2023-03-20 19:04:22 +08:00
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "wrong_uid" ) } }
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2023-03-20 19:04:22 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder" , OrgID : orgID , SignedInUser : nestedFolderUser } )
2024-07-24 00:07:27 +08:00
require . ErrorIs ( t , err , dashboards . ErrMoveAccessDenied )
2022-12-20 21:00:33 +08:00
} )
2023-03-20 19:04:22 +08:00
t . Run ( "move with the right permissions succeeds" , func ( t * testing . T ) {
dashStore := & dashboards . FakeDashboardStore { }
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2022-12-20 21:00:33 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-03-20 19:04:22 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
nestedFolderStore . ExpectedParentFolders = [ ] * folder . Folder {
{ UID : "newFolder" , ParentUID : "newFolder" } ,
{ UID : "newFolder2" , ParentUID : "newFolder2" } ,
{ UID : "newFolder3" , ParentUID : "newFolder3" } ,
}
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
2024-07-24 00:07:27 +08:00
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string {
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "myFolder" ) , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "newFolder" ) } ,
}
2023-03-20 19:04:22 +08:00
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2023-10-24 15:04:45 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder" , OrgID : orgID , SignedInUser : nestedFolderUser } )
2023-03-20 19:04:22 +08:00
require . NoError ( t , err )
2024-08-01 23:20:38 +08:00
// Parent write access check will eventually be replaced with scoped folder creation check
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string {
dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "myFolder" ) , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "newFolder2" ) } ,
}
_ , err = folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder2" , OrgID : orgID , SignedInUser : nestedFolderUser } )
require . NoError ( t , err )
2023-03-20 19:04:22 +08:00
} )
2024-06-10 22:17:51 +08:00
t . Run ( "cannot move the k6 folder even when has permissions to move folders" , func ( t * testing . T ) {
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceAllScope ( ) } }
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , & dashboards . FakeDashboardStore { } , foldertest . NewFakeFolderStore ( t ) , folder . NewFakeStore ( ) , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2024-06-10 22:17:51 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : accesscontrol . K6FolderUID , NewParentUID : "newFolder" , OrgID : orgID , SignedInUser : nestedFolderUser } )
require . Error ( t , err , folder . ErrBadRequest )
} )
t . Run ( "cannot move a k6 subfolder even when has permissions to move folders" , func ( t * testing . T ) {
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceAllScope ( ) } }
childUID := "k6-app-child"
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-06-10 22:17:51 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder {
OrgID : orgID ,
UID : childUID ,
ParentUID : accesscontrol . K6FolderUID ,
}
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , & dashboards . FakeDashboardStore { } , foldertest . NewFakeFolderStore ( t ) , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2024-06-10 22:17:51 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : childUID , NewParentUID : "newFolder" , OrgID : orgID , SignedInUser : nestedFolderUser } )
require . Error ( t , err , folder . ErrBadRequest )
} )
2023-03-20 19:04:22 +08:00
t . Run ( "move to the root folder without folder creation permissions fails" , func ( t * testing . T ) {
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
2023-01-18 18:22:23 +08:00
2023-03-20 19:04:22 +08:00
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "" ) } }
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2023-03-20 19:04:22 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "" , OrgID : orgID , SignedInUser : nestedFolderUser } )
2022-12-20 21:00:33 +08:00
require . Error ( t , err , dashboards . ErrFolderAccessDenied )
} )
2024-08-01 23:20:38 +08:00
t . Run ( "move to the root folder with root folder creation permissions succeeds" , func ( t * testing . T ) {
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
nestedFolderStore . ExpectedParentFolders = [ ] * folder . Folder {
2022-12-20 21:00:33 +08:00
{ UID : "newFolder" , ParentUID : "newFolder" } ,
{ UID : "newFolder2" , ParentUID : "newFolder2" } ,
{ UID : "newFolder3" , ParentUID : "newFolder3" } ,
}
2023-01-18 18:22:23 +08:00
2023-03-20 19:04:22 +08:00
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
2024-08-01 23:20:38 +08:00
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string {
dashboards . ActionFoldersCreate : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( folder . GeneralFolderUID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "myFolder" ) ,
} ,
}
2023-03-20 19:04:22 +08:00
2024-05-10 18:56:52 +08:00
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2023-10-24 15:04:45 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "" , OrgID : orgID , SignedInUser : nestedFolderUser } )
2022-11-10 22:06:52 +08:00
require . NoError ( t , err )
2023-10-24 15:04:45 +08:00
// the folder is set inside InTransaction() but the fake one is called
// require.NotNil(t, f)
2022-11-10 22:06:52 +08:00
} )
2024-08-01 23:20:38 +08:00
t . Run ( "move to the root folder with only subfolder creation permissions fails" , func ( t * testing . T ) {
dashStore := & dashboards . FakeDashboardStore { }
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2024-08-01 23:20:38 +08:00
nestedFolderUser := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
nestedFolderUser . Permissions [ orgID ] = map [ string ] [ ] string { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "some_subfolder" ) } }
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2024-08-01 23:20:38 +08:00
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "" , OrgID : orgID , SignedInUser : nestedFolderUser } )
require . Error ( t , err )
} )
2022-12-08 21:49:17 +08:00
t . Run ( "move when parentUID in the current subtree returns error from nested folder service" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true , CanViewValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
nestedFolderStore . ExpectedError = folder . ErrCircularReference
2023-01-18 18:22:23 +08:00
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2023-03-15 16:51:37 +08:00
} , dbtest . NewFakeDB ( ) )
2023-01-18 18:22:23 +08:00
f , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder" , OrgID : orgID , SignedInUser : usr } )
2022-12-08 21:49:17 +08:00
require . Error ( t , err , folder . ErrCircularReference )
require . Nil ( t , f )
} )
t . Run ( "move when new parentUID depth + subTree height bypassed maximum depth returns error" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true , CanViewValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
nestedFolderStore . ExpectedParentFolders = [ ] * folder . Folder {
2022-12-08 21:49:17 +08:00
{ UID : "newFolder" , ParentUID : "newFolder" } ,
{ UID : "newFolder2" , ParentUID : "newFolder2" } ,
}
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolderHeight = 5
2023-01-18 18:22:23 +08:00
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2023-03-15 16:51:37 +08:00
} , dbtest . NewFakeDB ( ) )
2023-01-18 18:22:23 +08:00
f , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder2" , OrgID : orgID , SignedInUser : usr } )
2022-12-08 21:49:17 +08:00
require . Error ( t , err , folder . ErrMaximumDepthReached )
require . Nil ( t , f )
} )
t . Run ( "move when parentUID in the current subtree returns error from nested folder service" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true , CanViewValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-01-18 18:22:23 +08:00
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
nestedFolderStore . ExpectedFolder = & folder . Folder { UID : "myFolder" , ParentUID : "newFolder" }
nestedFolderStore . ExpectedParentFolders = [ ] * folder . Folder { { UID : "myFolder" , ParentUID : "12345" } , { UID : "12345" , ParentUID : "" } }
2023-01-18 18:22:23 +08:00
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2023-03-15 16:51:37 +08:00
} , dbtest . NewFakeDB ( ) )
2023-01-18 18:22:23 +08:00
f , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : "myFolder" , NewParentUID : "newFolder2" , OrgID : orgID , SignedInUser : usr } )
2022-12-08 21:49:17 +08:00
require . Error ( t , err , folder . ErrCircularReference )
require . Nil ( t , f )
} )
2022-11-23 22:44:45 +08:00
t . Run ( "create returns error if maximum depth reached" , func ( t * testing . T ) {
// This test creates and deletes the dashboard, so needs some extra setup.
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
2023-01-18 23:47:59 +08:00
// dashboard store commands that should be called.
2023-01-18 18:22:23 +08:00
dashStore := & dashboards . FakeDashboardStore { }
2023-01-18 23:47:59 +08:00
dashStore . On ( "ValidateDashboardBeforeSave" , mock . Anything , mock . AnythingOfType ( "*dashboards.Dashboard" ) , mock . AnythingOfType ( "bool" ) ) . Return ( true , nil ) . Times ( 2 )
2023-01-16 23:33:55 +08:00
dashStore . On ( "SaveDashboard" , mock . Anything , mock . AnythingOfType ( "dashboards.SaveDashboardCommand" ) ) . Return ( & dashboards . Dashboard { } , nil )
2022-11-23 22:44:45 +08:00
2023-02-01 21:43:21 +08:00
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2023-11-16 19:11:35 +08:00
dashboardFolderStore . On ( "GetFolderByUID" , mock . Anything , mock . AnythingOfType ( "int64" ) , mock . AnythingOfType ( "string" ) ) . Return ( & folder . Folder { } , nil )
2023-01-20 00:38:07 +08:00
2022-11-23 22:44:45 +08:00
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 ) } )
}
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-01-20 00:38:07 +08:00
//nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
nestedFolderStore . ExpectedParentFolders = parents
2023-01-18 18:22:23 +08:00
2024-04-24 16:38:40 +08:00
db , _ := sqlstore . InitTestDB ( t )
2023-01-20 00:38:07 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
2023-01-18 18:22:23 +08:00
ExpectedEvaluate : true ,
2024-04-24 16:38:40 +08:00
} , db )
2023-01-18 18:22:23 +08:00
_ , err := folderSvc . Create ( context . Background ( ) , & folder . CreateFolderCommand {
2022-11-23 22:44:45 +08:00
Title : "folder" ,
OrgID : orgID ,
ParentUID : parents [ len ( parents ) - 1 ] . UID ,
UID : util . GenerateShortUID ( ) ,
SignedInUser : usr ,
} )
assert . ErrorIs ( t , err , folder . ErrMaximumDepthReached )
} )
2023-02-08 23:16:53 +08:00
t . Run ( "get default folder, no error" , func ( t * testing . T ) {
g := guardian . New
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian { CanSaveValue : true } )
t . Cleanup ( func ( ) {
guardian . New = g
} )
// dashboard store commands that should be called.
dashStore := & dashboards . FakeDashboardStore { }
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
2024-09-30 16:28:47 +08:00
nestedFolderStore := folder . NewFakeStore ( )
2023-02-08 23:16:53 +08:00
nestedFolderStore . ExpectedError = folder . ErrFolderNotFound
folderSvc := setup ( t , dashStore , dashboardFolderStore , nestedFolderStore , featuremgmt . WithFeatures ( "nestedFolders" ) , actest . FakeAccessControl {
ExpectedEvaluate : true ,
2023-03-15 16:51:37 +08:00
} , dbtest . NewFakeDB ( ) )
2023-02-08 23:16:53 +08:00
_ , err := folderSvc . Get ( context . Background ( ) , & folder . GetFolderQuery {
OrgID : orgID ,
2023-11-15 23:30:00 +08:00
ID : & folder . GeneralFolder . ID , // nolint:staticcheck
2023-02-08 23:16:53 +08:00
SignedInUser : usr ,
} )
require . NoError ( t , err )
} )
2022-11-08 21:59:55 +08:00
} )
}
2023-01-18 18:22:23 +08:00
2023-12-05 23:13:31 +08:00
func TestIntegrationNestedFolderSharedWithMe ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
2024-09-26 07:21:39 +08:00
db , cfg := sqlstore . InitTestDB ( t )
2023-12-05 23:13:31 +08:00
quotaService := quotatest . New ( false , nil )
folderStore := ProvideDashboardFolderStore ( db )
featuresFlagOn := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOn , tagimpl . ProvideService ( db ) )
2023-12-05 23:13:31 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2023-12-05 23:13:31 +08:00
b := bus . ProvideBus ( tracing . InitializeTracerForTest ( ) )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( featuresFlagOn )
2023-12-05 23:13:31 +08:00
serviceWithFlagOn := & Service {
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2023-12-05 23:13:31 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : featuresFlagOn ,
bus : b ,
db : db ,
accessControl : ac ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2023-12-05 23:13:31 +08:00
}
dashboardPermissions := acmock . NewMockedPermissionsService ( )
dashboardService , err := dashboardservice . ProvideDashboardServiceImpl (
2024-03-14 22:36:35 +08:00
cfg , dashStore , folderStore ,
2023-12-05 23:13:31 +08:00
featuresFlagOn ,
acmock . NewMockedPermissionsService ( ) ,
actest . FakeAccessControl { } ,
2024-01-31 23:25:11 +08:00
serviceWithFlagOn ,
2024-09-30 16:28:47 +08:00
nestedFolderStore ,
2023-12-05 23:13:31 +08:00
nil ,
2025-02-12 03:14:25 +08:00
client . MockTestRestConfig { } ,
2025-01-02 23:39:45 +08:00
nil ,
2025-01-10 13:21:21 +08:00
quotaService ,
nil ,
2025-01-24 04:23:59 +08:00
nil ,
2025-02-14 19:34:52 +08:00
nil ,
2023-12-05 23:13:31 +08:00
)
require . NoError ( t , err )
2025-01-22 04:57:43 +08:00
dashboardService . RegisterDashboardPermissions ( dashboardPermissions )
2023-12-05 23:13:31 +08:00
signedInUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersRead : { } ,
} ,
} }
signedInAdminUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersCreate : { } ,
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersAll } ,
2024-01-27 01:12:45 +08:00
dashboards . ActionFoldersRead : { dashboards . ScopeFoldersAll } ,
2023-12-05 23:13:31 +08:00
} ,
} }
createCmd := folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
SignedInUser : & signedInAdminUser ,
}
2024-01-31 23:25:11 +08:00
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
} )
2023-12-05 23:13:31 +08:00
t . Run ( "Should get folders shared with given user" , func ( t * testing . T ) {
depth := 3
2024-10-10 19:22:57 +08:00
ancestorFoldersWithPermissions := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "withPermissions" , createCmd , true )
ancestorFoldersWithoutPermissions := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "withoutPermissions" , createCmd , true )
2023-12-05 23:13:31 +08:00
2024-01-25 15:27:13 +08:00
parent , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestorFoldersWithoutPermissions [ 0 ] . UID )
2023-12-05 23:13:31 +08:00
require . NoError ( t , err )
2024-01-25 15:27:13 +08:00
subfolder , err := serviceWithFlagOn . dashboardFolderStore . GetFolderByUID ( context . Background ( ) , orgID , ancestorFoldersWithoutPermissions [ 1 ] . UID )
2023-12-05 23:13:31 +08:00
require . NoError ( t , err )
// nolint:staticcheck
dash1 := insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in parent" , orgID , parent . ID , parent . UID , "prod" )
// nolint:staticcheck
dash2 := insertTestDashboard ( t , serviceWithFlagOn . dashboardStore , "dashboard in subfolder" , orgID , subfolder . ID , subfolder . UID , "prod" )
signedInUser . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = [ ] string {
2024-01-25 15:27:13 +08:00
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( ancestorFoldersWithPermissions [ 0 ] . UID ) ,
2023-12-05 23:13:31 +08:00
// Add permission to the subfolder of folder with permission (to check deduplication)
2024-01-25 15:27:13 +08:00
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( ancestorFoldersWithPermissions [ 1 ] . UID ) ,
2023-12-05 23:13:31 +08:00
// Add permission to the subfolder of folder without permission
2024-01-25 15:27:13 +08:00
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( ancestorFoldersWithoutPermissions [ 1 ] . UID ) ,
2023-12-05 23:13:31 +08:00
}
signedInUser . Permissions [ orgID ] [ dashboards . ActionDashboardsRead ] = [ ] string {
dashboards . ScopeDashboardsProvider . GetResourceScopeUID ( dash1 . UID ) ,
dashboards . ScopeDashboardsProvider . GetResourceScopeUID ( dash2 . UID ) ,
}
getSharedCmd := folder . GetChildrenQuery {
UID : folder . SharedWithMeFolderUID ,
OrgID : orgID ,
SignedInUser : & signedInUser ,
}
sharedFolders , err := serviceWithFlagOn . GetChildren ( context . Background ( ) , & getSharedCmd )
sharedFoldersUIDs := make ( [ ] string , 0 )
for _ , f := range sharedFolders {
sharedFoldersUIDs = append ( sharedFoldersUIDs , f . UID )
}
require . NoError ( t , err )
require . Len ( t , sharedFolders , 1 )
2024-01-25 15:27:13 +08:00
require . Contains ( t , sharedFoldersUIDs , ancestorFoldersWithoutPermissions [ 1 ] . UID )
require . NotContains ( t , sharedFoldersUIDs , ancestorFoldersWithPermissions [ 1 ] . UID )
2023-12-05 23:13:31 +08:00
sharedDashboards , err := dashboardService . GetDashboardsSharedWithUser ( context . Background ( ) , & signedInUser )
sharedDashboardsUIDs := make ( [ ] string , 0 )
for _ , d := range sharedDashboards {
sharedDashboardsUIDs = append ( sharedDashboardsUIDs , d . UID )
}
require . NoError ( t , err )
require . Len ( t , sharedDashboards , 1 )
require . Contains ( t , sharedDashboardsUIDs , dash1 . UID )
require . NotContains ( t , sharedDashboardsUIDs , dash2 . UID )
t . Cleanup ( func ( ) {
2024-01-31 23:25:11 +08:00
//guardian.New = origNewGuardian
2024-01-31 00:26:34 +08:00
toDelete := make ( [ ] string , 0 , len ( ancestorFoldersWithPermissions ) + len ( ancestorFoldersWithoutPermissions ) )
for _ , ancestor := range append ( ancestorFoldersWithPermissions , ancestorFoldersWithoutPermissions ... ) {
toDelete = append ( toDelete , ancestor . UID )
2023-12-05 23:13:31 +08:00
}
2024-01-31 00:26:34 +08:00
err := serviceWithFlagOn . store . Delete ( context . Background ( ) , toDelete , orgID )
2024-06-10 22:17:51 +08:00
assert . NoError ( t , err )
} )
} )
t . Run ( "Should not list k6 folders or subfolders" , func ( t * testing . T ) {
_ , err = nestedFolderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : accesscontrol . K6FolderUID ,
OrgID : orgID ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
k6ChildFolder , err := nestedFolderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "k6-app-child" ,
ParentUID : accesscontrol . K6FolderUID ,
OrgID : orgID ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
unrelatedFolder , err := nestedFolderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "another-folder" ,
OrgID : orgID ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
folders , err := serviceWithFlagOn . GetFolders ( context . Background ( ) , folder . GetFoldersQuery {
OrgID : orgID ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
assert . Equal ( t , 1 , len ( folders ) , "should not return k6 folders or subfolders" )
assert . Equal ( t , unrelatedFolder . UID , folders [ 0 ] . UID )
// Service accounts should be able to list k6 folders
svcAccountUser := user . SignedInUser { UserID : 2 , IsServiceAccount : true , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersRead : { dashboards . ScopeFoldersAll } ,
} ,
} }
folders , err = serviceWithFlagOn . GetFolders ( context . Background ( ) , folder . GetFoldersQuery {
OrgID : orgID ,
SignedInUser : & svcAccountUser ,
} )
require . NoError ( t , err )
assert . Equal ( t , 3 , len ( folders ) , "service accounts should be able to list k6 folders" )
t . Cleanup ( func ( ) {
//guardian.New = origNewGuardian
toDelete := [ ] string { k6ChildFolder . UID , accesscontrol . K6FolderUID , unrelatedFolder . UID }
err := serviceWithFlagOn . store . Delete ( context . Background ( ) , toDelete , orgID )
2024-01-31 00:26:34 +08:00
assert . NoError ( t , err )
2023-12-05 23:13:31 +08:00
} )
} )
2024-01-25 15:27:13 +08:00
t . Run ( "Should get org folders visible" , func ( t * testing . T ) {
depth := 3
// create folder sctructure like this:
// tree1-folder-0
// └──tree1-folder-1
// └──tree1-folder-2
// tree2-folder-0
// └──tree2-folder-1
// └──tree2-folder-2
2024-10-10 19:22:57 +08:00
tree1 := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "tree1-" , createCmd , true )
tree2 := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOn , depth , "tree2-" , createCmd , true )
2024-01-25 15:27:13 +08:00
signedInUser . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = [ ] string {
// Add permission to tree1-folder-0
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( tree1 [ 0 ] . UID ) ,
// Add permission to the subfolder of folder with permission (tree1-folder-1) to check deduplication
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( tree1 [ 1 ] . UID ) ,
// Add permission to the subfolder of folder without permission (tree2-folder-1)
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( tree2 [ 1 ] . UID ) ,
}
t . Cleanup ( func ( ) {
2024-01-31 00:26:34 +08:00
toDelete := make ( [ ] string , 0 , len ( tree1 ) + len ( tree2 ) )
for _ , f := range append ( tree1 , tree2 ... ) {
toDelete = append ( toDelete , f . UID )
2024-01-25 15:27:13 +08:00
}
2024-01-31 00:26:34 +08:00
err := serviceWithFlagOn . store . Delete ( context . Background ( ) , toDelete , orgID )
assert . NoError ( t , err )
2024-01-25 15:27:13 +08:00
} )
testCases := [ ] struct {
name string
cmd folder . GetFoldersQuery
expected [ ] * folder . Folder
} {
{
name : "Should get all org folders visible to the user" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
SignedInUser : & signedInUser ,
} ,
expected : [ ] * folder . Folder {
{
UID : tree1 [ 0 ] . UID ,
} ,
{
UID : tree1 [ 1 ] . UID ,
} ,
{
UID : tree1 [ 2 ] . UID ,
} ,
{
UID : tree2 [ 1 ] . UID ,
} ,
{
UID : tree2 [ 2 ] . UID ,
} ,
} ,
} ,
{
name : "Should get all org folders visible to the user with fullpath" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
WithFullpath : true ,
SignedInUser : & signedInUser ,
} ,
expected : [ ] * folder . Folder {
{
UID : tree1 [ 0 ] . UID ,
Fullpath : "tree1-folder-0" ,
} ,
{
UID : tree1 [ 1 ] . UID ,
Fullpath : "tree1-folder-0/tree1-folder-1" ,
} ,
{
UID : tree1 [ 2 ] . UID ,
Fullpath : "tree1-folder-0/tree1-folder-1/tree1-folder-2" ,
} ,
{
UID : tree2 [ 1 ] . UID ,
Fullpath : "tree2-folder-0/tree2-folder-1" ,
} ,
{
UID : tree2 [ 2 ] . UID ,
Fullpath : "tree2-folder-0/tree2-folder-1/tree2-folder-2" ,
} ,
} ,
} ,
{
name : "Should get all org folders visible to the user with fullpath UIDs" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
WithFullpathUIDs : true ,
SignedInUser : & signedInUser ,
} ,
expected : [ ] * folder . Folder {
{
UID : tree1 [ 0 ] . UID ,
FullpathUIDs : strings . Join ( [ ] string { tree1 [ 0 ] . UID } , "/" ) ,
} ,
{
UID : tree1 [ 1 ] . UID ,
FullpathUIDs : strings . Join ( [ ] string { tree1 [ 0 ] . UID , tree1 [ 1 ] . UID } , "/" ) ,
} ,
{
UID : tree1 [ 2 ] . UID ,
FullpathUIDs : strings . Join ( [ ] string { tree1 [ 0 ] . UID , tree1 [ 1 ] . UID , tree1 [ 2 ] . UID } , "/" ) ,
} ,
{
UID : tree2 [ 1 ] . UID ,
FullpathUIDs : strings . Join ( [ ] string { tree2 [ 0 ] . UID , tree2 [ 1 ] . UID } , "/" ) ,
} ,
{
UID : tree2 [ 2 ] . UID ,
FullpathUIDs : strings . Join ( [ ] string { tree2 [ 0 ] . UID , tree2 [ 1 ] . UID , tree2 [ 2 ] . UID } , "/" ) ,
} ,
} ,
} ,
{
name : "Should get specific org folders visible to the user" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
UIDs : [ ] string { tree1 [ 0 ] . UID , tree2 [ 0 ] . UID , tree2 [ 1 ] . UID } ,
SignedInUser : & signedInUser ,
} ,
expected : [ ] * folder . Folder {
{
UID : tree1 [ 0 ] . UID ,
} ,
{
UID : tree2 [ 1 ] . UID ,
} ,
} ,
} ,
{
name : "Should get all org folders visible to the user with admin permissions" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
SignedInUser : & signedInAdminUser ,
} ,
expected : [ ] * folder . Folder {
{
UID : tree1 [ 0 ] . UID ,
Fullpath : "tree1-folder-0" ,
FullpathUIDs : strings . Join ( [ ] string { tree1 [ 0 ] . UID } , "/" ) ,
} ,
{
UID : tree1 [ 1 ] . UID ,
Fullpath : "tree1-folder-0/tree1-folder-1" ,
FullpathUIDs : strings . Join ( [ ] string { tree1 [ 0 ] . UID , tree1 [ 1 ] . UID } , "/" ) ,
} ,
{
UID : tree1 [ 2 ] . UID ,
Fullpath : "tree1-folder-0/tree1-folder-1/tree1-folder-2" ,
} ,
{
UID : tree2 [ 0 ] . UID ,
Fullpath : "tree2-folder-0" ,
FullpathUIDs : strings . Join ( [ ] string { tree2 [ 0 ] . UID } , "/" ) ,
} ,
{
UID : tree2 [ 1 ] . UID ,
Fullpath : "tree2-folder-0/tree2-folder-1" ,
FullpathUIDs : strings . Join ( [ ] string { tree2 [ 0 ] . UID , tree2 [ 1 ] . UID } , "/" ) ,
} ,
{
UID : tree2 [ 2 ] . UID ,
Fullpath : "tree2-folder-0/tree2-folder-1/tree2-folder-2" ,
FullpathUIDs : strings . Join ( [ ] string { tree2 [ 0 ] . UID , tree2 [ 1 ] . UID , tree2 [ 2 ] . UID } , "/" ) ,
} ,
} ,
} ,
2024-01-27 01:12:45 +08:00
{
name : "Should not get any folders if user has no permissions" ,
cmd : folder . GetFoldersQuery {
OrgID : orgID ,
SignedInUser : & user . SignedInUser { UserID : 999 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : { } ,
} } ,
} ,
expected : nil ,
} ,
2024-01-25 15:27:13 +08:00
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
actualFolders , err := serviceWithFlagOn . GetFolders ( context . Background ( ) , tc . cmd )
require . NoError ( t , err )
require . NoError ( t , err )
require . Len ( t , actualFolders , len ( tc . expected ) )
2024-01-25 16:55:44 +08:00
for _ , expected := range tc . expected {
var actualFolder * folder . Folder
for _ , f := range actualFolders {
if f . UID == expected . UID {
actualFolder = f
break
}
}
if actualFolder == nil {
t . Fatalf ( "expected folder with UID %s not found" , expected . UID )
}
2024-01-25 15:27:13 +08:00
if tc . cmd . WithFullpath {
require . Equal ( t , expected . Fullpath , actualFolder . Fullpath )
} else {
require . Empty ( t , actualFolder . Fullpath )
}
if tc . cmd . WithFullpathUIDs {
require . Equal ( t , expected . FullpathUIDs , actualFolder . FullpathUIDs )
} else {
require . Empty ( t , actualFolder . FullpathUIDs )
}
}
} )
}
} )
2023-12-05 23:13:31 +08:00
}
2024-02-14 01:47:46 +08:00
func TestFolderServiceGetFolder ( t * testing . T ) {
2024-09-26 07:21:39 +08:00
db , _ := sqlstore . InitTestDB ( t )
2024-02-14 01:47:46 +08:00
signedInAdminUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersCreate : { } ,
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersAll } ,
dashboards . ActionFoldersRead : { dashboards . ScopeFoldersAll } ,
} ,
} }
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
} )
getSvc := func ( features featuremgmt . FeatureToggles ) Service {
folderStore := ProvideDashboardFolderStore ( db )
cfg := setting . NewCfg ( )
featuresFlagOff := featuremgmt . WithFeatures ( )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOff , tagimpl . ProvideService ( db ) )
2024-02-14 01:47:46 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2024-02-14 01:47:46 +08:00
b := bus . ProvideBus ( tracing . InitializeTracerForTest ( ) )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( featuresFlagOff )
2024-02-14 01:47:46 +08:00
return Service {
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2024-02-14 01:47:46 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : features ,
bus : b ,
db : db ,
accessControl : ac ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
}
}
folderSvcOn := getSvc ( featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders ) )
folderSvcOff := getSvc ( featuremgmt . WithFeatures ( ) )
createCmd := folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
SignedInUser : & signedInAdminUser ,
}
depth := 3
2024-10-10 19:22:57 +08:00
folders := CreateSubtreeInStore ( t , folderSvcOn . store , & folderSvcOn , depth , "get/folder-" , createCmd , false )
2024-02-14 01:47:46 +08:00
f := folders [ 1 ]
testCases := [ ] struct {
2024-10-10 19:22:57 +08:00
name string
svc * Service
WithFullpath bool
WithFullpathUIDs bool
expectedFullpath string
expectedFullpathUIDs string
2024-02-14 01:47:46 +08:00
} {
{
name : "when flag is off" ,
svc : & folderSvcOff ,
expectedFullpath : f . Title ,
} ,
{
name : "when flag is on and WithFullpath is false" ,
svc : & folderSvcOn ,
WithFullpath : false ,
expectedFullpath : "" ,
} ,
{
name : "when flag is on and WithFullpath is true" ,
svc : & folderSvcOn ,
WithFullpath : true ,
expectedFullpath : "get\\/folder-folder-0/get\\/folder-folder-1" ,
} ,
2024-10-10 19:22:57 +08:00
{
name : "when flag is on and WithFullpathUIDs is false" ,
svc : & folderSvcOn ,
WithFullpathUIDs : false ,
expectedFullpathUIDs : "" ,
} ,
{
name : "when flag is on and WithFullpathUIDs is true" ,
svc : & folderSvcOn ,
WithFullpathUIDs : true ,
expectedFullpathUIDs : "uidfor-0/uidfor-1" ,
} ,
2024-02-14 01:47:46 +08:00
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
q := folder . GetFolderQuery {
OrgID : orgID ,
UID : & f . UID ,
WithFullpath : tc . WithFullpath ,
SignedInUser : & signedInAdminUser ,
}
fldr , err := tc . svc . Get ( context . Background ( ) , & q )
require . NoError ( t , err )
require . Equal ( t , f . UID , fldr . UID )
require . Equal ( t , tc . expectedFullpath , fldr . Fullpath )
} )
}
}
2024-02-06 22:18:40 +08:00
func TestFolderServiceGetFolders ( t * testing . T ) {
2024-09-26 07:21:39 +08:00
db , cfg := sqlstore . InitTestDB ( t )
2024-02-06 22:18:40 +08:00
folderStore := ProvideDashboardFolderStore ( db )
featuresFlagOff := featuremgmt . WithFeatures ( )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOff , tagimpl . ProvideService ( db ) )
2024-02-06 22:18:40 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2024-02-06 22:18:40 +08:00
b := bus . ProvideBus ( tracing . InitializeTracerForTest ( ) )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( featuresFlagOff )
2024-02-06 22:18:40 +08:00
serviceWithFlagOff := & Service {
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2024-02-06 22:18:40 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : featuresFlagOff ,
bus : b ,
db : db ,
accessControl : ac ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2024-02-06 22:18:40 +08:00
}
signedInAdminUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersCreate : { } ,
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersAll } ,
dashboards . ActionFoldersRead : { dashboards . ScopeFoldersAll } ,
} ,
} }
createCmd := folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
SignedInUser : & signedInAdminUser ,
}
guardian . MockDashboardGuardian ( & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanViewValue : true ,
} )
prefix := "getfolders/ff/off"
2024-10-10 19:22:57 +08:00
folders := CreateSubtreeInStore ( t , nestedFolderStore , serviceWithFlagOff , 5 , prefix , createCmd , true )
2024-02-06 22:18:40 +08:00
f := folders [ rand . Intn ( len ( folders ) ) ]
t . Run ( "when flag is off" , func ( t * testing . T ) {
t . Run ( "full path should be a title" , func ( t * testing . T ) {
q := folder . GetFoldersQuery {
OrgID : orgID ,
WithFullpath : true ,
WithFullpathUIDs : true ,
SignedInUser : & signedInAdminUser ,
UIDs : [ ] string { f . UID } ,
}
fldrs , err := serviceWithFlagOff . GetFolders ( context . Background ( ) , q )
require . NoError ( t , err )
require . Len ( t , fldrs , 1 )
require . Equal ( t , f . UID , fldrs [ 0 ] . UID )
require . Equal ( t , f . Title , fldrs [ 0 ] . Title )
require . Equal ( t , f . Title , fldrs [ 0 ] . Fullpath )
t . Run ( "path should not be escaped" , func ( t * testing . T ) {
require . Contains ( t , fldrs [ 0 ] . Fullpath , prefix )
require . Contains ( t , fldrs [ 0 ] . Title , prefix )
} )
} )
} )
}
2024-03-15 20:05:27 +08:00
// TODO replace it with an API test under /pkg/tests/api/folders
// whenever the golang client with get updated to allow filtering child folders by permission
func TestGetChildrenFilterByPermission ( t * testing . T ) {
2024-09-26 07:21:39 +08:00
db , cfg := sqlstore . InitTestDB ( t )
2024-03-15 20:05:27 +08:00
signedInAdminUser := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
2024-08-01 23:20:38 +08:00
dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersAll } ,
2024-03-15 20:05:27 +08:00
dashboards . ActionFoldersWrite : { dashboards . ScopeFoldersAll } ,
dashboards . ActionFoldersRead : { dashboards . ScopeFoldersAll } ,
} ,
} }
folderStore := ProvideDashboardFolderStore ( db )
featuresFlagOff := featuremgmt . WithFeatures ( )
2025-01-10 13:21:21 +08:00
dashStore , err := database . ProvideDashboardStore ( db , cfg , featuresFlagOff , tagimpl . ProvideService ( db ) )
2024-03-15 20:05:27 +08:00
require . NoError ( t , err )
2024-05-02 15:14:12 +08:00
nestedFolderStore := ProvideStore ( db )
2024-03-15 20:05:27 +08:00
b := bus . ProvideBus ( tracing . InitializeTracerForTest ( ) )
2025-01-14 17:26:15 +08:00
ac := acimpl . ProvideAccessControl ( featuresFlagOff )
2024-03-15 20:05:27 +08:00
features := featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders )
folderSvcOn := & Service {
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2024-03-15 20:05:27 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : folderStore ,
store : nestedFolderStore ,
features : features ,
bus : b ,
db : db ,
accessControl : ac ,
registry : make ( map [ string ] folder . RegistryService ) ,
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2024-03-15 20:05:27 +08:00
}
origGuardian := guardian . New
fakeGuardian := & guardian . FakeDashboardGuardian {
CanSaveValue : true ,
CanEditUIDs : [ ] string { } ,
CanViewUIDs : [ ] string { } ,
}
guardian . MockDashboardGuardian ( fakeGuardian )
t . Cleanup ( func ( ) {
guardian . New = origGuardian
} )
viewer := user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string {
orgID : {
dashboards . ActionFoldersRead : { } ,
dashboards . ActionFoldersWrite : { } ,
} ,
} }
// no view permission
// |_ subfolder under no view permission with view permission
// |_ subfolder under no view permission with view permissionn and with edit permission
// with edit permission
// |_ subfolder under with edit permission
// no edit permission
// |_ subfolder under no edit permission
// |_ subfolder under no edit permission with edit permission
noViewPermission , err := folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
Title : "no view permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
f , err := folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : noViewPermission . UID ,
Title : "subfolder under no view permission with view permission" ,
SignedInUser : & signedInAdminUser ,
} )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( f . UID ) )
fakeGuardian . CanViewUIDs = append ( fakeGuardian . CanViewUIDs , f . UID )
require . NoError ( t , err )
f , err = folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : noViewPermission . UID ,
Title : "subfolder under no view permission with view permission and with edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( f . UID ) )
fakeGuardian . CanViewUIDs = append ( fakeGuardian . CanViewUIDs , f . UID )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( f . UID ) )
fakeGuardian . CanEditUIDs = append ( fakeGuardian . CanEditUIDs , f . UID )
withEditPermission , err := folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
Title : "with edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( withEditPermission . UID ) )
fakeGuardian . CanViewUIDs = append ( fakeGuardian . CanViewUIDs , withEditPermission . UID )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( withEditPermission . UID ) )
fakeGuardian . CanEditUIDs = append ( fakeGuardian . CanEditUIDs , withEditPermission . UID )
_ , err = folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : withEditPermission . UID ,
Title : "subfolder under with edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
noEditPermission , err := folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : "" ,
Title : "no edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersRead ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( noEditPermission . UID ) )
fakeGuardian . CanViewUIDs = append ( fakeGuardian . CanViewUIDs , noEditPermission . UID )
_ , err = folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : noEditPermission . UID ,
Title : "subfolder under no edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
f , err = folderSvcOn . Create ( context . Background ( ) , & folder . CreateFolderCommand {
OrgID : orgID ,
ParentUID : noEditPermission . UID ,
Title : "subfolder under no edit permission with edit permission" ,
SignedInUser : & signedInAdminUser ,
} )
require . NoError ( t , err )
viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] = append ( viewer . Permissions [ orgID ] [ dashboards . ActionFoldersWrite ] , dashboards . ScopeFoldersProvider . GetResourceScopeUID ( f . UID ) )
fakeGuardian . CanEditUIDs = append ( fakeGuardian . CanEditUIDs , f . UID )
testCases := [ ] struct {
name string
q folder . GetChildrenQuery
expectedErr error
expectedFolders [ ] string
} {
{
name : "should return root folders with view permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
} ,
expectedFolders : [ ] string {
"Shared with me" ,
"no edit permission" ,
"with edit permission" } ,
} ,
{
name : "should return subfolders with view permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
UID : noEditPermission . UID ,
} ,
expectedFolders : [ ] string {
"subfolder under no edit permission" ,
"subfolder under no edit permission with edit permission" } ,
} ,
{
name : "should return shared with me folders with view permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
UID : folder . SharedWithMeFolderUID ,
} ,
expectedFolders : [ ] string {
"subfolder under no view permission with view permission" ,
"subfolder under no view permission with view permission and with edit permission" } ,
} ,
{
name : "should return root folders with edit permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
Permission : dashboardaccess . PERMISSION_EDIT ,
} ,
expectedFolders : [ ] string {
"Shared with me" ,
"with edit permission" } ,
} ,
{
name : "should fail returning subfolders with edit permission when parent folder has no edit permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
Permission : dashboardaccess . PERMISSION_EDIT ,
UID : noEditPermission . UID ,
} ,
expectedErr : dashboards . ErrFolderAccessDenied ,
} ,
{
name : "should return shared with me folders with edit permission" ,
q : folder . GetChildrenQuery {
OrgID : orgID ,
SignedInUser : & viewer ,
Permission : dashboardaccess . PERMISSION_EDIT ,
UID : folder . SharedWithMeFolderUID ,
} ,
expectedFolders : [ ] string {
"subfolder under no edit permission with edit permission" ,
"subfolder under no view permission with view permission and with edit permission" ,
} ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
folders , err := folderSvcOn . GetChildren ( context . Background ( ) , & tc . q )
if tc . expectedErr != nil {
require . Error ( t , err )
require . Equal ( t , tc . expectedErr , err )
} else {
require . NoError ( t , err )
actual := make ( [ ] string , 0 , len ( folders ) )
for _ , f := range folders {
actual = append ( actual , f . Title )
}
if cmp . Diff ( tc . expectedFolders , actual ) != "" {
t . Fatalf ( "unexpected folders: %s" , cmp . Diff ( tc . expectedFolders , actual ) )
}
}
} )
}
}
2024-07-24 00:07:27 +08:00
func TestIntegration_canMove ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
dashStore := & dashboards . FakeDashboardStore { }
dashboardFolderStore := foldertest . NewFakeFolderStore ( t )
db , cfg := sqlstore . InitTestDB ( t )
folderStore := ProvideStore ( db )
orgID := CreateOrg ( t , db , cfg )
adminUsr := & user . SignedInUser { OrgID : orgID , OrgRole : org . RoleAdmin }
// Set up source folder and a source folder parent
sourceParent , err := folderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "source-parent" ,
OrgID : orgID ,
Title : "Source parent" ,
SignedInUser : adminUsr ,
} )
require . NoError ( t , err )
sourceFolder , err := folderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "source" ,
OrgID : orgID ,
Title : "Source" ,
ParentUID : sourceParent . UID ,
SignedInUser : adminUsr ,
} )
require . NoError ( t , err )
// Set up destination folder and destination folder parent
destParent , err := folderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "destination-parent" ,
OrgID : orgID ,
Title : "Destination parent" ,
SignedInUser : adminUsr ,
} )
require . NoError ( t , err )
destFolder , err := folderStore . Create ( context . Background ( ) , folder . CreateFolderCommand {
UID : "destination" ,
OrgID : orgID ,
Title : "Destination" ,
ParentUID : destParent . UID ,
SignedInUser : adminUsr ,
} )
require . NoError ( t , err )
features := featuremgmt . WithFeatures ( "nestedFolders" )
2025-01-14 17:26:15 +08:00
folderSvc := setup ( t , dashStore , dashboardFolderStore , folderStore , features , acimpl . ProvideAccessControl ( features ) , dbtest . NewFakeDB ( ) )
2024-07-24 00:07:27 +08:00
testCases := [ ] struct {
description string
destinationFolder string
permissions map [ string ] [ ] string
expectedErr error
} {
{
description : "can move a folder if has edit access to both folders" ,
destinationFolder : destFolder . UID ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
} ,
} ,
{
description : "can't move a folder if missing write access to the destination folder" ,
destinationFolder : destFolder . UID ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
} ,
dashboards . ActionFoldersRead : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
} ,
expectedErr : dashboards . ErrMoveAccessDenied ,
} ,
{
description : "can't move a folder to the root if missing folder create permissions" ,
destinationFolder : "" ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
} ,
} ,
expectedErr : dashboards . ErrMoveAccessDenied ,
} ,
{
description : "can move a folder to the root with folder create permissions" ,
destinationFolder : "" ,
permissions : map [ string ] [ ] string {
2024-08-01 23:20:38 +08:00
dashboards . ActionFoldersCreate : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( folder . GeneralFolderUID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
} ,
2024-07-24 00:07:27 +08:00
} ,
} ,
{
description : "can't move a folder to another folder where user has higher plugin permissions" ,
destinationFolder : destFolder . UID ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
"some_plugin:action" : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
} ,
expectedErr : dashboards . ErrFolderAccessEscalation ,
} ,
{
description : "can move a folder to another folder where user has lower permissions" ,
destinationFolder : destFolder . UID ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
"some_plugin:action" : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
} ,
} ,
} ,
{
description : "can't move a folder to another folder where user has higher plugin permissions through inheritance" ,
destinationFolder : destFolder . UID ,
permissions : map [ string ] [ ] string {
dashboards . ActionFoldersWrite : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( sourceFolder . UID ) ,
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destFolder . UID ) ,
} ,
"some_plugin:action" : {
dashboards . ScopeFoldersProvider . GetResourceScopeUID ( destParent . UID ) ,
} ,
} ,
expectedErr : dashboards . ErrFolderAccessEscalation ,
} ,
}
for _ , tc := range testCases {
usr := & user . SignedInUser { UserID : 1 , OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { } }
usr . Permissions [ orgID ] = tc . permissions
t . Run ( tc . description , func ( t * testing . T ) {
_ , err := folderSvc . Move ( context . Background ( ) , & folder . MoveFolderCommand { UID : sourceFolder . UID , NewParentUID : tc . destinationFolder , OrgID : orgID , SignedInUser : usr } )
if tc . expectedErr == nil {
require . NoError ( t , err )
} else {
require . ErrorIs ( t , err , tc . expectedErr )
}
} )
}
}
2024-02-26 18:27:22 +08:00
func TestSupportBundle ( t * testing . T ) {
f := func ( uid , parent string ) * folder . Folder { return & folder . Folder { UID : uid , ParentUID : parent } }
for _ , tc := range [ ] struct {
Folders [ ] * folder . Folder
ExpectedTotal int
ExpectedDepths map [ int ] int
ExpectedChildren map [ int ] int
} {
// Empty folder list
{
Folders : [ ] * folder . Folder { } ,
ExpectedTotal : 0 ,
ExpectedDepths : map [ int ] int { } ,
ExpectedChildren : map [ int ] int { } ,
} ,
// Single folder
{
Folders : [ ] * folder . Folder { f ( "a" , "" ) } ,
ExpectedTotal : 1 ,
ExpectedDepths : map [ int ] int { 1 : 1 } ,
ExpectedChildren : map [ int ] int { 0 : 1 } ,
} ,
// Flat folders
{
Folders : [ ] * folder . Folder { f ( "a" , "" ) , f ( "b" , "" ) , f ( "c" , "" ) } ,
ExpectedTotal : 3 ,
ExpectedDepths : map [ int ] int { 1 : 3 } ,
ExpectedChildren : map [ int ] int { 0 : 3 } ,
} ,
// Nested folders
{
Folders : [ ] * folder . Folder { f ( "a" , "" ) , f ( "ab" , "a" ) , f ( "ac" , "a" ) , f ( "x" , "" ) , f ( "xy" , "x" ) , f ( "xyz" , "xy" ) } ,
ExpectedTotal : 6 ,
ExpectedDepths : map [ int ] int { 1 : 2 , 2 : 3 , 3 : 1 } ,
ExpectedChildren : map [ int ] int { 0 : 3 , 1 : 2 , 2 : 1 } ,
} ,
} {
svc := & Service { }
supportItem , err := svc . supportItemFromFolders ( tc . Folders )
if err != nil {
t . Fatal ( err )
}
stats := struct {
Total int ` json:"total" `
Depths map [ int ] int ` json:"depths" `
Children map [ int ] int ` json:"children" `
} { }
if err := json . Unmarshal ( supportItem . FileBytes , & stats ) ; err != nil {
t . Fatal ( err )
}
if stats . Total != tc . ExpectedTotal {
t . Error ( "Total mismatch" , stats , tc )
}
if fmt . Sprint ( stats . Depths ) != fmt . Sprint ( tc . ExpectedDepths ) {
t . Error ( "Depths mismatch" , stats , tc . ExpectedDepths )
}
if fmt . Sprint ( stats . Children ) != fmt . Sprint ( tc . ExpectedChildren ) {
t . Error ( "Depths mismatch" , stats , tc . ExpectedChildren )
}
}
}
2024-10-10 19:22:57 +08:00
func CreateSubtreeInStore ( t * testing . T , store folder . Store , service * Service , depth int , prefix string , cmd folder . CreateFolderCommand , randomUID bool ) [ ] * folder . Folder {
2023-03-15 16:51:37 +08:00
t . Helper ( )
2024-01-25 15:27:13 +08:00
folders := make ( [ ] * folder . Folder , 0 , depth )
2023-03-15 16:51:37 +08:00
for i := 0 ; i < depth ; i ++ {
title := fmt . Sprintf ( "%sfolder-%d" , prefix , i )
cmd . Title = title
cmd . UID = util . GenerateShortUID ( )
2024-10-10 19:22:57 +08:00
if ! randomUID {
cmd . UID = fmt . Sprintf ( "uidfor-%d" , i )
}
2024-08-01 23:20:38 +08:00
cmd . OrgID = orgID
cmd . SignedInUser = & user . SignedInUser { OrgID : orgID , Permissions : map [ int64 ] map [ string ] [ ] string { orgID : { dashboards . ActionFoldersCreate : { dashboards . ScopeFoldersAll } } } }
2023-03-15 16:51:37 +08:00
f , err := service . Create ( context . Background ( ) , & cmd )
require . NoError ( t , err )
require . Equal ( t , title , f . Title )
require . NotEmpty ( t , f . UID )
2024-01-25 15:27:13 +08:00
folders = append ( folders , f )
2023-03-15 16:51:37 +08:00
cmd . ParentUID = f . UID
}
2024-01-25 15:27:13 +08:00
return folders
2023-03-15 16:51:37 +08:00
}
2024-09-30 16:28:47 +08:00
func setup ( t * testing . T , dashStore dashboards . Store , dashboardFolderStore folder . FolderStore , nestedFolderStore folder . Store , features featuremgmt . FeatureToggles , ac accesscontrol . AccessControl , db db . DB ) folder . Service {
2023-01-18 18:22:23 +08:00
t . Helper ( )
// nothing enabled yet
return & Service {
2024-05-02 15:14:12 +08:00
log : slog . New ( logtest . NewTestHandler ( t ) ) . With ( "logger" , "test-folder-service" ) ,
2023-01-20 00:38:07 +08:00
dashboardStore : dashStore ,
dashboardFolderStore : dashboardFolderStore ,
store : nestedFolderStore ,
features : features ,
accessControl : ac ,
2023-03-15 16:51:37 +08:00
db : db ,
2023-12-05 23:13:31 +08:00
metrics : newFoldersMetrics ( nil ) ,
2024-08-13 18:26:26 +08:00
tracer : tracing . InitializeTracerForTest ( ) ,
2023-01-18 18:22:23 +08:00
}
}
2023-06-02 22:38:02 +08:00
func createRule ( t * testing . T , store * ngstore . DBstore , folderUID , title string ) * models . AlertRule {
t . Helper ( )
2024-09-13 01:20:33 +08:00
gen := models . RuleGen
rule := gen . With (
gen . WithOrgID ( orgID ) ,
gen . WithTitle ( title ) ,
gen . WithNamespaceUID ( folderUID ) ,
gen . WithIntervalSeconds ( 10 ) ,
) . Generate ( )
2025-01-25 01:09:17 +08:00
ids , err := store . InsertAlertRules ( context . Background ( ) , nil , [ ] models . AlertRule { rule } )
2023-06-02 22:38:02 +08:00
require . NoError ( t , err )
2024-09-13 01:20:33 +08:00
result , err := store . GetAlertRuleByUID ( context . Background ( ) , & models . GetAlertRuleByUIDQuery { OrgID : orgID , UID : ids [ 0 ] . UID } )
require . NoError ( t , err )
return result
2023-06-02 22:38:02 +08:00
}
2024-05-31 16:09:20 +08:00
func TestSplitFullpath ( t * testing . T ) {
tests := [ ] struct {
name string
input string
expected [ ] string
} {
{
name : "empty string" ,
input : "" ,
expected : [ ] string { } ,
} ,
{
name : "root folder" ,
input : "/" ,
expected : [ ] string { } ,
} ,
{
name : "single folder" ,
input : "folder" ,
expected : [ ] string { "folder" } ,
} ,
{
name : "single folder with leading slash" ,
input : "/folder" ,
expected : [ ] string { "folder" } ,
} ,
{
name : "nested folder" ,
input : "folder/subfolder/subsubfolder" ,
expected : [ ] string { "folder" , "subfolder" , "subsubfolder" } ,
} ,
{
name : "escaped slashes" ,
input : "folder\\/with\\/slashes" ,
expected : [ ] string { "folder/with/slashes" } ,
} ,
{
name : "nested folder with escaped slashes" ,
input : "folder\\/with\\/slashes/subfolder\\/with\\/slashes" ,
expected : [ ] string { "folder/with/slashes" , "subfolder/with/slashes" } ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
actual := SplitFullpath ( tt . input )
assert . Equal ( t , tt . expected , actual )
} )
}
}