dashboard_service_integration_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  1. package sqlstore
  2. import (
  3. "testing"
  4. "github.com/grafana/grafana/pkg/components/simplejson"
  5. "github.com/grafana/grafana/pkg/services/dashboards"
  6. "github.com/grafana/grafana/pkg/services/guardian"
  7. "github.com/grafana/grafana/pkg/bus"
  8. "github.com/grafana/grafana/pkg/models"
  9. . "github.com/smartystreets/goconvey/convey"
  10. )
  11. func TestIntegratedDashboardService(t *testing.T) {
  12. Convey("Dashboard service integration tests", t, func() {
  13. InitTestDB(t)
  14. var testOrgId int64 = 1
  15. Convey("Given saved folders and dashboards in organization A", func() {
  16. bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
  17. return nil
  18. })
  19. bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
  20. return nil
  21. })
  22. bus.AddHandler("test", func(cmd *models.IsDashboardProvisionedQuery) error {
  23. cmd.Result = false
  24. return nil
  25. })
  26. savedFolder := saveTestFolder("Saved folder", testOrgId)
  27. savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
  28. saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
  29. savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
  30. otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
  31. Convey("Should return dashboard model", func() {
  32. So(savedFolder.Title, ShouldEqual, "Saved folder")
  33. So(savedFolder.Slug, ShouldEqual, "saved-folder")
  34. So(savedFolder.Id, ShouldNotEqual, 0)
  35. So(savedFolder.IsFolder, ShouldBeTrue)
  36. So(savedFolder.FolderId, ShouldEqual, 0)
  37. So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
  38. So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
  39. So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
  40. So(savedDashInFolder.Id, ShouldNotEqual, 0)
  41. So(savedDashInFolder.IsFolder, ShouldBeFalse)
  42. So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
  43. So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
  44. })
  45. // Basic validation tests
  46. Convey("When saving a dashboard with non-existing id", func() {
  47. cmd := models.SaveDashboardCommand{
  48. OrgId: testOrgId,
  49. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  50. "id": float64(123412321),
  51. "title": "Expect error",
  52. }),
  53. }
  54. err := callSaveWithError(cmd)
  55. Convey("It should result in not found error", func() {
  56. So(err, ShouldNotBeNil)
  57. So(err, ShouldEqual, models.ErrDashboardNotFound)
  58. })
  59. })
  60. // Given other organization
  61. Convey("Given organization B", func() {
  62. var otherOrgId int64 = 2
  63. Convey("When saving a dashboard with id that are saved in organization A", func() {
  64. cmd := models.SaveDashboardCommand{
  65. OrgId: otherOrgId,
  66. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  67. "id": savedDashInFolder.Id,
  68. "title": "Expect error",
  69. }),
  70. Overwrite: false,
  71. }
  72. err := callSaveWithError(cmd)
  73. Convey("It should result in not found error", func() {
  74. So(err, ShouldNotBeNil)
  75. So(err, ShouldEqual, models.ErrDashboardNotFound)
  76. })
  77. })
  78. permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
  79. Convey("When saving a dashboard with uid that are saved in organization A", func() {
  80. var otherOrgId int64 = 2
  81. cmd := models.SaveDashboardCommand{
  82. OrgId: otherOrgId,
  83. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  84. "uid": savedDashInFolder.Uid,
  85. "title": "Dash with existing uid in other org",
  86. }),
  87. Overwrite: false,
  88. }
  89. res := callSaveWithResult(cmd)
  90. Convey("It should create dashboard in other organization", func() {
  91. So(res, ShouldNotBeNil)
  92. query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
  93. err := bus.Dispatch(&query)
  94. So(err, ShouldBeNil)
  95. So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
  96. So(query.Result.Id, ShouldEqual, res.Id)
  97. So(query.Result.OrgId, ShouldEqual, otherOrgId)
  98. So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
  99. })
  100. })
  101. })
  102. })
  103. // Given user has no permission to save
  104. permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
  105. Convey("When trying to create a new dashboard in the General folder", func() {
  106. cmd := models.SaveDashboardCommand{
  107. OrgId: testOrgId,
  108. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  109. "title": "Dash",
  110. }),
  111. UserId: 10000,
  112. Overwrite: true,
  113. }
  114. err := callSaveWithError(cmd)
  115. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  116. So(err, ShouldNotBeNil)
  117. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  118. So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
  119. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  120. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  121. })
  122. })
  123. Convey("When trying to create a new dashboard in other folder", func() {
  124. cmd := models.SaveDashboardCommand{
  125. OrgId: testOrgId,
  126. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  127. "title": "Dash",
  128. }),
  129. FolderId: otherSavedFolder.Id,
  130. UserId: 10000,
  131. Overwrite: true,
  132. }
  133. err := callSaveWithError(cmd)
  134. Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
  135. So(err, ShouldNotBeNil)
  136. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  137. So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
  138. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  139. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  140. })
  141. })
  142. Convey("When trying to update a dashboard by existing id in the General folder", func() {
  143. cmd := models.SaveDashboardCommand{
  144. OrgId: testOrgId,
  145. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  146. "id": savedDashInGeneralFolder.Id,
  147. "title": "Dash",
  148. }),
  149. FolderId: savedDashInGeneralFolder.FolderId,
  150. UserId: 10000,
  151. Overwrite: true,
  152. }
  153. err := callSaveWithError(cmd)
  154. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  155. So(err, ShouldNotBeNil)
  156. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  157. So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInGeneralFolder.Id)
  158. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  159. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  160. })
  161. })
  162. Convey("When trying to update a dashboard by existing id in other folder", func() {
  163. cmd := models.SaveDashboardCommand{
  164. OrgId: testOrgId,
  165. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  166. "id": savedDashInFolder.Id,
  167. "title": "Dash",
  168. }),
  169. FolderId: savedDashInFolder.FolderId,
  170. UserId: 10000,
  171. Overwrite: true,
  172. }
  173. err := callSaveWithError(cmd)
  174. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  175. So(err, ShouldNotBeNil)
  176. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  177. So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInFolder.Id)
  178. So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
  179. So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
  180. })
  181. })
  182. })
  183. // Given user has permission to save
  184. permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
  185. Convey("and overwrite flag is set to false", func() {
  186. shouldOverwrite := false
  187. Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
  188. cmd := models.SaveDashboardCommand{
  189. OrgId: testOrgId,
  190. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  191. "id": nil,
  192. "title": savedDashInFolder.Title,
  193. }),
  194. FolderId: 0,
  195. Overwrite: shouldOverwrite,
  196. }
  197. res := callSaveWithResult(cmd)
  198. So(res, ShouldNotBeNil)
  199. Convey("It should create a new dashboard", func() {
  200. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  201. err := bus.Dispatch(&query)
  202. So(err, ShouldBeNil)
  203. So(query.Result.Id, ShouldEqual, res.Id)
  204. So(query.Result.FolderId, ShouldEqual, 0)
  205. })
  206. })
  207. Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
  208. cmd := models.SaveDashboardCommand{
  209. OrgId: testOrgId,
  210. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  211. "id": nil,
  212. "title": savedDashInGeneralFolder.Title,
  213. }),
  214. FolderId: savedFolder.Id,
  215. Overwrite: shouldOverwrite,
  216. }
  217. res := callSaveWithResult(cmd)
  218. So(res, ShouldNotBeNil)
  219. Convey("It should create a new dashboard", func() {
  220. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  221. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  222. err := bus.Dispatch(&query)
  223. So(err, ShouldBeNil)
  224. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  225. })
  226. })
  227. Convey("When creating a folder with same name as dashboard in other folder", func() {
  228. cmd := models.SaveDashboardCommand{
  229. OrgId: testOrgId,
  230. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  231. "id": nil,
  232. "title": savedDashInFolder.Title,
  233. }),
  234. IsFolder: true,
  235. Overwrite: shouldOverwrite,
  236. }
  237. res := callSaveWithResult(cmd)
  238. So(res, ShouldNotBeNil)
  239. Convey("It should create a new folder", func() {
  240. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  241. So(res.IsFolder, ShouldBeTrue)
  242. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  243. err := bus.Dispatch(&query)
  244. So(err, ShouldBeNil)
  245. So(query.Result.FolderId, ShouldEqual, 0)
  246. So(query.Result.IsFolder, ShouldBeTrue)
  247. })
  248. })
  249. Convey("When saving a dashboard without id and uid and unique title in folder", func() {
  250. cmd := models.SaveDashboardCommand{
  251. OrgId: testOrgId,
  252. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  253. "title": "Dash without id and uid",
  254. }),
  255. Overwrite: shouldOverwrite,
  256. }
  257. res := callSaveWithResult(cmd)
  258. So(res, ShouldNotBeNil)
  259. Convey("It should create a new dashboard", func() {
  260. So(res.Id, ShouldBeGreaterThan, 0)
  261. So(len(res.Uid), ShouldBeGreaterThan, 0)
  262. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  263. err := bus.Dispatch(&query)
  264. So(err, ShouldBeNil)
  265. So(query.Result.Id, ShouldEqual, res.Id)
  266. So(query.Result.Uid, ShouldEqual, res.Uid)
  267. })
  268. })
  269. Convey("When saving a dashboard when dashboard id is zero ", func() {
  270. cmd := models.SaveDashboardCommand{
  271. OrgId: testOrgId,
  272. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  273. "id": 0,
  274. "title": "Dash with zero id",
  275. }),
  276. Overwrite: shouldOverwrite,
  277. }
  278. res := callSaveWithResult(cmd)
  279. So(res, ShouldNotBeNil)
  280. Convey("It should create a new dashboard", func() {
  281. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  282. err := bus.Dispatch(&query)
  283. So(err, ShouldBeNil)
  284. So(query.Result.Id, ShouldEqual, res.Id)
  285. })
  286. })
  287. Convey("When saving a dashboard in non-existing folder", func() {
  288. cmd := models.SaveDashboardCommand{
  289. OrgId: testOrgId,
  290. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  291. "title": "Expect error",
  292. }),
  293. FolderId: 123412321,
  294. Overwrite: shouldOverwrite,
  295. }
  296. err := callSaveWithError(cmd)
  297. Convey("It should result in folder not found error", func() {
  298. So(err, ShouldNotBeNil)
  299. So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
  300. })
  301. })
  302. Convey("When updating an existing dashboard by id without current version", func() {
  303. cmd := models.SaveDashboardCommand{
  304. OrgId: 1,
  305. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  306. "id": savedDashInGeneralFolder.Id,
  307. "title": "test dash 23",
  308. }),
  309. FolderId: savedFolder.Id,
  310. Overwrite: shouldOverwrite,
  311. }
  312. err := callSaveWithError(cmd)
  313. Convey("It should result in version mismatch error", func() {
  314. So(err, ShouldNotBeNil)
  315. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  316. })
  317. })
  318. Convey("When updating an existing dashboard by id with current version", func() {
  319. cmd := models.SaveDashboardCommand{
  320. OrgId: 1,
  321. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  322. "id": savedDashInGeneralFolder.Id,
  323. "title": "Updated title",
  324. "version": savedDashInGeneralFolder.Version,
  325. }),
  326. FolderId: savedFolder.Id,
  327. Overwrite: shouldOverwrite,
  328. }
  329. res := callSaveWithResult(cmd)
  330. So(res, ShouldNotBeNil)
  331. Convey("It should update dashboard", func() {
  332. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  333. err := bus.Dispatch(&query)
  334. So(err, ShouldBeNil)
  335. So(query.Result.Title, ShouldEqual, "Updated title")
  336. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  337. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  338. })
  339. })
  340. Convey("When updating an existing dashboard by uid without current version", func() {
  341. cmd := models.SaveDashboardCommand{
  342. OrgId: 1,
  343. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  344. "uid": savedDashInFolder.Uid,
  345. "title": "test dash 23",
  346. }),
  347. FolderId: 0,
  348. Overwrite: shouldOverwrite,
  349. }
  350. err := callSaveWithError(cmd)
  351. Convey("It should result in version mismatch error", func() {
  352. So(err, ShouldNotBeNil)
  353. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  354. })
  355. })
  356. Convey("When updating an existing dashboard by uid with current version", func() {
  357. cmd := models.SaveDashboardCommand{
  358. OrgId: 1,
  359. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  360. "uid": savedDashInFolder.Uid,
  361. "title": "Updated title",
  362. "version": savedDashInFolder.Version,
  363. }),
  364. FolderId: 0,
  365. Overwrite: shouldOverwrite,
  366. }
  367. res := callSaveWithResult(cmd)
  368. So(res, ShouldNotBeNil)
  369. Convey("It should update dashboard", func() {
  370. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  371. err := bus.Dispatch(&query)
  372. So(err, ShouldBeNil)
  373. So(query.Result.Title, ShouldEqual, "Updated title")
  374. So(query.Result.FolderId, ShouldEqual, 0)
  375. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  376. })
  377. })
  378. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  379. cmd := models.SaveDashboardCommand{
  380. OrgId: testOrgId,
  381. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  382. "id": nil,
  383. "title": savedDashInFolder.Title,
  384. }),
  385. FolderId: savedDashInFolder.FolderId,
  386. Overwrite: shouldOverwrite,
  387. }
  388. err := callSaveWithError(cmd)
  389. Convey("It should result in dashboard with same name in folder error", func() {
  390. So(err, ShouldNotBeNil)
  391. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  392. })
  393. })
  394. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  395. cmd := models.SaveDashboardCommand{
  396. OrgId: testOrgId,
  397. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  398. "id": nil,
  399. "title": savedDashInGeneralFolder.Title,
  400. }),
  401. FolderId: savedDashInGeneralFolder.FolderId,
  402. Overwrite: shouldOverwrite,
  403. }
  404. err := callSaveWithError(cmd)
  405. Convey("It should result in dashboard with same name in folder error", func() {
  406. So(err, ShouldNotBeNil)
  407. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  408. })
  409. })
  410. Convey("When creating a folder with same name as existing folder", func() {
  411. cmd := models.SaveDashboardCommand{
  412. OrgId: testOrgId,
  413. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  414. "id": nil,
  415. "title": savedFolder.Title,
  416. }),
  417. IsFolder: true,
  418. Overwrite: shouldOverwrite,
  419. }
  420. err := callSaveWithError(cmd)
  421. Convey("It should result in dashboard with same name in folder error", func() {
  422. So(err, ShouldNotBeNil)
  423. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  424. })
  425. })
  426. })
  427. Convey("and overwrite flag is set to true", func() {
  428. shouldOverwrite := true
  429. Convey("When updating an existing dashboard by id without current version", func() {
  430. cmd := models.SaveDashboardCommand{
  431. OrgId: 1,
  432. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  433. "id": savedDashInGeneralFolder.Id,
  434. "title": "Updated title",
  435. }),
  436. FolderId: savedFolder.Id,
  437. Overwrite: shouldOverwrite,
  438. }
  439. res := callSaveWithResult(cmd)
  440. So(res, ShouldNotBeNil)
  441. Convey("It should update dashboard", func() {
  442. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  443. err := bus.Dispatch(&query)
  444. So(err, ShouldBeNil)
  445. So(query.Result.Title, ShouldEqual, "Updated title")
  446. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  447. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  448. })
  449. })
  450. Convey("When updating an existing dashboard by uid without current version", func() {
  451. cmd := models.SaveDashboardCommand{
  452. OrgId: 1,
  453. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  454. "uid": savedDashInFolder.Uid,
  455. "title": "Updated title",
  456. }),
  457. FolderId: 0,
  458. Overwrite: shouldOverwrite,
  459. }
  460. res := callSaveWithResult(cmd)
  461. So(res, ShouldNotBeNil)
  462. Convey("It should update dashboard", func() {
  463. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  464. err := bus.Dispatch(&query)
  465. So(err, ShouldBeNil)
  466. So(query.Result.Title, ShouldEqual, "Updated title")
  467. So(query.Result.FolderId, ShouldEqual, 0)
  468. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  469. })
  470. })
  471. Convey("When updating uid for existing dashboard using id", func() {
  472. cmd := models.SaveDashboardCommand{
  473. OrgId: 1,
  474. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  475. "id": savedDashInFolder.Id,
  476. "uid": "new-uid",
  477. "title": savedDashInFolder.Title,
  478. }),
  479. Overwrite: shouldOverwrite,
  480. }
  481. res := callSaveWithResult(cmd)
  482. Convey("It should update dashboard", func() {
  483. So(res, ShouldNotBeNil)
  484. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  485. So(res.Uid, ShouldEqual, "new-uid")
  486. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  487. err := bus.Dispatch(&query)
  488. So(err, ShouldBeNil)
  489. So(query.Result.Uid, ShouldEqual, "new-uid")
  490. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  491. })
  492. })
  493. Convey("When updating uid to an existing uid for existing dashboard using id", func() {
  494. cmd := models.SaveDashboardCommand{
  495. OrgId: 1,
  496. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  497. "id": savedDashInFolder.Id,
  498. "uid": savedDashInGeneralFolder.Uid,
  499. "title": savedDashInFolder.Title,
  500. }),
  501. Overwrite: shouldOverwrite,
  502. }
  503. err := callSaveWithError(cmd)
  504. Convey("It should result in same uid exists error", func() {
  505. So(err, ShouldNotBeNil)
  506. So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
  507. })
  508. })
  509. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  510. cmd := models.SaveDashboardCommand{
  511. OrgId: testOrgId,
  512. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  513. "id": nil,
  514. "title": savedDashInFolder.Title,
  515. }),
  516. FolderId: savedDashInFolder.FolderId,
  517. Overwrite: shouldOverwrite,
  518. }
  519. res := callSaveWithResult(cmd)
  520. Convey("It should overwrite existing dashboard", func() {
  521. So(res, ShouldNotBeNil)
  522. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  523. So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
  524. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  525. err := bus.Dispatch(&query)
  526. So(err, ShouldBeNil)
  527. So(query.Result.Id, ShouldEqual, res.Id)
  528. So(query.Result.Uid, ShouldEqual, res.Uid)
  529. })
  530. })
  531. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  532. cmd := models.SaveDashboardCommand{
  533. OrgId: testOrgId,
  534. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  535. "id": nil,
  536. "title": savedDashInGeneralFolder.Title,
  537. }),
  538. FolderId: savedDashInGeneralFolder.FolderId,
  539. Overwrite: shouldOverwrite,
  540. }
  541. res := callSaveWithResult(cmd)
  542. Convey("It should overwrite existing dashboard", func() {
  543. So(res, ShouldNotBeNil)
  544. So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
  545. So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
  546. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  547. err := bus.Dispatch(&query)
  548. So(err, ShouldBeNil)
  549. So(query.Result.Id, ShouldEqual, res.Id)
  550. So(query.Result.Uid, ShouldEqual, res.Uid)
  551. })
  552. })
  553. Convey("When trying to update existing folder to a dashboard using id", func() {
  554. cmd := models.SaveDashboardCommand{
  555. OrgId: 1,
  556. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  557. "id": savedFolder.Id,
  558. "title": "new title",
  559. }),
  560. IsFolder: false,
  561. Overwrite: shouldOverwrite,
  562. }
  563. err := callSaveWithError(cmd)
  564. Convey("It should result in type mismatch error", func() {
  565. So(err, ShouldNotBeNil)
  566. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  567. })
  568. })
  569. Convey("When trying to update existing dashboard to a folder using id", func() {
  570. cmd := models.SaveDashboardCommand{
  571. OrgId: 1,
  572. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  573. "id": savedDashInFolder.Id,
  574. "title": "new folder title",
  575. }),
  576. IsFolder: true,
  577. Overwrite: shouldOverwrite,
  578. }
  579. err := callSaveWithError(cmd)
  580. Convey("It should result in type mismatch error", func() {
  581. So(err, ShouldNotBeNil)
  582. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  583. })
  584. })
  585. Convey("When trying to update existing folder to a dashboard using uid", func() {
  586. cmd := models.SaveDashboardCommand{
  587. OrgId: 1,
  588. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  589. "uid": savedFolder.Uid,
  590. "title": "new title",
  591. }),
  592. IsFolder: false,
  593. Overwrite: shouldOverwrite,
  594. }
  595. err := callSaveWithError(cmd)
  596. Convey("It should result in type mismatch error", func() {
  597. So(err, ShouldNotBeNil)
  598. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  599. })
  600. })
  601. Convey("When trying to update existing dashboard to a folder using uid", func() {
  602. cmd := models.SaveDashboardCommand{
  603. OrgId: 1,
  604. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  605. "uid": savedDashInFolder.Uid,
  606. "title": "new folder title",
  607. }),
  608. IsFolder: true,
  609. Overwrite: shouldOverwrite,
  610. }
  611. err := callSaveWithError(cmd)
  612. Convey("It should result in type mismatch error", func() {
  613. So(err, ShouldNotBeNil)
  614. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  615. })
  616. })
  617. Convey("When trying to update existing folder to a dashboard using title", func() {
  618. cmd := models.SaveDashboardCommand{
  619. OrgId: 1,
  620. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  621. "title": savedFolder.Title,
  622. }),
  623. IsFolder: false,
  624. Overwrite: shouldOverwrite,
  625. }
  626. err := callSaveWithError(cmd)
  627. Convey("It should result in dashboard with same name as folder error", func() {
  628. So(err, ShouldNotBeNil)
  629. So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
  630. })
  631. })
  632. Convey("When trying to update existing dashboard to a folder using title", func() {
  633. cmd := models.SaveDashboardCommand{
  634. OrgId: 1,
  635. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  636. "title": savedDashInGeneralFolder.Title,
  637. }),
  638. IsFolder: true,
  639. Overwrite: shouldOverwrite,
  640. }
  641. err := callSaveWithError(cmd)
  642. Convey("It should result in folder with same name as dashboard error", func() {
  643. So(err, ShouldNotBeNil)
  644. So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
  645. })
  646. })
  647. })
  648. })
  649. })
  650. })
  651. }
  652. type scenarioContext struct {
  653. dashboardGuardianMock *guardian.FakeDashboardGuardian
  654. }
  655. type scenarioFunc func(c *scenarioContext)
  656. func dashboardGuardianScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
  657. Convey(desc, func() {
  658. origNewDashboardGuardian := guardian.New
  659. guardian.MockDashboardGuardian(mock)
  660. sc := &scenarioContext{
  661. dashboardGuardianMock: mock,
  662. }
  663. defer func() {
  664. guardian.New = origNewDashboardGuardian
  665. }()
  666. fn(sc)
  667. })
  668. }
  669. type dashboardPermissionScenarioContext struct {
  670. dashboardGuardianMock *guardian.FakeDashboardGuardian
  671. }
  672. type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
  673. func dashboardPermissionScenario(desc string, mock *guardian.FakeDashboardGuardian, fn dashboardPermissionScenarioFunc) {
  674. Convey(desc, func() {
  675. origNewDashboardGuardian := guardian.New
  676. guardian.MockDashboardGuardian(mock)
  677. sc := &dashboardPermissionScenarioContext{
  678. dashboardGuardianMock: mock,
  679. }
  680. defer func() {
  681. guardian.New = origNewDashboardGuardian
  682. }()
  683. fn(sc)
  684. })
  685. }
  686. func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
  687. mock := &guardian.FakeDashboardGuardian{
  688. CanSaveValue: canSave,
  689. }
  690. dashboardPermissionScenario(desc, mock, fn)
  691. }
  692. func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
  693. dto := toSaveDashboardDto(cmd)
  694. res, _ := dashboards.NewService().SaveDashboard(&dto)
  695. return res
  696. }
  697. func callSaveWithError(cmd models.SaveDashboardCommand) error {
  698. dto := toSaveDashboardDto(cmd)
  699. _, err := dashboards.NewService().SaveDashboard(&dto)
  700. return err
  701. }
  702. func dashboardServiceScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
  703. Convey(desc, func() {
  704. origNewDashboardGuardian := guardian.New
  705. guardian.MockDashboardGuardian(mock)
  706. sc := &scenarioContext{
  707. dashboardGuardianMock: mock,
  708. }
  709. defer func() {
  710. guardian.New = origNewDashboardGuardian
  711. }()
  712. fn(sc)
  713. })
  714. }
  715. func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
  716. cmd := models.SaveDashboardCommand{
  717. OrgId: orgId,
  718. FolderId: folderId,
  719. IsFolder: false,
  720. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  721. "id": nil,
  722. "title": title,
  723. }),
  724. }
  725. dto := dashboards.SaveDashboardDTO{
  726. OrgId: orgId,
  727. Dashboard: cmd.GetDashboardModel(),
  728. User: &models.SignedInUser{
  729. UserId: 1,
  730. OrgRole: models.ROLE_ADMIN,
  731. },
  732. }
  733. res, err := dashboards.NewService().SaveDashboard(&dto)
  734. So(err, ShouldBeNil)
  735. return res
  736. }
  737. func saveTestFolder(title string, orgId int64) *models.Dashboard {
  738. cmd := models.SaveDashboardCommand{
  739. OrgId: orgId,
  740. FolderId: 0,
  741. IsFolder: true,
  742. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  743. "id": nil,
  744. "title": title,
  745. }),
  746. }
  747. dto := dashboards.SaveDashboardDTO{
  748. OrgId: orgId,
  749. Dashboard: cmd.GetDashboardModel(),
  750. User: &models.SignedInUser{
  751. UserId: 1,
  752. OrgRole: models.ROLE_ADMIN,
  753. },
  754. }
  755. res, err := dashboards.NewService().SaveDashboard(&dto)
  756. So(err, ShouldBeNil)
  757. return res
  758. }
  759. func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
  760. dash := (&cmd).GetDashboardModel()
  761. return dashboards.SaveDashboardDTO{
  762. Dashboard: dash,
  763. Message: cmd.Message,
  764. OrgId: cmd.OrgId,
  765. User: &models.SignedInUser{UserId: cmd.UserId},
  766. Overwrite: cmd.Overwrite,
  767. }
  768. }