dashboard_service_integration_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  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 saving a dashboard with id that are saved 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 saving a dashboard with uid that are saved 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 dashboard in other organization", 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 trying to create 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 call dashboard guardian 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 trying to create 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 call dashboard guardian 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 trying to update a dashboard by existing id in the General folder", func() {
  139. cmd := models.SaveDashboardCommand{
  140. OrgId: testOrgId,
  141. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  142. "id": savedDashInGeneralFolder.Id,
  143. "title": "Dash",
  144. }),
  145. FolderId: savedDashInGeneralFolder.FolderId,
  146. UserId: 10000,
  147. Overwrite: true,
  148. }
  149. err := callSaveWithError(cmd)
  150. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  151. So(err, ShouldNotBeNil)
  152. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  153. So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInGeneralFolder.Id)
  154. So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
  155. So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
  156. })
  157. })
  158. Convey("When trying to update a dashboard by existing id in other folder", func() {
  159. cmd := models.SaveDashboardCommand{
  160. OrgId: testOrgId,
  161. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  162. "id": savedDashInFolder.Id,
  163. "title": "Dash",
  164. }),
  165. FolderId: savedDashInFolder.FolderId,
  166. UserId: 10000,
  167. Overwrite: true,
  168. }
  169. err := callSaveWithError(cmd)
  170. Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
  171. So(err, ShouldNotBeNil)
  172. So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
  173. So(sc.dashboardGuardianMock.dashId, ShouldEqual, savedDashInFolder.Id)
  174. So(sc.dashboardGuardianMock.orgId, ShouldEqual, cmd.OrgId)
  175. So(sc.dashboardGuardianMock.user.UserId, ShouldEqual, cmd.UserId)
  176. })
  177. })
  178. })
  179. // Given user has permission to save
  180. permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
  181. Convey("and overwrite flag is set to false", func() {
  182. shouldOverwrite := false
  183. Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
  184. cmd := models.SaveDashboardCommand{
  185. OrgId: testOrgId,
  186. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  187. "id": nil,
  188. "title": savedDashInFolder.Title,
  189. }),
  190. FolderId: 0,
  191. Overwrite: shouldOverwrite,
  192. }
  193. res := callSaveWithResult(cmd)
  194. So(res, ShouldNotBeNil)
  195. Convey("It should create a new dashboard", func() {
  196. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  197. err := bus.Dispatch(&query)
  198. So(err, ShouldBeNil)
  199. So(query.Result.Id, ShouldEqual, res.Id)
  200. So(query.Result.FolderId, ShouldEqual, 0)
  201. })
  202. })
  203. Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
  204. cmd := models.SaveDashboardCommand{
  205. OrgId: testOrgId,
  206. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  207. "id": nil,
  208. "title": savedDashInGeneralFolder.Title,
  209. }),
  210. FolderId: savedFolder.Id,
  211. Overwrite: shouldOverwrite,
  212. }
  213. res := callSaveWithResult(cmd)
  214. So(res, ShouldNotBeNil)
  215. Convey("It should create a new dashboard", func() {
  216. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  217. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  218. err := bus.Dispatch(&query)
  219. So(err, ShouldBeNil)
  220. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  221. })
  222. })
  223. Convey("When creating a folder with same name as dashboard in other folder", func() {
  224. cmd := models.SaveDashboardCommand{
  225. OrgId: testOrgId,
  226. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  227. "id": nil,
  228. "title": savedDashInFolder.Title,
  229. }),
  230. IsFolder: true,
  231. Overwrite: shouldOverwrite,
  232. }
  233. res := callSaveWithResult(cmd)
  234. So(res, ShouldNotBeNil)
  235. Convey("It should create a new folder", func() {
  236. So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
  237. So(res.IsFolder, ShouldBeTrue)
  238. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  239. err := bus.Dispatch(&query)
  240. So(err, ShouldBeNil)
  241. So(query.Result.FolderId, ShouldEqual, 0)
  242. So(query.Result.IsFolder, ShouldBeTrue)
  243. })
  244. })
  245. Convey("When saving a dashboard without id and uid and unique title in folder", func() {
  246. cmd := models.SaveDashboardCommand{
  247. OrgId: testOrgId,
  248. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  249. "title": "Dash without id and uid",
  250. }),
  251. Overwrite: shouldOverwrite,
  252. }
  253. res := callSaveWithResult(cmd)
  254. So(res, ShouldNotBeNil)
  255. Convey("It should create a new dashboard", func() {
  256. So(res.Id, ShouldBeGreaterThan, 0)
  257. So(len(res.Uid), ShouldBeGreaterThan, 0)
  258. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  259. err := bus.Dispatch(&query)
  260. So(err, ShouldBeNil)
  261. So(query.Result.Id, ShouldEqual, res.Id)
  262. So(query.Result.Uid, ShouldEqual, res.Uid)
  263. })
  264. })
  265. Convey("When saving a dashboard when dashboard id is zero ", func() {
  266. cmd := models.SaveDashboardCommand{
  267. OrgId: testOrgId,
  268. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  269. "id": 0,
  270. "title": "Dash with zero id",
  271. }),
  272. Overwrite: shouldOverwrite,
  273. }
  274. res := callSaveWithResult(cmd)
  275. So(res, ShouldNotBeNil)
  276. Convey("It should create a new dashboard", func() {
  277. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  278. err := bus.Dispatch(&query)
  279. So(err, ShouldBeNil)
  280. So(query.Result.Id, ShouldEqual, res.Id)
  281. })
  282. })
  283. Convey("When saving a dashboard in non-existing folder", func() {
  284. cmd := models.SaveDashboardCommand{
  285. OrgId: testOrgId,
  286. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  287. "title": "Expect error",
  288. }),
  289. FolderId: 123412321,
  290. Overwrite: shouldOverwrite,
  291. }
  292. err := callSaveWithError(cmd)
  293. Convey("It should result in folder not found error", func() {
  294. So(err, ShouldNotBeNil)
  295. So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
  296. })
  297. })
  298. Convey("When updating an existing dashboard by id without current version", func() {
  299. cmd := models.SaveDashboardCommand{
  300. OrgId: 1,
  301. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  302. "id": savedDashInGeneralFolder.Id,
  303. "title": "test dash 23",
  304. }),
  305. FolderId: savedFolder.Id,
  306. Overwrite: shouldOverwrite,
  307. }
  308. err := callSaveWithError(cmd)
  309. Convey("It should result in version mismatch error", func() {
  310. So(err, ShouldNotBeNil)
  311. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  312. })
  313. })
  314. Convey("When updating an existing dashboard by id with current version", func() {
  315. cmd := models.SaveDashboardCommand{
  316. OrgId: 1,
  317. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  318. "id": savedDashInGeneralFolder.Id,
  319. "title": "Updated title",
  320. "version": savedDashInGeneralFolder.Version,
  321. }),
  322. FolderId: savedFolder.Id,
  323. Overwrite: shouldOverwrite,
  324. }
  325. res := callSaveWithResult(cmd)
  326. So(res, ShouldNotBeNil)
  327. Convey("It should update dashboard", func() {
  328. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  329. err := bus.Dispatch(&query)
  330. So(err, ShouldBeNil)
  331. So(query.Result.Title, ShouldEqual, "Updated title")
  332. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  333. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  334. })
  335. })
  336. Convey("When updating an existing dashboard by uid without current version", func() {
  337. cmd := models.SaveDashboardCommand{
  338. OrgId: 1,
  339. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  340. "uid": savedDashInFolder.Uid,
  341. "title": "test dash 23",
  342. }),
  343. FolderId: 0,
  344. Overwrite: shouldOverwrite,
  345. }
  346. err := callSaveWithError(cmd)
  347. Convey("It should result in version mismatch error", func() {
  348. So(err, ShouldNotBeNil)
  349. So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
  350. })
  351. })
  352. Convey("When updating an existing dashboard by uid with current version", func() {
  353. cmd := models.SaveDashboardCommand{
  354. OrgId: 1,
  355. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  356. "uid": savedDashInFolder.Uid,
  357. "title": "Updated title",
  358. "version": savedDashInFolder.Version,
  359. }),
  360. FolderId: 0,
  361. Overwrite: shouldOverwrite,
  362. }
  363. res := callSaveWithResult(cmd)
  364. So(res, ShouldNotBeNil)
  365. Convey("It should update dashboard", func() {
  366. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  367. err := bus.Dispatch(&query)
  368. So(err, ShouldBeNil)
  369. So(query.Result.Title, ShouldEqual, "Updated title")
  370. So(query.Result.FolderId, ShouldEqual, 0)
  371. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  372. })
  373. })
  374. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  375. cmd := models.SaveDashboardCommand{
  376. OrgId: testOrgId,
  377. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  378. "id": nil,
  379. "title": savedDashInFolder.Title,
  380. }),
  381. FolderId: savedDashInFolder.FolderId,
  382. Overwrite: shouldOverwrite,
  383. }
  384. err := callSaveWithError(cmd)
  385. Convey("It should result in dashboard with same name in folder error", func() {
  386. So(err, ShouldNotBeNil)
  387. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  388. })
  389. })
  390. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  391. cmd := models.SaveDashboardCommand{
  392. OrgId: testOrgId,
  393. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  394. "id": nil,
  395. "title": savedDashInGeneralFolder.Title,
  396. }),
  397. FolderId: savedDashInGeneralFolder.FolderId,
  398. Overwrite: shouldOverwrite,
  399. }
  400. err := callSaveWithError(cmd)
  401. Convey("It should result in dashboard with same name in folder error", func() {
  402. So(err, ShouldNotBeNil)
  403. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  404. })
  405. })
  406. Convey("When creating a folder with same name as existing folder", func() {
  407. cmd := models.SaveDashboardCommand{
  408. OrgId: testOrgId,
  409. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  410. "id": nil,
  411. "title": savedFolder.Title,
  412. }),
  413. IsFolder: true,
  414. Overwrite: shouldOverwrite,
  415. }
  416. err := callSaveWithError(cmd)
  417. Convey("It should result in dashboard with same name in folder error", func() {
  418. So(err, ShouldNotBeNil)
  419. So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
  420. })
  421. })
  422. })
  423. Convey("and overwrite flag is set to true", func() {
  424. shouldOverwrite := true
  425. Convey("When updating an existing dashboard by id without current version", func() {
  426. cmd := models.SaveDashboardCommand{
  427. OrgId: 1,
  428. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  429. "id": savedDashInGeneralFolder.Id,
  430. "title": "Updated title",
  431. }),
  432. FolderId: savedFolder.Id,
  433. Overwrite: shouldOverwrite,
  434. }
  435. res := callSaveWithResult(cmd)
  436. So(res, ShouldNotBeNil)
  437. Convey("It should update dashboard", func() {
  438. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
  439. err := bus.Dispatch(&query)
  440. So(err, ShouldBeNil)
  441. So(query.Result.Title, ShouldEqual, "Updated title")
  442. So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
  443. So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
  444. })
  445. })
  446. Convey("When updating an existing dashboard by uid without current version", func() {
  447. cmd := models.SaveDashboardCommand{
  448. OrgId: 1,
  449. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  450. "uid": savedDashInFolder.Uid,
  451. "title": "Updated title",
  452. }),
  453. FolderId: 0,
  454. Overwrite: shouldOverwrite,
  455. }
  456. res := callSaveWithResult(cmd)
  457. So(res, ShouldNotBeNil)
  458. Convey("It should update dashboard", func() {
  459. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  460. err := bus.Dispatch(&query)
  461. So(err, ShouldBeNil)
  462. So(query.Result.Title, ShouldEqual, "Updated title")
  463. So(query.Result.FolderId, ShouldEqual, 0)
  464. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  465. })
  466. })
  467. Convey("When updating uid for existing dashboard using id", func() {
  468. cmd := models.SaveDashboardCommand{
  469. OrgId: 1,
  470. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  471. "id": savedDashInFolder.Id,
  472. "uid": "new-uid",
  473. "title": savedDashInFolder.Title,
  474. }),
  475. Overwrite: shouldOverwrite,
  476. }
  477. res := callSaveWithResult(cmd)
  478. Convey("It should update dashboard", func() {
  479. So(res, ShouldNotBeNil)
  480. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  481. So(res.Uid, ShouldEqual, "new-uid")
  482. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
  483. err := bus.Dispatch(&query)
  484. So(err, ShouldBeNil)
  485. So(query.Result.Uid, ShouldEqual, "new-uid")
  486. So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
  487. })
  488. })
  489. Convey("When updating uid to an existing uid for existing dashboard using id", func() {
  490. cmd := models.SaveDashboardCommand{
  491. OrgId: 1,
  492. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  493. "id": savedDashInFolder.Id,
  494. "uid": savedDashInGeneralFolder.Uid,
  495. "title": savedDashInFolder.Title,
  496. }),
  497. Overwrite: shouldOverwrite,
  498. }
  499. err := callSaveWithError(cmd)
  500. Convey("It should result in same uid exists error", func() {
  501. So(err, ShouldNotBeNil)
  502. So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
  503. })
  504. })
  505. Convey("When creating a dashboard with same name as dashboard in other folder", func() {
  506. cmd := models.SaveDashboardCommand{
  507. OrgId: testOrgId,
  508. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  509. "id": nil,
  510. "title": savedDashInFolder.Title,
  511. }),
  512. FolderId: savedDashInFolder.FolderId,
  513. Overwrite: shouldOverwrite,
  514. }
  515. res := callSaveWithResult(cmd)
  516. Convey("It should overwrite existing dashboard", func() {
  517. So(res, ShouldNotBeNil)
  518. So(res.Id, ShouldEqual, savedDashInFolder.Id)
  519. So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
  520. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  521. err := bus.Dispatch(&query)
  522. So(err, ShouldBeNil)
  523. So(query.Result.Id, ShouldEqual, res.Id)
  524. So(query.Result.Uid, ShouldEqual, res.Uid)
  525. })
  526. })
  527. Convey("When creating a dashboard with same name as dashboard in General folder", func() {
  528. cmd := models.SaveDashboardCommand{
  529. OrgId: testOrgId,
  530. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  531. "id": nil,
  532. "title": savedDashInGeneralFolder.Title,
  533. }),
  534. FolderId: savedDashInGeneralFolder.FolderId,
  535. Overwrite: shouldOverwrite,
  536. }
  537. res := callSaveWithResult(cmd)
  538. Convey("It should overwrite existing dashboard", func() {
  539. So(res, ShouldNotBeNil)
  540. So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
  541. So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
  542. query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
  543. err := bus.Dispatch(&query)
  544. So(err, ShouldBeNil)
  545. So(query.Result.Id, ShouldEqual, res.Id)
  546. So(query.Result.Uid, ShouldEqual, res.Uid)
  547. })
  548. })
  549. Convey("When trying to update existing folder to a dashboard using id", func() {
  550. cmd := models.SaveDashboardCommand{
  551. OrgId: 1,
  552. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  553. "id": savedFolder.Id,
  554. "title": "new title",
  555. }),
  556. IsFolder: false,
  557. Overwrite: shouldOverwrite,
  558. }
  559. err := callSaveWithError(cmd)
  560. Convey("It should result in type mismatch error", func() {
  561. So(err, ShouldNotBeNil)
  562. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  563. })
  564. })
  565. Convey("When trying to update existing dashboard to a folder using id", func() {
  566. cmd := models.SaveDashboardCommand{
  567. OrgId: 1,
  568. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  569. "id": savedDashInFolder.Id,
  570. "title": "new folder title",
  571. }),
  572. IsFolder: true,
  573. Overwrite: shouldOverwrite,
  574. }
  575. err := callSaveWithError(cmd)
  576. Convey("It should result in type mismatch error", func() {
  577. So(err, ShouldNotBeNil)
  578. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  579. })
  580. })
  581. Convey("When trying to update existing folder to a dashboard using uid", func() {
  582. cmd := models.SaveDashboardCommand{
  583. OrgId: 1,
  584. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  585. "uid": savedFolder.Uid,
  586. "title": "new title",
  587. }),
  588. IsFolder: false,
  589. Overwrite: shouldOverwrite,
  590. }
  591. err := callSaveWithError(cmd)
  592. Convey("It should result in type mismatch error", func() {
  593. So(err, ShouldNotBeNil)
  594. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  595. })
  596. })
  597. Convey("When trying to update existing dashboard to a folder using uid", func() {
  598. cmd := models.SaveDashboardCommand{
  599. OrgId: 1,
  600. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  601. "uid": savedDashInFolder.Uid,
  602. "title": "new folder title",
  603. }),
  604. IsFolder: true,
  605. Overwrite: shouldOverwrite,
  606. }
  607. err := callSaveWithError(cmd)
  608. Convey("It should result in type mismatch error", func() {
  609. So(err, ShouldNotBeNil)
  610. So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
  611. })
  612. })
  613. Convey("When trying to update existing folder to a dashboard using title", func() {
  614. cmd := models.SaveDashboardCommand{
  615. OrgId: 1,
  616. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  617. "title": savedFolder.Title,
  618. }),
  619. IsFolder: false,
  620. Overwrite: shouldOverwrite,
  621. }
  622. err := callSaveWithError(cmd)
  623. Convey("It should result in dashboard with same name as folder error", func() {
  624. So(err, ShouldNotBeNil)
  625. So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
  626. })
  627. })
  628. Convey("When trying to update existing dashboard to a folder using title", func() {
  629. cmd := models.SaveDashboardCommand{
  630. OrgId: 1,
  631. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  632. "title": savedDashInGeneralFolder.Title,
  633. }),
  634. IsFolder: true,
  635. Overwrite: shouldOverwrite,
  636. }
  637. err := callSaveWithError(cmd)
  638. Convey("It should result in folder with same name as dashboard error", func() {
  639. So(err, ShouldNotBeNil)
  640. So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
  641. })
  642. })
  643. })
  644. })
  645. })
  646. })
  647. }
  648. func mockDashboardGuardian(mock *mockDashboardGuarder) {
  649. guardian.New = func(dashId int64, orgId int64, user *models.SignedInUser) guardian.DashboardGuardian {
  650. mock.orgId = orgId
  651. mock.dashId = dashId
  652. mock.user = user
  653. return mock
  654. }
  655. }
  656. type mockDashboardGuarder struct {
  657. dashId int64
  658. orgId int64
  659. user *models.SignedInUser
  660. canSave bool
  661. canSaveCallCounter int
  662. canEdit bool
  663. canView bool
  664. canAdmin bool
  665. hasPermission bool
  666. checkPermissionBeforeRemove bool
  667. checkPermissionBeforeUpdate bool
  668. }
  669. func (g *mockDashboardGuarder) CanSave() (bool, error) {
  670. g.canSaveCallCounter++
  671. return g.canSave, nil
  672. }
  673. func (g *mockDashboardGuarder) CanEdit() (bool, error) {
  674. return g.canEdit, nil
  675. }
  676. func (g *mockDashboardGuarder) CanView() (bool, error) {
  677. return g.canView, nil
  678. }
  679. func (g *mockDashboardGuarder) CanAdmin() (bool, error) {
  680. return g.canAdmin, nil
  681. }
  682. func (g *mockDashboardGuarder) HasPermission(permission models.PermissionType) (bool, error) {
  683. return g.hasPermission, nil
  684. }
  685. func (g *mockDashboardGuarder) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) {
  686. return g.checkPermissionBeforeUpdate, nil
  687. }
  688. func (g *mockDashboardGuarder) GetAcl() ([]*models.DashboardAclInfoDTO, error) {
  689. return nil, nil
  690. }
  691. type scenarioContext struct {
  692. dashboardGuardianMock *mockDashboardGuarder
  693. }
  694. type scenarioFunc func(c *scenarioContext)
  695. func dashboardGuardianScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
  696. Convey(desc, func() {
  697. origNewDashboardGuardian := guardian.New
  698. mockDashboardGuardian(mock)
  699. sc := &scenarioContext{
  700. dashboardGuardianMock: mock,
  701. }
  702. defer func() {
  703. guardian.New = origNewDashboardGuardian
  704. }()
  705. fn(sc)
  706. })
  707. }
  708. type dashboardPermissionScenarioContext struct {
  709. dashboardGuardianMock *mockDashboardGuarder
  710. }
  711. type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
  712. func dashboardPermissionScenario(desc string, mock *mockDashboardGuarder, fn dashboardPermissionScenarioFunc) {
  713. Convey(desc, func() {
  714. origNewDashboardGuardian := guardian.New
  715. mockDashboardGuardian(mock)
  716. sc := &dashboardPermissionScenarioContext{
  717. dashboardGuardianMock: mock,
  718. }
  719. defer func() {
  720. guardian.New = origNewDashboardGuardian
  721. }()
  722. fn(sc)
  723. })
  724. }
  725. func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
  726. mock := &mockDashboardGuarder{
  727. canSave: canSave,
  728. }
  729. dashboardPermissionScenario(desc, mock, fn)
  730. }
  731. func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
  732. dto := toSaveDashboardDto(cmd)
  733. res, _ := dashboards.NewService().SaveDashboard(&dto)
  734. return res
  735. }
  736. func callSaveWithError(cmd models.SaveDashboardCommand) error {
  737. dto := toSaveDashboardDto(cmd)
  738. _, err := dashboards.NewService().SaveDashboard(&dto)
  739. return err
  740. }
  741. func dashboardServiceScenario(desc string, mock *mockDashboardGuarder, fn scenarioFunc) {
  742. Convey(desc, func() {
  743. origNewDashboardGuardian := guardian.New
  744. mockDashboardGuardian(mock)
  745. sc := &scenarioContext{
  746. dashboardGuardianMock: mock,
  747. }
  748. defer func() {
  749. guardian.New = origNewDashboardGuardian
  750. }()
  751. fn(sc)
  752. })
  753. }
  754. func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
  755. cmd := models.SaveDashboardCommand{
  756. OrgId: orgId,
  757. FolderId: folderId,
  758. IsFolder: false,
  759. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  760. "id": nil,
  761. "title": title,
  762. }),
  763. }
  764. dto := dashboards.SaveDashboardDTO{
  765. OrgId: orgId,
  766. Dashboard: cmd.GetDashboardModel(),
  767. User: &models.SignedInUser{
  768. UserId: 1,
  769. OrgRole: models.ROLE_ADMIN,
  770. },
  771. }
  772. res, err := dashboards.NewService().SaveDashboard(&dto)
  773. So(err, ShouldBeNil)
  774. return res
  775. }
  776. func saveTestFolder(title string, orgId int64) *models.Dashboard {
  777. cmd := models.SaveDashboardCommand{
  778. OrgId: orgId,
  779. FolderId: 0,
  780. IsFolder: true,
  781. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  782. "id": nil,
  783. "title": title,
  784. }),
  785. }
  786. dto := dashboards.SaveDashboardDTO{
  787. OrgId: orgId,
  788. Dashboard: cmd.GetDashboardModel(),
  789. User: &models.SignedInUser{
  790. UserId: 1,
  791. OrgRole: models.ROLE_ADMIN,
  792. },
  793. }
  794. res, err := dashboards.NewService().SaveDashboard(&dto)
  795. So(err, ShouldBeNil)
  796. return res
  797. }
  798. func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
  799. dash := (&cmd).GetDashboardModel()
  800. return dashboards.SaveDashboardDTO{
  801. Dashboard: dash,
  802. Message: cmd.Message,
  803. OrgId: cmd.OrgId,
  804. User: &models.SignedInUser{UserId: cmd.UserId},
  805. Overwrite: cmd.Overwrite,
  806. }
  807. }