Files
grafana/pkg/tests/api/folders/api_folder_test.go
Peter Štibraný 7fd9ab9481 Replace check for integration tests. (#110707)
* Replace check for integration tests.
* Revert changes in pkg/tsdb/mysql packages.
* Fix formatting of few tests.
2025-09-08 15:49:49 +02:00

601 lines
20 KiB
Go

package folders
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/grafana/grafana-openapi-client-go/client/folders"
"github.com/grafana/grafana-openapi-client-go/models"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setFolderPermissions(t *testing.T, grafanaListedAddr string, folderUID string, permissions []map[string]interface{}) {
t.Helper()
permissionPayload := map[string]interface{}{
"items": permissions,
}
payloadBytes, err := json.Marshal(permissionPayload)
require.NoError(t, err)
u := fmt.Sprintf("http://admin:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID)
resp, err := http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
err = resp.Body.Close()
require.NoError(t, err)
}
func TestIntegrationFolderServiceGetFolder(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, p)
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
parentResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Parent Folder",
UID: "parent-folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, parentResp.Code())
childResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Child Folder",
UID: "child-folder",
ParentUID: parentResp.Payload.UID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, childResp.Code())
testCases := []struct {
name string
WithFullpath bool
WithFullpathUIDs bool
expectedFullpath string
expectedFullpathUIDs string
}{
{
name: "when flag is on and WithFullpath is false",
WithFullpath: false,
expectedFullpath: "",
},
{
name: "when flag is on and WithFullpath is true",
WithFullpath: true,
expectedFullpath: "Parent Folder/Child Folder",
},
{
name: "when flag is on and WithFullpathUIDs is false",
WithFullpathUIDs: false,
expectedFullpathUIDs: "",
},
{
name: "when flag is on and WithFullpathUIDs is true",
WithFullpathUIDs: true,
expectedFullpathUIDs: "parent-folder/child-folder",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp, err := adminClient.Folders.GetFolderByUID(childResp.Payload.UID)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Equal(t, childResp.Payload.UID, resp.Payload.UID)
require.Equal(t, childResp.Payload.CreatedBy, resp.Payload.CreatedBy)
require.Equal(t, childResp.Payload.UpdatedBy, resp.Payload.UpdatedBy)
})
}
}
func TestIntegrationUpdateFolder(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
t.Run("update folder should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.UpdateFolder(resp.Payload.UID, &models.UpdateFolderCommand{
Title: "new title",
Version: resp.Payload.Version,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Equal(t, "new title", resp.Payload.Title)
})
t.Run("When updating a folder it should trim leading and trailing spaces", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "my folder 2",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
updateResp, err := adminClient.Folders.UpdateFolder(resp.Payload.UID, &models.UpdateFolderCommand{
Title: " my updated folder 2 ",
Version: resp.Payload.Version,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, updateResp.Code())
require.Equal(t, "my updated folder 2", updateResp.Payload.Title)
})
}
func TestIntegrationCreateFolder(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
})
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
t.Run("create folder under root should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
t.Run("create folder with same name under root should succeed", func(t *testing.T) {
_, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
})
})
t.Run("When creating a folder it should trim leading and trailing spaces", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: " my folder ",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Equal(t, "my folder", resp.Payload.Title)
})
t.Run("create without UID, no error", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "myFolder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.NotEmpty(t, resp.Payload.UID)
})
}
func TestIntegrationNestedFolders(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableAnonymous: true,
EnableQuota: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
t.Run("create folder under root should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
t.Run("create folder with same name under root should succeed", func(t *testing.T) {
_, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "folder",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
})
})
t.Run("create subfolder should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "parent",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
parentUID := resp.Payload.UID
resp, err = adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "subfolder",
ParentUID: parentUID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
t.Run("create subfolder with same name should succeed", func(t *testing.T) {
resp, err = adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "subfolder",
ParentUID: parentUID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
})
t.Run("create subfolder with same name under other folder should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "other",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
other := resp.Payload.UID
resp, err = adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "subfolder",
ParentUID: other,
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Code())
assert.Equal(t, other, resp.Payload.ParentUID)
subfolderUnderOther := resp.Payload.UID
t.Run("move subfolder to other folder containing folder with the same name should be ok", func(t *testing.T) {
resp, err := adminClient.Folders.MoveFolder(subfolderUnderOther, &models.MoveFolderCommand{
ParentUID: parentUID,
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Code())
assert.Equal(t, parentUID, resp.Payload.ParentUID)
})
t.Run("move subfolder to root should succeed", func(t *testing.T) {
resp, err := adminClient.Folders.MoveFolder(subfolderUnderOther, &models.MoveFolderCommand{})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Code())
assert.Equal(t, "", resp.Payload.ParentUID)
})
t.Run("should prevent moving folders to escalate permissions", func(t *testing.T) {
store, cfg := env.SQLStore, env.Cfg
orgID := int64(1)
editorUser := tests.CreateUser(t, store, cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleViewer),
OrgID: orgID,
Password: "editor",
Login: "editor",
})
editorClient := tests.GetClient(grafanaListedAddr, "editor", "editor")
sourceResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Source Folder",
UID: "source-folder-limited",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, sourceResp.Code())
destResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Destination Folder",
UID: "dest-folder-higher",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, destResp.Code())
// downgrade to viewer on destination folder
setFolderPermissions(t, grafanaListedAddr, destResp.Payload.UID, []map[string]interface{}{
{
"userId": editorUser,
"permission": 1,
},
})
_, err = editorClient.Folders.MoveFolder(sourceResp.Payload.UID, &models.MoveFolderCommand{
ParentUID: destResp.Payload.UID,
})
require.Error(t, err)
})
})
})
t.Run("should delete children of a folder when deleted", func(t *testing.T) {
parentResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Parent Folder for Delete",
UID: "parent-folder-delete",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, parentResp.Code())
childResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Child Folder for Delete",
UID: "child-folder-delete",
ParentUID: parentResp.Payload.UID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, childResp.Code())
_, err = adminClient.Folders.DeleteFolder(folders.NewDeleteFolderParams().WithFolderUID(parentResp.Payload.UID))
require.NoError(t, err)
_, err = adminClient.Folders.GetFolderByUID(childResp.Payload.UID)
require.Error(t, err)
})
}
func TestIntegrationSharedWithMe(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
store, cfg := env.SQLStore, env.Cfg
orgID := int64(1)
noneUser := tests.CreateUser(t, store, cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleNone),
OrgID: orgID,
Password: "none",
Login: "none",
})
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
noneClient := tests.GetClient(grafanaListedAddr, "none", "none")
t.Run("Should get folders shared with given user", func(t *testing.T) {
folder1Resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Folder 1",
UID: "folder-1",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, folder1Resp.Code())
folder2Resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Folder 2",
UID: "folder-2",
ParentUID: folder1Resp.Payload.UID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, folder2Resp.Code())
setFolderPermissions(t, grafanaListedAddr, folder2Resp.Payload.UID, []map[string]interface{}{
{
"userId": noneUser,
"permission": 1,
},
})
resp, err := noneClient.Folders.GetFolders(nil)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Len(t, resp.Payload, 1)
})
}
func TestIntegrationBasicRoles(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
store, cfg := env.SQLStore, env.Cfg
orgID := int64(1)
tests.CreateUser(t, store, cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleViewer),
OrgID: orgID,
Password: "viewer",
Login: "viewer",
})
tests.CreateUser(t, store, cfg, user.CreateUserCommand{
OrgID: orgID,
DefaultOrgRole: string(org.RoleEditor),
Password: "editor",
Login: "editor",
})
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
viewerClient := tests.GetClient(grafanaListedAddr, "viewer", "viewer")
t.Run("Folder service tests", func(t *testing.T) {
t.Run("Given user has no permissions", func(t *testing.T) {
folderUID := "test-folder-no-perm"
t.Run("get folder by uid should return access denied error", func(t *testing.T) {
_, err := viewerClient.Folders.GetFolderByUID(folderUID)
require.Error(t, err)
})
t.Run("create folder should return access denied error", func(t *testing.T) {
_, err := viewerClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Test Folder",
UID: "test-create-no-perm",
})
require.Error(t, err)
})
t.Run("update folder should return access denied error", func(t *testing.T) {
_, err := viewerClient.Folders.UpdateFolder(folderUID, &models.UpdateFolderCommand{
Title: "Updated Title",
})
require.Error(t, err)
})
t.Run("delete folder by uid should return access denied error", func(t *testing.T) {
_, err := viewerClient.Folders.DeleteFolder(folders.NewDeleteFolderParams().WithFolderUID(folderUID))
require.Error(t, err)
})
})
t.Run("Given user has permission to save", func(t *testing.T) {
t.Run("create folder should be ok", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Test-Folder",
UID: "test-folder-create",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Equal(t, "Test-Folder", resp.Payload.Title)
})
t.Run("can never create a folder with the uid of general", func(t *testing.T) {
_, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Test-Folder",
UID: "general",
})
require.Error(t, err)
})
t.Run("update should be okay", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Folder to Update",
UID: "test-folder-update",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
updateResp, err := adminClient.Folders.UpdateFolder(resp.Payload.UID, &models.UpdateFolderCommand{
Title: "Updated Folder Title",
Version: resp.Payload.Version,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, updateResp.Code())
require.Equal(t, "Updated Folder Title", updateResp.Payload.Title)
})
t.Run("delete should be okay", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Folder to Delete",
UID: "test-folder-delete",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
_, err = adminClient.Folders.DeleteFolder(folders.NewDeleteFolderParams().WithFolderUID(resp.Payload.UID))
require.NoError(t, err)
})
})
t.Run("Given user has permission to view", func(t *testing.T) {
t.Run("get folder by uid should return folder", func(t *testing.T) {
resp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Viewable Folder",
UID: "test-folder-view",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
getResp, err := viewerClient.Folders.GetFolderByUID(resp.Payload.UID)
require.NoError(t, err)
require.Equal(t, http.StatusOK, getResp.Code())
require.Equal(t, "Viewable Folder", getResp.Payload.Title)
})
t.Run("get folder by uid and uid is general should return the root folder object", func(t *testing.T) {
resp, err := adminClient.Folders.GetFolderByUID("general")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.Code())
require.Equal(t, "Dashboards", resp.Payload.Title)
})
})
})
}
func TestIntegrationFineGrainedPermissions(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
store, cfg := env.SQLStore, env.Cfg
orgID := int64(1)
noneUser := tests.CreateUser(t, store, cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleNone),
OrgID: orgID,
Password: "none",
Login: "none",
})
adminClient := tests.GetClient(grafanaListedAddr, "admin", "admin")
noneClient := tests.GetClient(grafanaListedAddr, "none", "none")
// create parent -> child -> grandchild folder structure
parentResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Parent Folder",
UID: "parent-folder-fine-grained",
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, parentResp.Code())
childResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Child Folder",
UID: "child-folder-fine-grained",
ParentUID: parentResp.Payload.UID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, childResp.Code())
grandchildResp, err := adminClient.Folders.CreateFolder(&models.CreateFolderCommand{
Title: "Grandchild Folder",
UID: "grandchild-folder-fine-grained",
ParentUID: childResp.Payload.UID,
})
require.NoError(t, err)
require.Equal(t, http.StatusOK, grandchildResp.Code())
// only grant access to the child folder, should then get child & grandchildren folders
setFolderPermissions(t, grafanaListedAddr, childResp.Payload.UID, []map[string]interface{}{
{
"userId": noneUser,
"permission": 1,
},
})
childFolderResp, err := noneClient.Folders.GetFolderByUID(childResp.Payload.UID)
require.NoError(t, err)
require.Equal(t, http.StatusOK, childFolderResp.Code())
assert.Equal(t, "Child Folder", childFolderResp.Payload.Title)
grandchildFolderResp, err := noneClient.Folders.GetFolderByUID(grandchildResp.Payload.UID)
require.NoError(t, err)
require.Equal(t, http.StatusOK, grandchildFolderResp.Code())
assert.Equal(t, "Grandchild Folder", grandchildFolderResp.Payload.Title)
_, err = noneClient.Folders.GetFolderByUID(parentResp.Payload.UID)
require.Error(t, err, "None user should not be able to access parent folder directly")
}