dashboard_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. package api
  2. import (
  3. "encoding/json"
  4. "path/filepath"
  5. "testing"
  6. macaron "gopkg.in/macaron.v1"
  7. "github.com/go-macaron/session"
  8. "github.com/grafana/grafana/pkg/api/dtos"
  9. "github.com/grafana/grafana/pkg/bus"
  10. "github.com/grafana/grafana/pkg/components/simplejson"
  11. "github.com/grafana/grafana/pkg/middleware"
  12. m "github.com/grafana/grafana/pkg/models"
  13. "github.com/grafana/grafana/pkg/services/alerting"
  14. "github.com/grafana/grafana/pkg/services/dashboards"
  15. "github.com/grafana/grafana/pkg/setting"
  16. . "github.com/smartystreets/goconvey/convey"
  17. )
  18. type fakeDashboardRepo struct {
  19. inserted []*dashboards.SaveDashboardDTO
  20. provisioned []*m.DashboardProvisioning
  21. getDashboard []*m.Dashboard
  22. }
  23. func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardDTO) (*m.Dashboard, error) {
  24. repo.inserted = append(repo.inserted, json)
  25. return json.Dashboard, nil
  26. }
  27. func (repo *fakeDashboardRepo) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *m.DashboardProvisioning) (*m.Dashboard, error) {
  28. repo.inserted = append(repo.inserted, dto)
  29. return dto.Dashboard, nil
  30. }
  31. func (repo *fakeDashboardRepo) GetProvisionedDashboardData(name string) ([]*m.DashboardProvisioning, error) {
  32. return repo.provisioned, nil
  33. }
  34. var fakeRepo *fakeDashboardRepo
  35. func TestDashboardApiEndpoint(t *testing.T) {
  36. Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
  37. fakeDash := m.NewDashboard("Child dash")
  38. fakeDash.Id = 1
  39. fakeDash.FolderId = 1
  40. fakeDash.HasAcl = false
  41. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  42. query.Result = fakeDash
  43. return nil
  44. })
  45. viewerRole := m.ROLE_VIEWER
  46. editorRole := m.ROLE_EDITOR
  47. aclMockResp := []*m.DashboardAclInfoDTO{
  48. {Role: &viewerRole, Permission: m.PERMISSION_VIEW},
  49. {Role: &editorRole, Permission: m.PERMISSION_EDIT},
  50. }
  51. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  52. query.Result = aclMockResp
  53. return nil
  54. })
  55. bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
  56. query.Result = []*m.Team{}
  57. return nil
  58. })
  59. cmd := m.SaveDashboardCommand{
  60. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  61. "folderId": fakeDash.FolderId,
  62. "title": fakeDash.Title,
  63. "id": fakeDash.Id,
  64. }),
  65. }
  66. Convey("When user is an Org Viewer", func() {
  67. role := m.ROLE_VIEWER
  68. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  69. dash := GetDashboardShouldReturn200(sc)
  70. Convey("Should not be able to edit or save dashboard", func() {
  71. So(dash.Meta.CanEdit, ShouldBeFalse)
  72. So(dash.Meta.CanSave, ShouldBeFalse)
  73. So(dash.Meta.CanAdmin, ShouldBeFalse)
  74. })
  75. })
  76. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  77. CallDeleteDashboard(sc)
  78. So(sc.resp.Code, ShouldEqual, 403)
  79. })
  80. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  81. CallGetDashboardVersion(sc)
  82. So(sc.resp.Code, ShouldEqual, 403)
  83. })
  84. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  85. CallGetDashboardVersions(sc)
  86. So(sc.resp.Code, ShouldEqual, 403)
  87. })
  88. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  89. CallPostDashboard(sc)
  90. So(sc.resp.Code, ShouldEqual, 403)
  91. })
  92. })
  93. Convey("When user is an Org Editor", func() {
  94. role := m.ROLE_EDITOR
  95. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  96. dash := GetDashboardShouldReturn200(sc)
  97. Convey("Should be able to edit or save dashboard", func() {
  98. So(dash.Meta.CanEdit, ShouldBeTrue)
  99. So(dash.Meta.CanSave, ShouldBeTrue)
  100. So(dash.Meta.CanAdmin, ShouldBeFalse)
  101. })
  102. })
  103. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  104. CallDeleteDashboard(sc)
  105. So(sc.resp.Code, ShouldEqual, 200)
  106. })
  107. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  108. CallGetDashboardVersion(sc)
  109. So(sc.resp.Code, ShouldEqual, 200)
  110. })
  111. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  112. CallGetDashboardVersions(sc)
  113. So(sc.resp.Code, ShouldEqual, 200)
  114. })
  115. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  116. CallPostDashboard(sc)
  117. So(sc.resp.Code, ShouldEqual, 200)
  118. })
  119. Convey("When saving a dashboard folder in another folder", func() {
  120. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  121. query.Result = fakeDash
  122. query.Result.IsFolder = true
  123. return nil
  124. })
  125. invalidCmd := m.SaveDashboardCommand{
  126. FolderId: fakeDash.FolderId,
  127. IsFolder: true,
  128. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  129. "folderId": fakeDash.FolderId,
  130. "title": fakeDash.Title,
  131. }),
  132. }
  133. Convey("Should return an error", func() {
  134. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, invalidCmd, func(sc *scenarioContext) {
  135. CallPostDashboard(sc)
  136. So(sc.resp.Code, ShouldEqual, 400)
  137. })
  138. })
  139. })
  140. })
  141. })
  142. Convey("Given a dashboard with a parent folder which has an acl", t, func() {
  143. fakeDash := m.NewDashboard("Child dash")
  144. fakeDash.Id = 1
  145. fakeDash.FolderId = 1
  146. fakeDash.HasAcl = true
  147. setting.ViewersCanEdit = false
  148. aclMockResp := []*m.DashboardAclInfoDTO{
  149. {
  150. DashboardId: 1,
  151. Permission: m.PERMISSION_EDIT,
  152. UserId: 200,
  153. },
  154. }
  155. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  156. query.Result = aclMockResp
  157. return nil
  158. })
  159. bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
  160. query.Result = fakeDash
  161. return nil
  162. })
  163. bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
  164. query.Result = []*m.Team{}
  165. return nil
  166. })
  167. cmd := m.SaveDashboardCommand{
  168. FolderId: fakeDash.FolderId,
  169. Dashboard: simplejson.NewFromAny(map[string]interface{}{
  170. "id": fakeDash.Id,
  171. "folderId": fakeDash.FolderId,
  172. "title": fakeDash.Title,
  173. }),
  174. }
  175. Convey("When user is an Org Viewer and has no permissions for this dashboard", func() {
  176. role := m.ROLE_VIEWER
  177. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  178. sc.handlerFunc = GetDashboard
  179. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  180. Convey("Should be denied access", func() {
  181. So(sc.resp.Code, ShouldEqual, 403)
  182. })
  183. })
  184. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  185. CallDeleteDashboard(sc)
  186. So(sc.resp.Code, ShouldEqual, 403)
  187. })
  188. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  189. CallGetDashboardVersion(sc)
  190. So(sc.resp.Code, ShouldEqual, 403)
  191. })
  192. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  193. CallGetDashboardVersions(sc)
  194. So(sc.resp.Code, ShouldEqual, 403)
  195. })
  196. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  197. CallPostDashboard(sc)
  198. So(sc.resp.Code, ShouldEqual, 403)
  199. })
  200. })
  201. Convey("When user is an Org Editor and has no permissions for this dashboard", func() {
  202. role := m.ROLE_EDITOR
  203. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  204. sc.handlerFunc = GetDashboard
  205. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  206. Convey("Should be denied access", func() {
  207. So(sc.resp.Code, ShouldEqual, 403)
  208. })
  209. })
  210. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  211. CallDeleteDashboard(sc)
  212. So(sc.resp.Code, ShouldEqual, 403)
  213. })
  214. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  215. CallGetDashboardVersion(sc)
  216. So(sc.resp.Code, ShouldEqual, 403)
  217. })
  218. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  219. CallGetDashboardVersions(sc)
  220. So(sc.resp.Code, ShouldEqual, 403)
  221. })
  222. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  223. CallPostDashboard(sc)
  224. So(sc.resp.Code, ShouldEqual, 403)
  225. })
  226. })
  227. Convey("When user is an Org Viewer but has an edit permission", func() {
  228. role := m.ROLE_VIEWER
  229. mockResult := []*m.DashboardAclInfoDTO{
  230. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
  231. }
  232. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  233. query.Result = mockResult
  234. return nil
  235. })
  236. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  237. dash := GetDashboardShouldReturn200(sc)
  238. Convey("Should be able to get dashboard with edit rights", func() {
  239. So(dash.Meta.CanEdit, ShouldBeTrue)
  240. So(dash.Meta.CanSave, ShouldBeTrue)
  241. So(dash.Meta.CanAdmin, ShouldBeFalse)
  242. })
  243. })
  244. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  245. CallDeleteDashboard(sc)
  246. So(sc.resp.Code, ShouldEqual, 200)
  247. })
  248. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  249. CallGetDashboardVersion(sc)
  250. So(sc.resp.Code, ShouldEqual, 200)
  251. })
  252. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  253. CallGetDashboardVersions(sc)
  254. So(sc.resp.Code, ShouldEqual, 200)
  255. })
  256. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  257. CallPostDashboard(sc)
  258. So(sc.resp.Code, ShouldEqual, 200)
  259. })
  260. })
  261. Convey("When user is an Org Viewer and viewers can edit", func() {
  262. role := m.ROLE_VIEWER
  263. setting.ViewersCanEdit = true
  264. mockResult := []*m.DashboardAclInfoDTO{
  265. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
  266. }
  267. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  268. query.Result = mockResult
  269. return nil
  270. })
  271. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  272. dash := GetDashboardShouldReturn200(sc)
  273. Convey("Should be able to get dashboard with edit rights but can save should be false", func() {
  274. So(dash.Meta.CanEdit, ShouldBeTrue)
  275. So(dash.Meta.CanSave, ShouldBeFalse)
  276. So(dash.Meta.CanAdmin, ShouldBeFalse)
  277. })
  278. })
  279. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  280. CallDeleteDashboard(sc)
  281. So(sc.resp.Code, ShouldEqual, 403)
  282. })
  283. })
  284. Convey("When user is an Org Viewer but has an admin permission", func() {
  285. role := m.ROLE_VIEWER
  286. mockResult := []*m.DashboardAclInfoDTO{
  287. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
  288. }
  289. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  290. query.Result = mockResult
  291. return nil
  292. })
  293. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  294. dash := GetDashboardShouldReturn200(sc)
  295. Convey("Should be able to get dashboard with edit rights", func() {
  296. So(dash.Meta.CanEdit, ShouldBeTrue)
  297. So(dash.Meta.CanSave, ShouldBeTrue)
  298. So(dash.Meta.CanAdmin, ShouldBeTrue)
  299. })
  300. })
  301. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  302. CallDeleteDashboard(sc)
  303. So(sc.resp.Code, ShouldEqual, 200)
  304. })
  305. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  306. CallGetDashboardVersion(sc)
  307. So(sc.resp.Code, ShouldEqual, 200)
  308. })
  309. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  310. CallGetDashboardVersions(sc)
  311. So(sc.resp.Code, ShouldEqual, 200)
  312. })
  313. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  314. CallPostDashboard(sc)
  315. So(sc.resp.Code, ShouldEqual, 200)
  316. })
  317. })
  318. Convey("When user is an Org Editor but has a view permission", func() {
  319. role := m.ROLE_EDITOR
  320. mockResult := []*m.DashboardAclInfoDTO{
  321. {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
  322. }
  323. bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
  324. query.Result = mockResult
  325. return nil
  326. })
  327. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  328. dash := GetDashboardShouldReturn200(sc)
  329. Convey("Should not be able to edit or save dashboard", func() {
  330. So(dash.Meta.CanEdit, ShouldBeFalse)
  331. So(dash.Meta.CanSave, ShouldBeFalse)
  332. })
  333. })
  334. loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
  335. CallDeleteDashboard(sc)
  336. So(sc.resp.Code, ShouldEqual, 403)
  337. })
  338. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
  339. CallGetDashboardVersion(sc)
  340. So(sc.resp.Code, ShouldEqual, 403)
  341. })
  342. loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) {
  343. CallGetDashboardVersions(sc)
  344. So(sc.resp.Code, ShouldEqual, 403)
  345. })
  346. postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
  347. CallPostDashboard(sc)
  348. So(sc.resp.Code, ShouldEqual, 403)
  349. })
  350. })
  351. })
  352. }
  353. func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
  354. sc.handlerFunc = GetDashboard
  355. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  356. So(sc.resp.Code, ShouldEqual, 200)
  357. dash := dtos.DashboardFullWithMeta{}
  358. err := json.NewDecoder(sc.resp.Body).Decode(&dash)
  359. So(err, ShouldBeNil)
  360. return dash
  361. }
  362. func CallGetDashboardVersion(sc *scenarioContext) {
  363. bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
  364. query.Result = &m.DashboardVersion{}
  365. return nil
  366. })
  367. sc.handlerFunc = GetDashboardVersion
  368. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  369. }
  370. func CallGetDashboardVersions(sc *scenarioContext) {
  371. bus.AddHandler("test", func(query *m.GetDashboardVersionsQuery) error {
  372. query.Result = []*m.DashboardVersionDTO{}
  373. return nil
  374. })
  375. sc.handlerFunc = GetDashboardVersions
  376. sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
  377. }
  378. func CallDeleteDashboard(sc *scenarioContext) {
  379. bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
  380. return nil
  381. })
  382. sc.handlerFunc = DeleteDashboard
  383. sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
  384. }
  385. func CallPostDashboard(sc *scenarioContext) {
  386. bus.AddHandler("test", func(cmd *alerting.ValidateDashboardAlertsCommand) error {
  387. return nil
  388. })
  389. bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
  390. cmd.Result = &m.Dashboard{Id: 2, Slug: "Dash", Version: 2}
  391. return nil
  392. })
  393. bus.AddHandler("test", func(cmd *alerting.UpdateDashboardAlertsCommand) error {
  394. return nil
  395. })
  396. sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
  397. }
  398. func postDashboardScenario(desc string, url string, routePattern string, role m.RoleType, cmd m.SaveDashboardCommand, fn scenarioFunc) {
  399. Convey(desc+" "+url, func() {
  400. defer bus.ClearBusHandlers()
  401. sc := &scenarioContext{
  402. url: url,
  403. }
  404. viewsPath, _ := filepath.Abs("../../public/views")
  405. sc.m = macaron.New()
  406. sc.m.Use(macaron.Renderer(macaron.RenderOptions{
  407. Directory: viewsPath,
  408. Delims: macaron.Delims{Left: "[[", Right: "]]"},
  409. }))
  410. sc.m.Use(middleware.GetContextHandler())
  411. sc.m.Use(middleware.Sessioner(&session.Options{}))
  412. sc.defaultHandler = wrap(func(c *middleware.Context) Response {
  413. sc.context = c
  414. sc.context.UserId = TestUserID
  415. sc.context.OrgId = TestOrgID
  416. sc.context.OrgRole = role
  417. return PostDashboard(c, cmd)
  418. })
  419. fakeRepo = &fakeDashboardRepo{}
  420. dashboards.SetRepository(fakeRepo)
  421. sc.m.Post(routePattern, sc.defaultHandler)
  422. fn(sc)
  423. })
  424. }