dashboard_service_integration_test.go 33 KB

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