diff --git a/pkg/services/librarypanels/api.go b/pkg/services/librarypanels/api.go index b393d45e61c..5f251c13bcd 100644 --- a/pkg/services/librarypanels/api.go +++ b/pkg/services/librarypanels/api.go @@ -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) } diff --git a/pkg/services/librarypanels/database.go b/pkg/services/librarypanels/database.go index 8382d2094f7..4448fe45181 100644 --- a/pkg/services/librarypanels/database.go +++ b/pkg/services/librarypanels/database.go @@ -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 diff --git a/pkg/services/librarypanels/guard.go b/pkg/services/librarypanels/guard.go new file mode 100644 index 00000000000..10c2cc1a2a0 --- /dev/null +++ b/pkg/services/librarypanels/guard.go @@ -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 +} diff --git a/pkg/services/librarypanels/librarypanels_connections_test.go b/pkg/services/librarypanels/librarypanels_connections_test.go new file mode 100644 index 00000000000..816e6305f26 --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_connections_test.go @@ -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]) + }) +} diff --git a/pkg/services/librarypanels/librarypanels_create_test.go b/pkg/services/librarypanels/librarypanels_create_test.go new file mode 100644 index 00000000000..50acc2de8cc --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_create_test.go @@ -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) + } + }) +} diff --git a/pkg/services/librarypanels/librarypanels_delete_test.go b/pkg/services/librarypanels/librarypanels_delete_test.go new file mode 100644 index 00000000000..d32dcf6ebeb --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_delete_test.go @@ -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()) + }) +} diff --git a/pkg/services/librarypanels/librarypanels_get_all_test.go b/pkg/services/librarypanels/librarypanels_get_all_test.go new file mode 100644 index 00000000000..5e5f6e6b4aa --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_get_all_test.go @@ -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)) + }) +} diff --git a/pkg/services/librarypanels/librarypanels_get_test.go b/pkg/services/librarypanels/librarypanels_get_test.go new file mode 100644 index 00000000000..20b586916b2 --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_get_test.go @@ -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) + }) +} diff --git a/pkg/services/librarypanels/librarypanels_patch_test.go b/pkg/services/librarypanels/librarypanels_patch_test.go new file mode 100644 index 00000000000..c35e6b52cec --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_patch_test.go @@ -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()) + }) +} diff --git a/pkg/services/librarypanels/librarypanels_permissions_test.go b/pkg/services/librarypanels/librarypanels_permissions_test.go new file mode 100644 index 00000000000..4906c4cb0e2 --- /dev/null +++ b/pkg/services/librarypanels/librarypanels_permissions_test.go @@ -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) + } + }) + } +} diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index e08999b6019..67fd78067ad 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -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) }) diff --git a/pkg/services/librarypanels/models.go b/pkg/services/librarypanels/models.go index a0e76c24130..6e782e6b4fc 100644 --- a/pkg/services/librarypanels/models.go +++ b/pkg/services/librarypanels/models.go @@ -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"` }