package sqlstore

import (
	"testing"

	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/guardian"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/models"

	. "github.com/smartystreets/goconvey/convey"
)

func TestIntegratedDashboardService(t *testing.T) {
	Convey("Dashboard service integration tests", t, func() {
		InitTestDB(t)
		var testOrgId int64 = 1

		Convey("Given saved folders and dashboards in organization A", func() {
			bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
				return nil
			})

			bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
				return nil
			})

			bus.AddHandler("test", func(cmd *models.GetProvisionedDashboardDataByIdQuery) error {
				cmd.Result = nil
				return nil
			})

			savedFolder := saveTestFolder("Saved folder", testOrgId)
			savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
			saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
			savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
			otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)

			Convey("Should return dashboard model", func() {
				So(savedFolder.Title, ShouldEqual, "Saved folder")
				So(savedFolder.Slug, ShouldEqual, "saved-folder")
				So(savedFolder.Id, ShouldNotEqual, 0)
				So(savedFolder.IsFolder, ShouldBeTrue)
				So(savedFolder.FolderId, ShouldEqual, 0)
				So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)

				So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
				So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
				So(savedDashInFolder.Id, ShouldNotEqual, 0)
				So(savedDashInFolder.IsFolder, ShouldBeFalse)
				So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
				So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
			})

			// Basic validation tests

			Convey("When saving a dashboard with non-existing id", func() {
				cmd := models.SaveDashboardCommand{
					OrgId: testOrgId,
					Dashboard: simplejson.NewFromAny(map[string]interface{}{
						"id":    float64(123412321),
						"title": "Expect error",
					}),
				}

				err := callSaveWithError(cmd)

				Convey("It should result in not found error", func() {
					So(err, ShouldNotBeNil)
					So(err, ShouldEqual, models.ErrDashboardNotFound)
				})
			})

			// Given other organization

			Convey("Given organization B", func() {
				var otherOrgId int64 = 2

				Convey("When creating a dashboard with same id as dashboard in organization A", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: otherOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"id":    savedDashInFolder.Id,
							"title": "Expect error",
						}),
						Overwrite: false,
					}

					err := callSaveWithError(cmd)

					Convey("It should result in not found error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardNotFound)
					})
				})

				permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
					Convey("When creating a dashboard with same uid as dashboard in organization A", func() {
						var otherOrgId int64 = 2
						cmd := models.SaveDashboardCommand{
							OrgId: otherOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":   savedDashInFolder.Uid,
								"title": "Dash with existing uid in other org",
							}),
							Overwrite: false,
						}

						res := callSaveWithResult(cmd)

						Convey("It should create a new dashboard in organization B", func() {
							So(res, ShouldNotBeNil)

							query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
							So(query.Result.Id, ShouldEqual, res.Id)
							So(query.Result.OrgId, ShouldEqual, otherOrgId)
							So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
						})
					})
				})
			})

			// Given user has no permission to save

			permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {

				Convey("When creating a new dashboard in the General folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"title": "Dash",
						}),
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for General Folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When creating a new dashboard in other folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"title": "Dash",
						}),
						FolderId:  otherSavedFolder.Id,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for other folder with correct arguments and rsult in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When creating a new dashboard by existing title in folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"title": savedDashInFolder.Title,
						}),
						FolderId:  savedFolder.Id,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When creating a new dashboard by existing uid in folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"uid":   savedDashInFolder.Uid,
							"title": "New dash",
						}),
						FolderId:  savedFolder.Id,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When updating a dashboard by existing id in the General folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"id":    savedDashInGeneralFolder.Id,
							"title": "Dash",
						}),
						FolderId:  savedDashInGeneralFolder.FolderId,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInGeneralFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When updating a dashboard by existing id in other folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"id":    savedDashInFolder.Id,
							"title": "Dash",
						}),
						FolderId:  savedDashInFolder.FolderId,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When moving a dashboard by existing id to other folder from General folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"id":    savedDashInGeneralFolder.Id,
							"title": "Dash",
						}),
						FolderId:  otherSavedFolder.Id,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When moving a dashboard by existing id to the General folder from other folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"id":    savedDashInFolder.Id,
							"title": "Dash",
						}),
						FolderId:  0,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When moving a dashboard by existing uid to other folder from General folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"uid":   savedDashInGeneralFolder.Uid,
							"title": "Dash",
						}),
						FolderId:  otherSavedFolder.Id,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})

				Convey("When moving a dashboard by existing uid to the General folder from other folder", func() {
					cmd := models.SaveDashboardCommand{
						OrgId: testOrgId,
						Dashboard: simplejson.NewFromAny(map[string]interface{}{
							"uid":   savedDashInFolder.Uid,
							"title": "Dash",
						}),
						FolderId:  0,
						UserId:    10000,
						Overwrite: true,
					}

					err := callSaveWithError(cmd)

					Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
						So(err, ShouldNotBeNil)
						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)

						So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
					})
				})
			})

			// Given user has permission to save

			permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {

				Convey("and overwrite flag is set to false", func() {
					shouldOverwrite := false

					Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInFolder.Title,
							}),
							FolderId:  0,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should create a new dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldEqual, res.Id)
							So(query.Result.FolderId, ShouldEqual, 0)
						})
					})

					Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInGeneralFolder.Title,
							}),
							FolderId:  savedFolder.Id,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should create a new dashboard", func() {
							So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)

							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
						})
					})

					Convey("When creating a folder with same name as dashboard in other folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInFolder.Title,
							}),
							IsFolder:  true,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should create a new folder", func() {
							So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
							So(res.IsFolder, ShouldBeTrue)

							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.FolderId, ShouldEqual, 0)
							So(query.Result.IsFolder, ShouldBeTrue)
						})
					})

					Convey("When saving a dashboard without id and uid and unique title in folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"title": "Dash without id and uid",
							}),
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should create a new dashboard", func() {
							So(res.Id, ShouldBeGreaterThan, 0)
							So(len(res.Uid), ShouldBeGreaterThan, 0)
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldEqual, res.Id)
							So(query.Result.Uid, ShouldEqual, res.Uid)
						})
					})

					Convey("When saving a dashboard when dashboard id is zero ", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    0,
								"title": "Dash with zero id",
							}),
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should create a new dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldEqual, res.Id)
						})
					})

					Convey("When saving a dashboard in non-existing folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"title": "Expect error",
							}),
							FolderId:  123412321,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in folder not found error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
						})
					})

					Convey("When updating an existing dashboard by id without current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedDashInGeneralFolder.Id,
								"title": "test dash 23",
							}),
							FolderId:  savedFolder.Id,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in version mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
						})
					})

					Convey("When updating an existing dashboard by id with current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":      savedDashInGeneralFolder.Id,
								"title":   "Updated title",
								"version": savedDashInGeneralFolder.Version,
							}),
							FolderId:  savedFolder.Id,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should update dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Title, ShouldEqual, "Updated title")
							So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
							So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
						})
					})

					Convey("When updating an existing dashboard by uid without current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":   savedDashInFolder.Uid,
								"title": "test dash 23",
							}),
							FolderId:  0,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in version mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
						})
					})

					Convey("When updating an existing dashboard by uid with current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":     savedDashInFolder.Uid,
								"title":   "Updated title",
								"version": savedDashInFolder.Version,
							}),
							FolderId:  0,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should update dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Title, ShouldEqual, "Updated title")
							So(query.Result.FolderId, ShouldEqual, 0)
							So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
						})
					})

					Convey("When creating a dashboard with same name as dashboard in other folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInFolder.Title,
							}),
							FolderId:  savedDashInFolder.FolderId,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in dashboard with same name in folder error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
						})
					})

					Convey("When creating a dashboard with same name as dashboard in General folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInGeneralFolder.Title,
							}),
							FolderId:  savedDashInGeneralFolder.FolderId,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in dashboard with same name in folder error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
						})
					})

					Convey("When creating a folder with same name as existing folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedFolder.Title,
							}),
							IsFolder:  true,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in dashboard with same name in folder error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
						})
					})
				})

				Convey("and overwrite flag is set to true", func() {
					shouldOverwrite := true

					Convey("When updating an existing dashboard by id without current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedDashInGeneralFolder.Id,
								"title": "Updated title",
							}),
							FolderId:  savedFolder.Id,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should update dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Title, ShouldEqual, "Updated title")
							So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
							So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
						})
					})

					Convey("When updating an existing dashboard by uid without current version", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":   savedDashInFolder.Uid,
								"title": "Updated title",
							}),
							FolderId:  0,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)
						So(res, ShouldNotBeNil)

						Convey("It should update dashboard", func() {
							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Title, ShouldEqual, "Updated title")
							So(query.Result.FolderId, ShouldEqual, 0)
							So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
						})
					})

					Convey("When updating uid for existing dashboard using id", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedDashInFolder.Id,
								"uid":   "new-uid",
								"title": savedDashInFolder.Title,
							}),
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)

						Convey("It should update dashboard", func() {
							So(res, ShouldNotBeNil)
							So(res.Id, ShouldEqual, savedDashInFolder.Id)
							So(res.Uid, ShouldEqual, "new-uid")

							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Uid, ShouldEqual, "new-uid")
							So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
						})
					})

					Convey("When updating uid to an existing uid for existing dashboard using id", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedDashInFolder.Id,
								"uid":   savedDashInGeneralFolder.Uid,
								"title": savedDashInFolder.Title,
							}),
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in same uid exists error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
						})
					})

					Convey("When creating a dashboard with same name as dashboard in other folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInFolder.Title,
							}),
							FolderId:  savedDashInFolder.FolderId,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)

						Convey("It should overwrite existing dashboard", func() {
							So(res, ShouldNotBeNil)
							So(res.Id, ShouldEqual, savedDashInFolder.Id)
							So(res.Uid, ShouldEqual, savedDashInFolder.Uid)

							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldEqual, res.Id)
							So(query.Result.Uid, ShouldEqual, res.Uid)
						})
					})

					Convey("When creating a dashboard with same name as dashboard in General folder", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: testOrgId,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    nil,
								"title": savedDashInGeneralFolder.Title,
							}),
							FolderId:  savedDashInGeneralFolder.FolderId,
							Overwrite: shouldOverwrite,
						}

						res := callSaveWithResult(cmd)

						Convey("It should overwrite existing dashboard", func() {
							So(res, ShouldNotBeNil)
							So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
							So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)

							query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}

							err := bus.Dispatch(&query)
							So(err, ShouldBeNil)
							So(query.Result.Id, ShouldEqual, res.Id)
							So(query.Result.Uid, ShouldEqual, res.Uid)
						})
					})

					Convey("When updating existing folder to a dashboard using id", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedFolder.Id,
								"title": "new title",
							}),
							IsFolder:  false,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in type mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
						})
					})

					Convey("When updating existing dashboard to a folder using id", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"id":    savedDashInFolder.Id,
								"title": "new folder title",
							}),
							IsFolder:  true,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in type mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
						})
					})

					Convey("When updating existing folder to a dashboard using uid", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":   savedFolder.Uid,
								"title": "new title",
							}),
							IsFolder:  false,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in type mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
						})
					})

					Convey("When updating existing dashboard to a folder using uid", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"uid":   savedDashInFolder.Uid,
								"title": "new folder title",
							}),
							IsFolder:  true,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in type mismatch error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
						})
					})

					Convey("When updating existing folder to a dashboard using title", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"title": savedFolder.Title,
							}),
							IsFolder:  false,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in dashboard with same name as folder error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
						})
					})

					Convey("When updating existing dashboard to a folder using title", func() {
						cmd := models.SaveDashboardCommand{
							OrgId: 1,
							Dashboard: simplejson.NewFromAny(map[string]interface{}{
								"title": savedDashInGeneralFolder.Title,
							}),
							IsFolder:  true,
							Overwrite: shouldOverwrite,
						}

						err := callSaveWithError(cmd)

						Convey("It should result in folder with same name as dashboard error", func() {
							So(err, ShouldNotBeNil)
							So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
						})
					})
				})
			})
		})
	})
}

type dashboardPermissionScenarioContext struct {
	dashboardGuardianMock *guardian.FakeDashboardGuardian
}

type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)

func dashboardPermissionScenario(desc string, mock *guardian.FakeDashboardGuardian, fn dashboardPermissionScenarioFunc) {
	Convey(desc, func() {
		origNewDashboardGuardian := guardian.New
		guardian.MockDashboardGuardian(mock)

		sc := &dashboardPermissionScenarioContext{
			dashboardGuardianMock: mock,
		}

		defer func() {
			guardian.New = origNewDashboardGuardian
		}()

		fn(sc)
	})
}

func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
	mock := &guardian.FakeDashboardGuardian{
		CanSaveValue: canSave,
	}
	dashboardPermissionScenario(desc, mock, fn)
}

func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
	dto := toSaveDashboardDto(cmd)
	res, _ := dashboards.NewService().SaveDashboard(&dto, false)
	return res
}

func callSaveWithError(cmd models.SaveDashboardCommand) error {
	dto := toSaveDashboardDto(cmd)
	_, err := dashboards.NewService().SaveDashboard(&dto, false)
	return err
}

func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
	cmd := models.SaveDashboardCommand{
		OrgId:    orgId,
		FolderId: folderId,
		IsFolder: false,
		Dashboard: simplejson.NewFromAny(map[string]interface{}{
			"id":    nil,
			"title": title,
		}),
	}

	dto := dashboards.SaveDashboardDTO{
		OrgId:     orgId,
		Dashboard: cmd.GetDashboardModel(),
		User: &models.SignedInUser{
			UserId:  1,
			OrgRole: models.ROLE_ADMIN,
		},
	}

	res, err := dashboards.NewService().SaveDashboard(&dto, false)
	So(err, ShouldBeNil)

	return res
}

func saveTestFolder(title string, orgId int64) *models.Dashboard {
	cmd := models.SaveDashboardCommand{
		OrgId:    orgId,
		FolderId: 0,
		IsFolder: true,
		Dashboard: simplejson.NewFromAny(map[string]interface{}{
			"id":    nil,
			"title": title,
		}),
	}

	dto := dashboards.SaveDashboardDTO{
		OrgId:     orgId,
		Dashboard: cmd.GetDashboardModel(),
		User: &models.SignedInUser{
			UserId:  1,
			OrgRole: models.ROLE_ADMIN,
		},
	}

	res, err := dashboards.NewService().SaveDashboard(&dto, false)
	So(err, ShouldBeNil)

	return res
}

func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
	dash := (&cmd).GetDashboardModel()

	return dashboards.SaveDashboardDTO{
		Dashboard: dash,
		Message:   cmd.Message,
		OrgId:     cmd.OrgId,
		User:      &models.SignedInUser{UserId: cmd.UserId},
		Overwrite: cmd.Overwrite,
	}
}
