LibraryPanels: Adds folder checks and permissions (#31473)

* Refactor: adds permissions for library panel creation

* Refactor: checks folder permissions for patch requests

* Chore: changes after PR comments

* Refactor: adds permissions to delete

* Refactor: moves get all permission tests out of get all tests

* Chore: move out get all tests to a separate file

* Refactor: adds permissions to get handler

* Refactor: fixes a bug with getting library panels in General folder

* Refactor: adds permissions for connect/disconnect

* Refactor: adds permissions and tests for get connected dashboards

* Tests: adds tests for connected dashboards in General Folder
This commit is contained in:
Hugo Häggmark 2021-03-01 15:33:17 +01:00 committed by GitHub
parent 475fd994a0
commit 583b94557b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1333 additions and 690 deletions

View File

@ -4,6 +4,7 @@ import (
"errors"
"github.com/go-macaron/binding"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
@ -35,6 +36,12 @@ func (lps *LibraryPanelService) createHandler(c *models.ReqContext, cmd createLi
if errors.Is(err, errLibraryPanelAlreadyExists) {
return response.Error(400, errLibraryPanelAlreadyExists.Error(), err)
}
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
}
return response.Error(500, "Failed to create library panel", err)
}
@ -47,6 +54,12 @@ func (lps *LibraryPanelService) connectHandler(c *models.ReqContext) response.Re
if errors.Is(err, errLibraryPanelNotFound) {
return response.Error(404, errLibraryPanelNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
}
return response.Error(500, "Failed to connect library panel", err)
}
@ -60,6 +73,12 @@ func (lps *LibraryPanelService) deleteHandler(c *models.ReqContext) response.Res
if errors.Is(err, errLibraryPanelNotFound) {
return response.Error(404, errLibraryPanelNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
}
return response.Error(500, "Failed to delete library panel", err)
}
@ -76,6 +95,12 @@ func (lps *LibraryPanelService) disconnectHandler(c *models.ReqContext) response
if errors.Is(err, errLibraryPanelDashboardNotFound) {
return response.Error(404, errLibraryPanelDashboardNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
}
return response.Error(500, "Failed to disconnect library panel", err)
}
@ -128,6 +153,12 @@ func (lps *LibraryPanelService) patchHandler(c *models.ReqContext, cmd patchLibr
if errors.Is(err, errLibraryPanelNotFound) {
return response.Error(404, errLibraryPanelNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err)
}
if errors.Is(err, models.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err)
}
return response.Error(500, "Failed to update library panel", err)
}

View File

@ -66,6 +66,9 @@ func (lps *LibraryPanelService) createLibraryPanel(c *models.ReqContext, cmd cre
}
err := lps.SQLStore.WithTransactionalDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
if err := requirePermissionsOnFolder(c.SignedInUser, cmd.FolderID); err != nil {
return err
}
if _, err := session.Insert(&libraryPanel); err != nil {
if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return errLibraryPanelAlreadyExists
@ -108,6 +111,9 @@ func connectDashboard(session *sqlstore.DBSession, dialect migrator.Dialect, use
if err != nil {
return err
}
if err := requirePermissionsOnFolder(user, panel.FolderID); err != nil {
return err
}
// TODO add check that dashboard exists
@ -161,7 +167,9 @@ func (lps *LibraryPanelService) deleteLibraryPanel(c *models.ReqContext, uid str
if err != nil {
return err
}
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err
}
if _, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=?", panel.ID); err != nil {
return err
}
@ -187,6 +195,9 @@ func (lps *LibraryPanelService) disconnectDashboard(c *models.ReqContext, uid st
if err != nil {
return err
}
if err := requirePermissionsOnFolder(c.SignedInUser, panel.FolderID); err != nil {
return err
}
result, err := session.Exec("DELETE FROM library_panel_dashboard WHERE librarypanel_id=? and dashboard_id=?", panel.ID, dashboardID)
if err != nil {
@ -242,9 +253,31 @@ func getLibraryPanel(session *sqlstore.DBSession, uid string, orgID int64) (Libr
func (lps *LibraryPanelService) getLibraryPanel(c *models.ReqContext, uid string) (LibraryPanelDTO, error) {
var libraryPanel LibraryPanelWithMeta
err := lps.SQLStore.WithDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
var err error
libraryPanel, err = getLibraryPanel(session, uid, c.SignedInUser.OrgId)
return err
libraryPanels := make([]LibraryPanelWithMeta, 0)
builder := sqlstore.SQLBuilder{}
builder.Write(sqlStatmentLibrayPanelDTOWithMeta)
builder.Write(` WHERE lp.uid=? AND lp.org_id=? AND lp.folder_id=0`, uid, c.SignedInUser.OrgId)
builder.Write(" UNION ")
builder.Write(sqlStatmentLibrayPanelDTOWithMeta)
builder.Write(" INNER JOIN dashboard AS dashboard on lp.folder_id = dashboard.id AND lp.folder_id <> 0")
builder.Write(` WHERE lp.uid=? AND lp.org_id=?`, uid, c.SignedInUser.OrgId)
if c.SignedInUser.OrgRole != models.ROLE_ADMIN {
builder.WriteDashboardPermissionFilter(c.SignedInUser, models.PERMISSION_VIEW)
}
builder.Write(` OR dashboard.id=0`)
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryPanels); err != nil {
return err
}
if len(libraryPanels) == 0 {
return errLibraryPanelNotFound
}
if len(libraryPanels) > 1 {
return fmt.Errorf("found %d panels, while expecting at most one", len(libraryPanels))
}
libraryPanel = libraryPanels[0]
return nil
})
dto := LibraryPanelDTO{
@ -281,8 +314,11 @@ func (lps *LibraryPanelService) getAllLibraryPanels(c *models.ReqContext, limit
err := lps.SQLStore.WithDbSession(c.Context.Req.Context(), func(session *sqlstore.DBSession) error {
builder := sqlstore.SQLBuilder{}
builder.Write(sqlStatmentLibrayPanelDTOWithMeta)
builder.Write(" LEFT JOIN dashboard AS dashboard on lp.folder_id = dashboard.id")
builder.Write(` WHERE lp.org_id = ?`, c.SignedInUser.OrgId)
builder.Write(` WHERE lp.org_id=? AND lp.folder_id=0`, c.SignedInUser.OrgId)
builder.Write(" UNION ")
builder.Write(sqlStatmentLibrayPanelDTOWithMeta)
builder.Write(" INNER JOIN dashboard AS dashboard on lp.folder_id = dashboard.id AND lp.folder_id<>0")
builder.Write(` WHERE lp.org_id=?`, c.SignedInUser.OrgId)
if c.SignedInUser.OrgRole != models.ROLE_ADMIN {
builder.WriteDashboardPermissionFilter(c.SignedInUser, models.PERMISSION_VIEW)
}
@ -336,12 +372,15 @@ func (lps *LibraryPanelService) getConnectedDashboards(c *models.ReqContext, uid
if err != nil {
return err
}
var libraryPanelDashboards []libraryPanelDashboard
session.Table("library_panel_dashboard")
session.Where("librarypanel_id=?", panel.ID)
err = session.Find(&libraryPanelDashboards)
if err != nil {
builder := sqlstore.SQLBuilder{}
builder.Write("SELECT lpd.* FROM library_panel_dashboard lpd")
builder.Write(" INNER JOIN dashboard AS dashboard on lpd.dashboard_id = dashboard.id")
builder.Write(` WHERE lpd.librarypanel_id=?`, panel.ID)
if c.SignedInUser.OrgRole != models.ROLE_ADMIN {
builder.WriteDashboardPermissionFilter(c.SignedInUser, models.PERMISSION_VIEW)
}
if err := session.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&libraryPanelDashboards); err != nil {
return err
}
@ -399,6 +438,29 @@ func (lps *LibraryPanelService) getLibraryPanelsForDashboardID(c *models.ReqCont
return libraryPanelMap, err
}
func handleFolderIDPatches(panelToPatch *LibraryPanel, fromFolderID int64, toFolderID int64, user *models.SignedInUser) error {
// FolderID was not provided in the PATCH request
if toFolderID == -1 {
toFolderID = fromFolderID
}
// FolderID was provided in the PATCH request
if toFolderID != -1 && toFolderID != fromFolderID {
if err := requirePermissionsOnFolder(user, toFolderID); err != nil {
return err
}
}
// Always check permissions for the folder where library panel resides
if err := requirePermissionsOnFolder(user, fromFolderID); err != nil {
return err
}
panelToPatch.FolderID = toFolderID
return nil
}
// patchLibraryPanel updates a Library Panel.
func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patchLibraryPanelCommand, uid string) (LibraryPanelDTO, error) {
var dto LibraryPanelDTO
@ -421,19 +483,18 @@ func (lps *LibraryPanelService) patchLibraryPanel(c *models.ReqContext, cmd patc
UpdatedBy: c.SignedInUser.UserId,
}
if cmd.FolderID == 0 {
libraryPanel.FolderID = panelInDB.FolderID
}
if cmd.Name == "" {
libraryPanel.Name = panelInDB.Name
}
if cmd.Model == nil {
libraryPanel.Model = panelInDB.Model
}
if err := handleFolderIDPatches(&libraryPanel, panelInDB.FolderID, cmd.FolderID, c.SignedInUser); err != nil {
return err
}
if err := syncTitleWithName(&libraryPanel); err != nil {
return err
}
if rowsAffected, err := session.ID(panelInDB.ID).Update(&libraryPanel); err != nil {
if lps.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return errLibraryPanelAlreadyExists

View File

@ -0,0 +1,39 @@
package librarypanels
import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
)
func isGeneralFolder(folderID int64) bool {
return folderID == 0
}
func requirePermissionsOnFolder(user *models.SignedInUser, folderID int64) error {
if isGeneralFolder(folderID) && user.HasRole(models.ROLE_EDITOR) {
return nil
}
if isGeneralFolder(folderID) && user.HasRole(models.ROLE_VIEWER) {
return models.ErrFolderAccessDenied
}
s := dashboards.NewFolderService(user.OrgId, user)
folder, err := s.GetFolderByID(folderID)
if err != nil {
return err
}
g := guardian.New(folder.Id, user.OrgId, user)
canEdit, err := g.CanEdit()
if err != nil {
return err
}
if !canEdit {
return models.ErrFolderAccessDenied
}
return nil
}

View File

@ -0,0 +1,97 @@
package librarypanels
import (
"encoding/json"
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
func TestConnectLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to create a connection for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to create a connection that already exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
}
func TestDisconnectLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection with a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
resp := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection that does exist, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
}
func TestGetConnectedDashboards(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists, but has no connections, it should return none",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 0, len(dashResult.Result))
})
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists and has connections, it should return connected dashboard IDs",
func(t *testing.T, sc scenarioContext) {
firstDash := createDashboard(t, sc.user, "Dash 1", 0)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(firstDash.Id, 10)})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
secondDash := createDashboard(t, sc.user, "Dash 2", 0)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": strconv.FormatInt(secondDash.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 2, len(dashResult.Result))
require.Equal(t, firstDash.Id, dashResult.Result[0])
require.Equal(t, secondDash.Id, dashResult.Result[1])
})
}

View File

@ -0,0 +1,96 @@
package librarypanels
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestCreateLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to create a library panel that already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to create a library panel that does not exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: sc.initialResult.Result.UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: sc.initialResult.Result.Meta.Created,
Updated: sc.initialResult.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, sc.initialResult, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
testScenario(t, "When an admin tries to create a library panel where name and panel title differ, it should update panel title",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result.UID,
Name: "Library Panel Name",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Library Panel Name",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
}

View File

@ -0,0 +1,33 @@
package librarypanels
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
func TestDeleteLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
}

View File

@ -0,0 +1,152 @@
package librarypanels
import (
"encoding/json"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
func TestGetAllLibraryPanels(t *testing.T) {
testScenario(t, "When an admin tries to get all library panels and none exists, it should return none",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.NotNil(t, result.Result)
require.Equal(t, 0, len(result.Result))
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels and two exist, it should succeed",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel2")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
var expected = libraryPanelsResult{
Result: []libraryPanel{
{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result[0].UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result[0].Meta.Created,
Updated: result.Result[0].Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
},
},
{
ID: 2,
OrgID: 1,
FolderID: 1,
UID: result.Result[1].UID,
Name: "Text - Library Panel2",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel2",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result[1].Meta.Created,
Updated: result.Result[1].Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels and two exist but only one is connected, it should succeed and return correct connected dashboards",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel2")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "1"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var results libraryPanelsResult
err := json.Unmarshal(resp.Body(), &results)
require.NoError(t, err)
require.Equal(t, int64(0), results.Result[0].Meta.ConnectedDashboards)
require.Equal(t, int64(2), results.Result[1].Meta.ConnectedDashboards)
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels in a different org, none should be returned",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.Equal(t, 1, len(result.Result))
require.Equal(t, int64(1), result.Result[0].FolderID)
require.Equal(t, "Text - Library Panel", result.Result[0].Name)
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
result = libraryPanelsResult{}
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.NotNil(t, result.Result)
require.Equal(t, 0, len(result.Result))
})
}

View File

@ -0,0 +1,85 @@
package librarypanels
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
)
func TestGetLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that exists, it should succeed and return correct result",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getHandler(sc.reqContext)
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result.UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: UserInDbName,
AvatarUrl: UserInDbAvatar,
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that exists in an other org, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel with 2 connected dashboards, it should succeed and return correct connected dashboards",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.getHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
require.Equal(t, int64(2), result.Result.Meta.ConnectedDashboards)
})
}

View File

@ -0,0 +1,189 @@
package librarypanels
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestPatchLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id,
Name: "Panel - New name",
Model: []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"title": "Model - New name",
"type": "text"
}
`),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: newFolder.Id,
UID: sc.initialResult.Result.UID,
Name: "Panel - New name",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Panel - New name",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 2,
Created: sc.initialResult.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
cmd := patchLibraryPanelCommand{
FolderID: newFolder.Id,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.FolderID = newFolder.Id
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with name only, it should change name successfully, sync title and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
FolderID: -1,
Name: "New Name",
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Name = "New Name"
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
sc.initialResult.Result.Model["title"] = "New Name"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with model only, it should change model successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
FolderID: -1,
Model: []byte(`{ "title": "New Model Title", "name": "New Model Name" }`),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Model = map[string]interface{}{
"title": "Text - Library Panel",
"name": "New Model Name",
}
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{FolderID: -1}
sc.reqContext.UserId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2)
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Another Panel")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
cmd := patchLibraryPanelCommand{
Name: "Text - Library Panel",
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
newFolder := createFolderWithACL(t, "NewFolder", sc.user, []folderACLItem{})
command := getCreateCommand(newFolder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
cmd := patchLibraryPanelCommand{
FolderID: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
FolderID: sc.folder.Id,
}
sc.reqContext.OrgId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
})
}

View File

@ -0,0 +1,459 @@
package librarypanels
import (
"encoding/json"
"fmt"
"strconv"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/require"
)
func TestLibraryPanelPermissions(t *testing.T) {
var defaultPermissions = []folderACLItem{}
var adminOnlyPermissions = []folderACLItem{{models.ROLE_ADMIN, models.PERMISSION_EDIT}}
var editorOnlyPermissions = []folderACLItem{{models.ROLE_EDITOR, models.PERMISSION_EDIT}}
var editorAndViewerPermissions = []folderACLItem{{models.ROLE_EDITOR, models.PERMISSION_EDIT}, {models.ROLE_VIEWER, models.PERMISSION_EDIT}}
var viewerOnlyPermissions = []folderACLItem{{models.ROLE_VIEWER, models.PERMISSION_EDIT}}
var everyonePermissions = []folderACLItem{{models.ROLE_ADMIN, models.PERMISSION_EDIT}, {models.ROLE_EDITOR, models.PERMISSION_EDIT}, {models.ROLE_VIEWER, models.PERMISSION_EDIT}}
var noPermissions = []folderACLItem{{models.ROLE_VIEWER, models.PERMISSION_VIEW}}
var folderCases = [][]folderACLItem{
defaultPermissions,
adminOnlyPermissions,
editorOnlyPermissions,
editorAndViewerPermissions,
viewerOnlyPermissions,
everyonePermissions,
noPermissions,
}
var defaultDesc = "default permissions"
var adminOnlyDesc = "admin only permissions"
var editorOnlyDesc = "editor only permissions"
var editorAndViewerDesc = "editor and viewer permissions"
var viewerOnlyDesc = "viewer only permissions"
var everyoneDesc = "everyone has editor permissions"
var noDesc = "everyone has view permissions"
var accessCases = []struct {
role models.RoleType
items []folderACLItem
desc string
status int
}{
{models.ROLE_ADMIN, defaultPermissions, defaultDesc, 200},
{models.ROLE_ADMIN, adminOnlyPermissions, adminOnlyDesc, 200},
{models.ROLE_ADMIN, editorOnlyPermissions, editorOnlyDesc, 200},
{models.ROLE_ADMIN, editorAndViewerPermissions, editorAndViewerDesc, 200},
{models.ROLE_ADMIN, viewerOnlyPermissions, viewerOnlyDesc, 200},
{models.ROLE_ADMIN, everyonePermissions, everyoneDesc, 200},
{models.ROLE_ADMIN, noPermissions, noDesc, 200},
{models.ROLE_EDITOR, defaultPermissions, defaultDesc, 200},
{models.ROLE_EDITOR, adminOnlyPermissions, adminOnlyDesc, 403},
{models.ROLE_EDITOR, editorOnlyPermissions, editorOnlyDesc, 200},
{models.ROLE_EDITOR, editorAndViewerPermissions, editorAndViewerDesc, 200},
{models.ROLE_EDITOR, viewerOnlyPermissions, viewerOnlyDesc, 403},
{models.ROLE_EDITOR, everyonePermissions, everyoneDesc, 200},
{models.ROLE_EDITOR, noPermissions, noDesc, 403},
{models.ROLE_VIEWER, defaultPermissions, defaultDesc, 403},
{models.ROLE_VIEWER, adminOnlyPermissions, adminOnlyDesc, 403},
{models.ROLE_VIEWER, editorOnlyPermissions, editorOnlyDesc, 403},
{models.ROLE_VIEWER, editorAndViewerPermissions, editorAndViewerDesc, 200},
{models.ROLE_VIEWER, viewerOnlyPermissions, viewerOnlyDesc, 200},
{models.ROLE_VIEWER, everyonePermissions, everyoneDesc, 200},
{models.ROLE_VIEWER, noPermissions, noDesc, 403},
}
for _, testCase := range accessCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, "Everyone", sc.user, everyonePermissions)
command := getCreateCommand(fromFolder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
fromFolder := createFolderWithACL(t, "Everyone", sc.user, testCase.items)
command := getCreateCommand(fromFolder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
toFolder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: toFolder.Id}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.deleteHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in a folder with %s, it should return correct status", testCase.role, testCase.desc),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, testCase.items)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
cmd := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
}
var generalFolderCases = []struct {
role models.RoleType
status int
}{
{models.ROLE_ADMIN, 200},
{models.ROLE_EDITOR, 200},
{models.ROLE_VIEWER, 403},
}
for _, testCase := range generalFolderCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: 0}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it from the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: folder.Id}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.deleteHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to connect a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to disconnect a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, testCase.status, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to get connected dashboards in the General folder for a library panel in the General folder, it should return correct status", testCase.role),
func(t *testing.T, sc scenarioContext) {
dashboard := createDashboard(t, sc.user, "General Folder Dash", 0)
cmd := getCreateCommand(0, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 200, resp.Status())
require.Equal(t, 1, len(dashResult.Result))
require.Equal(t, dashboard.Id, dashResult.Result[0])
})
}
var missingFolderCases = []struct {
role models.RoleType
}{
{models.ROLE_ADMIN},
{models.ROLE_EDITOR},
{models.ROLE_VIEWER},
}
for _, testCase := range missingFolderCases {
testScenario(t, fmt.Sprintf("When %s tries to create a library panel in a folder that doesn't exist, it should fail", testCase.role),
func(t *testing.T, sc scenarioContext) {
sc.reqContext.SignedInUser.OrgRole = testCase.role
command := getCreateCommand(-100, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 404, resp.Status())
})
testScenario(t, fmt.Sprintf("When %s tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", testCase.role),
func(t *testing.T, sc scenarioContext) {
folder := createFolderWithACL(t, "Folder", sc.user, everyonePermissions)
command := getCreateCommand(folder.Id, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
result := validateAndUnMarshalResponse(t, resp)
sc.reqContext.SignedInUser.OrgRole = testCase.role
cmd := patchLibraryPanelCommand{FolderID: -100}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
})
}
var getAllCases = []struct {
role models.RoleType
panels int
folderIndexes []int
}{
{models.ROLE_ADMIN, 7, []int{0, 1, 2, 3, 4, 5, 6}},
{models.ROLE_EDITOR, 6, []int{0, 2, 3, 4, 5, 6}},
{models.ROLE_VIEWER, 5, []int{0, 3, 4, 5, 6}},
}
for _, testCase := range getAllCases {
testScenario(t, fmt.Sprintf("When %s tries to get all library panels, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
var results []libraryPanel
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
result.Result.Meta.CreatedBy.Name = UserInDbName
result.Result.Meta.CreatedBy.AvatarUrl = UserInDbAvatar
result.Result.Meta.UpdatedBy.Name = UserInDbName
result.Result.Meta.UpdatedBy.AvatarUrl = UserInDbAvatar
results = append(results, result.Result)
}
sc.reqContext.SignedInUser.OrgRole = testCase.role
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var actual libraryPanelsResult
err := json.Unmarshal(resp.Body(), &actual)
require.NoError(t, err)
require.Equal(t, testCase.panels, len(actual.Result))
for _, folderIndex := range testCase.folderIndexes {
var folderID = int64(folderIndex + 2) // testScenario creates one folder and general folder doesn't count
var foundResult libraryPanel
var actualResult libraryPanel
for _, result := range results {
if result.FolderID == folderID {
foundResult = result
break
}
}
require.NotEmpty(t, foundResult)
for _, result := range actual.Result {
if result.FolderID == folderID {
actualResult = result
break
}
}
require.NotEmpty(t, actualResult)
if diff := cmp.Diff(foundResult, actualResult, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
}
})
testScenario(t, fmt.Sprintf("When %s tries to get all library panels from General folder, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
cmd := getCreateCommand(0, "Library Panel in General Folder")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
result.Result.Meta.CreatedBy.Name = UserInDbName
result.Result.Meta.CreatedBy.AvatarUrl = UserInDbAvatar
result.Result.Meta.UpdatedBy.Name = UserInDbName
result.Result.Meta.UpdatedBy.AvatarUrl = UserInDbAvatar
sc.reqContext.SignedInUser.OrgRole = testCase.role
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var actual libraryPanelsResult
err := json.Unmarshal(resp.Body(), &actual)
require.NoError(t, err)
require.Equal(t, 1, len(actual.Result))
if diff := cmp.Diff(result.Result, actual.Result[0], getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
testScenario(t, fmt.Sprintf("When %s tries to get connected dashboards for a library panel, it should return correct connected dashboard IDs", testCase.role),
func(t *testing.T, sc scenarioContext) {
cmd := getCreateCommand(0, "Library Panel in General Folder")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
dashboard := createDashboard(t, sc.user, "Some Folder Dash", folder.Id)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": strconv.FormatInt(dashboard.Id, 10)})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
}
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, testCase.panels, len(dashResult.Result))
})
}
var getCases = []struct {
role models.RoleType
statuses []int
}{
{models.ROLE_ADMIN, []int{200, 200, 200, 200, 200, 200, 200}},
{models.ROLE_EDITOR, []int{200, 404, 200, 200, 200, 200, 200}},
{models.ROLE_VIEWER, []int{200, 404, 404, 200, 200, 200, 200}},
}
for _, testCase := range getCases {
testScenario(t, fmt.Sprintf("When %s tries to get a library panel, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
var results []libraryPanel
for i, folderCase := range folderCases {
folder := createFolderWithACL(t, fmt.Sprintf("Folder%v", i), sc.user, folderCase)
cmd := getCreateCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i))
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
result.Result.Meta.CreatedBy.Name = UserInDbName
result.Result.Meta.CreatedBy.AvatarUrl = UserInDbAvatar
result.Result.Meta.UpdatedBy.Name = UserInDbName
result.Result.Meta.UpdatedBy.AvatarUrl = UserInDbAvatar
results = append(results, result.Result)
}
sc.reqContext.SignedInUser.OrgRole = testCase.role
for i, result := range results {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.UID})
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, testCase.statuses[i], resp.Status())
}
})
testScenario(t, fmt.Sprintf("When %s tries to get a library panel from General folder, it should return correct response", testCase.role),
func(t *testing.T, sc scenarioContext) {
cmd := getCreateCommand(0, "Library Panel in General Folder")
resp := sc.service.createHandler(sc.reqContext, cmd)
result := validateAndUnMarshalResponse(t, resp)
result.Result.Meta.CreatedBy.Name = UserInDbName
result.Result.Meta.CreatedBy.AvatarUrl = UserInDbAvatar
result.Result.Meta.UpdatedBy.Name = UserInDbName
result.Result.Meta.UpdatedBy.AvatarUrl = UserInDbAvatar
sc.reqContext.SignedInUser.OrgRole = testCase.role
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.getHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var actual libraryPanelResult
err := json.Unmarshal(resp.Body(), &actual)
require.NoError(t, err)
if diff := cmp.Diff(result.Result, actual.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}

View File

@ -22,652 +22,8 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func TestCreateLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to create a library panel that already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to create a library panel that does not exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: sc.initialResult.Result.UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: sc.initialResult.Result.Meta.Created,
Updated: sc.initialResult.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, sc.initialResult, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
testScenario(t, "When an admin tries to create a library panel where name and panel title differ, it should update panel title",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(1, "Library Panel Name")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result.UID,
Name: "Library Panel Name",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Library Panel Name",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
}
func TestConnectLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to create a connection for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to create a connection that already exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
}
func TestDeleteLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to delete a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp := sc.service.deleteHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
}
func TestDisconnectLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection with a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown", ":dashboardId": "1"})
resp := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to remove a connection that does exist, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.disconnectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
})
}
func TestGetLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that exists, it should succeed and return correct result",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getHandler(sc.reqContext)
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result.UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel that exists in an other org, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp := sc.service.getHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get a library panel with 2 connected dashboards, it should succeed and return correct connected dashboards",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.getHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
require.Equal(t, int64(2), result.Result.Meta.ConnectedDashboards)
})
}
func TestGetAllLibraryPanels(t *testing.T) {
testScenario(t, "When an admin tries to get all library panels and none exists, it should return none",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.NotNil(t, result.Result)
require.Equal(t, 0, len(result.Result))
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels and two exist, it should succeed",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel2")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
var expected = libraryPanelsResult{
Result: []libraryPanel{
{
ID: 1,
OrgID: 1,
FolderID: 1,
UID: result.Result[0].UID,
Name: "Text - Library Panel",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result[0].Meta.Created,
Updated: result.Result[0].Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
},
},
{
ID: 2,
OrgID: 1,
FolderID: 1,
UID: result.Result[1].UID,
Name: "Text - Library Panel2",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Text - Library Panel2",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 0,
Created: result.Result[1].Meta.Created,
Updated: result.Result[1].Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels and two exist but only one is connected, it should succeed and return correct connected dashboards",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Text - Library Panel2")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "1"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var results libraryPanelsResult
err := json.Unmarshal(resp.Body(), &results)
require.NoError(t, err)
require.Equal(t, int64(0), results.Result[0].Meta.ConnectedDashboards)
require.Equal(t, int64(2), results.Result[1].Meta.ConnectedDashboards)
})
scenarioWithLibraryPanel(t, "When an admin tries to get all library panels in a different org, none should be returned",
func(t *testing.T, sc scenarioContext) {
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.Equal(t, 1, len(result.Result))
require.Equal(t, int64(1), result.Result[0].FolderID)
require.Equal(t, "Text - Library Panel", result.Result[0].Name)
sc.reqContext.SignedInUser.OrgId = 2
sc.reqContext.SignedInUser.OrgRole = models.ROLE_ADMIN
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
result = libraryPanelsResult{}
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.NotNil(t, result.Result)
require.Equal(t, 0, len(result.Result))
})
testScenario(t, "When an user tries to get all library panels, library panels in folders where the user has no access should not be returned",
func(t *testing.T, sc scenarioContext) {
updateFolderACL(t, sc, models.ROLE_EDITOR, models.PERMISSION_EDIT)
command := getCreateCommand(sc.folder.Id, "Editor - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
cmd := models.CreateFolderCommand{
Uid: "AdminOnlyFolder",
Title: "Admin Only Folder",
}
createFolder(t, &sc, &cmd)
updateFolderACL(t, sc, models.ROLE_ADMIN, models.PERMISSION_ADMIN)
command = getCreateCommand(sc.folder.Id, "Admin - Library Panel")
resp = sc.service.createHandler(sc.reqContext, command)
require.Equal(t, 200, resp.Status())
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryPanelsResult
err := json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.Equal(t, 2, len(result.Result))
require.Equal(t, int64(1), result.Result[0].FolderID)
require.Equal(t, int64(2), cmd.Result.Id)
require.Equal(t, "Editor - Library Panel", result.Result[0].Name)
require.Equal(t, "Admin - Library Panel", result.Result[1].Name)
sc.reqContext.SignedInUser.OrgRole = models.ROLE_EDITOR
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
result = libraryPanelsResult{}
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.Equal(t, 1, len(result.Result))
require.Equal(t, int64(1), result.Result[0].FolderID)
require.Equal(t, "Editor - Library Panel", result.Result[0].Name)
sc.reqContext.SignedInUser.OrgRole = models.ROLE_VIEWER
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
result = libraryPanelsResult{}
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
require.NotNil(t, result.Result)
require.Equal(t, 0, len(result.Result))
})
}
func TestGetConnectedDashboards(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists, but has no connections, it should return none",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 0, len(dashResult.Result))
})
scenarioWithLibraryPanel(t, "When an admin tries to get connected dashboards for a library panel that exists and has connections, it should return connected dashboard IDs",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "11"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "12"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.getConnectedDashboardsHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var dashResult libraryPanelDashboardsResult
err := json.Unmarshal(resp.Body(), &dashResult)
require.NoError(t, err)
require.Equal(t, 2, len(dashResult.Result))
require.Equal(t, int64(11), dashResult.Result[0])
require.Equal(t, int64(12), dashResult.Result[1])
})
}
func TestPatchLibraryPanel(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": "unknown"})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel that exists, it should succeed",
func(t *testing.T, sc scenarioContext) {
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "1"})
resp := sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID, ":dashboardId": "2"})
resp = sc.service.connectHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
cmd := patchLibraryPanelCommand{
FolderID: 2,
Name: "Panel - New name",
Model: []byte(`
{
"datasource": "${DS_GDEV-TESTDATA}",
"id": 1,
"title": "Model - New name",
"type": "text"
}
`),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
var expected = libraryPanelResult{
Result: libraryPanel{
ID: 1,
OrgID: 1,
FolderID: 2,
UID: sc.initialResult.Result.UID,
Name: "Panel - New name",
Model: map[string]interface{}{
"datasource": "${DS_GDEV-TESTDATA}",
"id": float64(1),
"title": "Panel - New name",
"type": "text",
},
Meta: LibraryPanelDTOMeta{
CanEdit: true,
ConnectedDashboards: 2,
Created: sc.initialResult.Result.Meta.Created,
Updated: result.Result.Meta.Updated,
CreatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "user_in_db",
AvatarUrl: "/avatar/402d08de060496d6b6874495fe20f5ad",
},
UpdatedBy: LibraryPanelDTOMetaUser{
ID: 1,
Name: "signed_in_user",
AvatarUrl: "/avatar/37524e1eb8b3e32850b57db0a19af93b",
},
},
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with folder only, it should change folder successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
FolderID: 100,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 200, resp.Status())
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.FolderID = int64(100)
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with name only, it should change name successfully, sync title and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
Name: "New Name",
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Name = "New Name"
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
sc.initialResult.Result.Model["title"] = "New Name"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with model only, it should change model successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
Model: []byte(`{ "title": "New Model Title", "name": "New Model Name" }`),
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Model = map[string]interface{}{
"title": "Text - Library Panel",
"name": "New Model Name",
}
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{}
sc.reqContext.UserId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
var result = validateAndUnMarshalResponse(t, resp)
sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2)
sc.initialResult.Result.Meta.CreatedBy.Name = "user_in_db"
sc.initialResult.Result.Meta.CreatedBy.AvatarUrl = "/avatar/402d08de060496d6b6874495fe20f5ad"
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
command := getCreateCommand(sc.folder.Id, "Another Panel")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
cmd := patchLibraryPanelCommand{
Name: "Text - Library Panel",
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel with a folder where a library panel with the same name already exists, it should fail",
func(t *testing.T, sc scenarioContext) {
createFolder(t, &sc, &models.CreateFolderCommand{
Uid: "NewTestFolder",
Title: "New Test Folder",
})
command := getCreateCommand(sc.folder.Id, "Text - Library Panel")
resp := sc.service.createHandler(sc.reqContext, command)
var result = validateAndUnMarshalResponse(t, resp)
cmd := patchLibraryPanelCommand{
FolderID: 1,
}
sc.reqContext.ReplaceAllParams(map[string]string{":uid": result.Result.UID})
resp = sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 400, resp.Status())
})
scenarioWithLibraryPanel(t, "When an admin tries to patch a library panel in another org, it should fail",
func(t *testing.T, sc scenarioContext) {
cmd := patchLibraryPanelCommand{
FolderID: sc.folder.Id,
}
sc.reqContext.OrgId = 2
sc.reqContext.ReplaceAllParams(map[string]string{":uid": sc.initialResult.Result.UID})
resp := sc.service.patchHandler(sc.reqContext, cmd)
require.Equal(t, 404, resp.Status())
})
}
const UserInDbName = "user_in_db"
const UserInDbAvatar = "/avatar/402d08de060496d6b6874495fe20f5ad"
func TestLoadLibraryPanelsForDashboard(t *testing.T) {
scenarioWithLibraryPanel(t, "When an admin tries to load a dashboard with a library panel, it should copy JSON properties from library panel",
@ -739,13 +95,13 @@ func TestLoadLibraryPanelsForDashboard(t *testing.T) {
"updated": sc.initialResult.Result.Meta.Updated,
"createdBy": map[string]interface{}{
"id": sc.initialResult.Result.Meta.CreatedBy.ID,
"name": "user_in_db",
"avatarUrl": "/avatar/402d08de060496d6b6874495fe20f5ad",
"name": UserInDbName,
"avatarUrl": UserInDbAvatar,
},
"updatedBy": map[string]interface{}{
"id": sc.initialResult.Result.Meta.UpdatedBy.ID,
"name": "user_in_db",
"avatarUrl": "/avatar/402d08de060496d6b6874495fe20f5ad",
"name": UserInDbName,
"avatarUrl": UserInDbAvatar,
},
},
},
@ -1343,26 +699,76 @@ type scenarioContext struct {
initialResult libraryPanelResult
}
func createFolder(t *testing.T, sc *scenarioContext, cmd *models.CreateFolderCommand) {
s := dashboards.NewFolderService(sc.user.OrgId, &sc.user)
err := s.CreateFolder(cmd)
require.NoError(t, err)
sc.folder = cmd.Result
type folderACLItem struct {
roleType models.RoleType
permission models.PermissionType
}
func updateFolderACL(t *testing.T, sc scenarioContext, roleType models.RoleType, permission models.PermissionType) {
cmd := models.UpdateDashboardAclCommand{
DashboardID: sc.folder.Id,
Items: []*models.DashboardAcl{
{
DashboardID: sc.folder.Id,
Role: &roleType,
Permission: permission,
Created: time.Now(),
Updated: time.Now(),
},
},
func createDashboard(t *testing.T, user models.SignedInUser, title string, folderID int64) *models.Dashboard {
dash := models.NewDashboard(title)
dash.FolderId = folderID
dashItem := &dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: "",
OrgId: user.OrgId,
User: &user,
Overwrite: false,
}
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
return nil
})
bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
cmd.Result = nil
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
dashboard, err := dashboards.NewService().SaveDashboard(dashItem, true)
require.NoError(t, err)
return dashboard
}
func createFolderWithACL(t *testing.T, title string, user models.SignedInUser, items []folderACLItem) *models.Folder {
s := dashboards.NewFolderService(user.OrgId, &user)
folderCmd := models.CreateFolderCommand{
Uid: title,
Title: title,
}
err := s.CreateFolder(&folderCmd)
require.NoError(t, err)
updateFolderACL(t, folderCmd.Result.Id, items)
return folderCmd.Result
}
func updateFolderACL(t *testing.T, folderID int64, items []folderACLItem) {
if len(items) == 0 {
return
}
cmd := models.UpdateDashboardAclCommand{
DashboardID: folderID,
}
for _, item := range items {
role := item.roleType
permission := item.permission
cmd.Items = append(cmd.Items, &models.DashboardAcl{
DashboardID: folderID,
Role: &role,
Permission: permission,
Created: time.Now(),
Updated: time.Now(),
})
}
err := bus.Dispatch(&cmd)
require.NoError(t, err)
}
@ -1428,7 +834,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
cmd := &models.CreateUserCommand{
Email: "user.in.db@test.com",
Name: "User In DB",
Login: "user_in_db",
Login: UserInDbName,
}
err := sqlstore.CreateUser(context.Background(), cmd)
require.NoError(t, err)
@ -1443,12 +849,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
},
}
folderCmd := models.CreateFolderCommand{
Uid: "testFolder",
Title: "TestFolder",
}
createFolder(t, &sc, &folderCmd)
sc.folder = createFolderWithACL(t, "ScenarioFolder", sc.user, []folderACLItem{})
fn(t, sc)
})

View File

@ -109,7 +109,7 @@ type createLibraryPanelCommand struct {
// patchLibraryPanelCommand is the command for patching a LibraryPanel
type patchLibraryPanelCommand struct {
FolderID int64 `json:"folderId"`
FolderID int64 `json:"folderId" binding:"Default(-1)"`
Name string `json:"name"`
Model json.RawMessage `json:"model"`
}