diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 631a7abe6b2..fef1af41415 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -112,7 +111,7 @@ func (hs *HTTPServer) AdminUpdateUserPermissions(c *models.ReqContext) response. return response.Error(http.StatusBadRequest, "id is invalid", err) } - err = updateUserPermissions(hs.SQLStore, userID, form.IsGrafanaAdmin) + err = hs.SQLStore.UpdateUserPermissions(userID, form.IsGrafanaAdmin) if err != nil { if errors.Is(err, models.ErrLastGrafanaAdmin) { return response.Error(400, models.ErrLastGrafanaAdmin.Error(), nil) @@ -230,10 +229,3 @@ func (hs *HTTPServer) AdminRevokeUserAuthToken(c *models.ReqContext) response.Re } return hs.revokeUserAuthTokenInternal(c, userID, cmd) } - -// updateUserPermissions updates the user's permissions. -// -// Stubbable by tests. -var updateUserPermissions = func(sqlStore *sqlstore.SQLStore, userID int64, isAdmin bool) error { - return sqlStore.UpdateUserPermissions(userID, isAdmin) -} diff --git a/pkg/api/admin_users_test.go b/pkg/api/admin_users_test.go index cbe4d2d6536..e79320ee2d0 100644 --- a/pkg/api/admin_users_test.go +++ b/pkg/api/admin_users_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,25 +35,18 @@ func TestAdminAPIEndpoint(t *testing.T) { updateCmd := dtos.AdminUpdateUserPermissionsForm{ IsGrafanaAdmin: false, } - + mock := mockstore.SQLStoreMock{ + ExpectedError: models.ErrLastGrafanaAdmin, + } putAdminScenario(t, "When calling PUT on", "/api/admin/users/1/permissions", "/api/admin/users/:id/permissions", role, updateCmd, func(sc *scenarioContext) { - // TODO: Use a fake SQLStore when it's represented by an interface - origUpdateUserPermissions := updateUserPermissions - t.Cleanup(func() { - updateUserPermissions = origUpdateUserPermissions - }) - - updateUserPermissions = func(sqlStore *sqlstore.SQLStore, userID int64, isAdmin bool) error { - return models.ErrLastGrafanaAdmin - } - sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() assert.Equal(t, 400, sc.resp.Code) - }) + }, mock) }) t.Run("When a server admin attempts to logout himself from all devices", func(t *testing.T) { + mock := mockstore.NewSQLStoreMock() adminLogoutUserScenario(t, "Should not be allowed when calling POST on", "/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) { bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { @@ -62,54 +56,41 @@ func TestAdminAPIEndpoint(t *testing.T) { sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 400, sc.resp.Code) - }) + }, mock) }) t.Run("When a server admin attempts to logout a non-existing user from all devices", func(t *testing.T) { + mock := mockstore.SQLStoreMock{ + ExpectedError: models.ErrUserNotFound, + } adminLogoutUserScenario(t, "Should return not found when calling POST on", "/api/admin/users/200/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) { - userID := int64(0) - - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - userID = cmd.Id - return models.ErrUserNotFound - }) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - assert.Equal(t, int64(200), userID) - }) + }, mock) }) t.Run("When a server admin attempts to revoke an auth token for a non-existing user", func(t *testing.T) { cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} - + mock := mockstore.SQLStoreMock{ + ExpectedError: models.ErrUserNotFound, + } adminRevokeUserAuthTokenScenario(t, "Should return not found when calling POST on", "/api/admin/users/200/revoke-auth-token", "/api/admin/users/:id/revoke-auth-token", cmd, func(sc *scenarioContext) { - var userID int64 - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - userID = cmd.Id - return models.ErrUserNotFound - }) - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - assert.Equal(t, int64(200), userID) - }) + }, mock) }) t.Run("When a server admin gets auth tokens for a non-existing user", func(t *testing.T) { + mock := mockstore.SQLStoreMock{ + ExpectedError: models.ErrUserNotFound, + } adminGetUserAuthTokensScenario(t, "Should return not found when calling GET on", "/api/admin/users/200/auth-tokens", "/api/admin/users/:id/auth-tokens", func(sc *scenarioContext) { - var userID int64 - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - userID = cmd.Id - return models.ErrUserNotFound - }) - sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - assert.Equal(t, int64(200), userID) - }) + }, mock) }) t.Run("When a server admin attempts to enable/disable a nonexistent user", func(t *testing.T) { @@ -305,12 +286,13 @@ func TestAdminAPIEndpoint(t *testing.T) { } func putAdminScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType, - cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) { + cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) hs := &HTTPServer{ - Cfg: setting.NewCfg(), + Cfg: setting.NewCfg(), + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) @@ -330,13 +312,14 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string }) } -func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) { +func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: auth.NewFakeUserAuthTokenService(), + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) @@ -357,7 +340,7 @@ func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern }) } -func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc) { +func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -366,6 +349,7 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) @@ -386,7 +370,7 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou }) } -func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) { +func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -395,6 +379,7 @@ func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, route hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go index d36f927ee2b..1a7caf1e65d 100644 --- a/pkg/api/alerting_test.go +++ b/pkg/api/alerting_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -81,6 +82,7 @@ func TestAlertingAPIEndpoint(t *testing.T) { }) }) + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() @@ -102,7 +104,7 @@ func TestAlertingAPIEndpoint(t *testing.T) { require.Nil(t, searchQuery) assert.NotNil(t, getAlertsQuery) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", @@ -140,7 +142,7 @@ func TestAlertingAPIEndpoint(t *testing.T) { assert.Equal(t, int64(2), getAlertsQuery.DashboardIDs[1]) assert.Equal(t, int64(5), getAlertsQuery.Limit) assert.Equal(t, "alertQuery", getAlertsQuery.Query) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) { @@ -149,7 +151,7 @@ func TestAlertingAPIEndpoint(t *testing.T) { sc.handlerFunc = GetAlertNotificationByID sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - }) + }, mock) } func callPauseAlert(sc *scenarioContext) { diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index e380b703d23..f51b3349d55 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/annotations" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/stretchr/testify/assert" ) @@ -55,6 +56,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { assert.Equal(t, 403, sc.resp.Code) }) + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { fakeAnnoRepo = &fakeAnnotationsRepo{} @@ -62,7 +64,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.handlerFunc = DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) }) @@ -84,7 +86,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) }) - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { fakeAnnoRepo = &fakeAnnotationsRepo{} @@ -92,7 +94,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.handlerFunc = DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) }) }) @@ -165,7 +167,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() assert.Equal(t, 403, sc.resp.Code) }) - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { setUp() @@ -174,7 +176,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.handlerFunc = DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) }) @@ -198,7 +200,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) }) - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { setUp() @@ -207,7 +209,7 @@ func TestAnnotationsAPIEndpoint(t *testing.T) { sc.handlerFunc = DeleteAnnotationByID sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) }) diff --git a/pkg/api/api.go b/pkg/api/api.go index ea6c3859c08..e1aff6d4591 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -154,7 +154,7 @@ func (hs *HTTPServer) registerRoutes() { userRoute.Delete("/stars/dashboard/:id", routing.Wrap(hs.UnstarDashboard)) userRoute.Put("/password", routing.Wrap(hs.ChangeUserPassword)) - userRoute.Get("/quotas", routing.Wrap(GetUserQuotas)) + userRoute.Get("/quotas", routing.Wrap(hs.GetUserQuotas)) userRoute.Put("/helpflags/:id", routing.Wrap(hs.SetHelpFlag)) // For dev purpose userRoute.Get("/helpflags/clear", routing.Wrap(hs.ClearHelpFlags)) @@ -264,7 +264,7 @@ func (hs *HTTPServer) registerRoutes() { // Preferences apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) { - prefRoute.Post("/set-home-dash", routing.Wrap(SetHomeDashboard)) + prefRoute.Post("/set-home-dash", routing.Wrap(hs.SetHomeDashboard)) }) // Data sources @@ -493,8 +493,8 @@ func (hs *HTTPServer) registerRoutes() { adminUserRoute.Delete("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(AdminDeleteUser)) adminUserRoute.Post("/:id/disable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser)) adminUserRoute.Post("/:id/enable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(AdminEnableUser)) - adminUserRoute.Get("/:id/quotas", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(GetUserQuotas)) - adminUserRoute.Put("/:id/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(UpdateUserQuota)) + adminUserRoute.Get("/:id/quotas", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(hs.GetUserQuotas)) + adminUserRoute.Put("/:id/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(hs.UpdateUserQuota)) adminUserRoute.Post("/:id/logout", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser)) adminUserRoute.Get("/:id/auth-tokens", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens)) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index ed2e956e096..4b337918132 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -40,15 +40,16 @@ import ( "github.com/stretchr/testify/require" ) -func loggedInUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) { - loggedInUserScenarioWithRole(t, desc, "GET", url, routePattern, models.ROLE_EDITOR, fn) +func loggedInUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) { + loggedInUserScenarioWithRole(t, desc, "GET", url, routePattern, models.ROLE_EDITOR, fn, sqlStore) } -func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url string, routePattern string, role models.RoleType, fn scenarioFunc) { +func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url string, routePattern string, role models.RoleType, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) sc := setupScenarioContext(t, url) + sc.sqlStore = sqlStore sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { sc.context = c sc.context.UserId = testUserID @@ -156,6 +157,7 @@ type scenarioContext struct { req *http.Request url string userAuthTokenService *auth.FakeUserAuthTokenService + sqlStore sqlstore.Store } func (sc *scenarioContext) exec() { diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index a79733baba1..d33958df6ce 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/dashboards" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/guardian" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" ) @@ -30,13 +31,13 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { return models.ErrDashboardNotFound }) } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() callGetDashboardPermissions(sc, hs) assert.Equal(t, 404, sc.resp.Code) - }) + }, mock) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -73,13 +74,13 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { return nil }) } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() callGetDashboardPermissions(sc, hs) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -125,6 +126,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { return nil }) } + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { @@ -139,7 +141,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { assert.Len(t, resp, 5) assert.Equal(t, int64(2), resp[0].UserId) assert.Equal(t, models.PERMISSION_VIEW, resp[0].Permission) - }) + }, mock) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -341,7 +343,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { return nil }) } - + mock := mockstore.NewSQLStoreMock() var resp []*models.DashboardAclInfoDTO loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:dashboardId/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { @@ -357,7 +359,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { assert.Equal(t, models.PERMISSION_EDIT, resp[0].Permission) assert.Equal(t, int64(4), resp[1].UserId) assert.Equal(t, models.PERMISSION_ADMIN, resp[1].Permission) - }) + }, mock) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ diff --git a/pkg/api/dashboard_snapshot_test.go b/pkg/api/dashboard_snapshot_test.go index ae83a137cf1..67f2f4cb922 100644 --- a/pkg/api/dashboard_snapshot_test.go +++ b/pkg/api/dashboard_snapshot_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -69,6 +70,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { } t.Run("When user has editor role and is not in the ACL", func(t *testing.T) { + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "Should not be able to delete snapshot when calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { mockSnapshotResult := setUpSnapshotTest(t) @@ -84,7 +86,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { assert.Equal(t, 403, sc.resp.Code) require.Nil(t, externalRequest) - }) + }, mock) }) t.Run("When user is anonymous", func(t *testing.T) { @@ -120,7 +122,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { {Role: &viewerRole, Permission: models.PERMISSION_VIEW}, {Role: &editorRole, Permission: models.PERMISSION_EDIT}, } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { mockSnapshotResult := setUpSnapshotTest(t) @@ -143,11 +145,12 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { assert.Equal(t, 1, respJSON.Get("id").MustInt()) assert.Equal(t, ts.URL, fmt.Sprintf("http://%s", externalRequest.Host)) assert.Equal(t, "/", externalRequest.URL.EscapedPath()) - }) + }, mock) }) t.Run("When user is editor and creator of the snapshot", func(t *testing.T) { aclMockResp = []*models.DashboardAclInfoDTO{} + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { mockSnapshotResult := setUpSnapshotTest(t) @@ -164,11 +167,12 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { assert.True(t, strings.HasPrefix(respJSON.Get("message").MustString(), "Snapshot deleted")) assert.Equal(t, 1, respJSON.Get("id").MustInt()) - }) + }, mock) }) t.Run("When deleting an external snapshot", func(t *testing.T) { aclMockResp = []*models.DashboardAclInfoDTO{} + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "Should gracefully delete local snapshot when remote snapshot has already been removed when calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { @@ -192,7 +196,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { assert.True(t, strings.HasPrefix(respJSON.Get("message").MustString(), "Snapshot deleted")) assert.Equal(t, 1, respJSON.Get("id").MustInt()) - }) + }, mock) loggedInUserScenarioWithRole(t, "Should fail to delete local snapshot when an unexpected 500 error occurs when calling DELETE on", "DELETE", @@ -213,7 +217,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { require.NoError(t, writeErr) assert.Equal(t, 500, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "Should fail to delete local snapshot when an unexpected remote error occurs when calling DELETE on", @@ -230,7 +234,7 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() assert.Equal(t, 500, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "Should be able to read a snapshot's unencrypted data when calling GET on", "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { @@ -247,6 +251,6 @@ func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) { id := dashboard.Get("id") assert.Equal(t, int64(100), id.MustInt64()) - }) + }, mock) }) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index c44ef24a034..5738c5300fd 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/assert" @@ -159,7 +160,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { t.Run("When user is an Org Viewer", func(t *testing.T) { role := models.ROLE_VIEWER - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUp() @@ -171,7 +172,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.False(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) assert.False(t, dash.Meta.CanAdmin) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { @@ -185,7 +186,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { @@ -193,7 +194,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersion(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { @@ -201,12 +202,12 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersions(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) t.Run("When user is an Org Editor", func(t *testing.T) { role := models.ROLE_EDITOR - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUp() @@ -217,7 +218,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) assert.False(t, dash.Meta.CanAdmin) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { @@ -227,10 +228,11 @@ func TestDashboardAPIEndpoint(t *testing.T) { Cfg: setting.NewCfg(), LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + SQLStore: mock, }) assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { @@ -238,7 +240,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersion(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { @@ -246,7 +248,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersions(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) }) @@ -256,6 +258,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { Live: newTestLive(t), LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + SQLStore: mockstore.NewSQLStoreMock(), } setUp := func() *testState { @@ -315,7 +318,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { t.Run("When user is an Org Viewer and has no permissions for this dashboard", func(t *testing.T) { role := models.ROLE_VIEWER - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUp() @@ -324,7 +327,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { @@ -333,7 +336,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callDeleteDashboardByUID(sc, hs) assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { @@ -341,7 +344,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersion(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { @@ -349,12 +352,12 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersions(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) t.Run("When user is an Org Editor and has no permissions for this dashboard", func(t *testing.T) { role := models.ROLE_EDITOR - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUp() @@ -364,7 +367,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { @@ -373,7 +376,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callDeleteDashboardByUID(sc, hs) assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { @@ -381,7 +384,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersion(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { @@ -389,7 +392,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callGetDashboardVersions(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) t.Run("When user is an Org Viewer but has an edit permission", func(t *testing.T) { @@ -407,7 +410,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { }) return state } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -418,29 +421,28 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) assert.False(t, dash.Meta.CanAdmin) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() - callDeleteDashboardByUID(sc, hs) assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersion(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersions(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) t.Run("When user is an Org Viewer and viewers can edit", func(t *testing.T) { @@ -466,7 +468,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { return state } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -477,7 +479,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.True(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) assert.False(t, dash.Meta.CanAdmin) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -485,7 +487,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { callDeleteDashboardByUID(sc, hs) assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) }) t.Run("When user is an Org Viewer but has an admin permission", func(t *testing.T) { @@ -503,7 +505,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { }) return state } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -513,7 +515,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.True(t, dash.Meta.CanEdit) assert.True(t, dash.Meta.CanSave) assert.True(t, dash.Meta.CanAdmin) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -521,21 +523,21 @@ func TestDashboardAPIEndpoint(t *testing.T) { callDeleteDashboardByUID(sc, hs) assert.Equal(t, 200, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersion(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersions(sc) assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) t.Run("When user is an Org Editor but has a view permission", func(t *testing.T) { @@ -553,7 +555,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { }) return state } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -561,7 +563,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) assert.False(t, dash.Meta.CanEdit) assert.False(t, dash.Meta.CanSave) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) { state := setUpInner() @@ -569,21 +571,21 @@ func TestDashboardAPIEndpoint(t *testing.T) { callDeleteDashboardByUID(sc, hs) assert.Equal(t, 403, sc.resp.Code) assert.Equal(t, "abcdefghi", state.dashQueries[0].Uid) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersion(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { setUpInner() callGetDashboardVersions(sc) assert.Equal(t, 403, sc.resp.Code) - }) + }, mock) }) }) @@ -964,7 +966,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { return nil }) } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/dashboards/db/abcdefghi", "/api/dashboards/db/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() @@ -972,12 +974,13 @@ func TestDashboardAPIEndpoint(t *testing.T) { Cfg: setting.NewCfg(), LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + SQLStore: mock, }) assert.Equal(t, 400, sc.resp.Code) result := sc.ToJSON() assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Error(), result.Get("error").MustString()) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() @@ -990,8 +993,9 @@ func TestDashboardAPIEndpoint(t *testing.T) { dash := getDashboardShouldReturn200WithConfig(sc, mock) assert.Equal(t, filepath.Join("test", "dashboard1.json"), dash.Meta.ProvisionedExternalId) - }) + }, mock) + mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When allowUiUpdates is true and calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp() @@ -1008,6 +1012,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { ProvisioningService: mock, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + SQLStore: mockSQLStore, } callGetDashboard(sc, hs) @@ -1018,7 +1023,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { require.NoError(t, err) assert.Equal(t, false, dash.Meta.Provisioned) - }) + }, mock) }) } @@ -1036,6 +1041,7 @@ func getDashboardShouldReturn200WithConfig(sc *scenarioContext, provisioningServ LibraryPanelService: &libraryPanelsService, LibraryElementService: &libraryElementsService, ProvisioningService: provisioningService, + SQLStore: sc.sqlStore, } callGetDashboard(sc, hs) diff --git a/pkg/api/datasources_test.go b/pkg/api/datasources_test.go index ac43d1cd016..4e95153c6ec 100644 --- a/pkg/api/datasources_test.go +++ b/pkg/api/datasources_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,6 +28,7 @@ const ( ) func TestDataSourcesProxy_userLoggedIn(t *testing.T) { + mock := mockstore.NewSQLStoreMock() loggedInUserScenario(t, "When calling GET on", "/api/datasources/", "/api/datasources/", func(sc *scenarioContext) { // Stubs the database query bus.AddHandler("test", func(ctx context.Context, query *models.GetDataSourcesQuery) error { @@ -45,6 +47,7 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) { Bus: bus.GetBus(), Cfg: setting.NewCfg(), pluginStore: &fakePluginStore{}, + SQLStore: mock, } sc.handlerFunc = hs.GetDataSources sc.fakeReq("GET", "/api/datasources").exec() @@ -57,7 +60,7 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) { assert.Equal(t, "BBB", respJSON[1]["name"]) assert.Equal(t, "mmm", respJSON[2]["name"]) assert.Equal(t, "ZZZ", respJSON[3]["name"]) - }) + }, mock) loggedInUserScenario(t, "Should be able to save a data source when calling DELETE on non-existing", "/api/datasources/name/12345", "/api/datasources/name/:name", func(sc *scenarioContext) { @@ -70,7 +73,7 @@ func TestDataSourcesProxy_userLoggedIn(t *testing.T) { sc.handlerFunc = hs.DeleteDataSourceByName sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - }) + }, mock) } // Adding data sources with invalid URLs should lead to an error. diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 410ae187862..a784b76e6b5 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/guardian" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" ) @@ -34,11 +35,11 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { dashboards.NewFolderService = origNewFolderService }) mockFolderService(mock) - + mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) assert.Equal(t, 404, sc.resp.Code) - }) + }, mockSQLStore) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -77,11 +78,11 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { } mockFolderService(mock) - + mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) assert.Equal(t, 403, sc.resp.Code) - }) + }, mockSQLStore) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -130,7 +131,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { } mockFolderService(mock) - + mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) assert.Equal(t, 200, sc.resp.Code) @@ -142,7 +143,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { assert.Len(t, resp, 5) assert.Equal(t, int64(2), resp[0].UserId) assert.Equal(t, models.PERMISSION_VIEW, resp[0].Permission) - }) + }, mockSQLStore) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ @@ -325,6 +326,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { mockFolderService(mock) var resp []*models.DashboardAclInfoDTO + mockSQLStore := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) assert.Equal(t, 200, sc.resp.Code) @@ -337,7 +339,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { assert.Equal(t, models.PERMISSION_EDIT, resp[0].Permission) assert.Equal(t, int64(4), resp[1].UserId) assert.Equal(t, models.PERMISSION_ADMIN, resp[1].Permission) - }) + }, mockSQLStore) cmd := dtos.UpdateDashboardAclCommand{ Items: []dtos.DashboardAclUpdateItem{ diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 70bf0c4c26f..3c84c2bf73f 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -107,7 +107,7 @@ type HTTPServer struct { LivePushGateway *pushhttp.Gateway ThumbService thumbs.Service ContextHandler *contexthandler.ContextHandler - SQLStore *sqlstore.SQLStore + SQLStore sqlstore.Store AlertEngine *alerting.AlertEngine LoadSchemaService *schemaloader.SchemaLoaderService AlertNG *ngalert.AlertNG diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 009e00d18a4..ee5dedb51c7 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -41,7 +42,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) sqlStore.Cfg = settings hs.SQLStore = sqlStore - + mock := mockstore.NewSQLStoreMock() loggedInUserScenario(t, "When calling GET on", "api/org/users", "api/org/users", func(sc *scenarioContext) { setUpGetOrgUsersDB(t, sqlStore) @@ -54,7 +55,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { err := json.Unmarshal(sc.resp.Body.Bytes(), &resp) require.NoError(t, err) assert.Len(t, resp, 3) - }) + }, mock) loggedInUserScenario(t, "When calling GET on", "api/org/users/search", "api/org/users/search", func(sc *scenarioContext) { setUpGetOrgUsersDB(t, sqlStore) @@ -72,7 +73,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, int64(3), resp.TotalCount) assert.Equal(t, 1000, resp.PerPage) assert.Equal(t, 1, resp.Page) - }) + }, mock) loggedInUserScenario(t, "When calling GET with page and limit query parameters on", "api/org/users/search", "api/org/users/search", func(sc *scenarioContext) { setUpGetOrgUsersDB(t, sqlStore) @@ -90,7 +91,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, int64(3), resp.TotalCount) assert.Equal(t, 2, resp.PerPage) assert.Equal(t, 2, resp.Page) - }) + }, mock) t.Run("Given there are two hidden users", func(t *testing.T) { settings.HiddenUsers = map[string]struct{}{ @@ -113,7 +114,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { assert.Len(t, resp, 2) assert.Equal(t, testUserLogin, resp[0].Login) assert.Equal(t, "user2", resp[1].Login) - }) + }, mock) loggedInUserScenarioWithRole(t, "When calling GET as an admin on", "GET", "api/org/users/lookup", "api/org/users/lookup", models.ROLE_ADMIN, func(sc *scenarioContext) { @@ -130,7 +131,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { assert.Len(t, resp, 2) assert.Equal(t, testUserLogin, resp[0].Login) assert.Equal(t, "user2", resp[1].Login) - }) + }, mock) }) } diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 431edfd16c5..f0293470579 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" @@ -143,7 +142,7 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon } query := models.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId} - if err := bus.Dispatch(c.Req.Context(), &query); err != nil { + if err := hs.SQLStore.GetPluginSettingById(c.Req.Context(), &query); err != nil { if !errors.Is(err, models.ErrPluginSettingNotFound) { return response.Error(500, "Failed to get login settings", nil) } @@ -175,7 +174,7 @@ func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext) response.Respons cmd.OrgId = c.OrgId cmd.PluginId = pluginID - if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil { + if err := hs.SQLStore.UpdatePluginSetting(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to update plugin setting", err) } diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index c7da91a0fdf..5a071b5d9b3 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -6,7 +6,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/web" ) @@ -18,7 +17,7 @@ const ( ) // POST /api/preferences/set-home-dash -func SetHomeDashboard(c *models.ReqContext) response.Response { +func (hs *HTTPServer) SetHomeDashboard(c *models.ReqContext) response.Response { cmd := models.SavePreferencesCommand{} if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) @@ -26,7 +25,7 @@ func SetHomeDashboard(c *models.ReqContext) response.Response { cmd.UserId = c.UserId cmd.OrgId = c.OrgId - if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil { + if err := hs.SQLStore.SavePreferences(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to set home dashboard", err) } diff --git a/pkg/api/quota.go b/pkg/api/quota.go index fee44d6ed46..cdd8c9762d1 100644 --- a/pkg/api/quota.go +++ b/pkg/api/quota.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" @@ -61,7 +60,7 @@ func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response { return response.Success("Organization quota updated") } -func GetUserQuotas(c *models.ReqContext) response.Response { +func (hs *HTTPServer) GetUserQuotas(c *models.ReqContext) response.Response { if !setting.Quota.Enabled { return response.Error(404, "Quotas not enabled", nil) } @@ -73,14 +72,14 @@ func GetUserQuotas(c *models.ReqContext) response.Response { query := models.GetUserQuotasQuery{UserId: id} - if err := bus.Dispatch(c.Req.Context(), &query); err != nil { + if err := hs.SQLStore.GetUserQuotas(c.Req.Context(), &query); err != nil { return response.Error(500, "Failed to get org quotas", err) } return response.JSON(200, query.Result) } -func UpdateUserQuota(c *models.ReqContext) response.Response { +func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response { cmd := models.UpdateUserQuotaCmd{} var err error if err := web.Bind(c.Req, &cmd); err != nil { @@ -99,7 +98,7 @@ func UpdateUserQuota(c *models.ReqContext) response.Response { return response.Error(404, "Invalid quota target", nil) } - if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil { + if err := hs.SQLStore.UpdateUserQuota(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to update org quotas", err) } return response.Success("Organization quota updated") diff --git a/pkg/api/team.go b/pkg/api/team.go index c695db38b4c..5db290459b2 100644 --- a/pkg/api/team.go +++ b/pkg/api/team.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -25,7 +24,7 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response { return response.Error(403, "Not allowed to create team.", nil) } - team, err := createTeam(hs.SQLStore, cmd.Name, cmd.Email, c.OrgId) + team, err := hs.SQLStore.CreateTeam(cmd.Name, cmd.Email, c.OrgId) if err != nil { if errors.Is(err, models.ErrTeamNameTaken) { return response.Error(409, "Team name taken", err) @@ -45,7 +44,6 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response { c.Logger.Warn("Could not add creator to team because is not a real user") } } - return response.JSON(200, &util.DynMap{ "teamId": team.Id, "message": "Team created", @@ -211,10 +209,3 @@ func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext) response.Respo return hs.updatePreferencesFor(c.Req.Context(), orgId, 0, teamId, &dtoCmd) } - -// createTeam creates a team. -// -// Stubbable by tests. -var createTeam = func(sqlStore *sqlstore.SQLStore, name, email string, orgID int64) (models.Team, error) { - return sqlStore.CreateTeam(name, email, orgID) -} diff --git a/pkg/api/team_members_test.go b/pkg/api/team_members_test.go index 37dd49b424f..8dba3bc7335 100644 --- a/pkg/api/team_members_test.go +++ b/pkg/api/team_members_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/teamguardian/database" "github.com/grafana/grafana/pkg/services/teamguardian/manager" "github.com/grafana/grafana/pkg/setting" @@ -47,6 +48,7 @@ func TestTeamMembersAPIEndpoint_userLoggedIn(t *testing.T) { License: &licensing.OSSLicensingService{}, SQLStore: sqlStore, } + mock := mockstore.NewSQLStoreMock() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "api/teams/1/members", "api/teams/:teamId/members", models.ROLE_ADMIN, func(sc *scenarioContext) { @@ -61,7 +63,7 @@ func TestTeamMembersAPIEndpoint_userLoggedIn(t *testing.T) { err := json.Unmarshal(sc.resp.Body.Bytes(), &resp) require.NoError(t, err) assert.Len(t, resp, 3) - }) + }, mock) t.Run("Given there is two hidden users", func(t *testing.T) { settings.HiddenUsers = map[string]struct{}{ @@ -86,7 +88,7 @@ func TestTeamMembersAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, "loginuser0", resp[0].Login) assert.Equal(t, "loginuser1", resp[1].Login) assert.Equal(t, "loginuser2", resp[2].Login) - }) + }, mock) }) } diff --git a/pkg/api/team_test.go b/pkg/api/team_test.go index 0e2a0a8c620..5f6803edc2e 100644 --- a/pkg/api/team_test.go +++ b/pkg/api/team_test.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/assert" @@ -34,7 +35,7 @@ func TestTeamAPIEndpoint(t *testing.T) { t.Run("Given two teams", func(t *testing.T) { hs := setupSimpleHTTPServer(nil) hs.SQLStore = sqlstore.InitTestDB(t) - + mock := mockstore.SQLStoreMock{} loggedInUserScenario(t, "When calling GET on", "/api/teams/search", "/api/teams/search", func(sc *scenarioContext) { _, err := hs.SQLStore.CreateTeam("team1", "", 1) require.NoError(t, err) @@ -50,7 +51,7 @@ func TestTeamAPIEndpoint(t *testing.T) { assert.EqualValues(t, 2, resp.TotalCount) assert.Equal(t, 2, len(resp.Teams)) - }) + }, mock) loggedInUserScenario(t, "When calling GET on", "/api/teams/search", "/api/teams/search", func(sc *scenarioContext) { _, err := hs.SQLStore.CreateTeam("team1", "", 1) @@ -67,29 +68,15 @@ func TestTeamAPIEndpoint(t *testing.T) { assert.EqualValues(t, 2, resp.TotalCount) assert.Equal(t, 0, len(resp.Teams)) - }) + }, mock) }) t.Run("When creating team with API key", func(t *testing.T) { hs := setupSimpleHTTPServer(nil) hs.Cfg.EditorsCanAdmin = true - + hs.SQLStore = mockstore.NewSQLStoreMock() teamName := "team foo" - // TODO: Use a fake SQLStore when it's represented by an interface - orgCreateTeam := createTeam - orgAddTeamMember := addOrUpdateTeamMember - t.Cleanup(func() { - createTeam = orgCreateTeam - addOrUpdateTeamMember = orgAddTeamMember - }) - - createTeamCalled := 0 - createTeam = func(sqlStore *sqlstore.SQLStore, name, email string, orgID int64) (models.Team, error) { - createTeamCalled++ - return models.Team{Name: teamName, Id: 42}, nil - } - addTeamMemberCalled := 0 addOrUpdateTeamMember = func(ctx context.Context, resourcePermissionService *resourcepermissions.Service, userID, orgID, teamID int64, permission string) error { @@ -109,9 +96,9 @@ func TestTeamAPIEndpoint(t *testing.T) { } c.OrgRole = models.ROLE_EDITOR c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName}) - hs.CreateTeam(c) - assert.Equal(t, createTeamCalled, 1) - assert.Equal(t, addTeamMemberCalled, 0) + r := hs.CreateTeam(c) + + assert.Equal(t, 200, r.Status()) assert.True(t, stub.warnCalled) assert.Equal(t, stub.warnMessage, "Could not add creator to team because is not a real user") }) @@ -125,10 +112,8 @@ func TestTeamAPIEndpoint(t *testing.T) { } c.OrgRole = models.ROLE_EDITOR c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName}) - createTeamCalled, addTeamMemberCalled = 0, 0 - hs.CreateTeam(c) - assert.Equal(t, createTeamCalled, 1) - assert.Equal(t, addTeamMemberCalled, 1) + r := hs.CreateTeam(c) + assert.Equal(t, 200, r.Status()) assert.False(t, stub.warnCalled) }) }) diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index e9aa77d46c3..727c08ccbad 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/secrets/database" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/setting" "golang.org/x/oauth2" @@ -41,7 +42,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { }, TotalCount: 2, } - + mock := mockstore.NewSQLStoreMock() loggedInUserScenario(t, "When calling GET on", "api/users/1", "api/users/:id", func(sc *scenarioContext) { fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC) secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore)) @@ -100,7 +101,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { resp.UpdatedAt = fakeNow resp.AvatarUrl = avatarUrl require.EqualValues(t, expected, resp) - }) + }, mock) loggedInUserScenario(t, "When calling GET on", "/api/users/lookup", "/api/users/lookup", func(sc *scenarioContext) { fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC) @@ -141,7 +142,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { require.Equal(t, "admin", resp.Login) require.Equal(t, "admin@test.com", resp.Email) require.True(t, resp.IsGrafanaAdmin) - }) + }, mock) loggedInUserScenario(t, "When calling GET on", "/api/users", "/api/users", func(sc *scenarioContext) { var sentLimit int @@ -165,7 +166,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) require.NoError(t, err) assert.Equal(t, 2, len(respJSON.MustArray())) - }) + }, mock) loggedInUserScenario(t, "When calling GET with page and limit querystring parameters on", "/api/users", "/api/users", func(sc *scenarioContext) { var sentLimit int @@ -185,7 +186,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, 10, sentLimit) assert.Equal(t, 2, sendPage) - }) + }, mock) loggedInUserScenario(t, "When calling GET on", "/api/users/search", "/api/users/search", func(sc *scenarioContext) { var sentLimit int @@ -211,7 +212,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, 2, respJSON.Get("totalCount").MustInt()) assert.Equal(t, 2, len(respJSON.Get("users").MustArray())) - }) + }, mock) loggedInUserScenario(t, "When calling GET with page and perpage querystring parameters on", "/api/users/search", "/api/users/search", func(sc *scenarioContext) { var sentLimit int @@ -231,5 +232,5 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { assert.Equal(t, 10, sentLimit) assert.Equal(t, 2, sendPage) - }) + }, mock) } diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index 5eb8c153b83..53b040bd531 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -32,7 +31,7 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *models.ReqContext) response.Respons func (hs *HTTPServer) logoutUserFromAllDevicesInternal(ctx context.Context, userID int64) response.Response { userQuery := models.GetUserByIdQuery{Id: userID} - if err := bus.Dispatch(ctx, &userQuery); err != nil { + if err := hs.SQLStore.GetUserById(ctx, &userQuery); err != nil { if errors.Is(err, models.ErrUserNotFound) { return response.Error(404, "User not found", err) } @@ -52,7 +51,7 @@ func (hs *HTTPServer) logoutUserFromAllDevicesInternal(ctx context.Context, user func (hs *HTTPServer) getUserAuthTokensInternal(c *models.ReqContext, userID int64) response.Response { userQuery := models.GetUserByIdQuery{Id: userID} - if err := bus.Dispatch(c.Req.Context(), &userQuery); err != nil { + if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil { if errors.Is(err, models.ErrUserNotFound) { return response.Error(404, "User not found", err) } @@ -118,8 +117,7 @@ func (hs *HTTPServer) getUserAuthTokensInternal(c *models.ReqContext, userID int func (hs *HTTPServer) revokeUserAuthTokenInternal(c *models.ReqContext, userID int64, cmd models.RevokeAuthTokenCmd) response.Response { userQuery := models.GetUserByIdQuery{Id: userID} - - if err := bus.Dispatch(c.Req.Context(), &userQuery); err != nil { + if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil { if errors.Is(err, models.ErrUserNotFound) { return response.Error(404, "User not found", err) } diff --git a/pkg/api/user_token_test.go b/pkg/api/user_token_test.go index 1d9ebeb0b70..68c523c4777 100644 --- a/pkg/api/user_token_test.go +++ b/pkg/api/user_token_test.go @@ -11,87 +11,73 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/stretchr/testify/assert" ) func TestUserTokenAPIEndpoint(t *testing.T) { + mock := mockstore.NewSQLStoreMock() t.Run("When current user attempts to revoke an auth token for a non-existing user", func(t *testing.T) { cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} - + mock.ExpectedError = models.ErrUserNotFound revokeUserAuthTokenScenario(t, "Should return not found when calling POST on", "/api/user/revoke-auth-token", "/api/user/revoke-auth-token", cmd, 200, func(sc *scenarioContext) { - var userID int64 - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - userID = cmd.Id - return models.ErrUserNotFound - }) - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - assert.Equal(t, int64(200), userID) - }) + }, mock) }) t.Run("When current user gets auth tokens for a non-existing user", func(t *testing.T) { + mock := mockstore.SQLStoreMock{ + ExpectedUser: &models.User{Id: 200}, + ExpectedError: models.ErrUserNotFound, + } getUserAuthTokensScenario(t, "Should return not found when calling GET on", "/api/user/auth-tokens", "/api/user/auth-tokens", 200, func(sc *scenarioContext) { - var userID int64 - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - userID = cmd.Id - return models.ErrUserNotFound - }) - sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - assert.Equal(t, int64(200), userID) - }) + }, mock) }) t.Run("When logging out an existing user from all devices", func(t *testing.T) { + mock := mockstore.SQLStoreMock{ + ExpectedUser: &models.User{Id: 200}, + } logoutUserFromAllDevicesInternalScenario(t, "Should be successful", 1, func(sc *scenarioContext) { - const userID int64 = 200 - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - cmd.Result = &models.User{Id: userID} - return nil - }) - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) t.Run("When logout a non-existing user from all devices", func(t *testing.T) { logoutUserFromAllDevicesInternalScenario(t, "Should return not found", testUserID, func(sc *scenarioContext) { - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - return models.ErrUserNotFound - }) + mock.ExpectedError = models.ErrUserNotFound sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 404, sc.resp.Code) - }) + }, mock) }) t.Run("When revoke an auth token for a user", func(t *testing.T) { cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} token := &models.UserToken{Id: 1} + mock := mockstore.SQLStoreMock{ + ExpectedUser: &models.User{Id: 200}, + } revokeUserAuthTokenInternalScenario(t, "Should be successful", cmd, 200, token, func(sc *scenarioContext) { - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - cmd.Result = &models.User{Id: 200} - return nil - }) - sc.userAuthTokenService.GetUserTokenProvider = func(ctx context.Context, userId, userTokenId int64) (*models.UserToken, error) { return &models.UserToken{Id: 2}, nil } sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 200, sc.resp.Code) - }) + }, mock) }) t.Run("When revoke the active auth token used by himself", func(t *testing.T) { cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} token := &models.UserToken{Id: 2} - + mock := mockstore.NewSQLStoreMock() revokeUserAuthTokenInternalScenario(t, "Should not be successful", cmd, testUserID, token, func(sc *scenarioContext) { bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { cmd.Result = &models.User{Id: testUserID} @@ -103,18 +89,13 @@ func TestUserTokenAPIEndpoint(t *testing.T) { } sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 400, sc.resp.Code) - }) + }, mock) }) t.Run("When gets auth tokens for a user", func(t *testing.T) { currentToken := &models.UserToken{Id: 1} - + mock := mockstore.NewSQLStoreMock() getUserAuthTokensInternalScenario(t, "Should be successful", currentToken, func(sc *scenarioContext) { - bus.AddHandler("test", func(ctx context.Context, cmd *models.GetUserByIdQuery) error { - cmd.Result = &models.User{Id: testUserID} - return nil - }) - tokens := []*models.UserToken{ { Id: 1, @@ -165,12 +146,12 @@ func TestUserTokenAPIEndpoint(t *testing.T) { assert.Equal(t, "11.0", resultTwo.Get("browserVersion").MustString()) assert.Equal(t, "iOS", resultTwo.Get("os").MustString()) assert.Equal(t, "11.0", resultTwo.Get("osVersion").MustString()) - }) + }, mock) }) } func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, - userId int64, fn scenarioFunc) { + userId int64, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -179,6 +160,7 @@ func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePat hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) @@ -199,7 +181,7 @@ func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePat }) } -func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, userId int64, fn scenarioFunc) { +func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, userId int64, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -208,6 +190,7 @@ func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePatte hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, url) @@ -227,13 +210,14 @@ func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePatte }) } -func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId int64, fn scenarioFunc) { +func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId int64, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(desc, func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: auth.NewFakeUserAuthTokenService(), + SQLStore: sqlStore, } sc := setupScenarioContext(t, "/") @@ -253,7 +237,7 @@ func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId } func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.RevokeAuthTokenCmd, userId int64, - token *models.UserToken, fn scenarioFunc) { + token *models.UserToken, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(desc, func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -262,6 +246,7 @@ func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.R hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, "/") @@ -275,14 +260,12 @@ func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.R return hs.revokeUserAuthTokenInternal(c, userId, cmd) }) - sc.m.Post("/", sc.defaultHandler) - fn(sc) }) } -func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models.UserToken, fn scenarioFunc) { +func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models.UserToken, fn scenarioFunc, sqlStore sqlstore.Store) { t.Run(desc, func(t *testing.T) { t.Cleanup(bus.ClearBusHandlers) @@ -291,6 +274,7 @@ func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models. hs := HTTPServer{ Bus: bus.GetBus(), AuthTokenService: fakeAuthTokenService, + SQLStore: sqlStore, } sc := setupScenarioContext(t, "/") diff --git a/pkg/server/wire.go b/pkg/server/wire.go index cf171ac8874..a98268e2146 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -65,6 +65,7 @@ import ( serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" "github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/teamguardian" teamguardianDatabase "github.com/grafana/grafana/pkg/services/teamguardian/database" teamguardianManager "github.com/grafana/grafana/pkg/services/teamguardian/manager" @@ -205,6 +206,7 @@ var wireSet = wire.NewSet( wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), + wire.Bind(new(sqlstore.Store), new(*sqlstore.SQLStore)), ) var wireTestSet = wire.NewSet( @@ -217,6 +219,8 @@ var wireTestSet = wire.NewSet( wire.Bind(new(notifications.Service), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationServiceMock)), + mockstore.NewSQLStoreMock, + wire.Bind(new(sqlstore.Store), new(*mockstore.SQLStoreMock)), ) func Initialize(cla setting.CommandLineArgs, opts Options, apiOpts api.ServerOptions) (*Server, error) { diff --git a/pkg/services/accesscontrol/middleware/middleware.go b/pkg/services/accesscontrol/middleware/middleware.go index ca5427b2c3b..333cae520af 100644 --- a/pkg/services/accesscontrol/middleware/middleware.go +++ b/pkg/services/accesscontrol/middleware/middleware.go @@ -91,7 +91,7 @@ func buildScopeParams(c *models.ReqContext) accesscontrol.ScopeParams { type OrgIDGetter func(c *models.ReqContext) (int64, error) -func AuthorizeInOrgMiddleware(ac accesscontrol.AccessControl, db *sqlstore.SQLStore) func(web.Handler, OrgIDGetter, accesscontrol.Evaluator) web.Handler { +func AuthorizeInOrgMiddleware(ac accesscontrol.AccessControl, db sqlstore.Store) func(web.Handler, OrgIDGetter, accesscontrol.Evaluator) web.Handler { return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator accesscontrol.Evaluator) web.Handler { if ac.IsDisabled() { return fallback diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index 7783611925d..b666447fc5b 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -9,7 +9,7 @@ import ( ) type SQLStoreMock struct { - SQLStore *sqlstore.SQLStore + ExpectedUser *models.User ExpectedError error } @@ -98,6 +98,7 @@ func (m SQLStoreMock) CreateUser(ctx context.Context, cmd models.CreateUserComma } func (m SQLStoreMock) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error { + query.Result = m.ExpectedUser return m.ExpectedError } @@ -150,7 +151,7 @@ func (m SQLStoreMock) DeleteUser(ctx context.Context, cmd *models.DeleteUserComm } func (m SQLStoreMock) UpdateUserPermissions(userID int64, isAdmin bool) error { - return nil // TODO: Implement + return m.ExpectedError } func (m SQLStoreMock) SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHelpFlagCommand) error { @@ -158,7 +159,11 @@ func (m SQLStoreMock) SetUserHelpFlag(ctx context.Context, cmd *models.SetUserHe } func (m SQLStoreMock) CreateTeam(name string, email string, orgID int64) (models.Team, error) { - return models.Team{}, nil // TODO: Implement + return models.Team{ + Name: name, + Email: email, + OrgId: orgID, + }, nil } func (m SQLStoreMock) UpdateTeam(ctx context.Context, cmd *models.UpdateTeamCommand) error { diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 0e09553c1ef..b38c2f9e4ea 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -30,7 +30,7 @@ func (ss *SQLStore) addUserQueryAndCommandHandlers() { bus.AddHandler("sql", ss.GetUserProfile) bus.AddHandler("sql", SearchUsers) bus.AddHandler("sql", ss.GetUserOrgList) - bus.AddHandler("sql", DisableUser) + bus.AddHandler("sql", ss.DisableUser) bus.AddHandler("sql", ss.BatchDisableUsers) bus.AddHandler("sql", ss.DeleteUser) bus.AddHandler("sql", ss.SetUserHelpFlag) @@ -320,7 +320,7 @@ func (ss *SQLStore) CreateUser(ctx context.Context, cmd models.CreateUserCommand return user, err } -func (ss *SQLStore) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error { +func (ss SQLStore) GetUserById(ctx context.Context, query *models.GetUserByIdQuery) error { return withDbSession(ctx, x, func(sess *DBSession) error { user := new(models.User) has, err := sess.ID(query.Id).Get(user) @@ -721,7 +721,7 @@ func SearchUsers(ctx context.Context, query *models.SearchUsersQuery) error { return err } -func DisableUser(ctx context.Context, cmd *models.DisableUserCommand) error { +func (ss *SQLStore) DisableUser(ctx context.Context, cmd *models.DisableUserCommand) error { user := models.User{} sess := x.Table("user")