mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 05:02:21 +08:00
649 lines
25 KiB
Go
649 lines
25 KiB
Go
package libraryelements
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/infra/serverlock"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/kinds/librarypanel"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
|
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
|
"github.com/grafana/grafana/pkg/services/apiserver"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
|
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
|
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
|
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
|
"github.com/grafana/grafana/pkg/services/search/sort"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
"github.com/grafana/grafana/pkg/web"
|
|
)
|
|
|
|
const userInDbName = "user_in_db"
|
|
const userInDbAvatar = "/avatar/402d08de060496d6b6874495fe20f5ad"
|
|
|
|
func TestMain(m *testing.M) {
|
|
testsuite.Run(m)
|
|
}
|
|
|
|
func TestIntegration_DeleteLibraryPanelsInFolder(t *testing.T) {
|
|
scenarioWithPanel(t, "When an admin tries to delete a folder that contains connected library elements, it should fail",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
dashJSON := map[string]any{
|
|
"panels": []any{
|
|
map[string]any{
|
|
"id": int64(1),
|
|
"gridPos": map[string]any{
|
|
"h": 6,
|
|
"w": 6,
|
|
"x": 0,
|
|
"y": 0,
|
|
},
|
|
},
|
|
map[string]any{
|
|
"id": int64(2),
|
|
"gridPos": map[string]any{
|
|
"h": 6,
|
|
"w": 6,
|
|
"x": 6,
|
|
"y": 0,
|
|
},
|
|
"libraryPanel": map[string]any{
|
|
"uid": sc.initialResult.Result.UID,
|
|
"name": sc.initialResult.Result.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dash := dashboards.Dashboard{
|
|
Title: "Testing DeleteLibraryElementsInFolder",
|
|
Data: simplejson.NewFromAny(dashJSON),
|
|
}
|
|
// nolint:staticcheck
|
|
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
|
|
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
|
|
require.NoError(t, err)
|
|
|
|
err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID)
|
|
require.EqualError(t, err, model.ErrFolderHasConnectedLibraryElements.Error())
|
|
})
|
|
|
|
scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
sc.service.AccessControl = acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
|
|
sc.service.AccessControl.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(sc.service.folderService))
|
|
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.folder.UID + "xxxx"})
|
|
resp := sc.service.deleteHandler(sc.reqContext)
|
|
require.Equal(t, http.StatusNotFound, resp.Status())
|
|
})
|
|
|
|
scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
// nolint:staticcheck
|
|
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "query0")
|
|
sc.reqContext.Req.Body = mockRequestBody(command)
|
|
resp := sc.service.createHandler(sc.reqContext)
|
|
require.Equal(t, 200, resp.Status())
|
|
|
|
resp = sc.service.getAllHandler(sc.reqContext)
|
|
require.Equal(t, 200, resp.Status())
|
|
var result libraryElementsSearch
|
|
err := json.Unmarshal(resp.Body(), &result)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result.Result)
|
|
require.Equal(t, 2, len(result.Result.Elements))
|
|
|
|
err = sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.UID)
|
|
require.NoError(t, err)
|
|
resp = sc.service.getAllHandler(sc.reqContext)
|
|
require.Equal(t, 200, resp.Status())
|
|
err = json.Unmarshal(resp.Body(), &result)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result.Result)
|
|
require.Equal(t, 0, len(result.Result.Elements))
|
|
})
|
|
}
|
|
|
|
func TestIntegration_GetLibraryPanelConnections(t *testing.T) {
|
|
scenarioWithPanel(t, "When an admin tries to get connections of library panel, it should succeed and return correct result",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
dashJSON := map[string]any{
|
|
"panels": []any{
|
|
map[string]any{
|
|
"id": int64(1),
|
|
"gridPos": map[string]any{
|
|
"h": 6,
|
|
"w": 6,
|
|
"x": 0,
|
|
"y": 0,
|
|
},
|
|
},
|
|
map[string]any{
|
|
"id": int64(2),
|
|
"gridPos": map[string]any{
|
|
"h": 6,
|
|
"w": 6,
|
|
"x": 6,
|
|
"y": 0,
|
|
},
|
|
"libraryPanel": map[string]any{
|
|
"uid": sc.initialResult.Result.UID,
|
|
"name": sc.initialResult.Result.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
dash := dashboards.Dashboard{
|
|
Title: "Testing GetLibraryPanelConnections",
|
|
Data: simplejson.NewFromAny(dashJSON),
|
|
}
|
|
// nolint:staticcheck
|
|
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
|
|
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
|
|
require.NoError(t, err)
|
|
|
|
// add a connection where the dashboard doesn't exist. Shouldn't be returned in the list
|
|
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, 99999999)
|
|
require.NoError(t, err)
|
|
|
|
var expected = func(res model.LibraryElementConnectionsResponse) model.LibraryElementConnectionsResponse {
|
|
return model.LibraryElementConnectionsResponse{
|
|
Result: []model.LibraryElementConnectionDTO{
|
|
{
|
|
ID: sc.initialResult.Result.ID,
|
|
Kind: sc.initialResult.Result.Kind,
|
|
ElementID: 1,
|
|
ConnectionID: dashInDB.ID,
|
|
ConnectionUID: dashInDB.UID,
|
|
Created: res.Result[0].Created,
|
|
CreatedBy: librarypanel.LibraryElementDTOMetaUser{
|
|
Id: 1,
|
|
Name: userInDbName,
|
|
AvatarUrl: userInDbAvatar,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
|
|
resp := sc.service.getConnectionsHandler(sc.reqContext)
|
|
var result = validateAndUnMarshalConnectionResponse(t, resp)
|
|
|
|
if diff := cmp.Diff(expected(result), result, getCompareOptions()...); diff != "" {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
|
|
scenarioWithPanel(t, "When a user tries to get connections of library panel, dashboards in inaccessible folders should not be returned",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
accessibleFolder := createFolder(t, sc, "AccessibleFolder", sc.service.folderService)
|
|
inaccessibleFolder := createFolder(t, sc, "InAccessibleFolder", sc.service.folderService)
|
|
restrictedUser := user.SignedInUser{
|
|
UserID: 2,
|
|
Name: "Non-Admin User",
|
|
Login: "non-admin-user",
|
|
OrgID: sc.user.OrgID,
|
|
OrgRole: org.RoleViewer,
|
|
LastSeenAt: time.Now(),
|
|
Permissions: map[int64]map[string][]string{
|
|
sc.user.OrgID: {
|
|
dashboards.ActionFoldersRead: {
|
|
dashboards.ScopeFoldersProvider.GetResourceScopeUID(accessibleFolder.UID),
|
|
},
|
|
dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsProvider.GetResourceScopeUID("*")},
|
|
},
|
|
},
|
|
}
|
|
|
|
command := getCreatePanelCommand(accessibleFolder.ID, accessibleFolder.UID, "Accessible Library Panel") // nolint:staticcheck
|
|
sc.reqContext.Req.Body = mockRequestBody(command)
|
|
resp := sc.service.createHandler(sc.reqContext)
|
|
libraryElement := validateAndUnMarshalResponse(t, resp)
|
|
|
|
dashJSON := map[string]any{
|
|
"panels": []any{
|
|
map[string]any{
|
|
"id": int64(1),
|
|
"gridPos": map[string]any{
|
|
"h": 6,
|
|
"w": 6,
|
|
"x": 0,
|
|
"y": 0,
|
|
},
|
|
"libraryPanel": map[string]any{
|
|
"uid": libraryElement.Result.UID,
|
|
"name": libraryElement.Result.Name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
accessibleDash := dashboards.Dashboard{
|
|
Title: "Accessible Dashboard",
|
|
Data: simplejson.NewFromAny(dashJSON),
|
|
}
|
|
|
|
// create the dashboard in the general folder, an accessible folder, and an inaccessible folder
|
|
dashInGeneral := createDashboard(t, sc.sqlStore, restrictedUser, &accessibleDash, 0, "")
|
|
err := sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{libraryElement.Result.UID}, dashInGeneral.ID)
|
|
require.NoError(t, err)
|
|
|
|
dashInAccessibleFolder := createDashboard(t, sc.sqlStore, restrictedUser, &accessibleDash, 0, accessibleFolder.UID)
|
|
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{libraryElement.Result.UID}, dashInAccessibleFolder.ID)
|
|
require.NoError(t, err)
|
|
|
|
dashInInaccessibleFolder := createDashboard(t, sc.sqlStore, restrictedUser, &accessibleDash, 0, inaccessibleFolder.UID)
|
|
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{libraryElement.Result.UID}, dashInInaccessibleFolder.ID)
|
|
require.NoError(t, err)
|
|
|
|
sc.reqContext.SignedInUser = &restrictedUser
|
|
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": libraryElement.Result.UID})
|
|
|
|
// connections should return the general folder one and the accessible folder one
|
|
connectionsResp := sc.service.getConnectionsHandler(sc.reqContext)
|
|
var result = validateAndUnMarshalConnectionResponse(t, connectionsResp)
|
|
require.Len(t, result.Result, 2)
|
|
uids := []string{result.Result[0].ConnectionUID, result.Result[1].ConnectionUID}
|
|
require.Contains(t, uids, dashInGeneral.UID)
|
|
require.Contains(t, uids, dashInAccessibleFolder.UID)
|
|
require.NotContains(t, uids, dashInInaccessibleFolder.UID)
|
|
})
|
|
|
|
scenarioWithPanel(t, "When an admin tries to create a connection with an element that exists, but the original folder does not, it should still succeed",
|
|
func(t *testing.T, sc scenarioContext) {
|
|
b, err := json.Marshal(map[string]string{"test": "test"})
|
|
require.NoError(t, err)
|
|
newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc)
|
|
sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll}
|
|
sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersDelete] = []string{dashboards.ScopeFoldersAll}
|
|
_, err = sc.service.createLibraryElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, model.CreateLibraryElementCommand{
|
|
FolderID: newFolder.ID, // nolint:staticcheck
|
|
FolderUID: &newFolder.UID,
|
|
Name: "Testing Library Panel With Deleted Folder",
|
|
Kind: 1,
|
|
Model: b,
|
|
UID: "panel-with-deleted-folder",
|
|
})
|
|
require.NoError(t, err)
|
|
err = sc.service.folderService.Delete(sc.reqContext.Req.Context(), &folder.DeleteFolderCommand{
|
|
UID: newFolder.UID,
|
|
OrgID: sc.reqContext.OrgID,
|
|
SignedInUser: sc.reqContext.SignedInUser,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
dash := dashboards.Dashboard{
|
|
Title: "Testing create element",
|
|
Data: simplejson.NewFromAny(map[string]any{}),
|
|
}
|
|
// nolint:staticcheck
|
|
dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID, sc.folder.UID)
|
|
err = sc.service.ConnectElementsToDashboard(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, []string{sc.initialResult.Result.UID}, dashInDB.ID)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
type libraryElement struct {
|
|
ID int64 `json:"id"`
|
|
OrgID int64 `json:"orgId"`
|
|
// Deprecated: use FolderUID instead
|
|
FolderID int64 `json:"folderId"`
|
|
FolderUID string `json:"folderUid"`
|
|
UID string `json:"uid"`
|
|
Name string `json:"name"`
|
|
Kind int64 `json:"kind"`
|
|
Type string `json:"type"`
|
|
Description string `json:"description"`
|
|
Model map[string]any `json:"model"`
|
|
Version int64 `json:"version"`
|
|
Meta model.LibraryElementDTOMeta `json:"meta"`
|
|
}
|
|
|
|
type libraryElementResult struct {
|
|
Result libraryElement `json:"result"`
|
|
}
|
|
|
|
type libraryElementArrayResult struct {
|
|
Result []libraryElement `json:"result"`
|
|
}
|
|
|
|
type libraryElementsSearch struct {
|
|
Result libraryElementsSearchResult `json:"result"`
|
|
}
|
|
|
|
type libraryElementsSearchResult struct {
|
|
TotalCount int64 `json:"totalCount"`
|
|
Elements []libraryElement `json:"elements"`
|
|
Page int `json:"page"`
|
|
PerPage int `json:"perPage"`
|
|
}
|
|
|
|
func getCreatePanelCommand(folderID int64, folderUID string, name string) model.CreateLibraryElementCommand {
|
|
command := getCreateCommandWithModel(folderID, folderUID, name, model.PanelElement, []byte(`
|
|
{
|
|
"datasource": "${DS_GDEV-TESTDATA}",
|
|
"id": 1,
|
|
"title": "Text - Library Panel",
|
|
"type": "text",
|
|
"description": "A description"
|
|
}
|
|
`))
|
|
|
|
return command
|
|
}
|
|
|
|
func getCreateCommandWithModel(folderID int64, folderUID, name string, kind model.LibraryElementKind, byteModel []byte) model.CreateLibraryElementCommand {
|
|
command := model.CreateLibraryElementCommand{
|
|
FolderUID: &folderUID,
|
|
Name: name,
|
|
Model: byteModel,
|
|
Kind: int64(kind),
|
|
}
|
|
|
|
return command
|
|
}
|
|
|
|
type scenarioContext struct {
|
|
ctx *web.Context
|
|
service *LibraryElementService
|
|
reqContext *contextmodel.ReqContext
|
|
user user.SignedInUser
|
|
folder *folder.Folder
|
|
initialResult libraryElementResult
|
|
sqlStore db.DB
|
|
log log.Logger
|
|
folderSvc folder.Service
|
|
}
|
|
|
|
func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash *dashboards.Dashboard, folderID int64, folderUID string) *dashboards.Dashboard {
|
|
// nolint:staticcheck
|
|
dash.FolderID = folderID
|
|
dash.FolderUID = folderUID
|
|
dashItem := &dashboards.SaveDashboardDTO{
|
|
Dashboard: dash,
|
|
Message: "",
|
|
OrgID: user.OrgID,
|
|
User: &user,
|
|
Overwrite: false,
|
|
}
|
|
|
|
features := featuremgmt.WithFeatures()
|
|
cfg := setting.NewCfg()
|
|
quotaService := quotatest.New(false, nil)
|
|
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
ac := actest.FakeAccessControl{ExpectedEvaluate: true}
|
|
folderPermissions := acmock.NewMockedPermissionsService()
|
|
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
|
dashboardPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
|
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
|
fStore := folderimpl.ProvideStore(sqlStore)
|
|
folderSvc := folderimpl.ProvideService(
|
|
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
|
|
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
|
_, err = folderSvc.Create(context.Background(), &folder.CreateFolderCommand{UID: folderUID, SignedInUser: &user, Title: folderUID + "-title"})
|
|
require.NoError(t, err)
|
|
service, err := dashboardservice.ProvideDashboardServiceImpl(
|
|
cfg, dashboardStore, folderStore,
|
|
features, folderPermissions, ac,
|
|
actest.FakeService{},
|
|
folderSvc,
|
|
nil,
|
|
client.MockTestRestConfig{},
|
|
nil,
|
|
quotaService,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
dualwrite.ProvideTestService(),
|
|
sort.ProvideService(),
|
|
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
|
|
kvstore.NewFakeKVStore(),
|
|
)
|
|
require.NoError(t, err)
|
|
service.RegisterDashboardPermissions(dashboardPermissions)
|
|
dashboard, err := service.SaveDashboard(context.Background(), dashItem, true)
|
|
require.NoError(t, err)
|
|
|
|
return dashboard
|
|
}
|
|
|
|
func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc folder.Service) *folder.Folder {
|
|
t.Helper()
|
|
ctx := identity.WithRequester(context.Background(), &sc.user)
|
|
folder, err := folderSvc.Create(ctx, &folder.CreateFolderCommand{
|
|
OrgID: sc.user.OrgID, Title: title, UID: "uid_for_" + title, SignedInUser: &sc.user,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user permissions on the newly created folder so that they can interact with library elements stored in it
|
|
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersWrite], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
|
|
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionFoldersRead], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
|
|
sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate] = append(sc.reqContext.Permissions[sc.user.OrgID][dashboards.ActionDashboardsCreate], dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.UID))
|
|
|
|
return folder
|
|
}
|
|
|
|
func validateAndUnMarshalResponse(t *testing.T, resp response.Response) libraryElementResult {
|
|
t.Helper()
|
|
|
|
require.Equal(t, 200, resp.Status())
|
|
|
|
var result = libraryElementResult{}
|
|
err := json.Unmarshal(resp.Body(), &result)
|
|
require.NoError(t, err)
|
|
|
|
return result
|
|
}
|
|
|
|
func validateAndUnMarshalConnectionResponse(t *testing.T, resp response.Response) model.LibraryElementConnectionsResponse {
|
|
t.Helper()
|
|
require.Equal(t, 200, resp.Status())
|
|
var result = model.LibraryElementConnectionsResponse{}
|
|
err := json.Unmarshal(resp.Body(), &result)
|
|
require.NoError(t, err)
|
|
return result
|
|
}
|
|
|
|
func validateAndUnMarshalArrayResponse(t *testing.T, resp response.Response) libraryElementArrayResult {
|
|
t.Helper()
|
|
|
|
require.Equal(t, 200, resp.Status())
|
|
var result = libraryElementArrayResult{}
|
|
err := json.Unmarshal(resp.Body(), &result)
|
|
require.NoError(t, err)
|
|
|
|
return result
|
|
}
|
|
|
|
// setupTestScenario performs the common setup for library element tests
|
|
func setupTestScenario(t *testing.T) scenarioContext {
|
|
t.Helper()
|
|
|
|
orgID := int64(1)
|
|
role := org.RoleAdmin
|
|
usr := user.SignedInUser{
|
|
UserID: 1,
|
|
Name: "Signed In User",
|
|
Login: "signed_in_user",
|
|
Email: "signed.in.user@test.com",
|
|
OrgID: orgID,
|
|
OrgRole: role,
|
|
LastSeenAt: time.Now(),
|
|
// Allow user to create folders and library elements
|
|
Permissions: map[int64]map[string][]string{
|
|
1: {
|
|
dashboards.ActionFoldersCreate: {dashboards.ScopeFoldersAll},
|
|
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll},
|
|
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll},
|
|
ActionLibraryPanelsCreate: {dashboards.ScopeFoldersAll},
|
|
ActionLibraryPanelsRead: {ScopeLibraryPanelsAll},
|
|
ActionLibraryPanelsWrite: {ScopeLibraryPanelsAll},
|
|
ActionLibraryPanelsDelete: {ScopeLibraryPanelsAll},
|
|
},
|
|
},
|
|
}
|
|
req := &http.Request{
|
|
Header: http.Header{
|
|
"Content-Type": []string{"application/json"},
|
|
},
|
|
}
|
|
ctx := identity.WithRequester(context.Background(), &usr)
|
|
req = req.WithContext(ctx)
|
|
webCtx := web.Context{Req: req}
|
|
|
|
features := featuremgmt.WithFeatures()
|
|
tracer := tracing.InitializeTracerForTest()
|
|
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
|
quotaService := quotatest.New(false, nil)
|
|
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
|
require.NoError(t, err)
|
|
ac := acimpl.ProvideAccessControl(features)
|
|
folderPermissions := acmock.NewMockedPermissionsService()
|
|
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
|
dashboardPermissions := acmock.NewMockedPermissionsService()
|
|
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
|
|
fStore := folderimpl.ProvideStore(sqlStore)
|
|
publicDash := &publicdashboards.FakePublicDashboardServiceWrapper{}
|
|
publicDash.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
|
folderSvc := folderimpl.ProvideService(
|
|
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
|
|
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
|
alertStore, err := ngstore.ProvideDBStore(cfg, features, sqlStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
err = folderSvc.RegisterService(alertStore)
|
|
require.NoError(t, err)
|
|
dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl(
|
|
cfg, dashboardStore, folderStore,
|
|
features, folderPermissions, ac, actest.FakeService{}, folderSvc,
|
|
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
|
|
serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()),
|
|
kvstore.NewFakeKVStore(),
|
|
)
|
|
require.NoError(t, dashSvcErr)
|
|
dashService.RegisterDashboardPermissions(dashboardPermissions)
|
|
service := LibraryElementService{
|
|
Cfg: cfg,
|
|
features: featuremgmt.WithFeatures(),
|
|
SQLStore: sqlStore,
|
|
folderService: folderSvc,
|
|
dashboardsService: dashService,
|
|
AccessControl: ac,
|
|
log: log.NewNopLogger(),
|
|
}
|
|
|
|
service.AccessControl.RegisterScopeAttributeResolver(LibraryPanelUIDScopeResolver(&service, folderSvc))
|
|
|
|
// deliberate difference between signed in user and user in db to make it crystal clear
|
|
// what to expect in the tests
|
|
// In the real world these are identical
|
|
cmd := user.CreateUserCommand{
|
|
Email: "user.in.db@test.com",
|
|
Name: "User In DB",
|
|
Login: userInDbName,
|
|
}
|
|
orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
|
|
require.NoError(t, err)
|
|
usrSvc, err := userimpl.ProvideService(
|
|
sqlStore, orgSvc, cfg, nil, nil, tracer,
|
|
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
)
|
|
require.NoError(t, err)
|
|
_, err = usrSvc.Create(context.Background(), &cmd)
|
|
require.NoError(t, err)
|
|
|
|
sc := scenarioContext{
|
|
user: usr,
|
|
ctx: &webCtx,
|
|
service: &service,
|
|
sqlStore: sqlStore,
|
|
reqContext: &contextmodel.ReqContext{
|
|
Context: &webCtx,
|
|
SignedInUser: &usr,
|
|
},
|
|
folderSvc: folderSvc,
|
|
}
|
|
|
|
sc.folder = createFolder(t, sc, "ScenarioFolder", folderSvc)
|
|
|
|
return sc
|
|
}
|
|
|
|
func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
|
t.Helper()
|
|
|
|
t.Run(desc, func(t *testing.T) {
|
|
sc := setupTestScenario(t)
|
|
|
|
// nolint:staticcheck
|
|
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Text - Library Panel")
|
|
sc.reqContext.Req.Body = mockRequestBody(command)
|
|
resp := sc.service.createHandler(sc.reqContext)
|
|
sc.initialResult = validateAndUnMarshalResponse(t, resp)
|
|
sc.log = log.New("libraryelements-test")
|
|
|
|
fn(t, sc)
|
|
})
|
|
}
|
|
|
|
// testScenario is a wrapper around t.Run performing common setup for library panel tests.
|
|
// It takes your real test function as a callback.
|
|
func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioContext)) {
|
|
t.Helper()
|
|
|
|
t.Run(desc, func(t *testing.T) {
|
|
sc := setupTestScenario(t)
|
|
|
|
fn(t, sc)
|
|
})
|
|
}
|
|
|
|
func getCompareOptions() []cmp.Option {
|
|
return []cmp.Option{
|
|
cmp.Transformer("Time", func(in time.Time) int64 {
|
|
return in.UTC().Unix()
|
|
}),
|
|
}
|
|
}
|
|
|
|
func mockRequestBody(v any) io.ReadCloser {
|
|
b, _ := json.Marshal(v)
|
|
return io.NopCloser(bytes.NewReader(b))
|
|
}
|