@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -11,142 +12,270 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/team/teamtest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
)
func TestFoldersAPIEndpoint ( t * testing . T ) {
func TestFoldersCreate APIEndpoint ( t * testing . T ) {
folderService := & foldertest . FakeService { }
setUpRBACGuardian ( t )
t . Run ( "Given a correct request for creating a folder" , func ( t * testing . T ) {
cmd := folder . CreateFolderCommand {
UID : "uid" ,
Title : "Folder" ,
}
folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}"
type testCase struct {
description string
expectedCode int
expectedFolder * folder . Folder
expectedFolderSvcError error
permissions [ ] accesscontrol . Permission
withNestedFolders bool
input string
}
tcs := [ ] testCase {
{
description : "folder creation succeeds given the correct request for creating a folder" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusOK ,
expectedFolder : & folder . Folder { ID : 1 , UID : "uid" , Title : "Folder" } ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails without permissions to create a folder" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusForbidden ,
permissions : [ ] accesscontrol . Permission { } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusConflict ,
expectedFolderSvcError : dashboards . ErrFolderWithSameUIDExists ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrFolderTitleEmpty ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrDashboardInvalidUid ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrDashboardUidTooLong ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusConflict ,
expectedFolderSvcError : dashboards . ErrFolderSameNameExists ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusForbidden ,
expectedFolderSvcError : dashboards . ErrFolderAccessDenied ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusNotFound ,
expectedFolderSvcError : dashboards . ErrFolderNotFound ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
{
description : "folder creation fails given folder service error %s" ,
input : folderWithoutParentInput ,
expectedCode : http . StatusPreconditionFailed ,
expectedFolderSvcError : dashboards . ErrFolderVersionMismatch ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersCreate } } ,
} ,
}
for _ , tc := range tcs {
folderService . ExpectedFolder = tc . expectedFolder
folderService . ExpectedError = tc . expectedFolderSvcError
folderPermService := acmock . NewMockedPermissionsService ( )
folderPermService . On ( "SetPermissions" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( [ ] accesscontrol . ResourcePermission { } , nil )
srv := SetupAPITestServer ( t , func ( hs * HTTPServer ) {
hs . Cfg = & setting . Cfg {
RBACEnabled : true ,
}
folderService . ExpectedFolder = & folder . Folder { ID : 1 , UID : "uid" , Title : "Folder" }
if tc . withNestedFolders {
hs . Features = featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders )
}
hs . folderService = folderService
hs . folderPermissionsService = folderPermService
hs . accesscontrolService = actest . FakeService { }
} )
t . Run ( testDescription ( tc . description , tc . expectedFolderSvcError ) , func ( t * testing . T ) {
input := strings . NewReader ( tc . input )
req := srv . NewPostRequest ( "/api/folders" , input )
req = webtest . RequestWithSignedInUser ( req , userWithPermissions ( 1 , tc . permissions ) )
resp , err := srv . SendJSON ( req )
require . NoError ( t , err )
require . Equal ( t , tc . expectedCode , resp . StatusCode )
createFolderScenario ( t , "When calling POST on" , "/api/folders" , "/api/folders" , folderService , cmd ,
func ( sc * scenarioContext ) {
callCreateFolder ( sc )
folder := dtos . Folder { }
err = json . NewDecoder ( resp . Body ) . Decode ( & folder )
require . NoError ( t , err )
require . NoError ( t , resp . Body . Close ( ) )
folder := dtos . Folder { }
err := json . NewDecoder ( sc . resp . Body ) . Decode ( & folder )
require . NoError ( t , err )
if tc . expectedCode == http . StatusOK {
assert . Equal ( t , int64 ( 1 ) , folder . Id )
assert . Equal ( t , "uid" , folder . Uid )
assert . Equal ( t , "Folder" , folder . Title )
} )
} )
t . Run ( "Given incorrect requests for creating a folder" , func ( t * testing . T ) {
t . Cleanup ( func ( ) {
folderService . ExpectedError = nil
}
} )
testCases := [ ] struct {
Error error
ExpectedStatusCode int
} {
{ Error : dashboards . ErrFolderWithSameUIDExists , ExpectedStatusCode : 409 } ,
{ Error : dashboards . ErrFolderTitleEmpty , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrFolderSameNameExists , ExpectedStatusCode : 409 } ,
{ Error : dashboards . ErrDashboardInvalidUid , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrDashboardUidTooLong , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrFolderAccessDenied , ExpectedStatusCode : 403 } ,
{ Error : dashboards . ErrFolderNotFound , ExpectedStatusCode : 404 } ,
{ Error : dashboards . ErrFolderVersionMismatch , ExpectedStatusCode : 412 } ,
}
cmd := folder . CreateFolderCommand {
UID : "uid" ,
Title : "Folder" ,
}
for _ , tc := range testCases {
folderService . ExpectedError = tc . Error
}
}
createFolderScenario ( t , fmt . Sprintf ( "Expect '%s' error when calling POST on" , tc . Error . Error ( ) ) ,
"/api/folders" , "/api/folders" , folderService , cmd , func ( sc * scenarioContext ) {
callCreateFolder ( sc )
assert . Equalf ( t , tc . ExpectedStatusCode , sc . resp . Code , "Wrong status code for error %s" , tc . Error )
} )
}
} )
func TestFoldersUpdateAPIEndpoint ( t * testing . T ) {
folderService := & foldertest . FakeService { }
setUpRBACGuardian ( t )
t . Run ( "Given a correct request for updating a folder" , func ( t * testing . T ) {
title := "Folder upd"
cmd := folder . UpdateFolderCommand {
NewTitle : & title ,
}
type testCase struct {
description string
expectedCode int
expectedFolder * folder . Folder
expectedFolderSvcError error
permissions [ ] accesscontrol . Permission
}
tcs := [ ] testCase {
{
description : "folder updating succeeds given the correct request and permissions to update a folder" ,
expectedCode : http . StatusOK ,
expectedFolder : & folder . Folder { ID : 1 , UID : "uid" , Title : "Folder upd" } ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails without permissions to update a folder" ,
expectedCode : http . StatusForbidden ,
permissions : [ ] accesscontrol . Permission { } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusConflict ,
expectedFolderSvcError : dashboards . ErrFolderWithSameUIDExists ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrFolderTitleEmpty ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrDashboardInvalidUid ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusBadRequest ,
expectedFolderSvcError : dashboards . ErrDashboardUidTooLong ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusConflict ,
expectedFolderSvcError : dashboards . ErrFolderSameNameExists ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusForbidden ,
expectedFolderSvcError : dashboards . ErrFolderAccessDenied ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusNotFound ,
expectedFolderSvcError : dashboards . ErrFolderNotFound ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
{
description : "folder updating fails given folder service error %s" ,
expectedCode : http . StatusPreconditionFailed ,
expectedFolderSvcError : dashboards . ErrFolderVersionMismatch ,
permissions : [ ] accesscontrol . Permission { { Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersAll } } ,
} ,
}
for _ , tc := range tcs {
folderService . ExpectedFolder = tc . expectedFolder
folderService . ExpectedError = tc . expectedFolderSvcError
srv := SetupAPITestServer ( t , func ( hs * HTTPServer ) {
hs . Cfg = & setting . Cfg {
RBACEnabled : true ,
}
hs . folderService = folderService
} )
folderService . ExpectedFolder = & folder . Folder { ID : 1 , UID : "uid" , Title : "Folder upd" }
t . Run ( testDescription ( tc . description , tc . expectedFolderSvcError ) , func ( t * testing . T ) {
input := strings . NewReader ( "{ \"uid\": \"uid\", \"title\": \"Folder upd\" }" )
req := srv . NewRequest ( http . MethodPut , "/api/folders/uid" , input )
req = webtest . RequestWithSignedInUser ( req , userWithPermissions ( 1 , tc . permissions ) )
resp , err := srv . SendJSON ( req )
require . NoError ( t , err )
require . Equal ( t , tc . expectedCode , resp . StatusCode )
updateFolderScenario ( t , "When calling PUT on" , "/api/folders/uid" , "/api/folders/:uid" , folderService , cmd ,
func ( sc * scenarioContext ) {
callUpdateFolder ( sc )
folder := dtos . Folder { }
err = json . NewDecoder ( resp . Body ) . Decode ( & folder )
require . NoError ( t , err )
require . NoError ( t , resp . Body . Close ( ) )
folder := dtos . Folder { }
err := json . NewDecoder ( sc . resp . Body ) . Decode ( & folder )
require . NoError ( t , err )
if tc . expectedCode == http . StatusOK {
assert . Equal ( t , int64 ( 1 ) , folder . Id )
assert . Equal ( t , "uid" , folder . Uid )
assert . Equal ( t , "Folder upd" , folder . Title )
} )
} )
t . Run ( "Given incorrect requests for updating a folder" , func ( t * testing . T ) {
testCases := [ ] struct {
Error error
ExpectedStatusCode int
} {
{ Error : dashboards . ErrFolderWithSameUIDExists , ExpectedStatusCode : 409 } ,
{ Error : dashboards . ErrFolderTitleEmpty , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrFolderSameNameExists , ExpectedStatusCode : 409 } ,
{ Error : dashboards . ErrDashboardInvalidUid , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrDashboardUidTooLong , ExpectedStatusCode : 400 } ,
{ Error : dashboards . ErrFolderAccessDenied , ExpectedStatusCode : 403 } ,
{ Error : dashboards . ErrFolderNotFound , ExpectedStatusCode : 404 } ,
{ Error : dashboards . ErrFolderVersionMismatch , ExpectedStatusCode : 412 } ,
}
title := "Folder upd"
cmd := folder . UpdateFolderCommand {
NewTitle : & title ,
}
}
} )
}
}
for _ , tc := range testCases {
folderService . ExpectedError = tc . Error
updateFolderScenario ( t , fmt . Sprintf ( "Expect '%s' error when calling PUT on" , tc . Error . Error ( ) ) ,
"/api/folders/uid" , "/api/folders/:uid" , folderService , cmd , func ( sc * scenarioContext ) {
callUpdateFolder ( sc )
assert . Equalf ( t , tc . ExpectedStatusCode , sc . resp . Code , "Wrong status code for %s" , tc . Error )
} )
}
} )
func testDescription ( description string , expectedErr error ) string {
if expectedErr != nil {
return fmt . Sprintf ( description , expectedErr . Error ( ) )
} else {
return description
}
}
func TestHTTPServer_FolderMetadata ( t * testing . T ) {
setUpRBACGuardian ( t )
folderService := & foldertest . FakeService { }
server := SetupAPITestServer ( t , func ( hs * HTTPServer ) {
hs . Cfg = & setting . Cfg {
RBACEnabled : true ,
}
hs . folderService = folderService
hs . AccessControl = acmock . New ( )
hs . QuotaService = quotatest . New ( false , nil )
hs . SearchService = & mockSearchService {
ExpectedResult : model . HitList { } ,
@ -229,76 +358,53 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
} )
}
func callCreateFolder ( sc * scenarioContext ) {
sc . fakeReqWithParams ( "POST" , sc . url , map [ string ] string { } ) . exec ( )
}
func createFolderScenario ( t * testing . T , desc string , url string , routePattern string , folderService folder . Service ,
cmd folder . CreateFolderCommand , fn scenarioFunc ) {
func TestFolderMoveAPIEndpoint ( t * testing . T ) {
folderService := & foldertest . FakeService { }
setUpRBACGuardian ( t )
t . Run ( fmt . Sprintf ( "%s %s" , desc , url ) , func ( t * testing . T ) {
aclMockResp := [ ] * dashboards . DashboardACLInfoDTO { }
teamSvc := & teamtest . FakeService { }
dashSvc := & dashboards . FakeDashboardService { }
qResult1 := aclMockResp
dashSvc . On ( "GetDashboardACLInfoList" , mock . Anything , mock . AnythingOfType ( "*dashboards.GetDashboardACLInfoListQuery" ) ) . Return ( qResult1 , nil )
qResult := & dashboards . Dashboard { }
dashSvc . On ( "GetDashboard" , mock . Anything , mock . AnythingOfType ( "*dashboards.GetDashboardQuery" ) ) . Return ( qResult , nil )
store := dbtest . NewFakeDB ( )
guardian . InitLegacyGuardian ( setting . NewCfg ( ) , store , dashSvc , teamSvc )
folderPermissions := acmock . NewMockedPermissionsService ( )
folderPermissions . On ( "SetPermissions" , mock . Anything , mock . Anything , mock . Anything , mock . Anything ) . Return ( [ ] accesscontrol . ResourcePermission { } , nil )
hs := HTTPServer {
AccessControl : acmock . New ( ) ,
folderService : folderService ,
Cfg : setting . NewCfg ( ) ,
Features : featuremgmt . WithFeatures ( ) ,
accesscontrolService : actest . FakeService { } ,
folderPermissionsService : folderPermissions ,
}
sc := setupScenarioContext ( t , url )
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
c . Req . Body = mockRequestBody ( cmd )
c . Req . Header . Add ( "Content-Type" , "application/json" )
sc . context = c
sc . context . SignedInUser = & user . SignedInUser { OrgID : testOrgID , UserID : testUserID }
return hs . CreateFolder ( c )
type testCase struct {
description string
expectedCode int
permissions [ ] accesscontrol . Permission
newParentUid string
}
tcs := [ ] testCase {
{
description : "can move folder to another folder with specific permissions" ,
newParentUid : "newParentUid" ,
expectedCode : http . StatusOK ,
permissions : [ ] accesscontrol . Permission {
{ Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "uid" ) } ,
{ Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "newParentUid" ) } ,
} ,
} ,
{
description : "forbidden to move folder to another folder without the write access on the folder being moved" ,
newParentUid : "newParentUid" ,
expectedCode : http . StatusForbidden ,
permissions : [ ] accesscontrol . Permission {
{ Action : dashboards . ActionFoldersWrite , Scope : dashboards . ScopeFoldersProvider . GetResourceScopeUID ( "newParentUid" ) } ,
} ,
} ,
}
for _ , tc := range tcs {
srv := SetupAPITestServer ( t , func ( hs * HTTPServer ) {
hs . Cfg = & setting . Cfg {
RBACEnabled : true ,
}
hs . Features = featuremgmt . WithFeatures ( featuremgmt . FlagNestedFolders )
hs . folderService = folderService
} )
sc . m . Post ( routePattern , sc . defaultHandler )
fn ( sc )
} )
}
func callUpdateFolder ( sc * scenarioContext ) {
sc . fakeReqWithParams ( "PUT" , sc . url , map [ string ] string { } ) . exec ( )
}
func updateFolderScenario ( t * testing . T , desc string , url string , routePattern string , folderService folder . Service ,
cmd folder . UpdateFolderCommand , fn scenarioFunc ) {
setUpRBACGuardian ( t )
t . Run ( fmt . Sprintf ( "%s %s" , desc , url ) , func ( t * testing . T ) {
hs := HTTPServer {
Cfg : setting . NewCfg ( ) ,
AccessControl : acmock . New ( ) ,
folderService : folderService ,
}
sc := setupScenarioContext ( t , url )
sc . defaultHandler = routing . Wrap ( func ( c * contextmodel . ReqContext ) response . Response {
c . Req . Body = mockRequestBody ( cmd )
c . Req . Header . Add ( "Content-Type" , "application/json" )
sc . context = c
sc . context . SignedInUser = & user . SignedInUser { OrgID : testOrgID , UserID : testUserID }
return hs . UpdateFolder ( c )
t . Run ( tc . description , func ( t * testing . T ) {
input := strings . NewReader ( fmt . Sprintf ( "{ \"parentUid\": \"%s\"}" , tc . newParentUid ) )
req := srv . NewRequest ( http . MethodPost , "/api/folders/uid/move" , input )
req = webtest . RequestWithSignedInUser ( req , userWithPermissions ( 1 , tc . permissions ) )
resp , err := srv . SendJSON ( req )
require . NoError ( t , err )
require . Equal ( t , tc . expectedCode , resp . StatusCode )
require . NoError ( t , resp . Body . Close ( ) )
} )
sc . m . Put ( routePattern , sc . defaultHandler )
fn ( sc )
} )
}
}