Chore: Convert API tests to standard Go lib (#29009)

* Chore: Convert tests to standard Go lib

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Arve Knudsen
2020-11-13 09:52:38 +01:00
committed by GitHub
parent df8f63de7f
commit cb62e69997
27 changed files with 2816 additions and 2589 deletions

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
@@ -8,288 +9,356 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
const ( const (
TestLogin = "test@example.com" testLogin = "test@example.com"
TestPassword = "password" testPassword = "password"
nonExistingOrgID = 1000 nonExistingOrgID = 1000
) )
func TestAdminApiEndpoint(t *testing.T) { func TestAdminAPIEndpoint(t *testing.T) {
role := models.ROLE_ADMIN const role = models.ROLE_ADMIN
Convey("Given a server admin attempts to remove themself as an admin", t, func() {
t.Run("Given a server admin attempts to remove themself as an admin", func(t *testing.T) {
updateCmd := dtos.AdminUpdateUserPermissionsForm{ updateCmd := dtos.AdminUpdateUserPermissionsForm{
IsGrafanaAdmin: false, IsGrafanaAdmin: false,
} }
bus.AddHandler("test", func(cmd *models.UpdateUserPermissionsCommand) error { putAdminScenario(t, "When calling PUT on", "/api/admin/users/1/permissions",
return models.ErrLastGrafanaAdmin "/api/admin/users/:id/permissions", role, updateCmd, func(sc *scenarioContext) {
}) bus.AddHandler("test", func(cmd *models.UpdateUserPermissionsCommand) error {
return models.ErrLastGrafanaAdmin
})
putAdminScenario("When calling PUT on", "/api/admin/users/1/permissions", "/api/admin/users/:id/permissions", role, updateCmd, func(sc *scenarioContext) { sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() assert.Equal(t, 400, sc.resp.Code)
So(sc.resp.Code, ShouldEqual, 400) })
})
}) })
Convey("When a server admin attempts to logout himself from all devices", t, func() { t.Run("When a server admin attempts to logout himself from all devices", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error { adminLogoutUserScenario(t, "Should not be allowed when calling POST on",
cmd.Result = &models.User{Id: TestUserID} "/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) {
return nil bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
}) cmd.Result = &models.User{Id: testUserID}
return nil
})
adminLogoutUserScenario("Should not be allowed when calling POST on", "/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) { sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() assert.Equal(t, 400, sc.resp.Code)
So(sc.resp.Code, ShouldEqual, 400) })
})
}) })
Convey("When a server admin attempts to logout a non-existing user from all devices", t, func() { t.Run("When a server admin attempts to logout a non-existing user from all devices", func(t *testing.T) {
userId := int64(0) adminLogoutUserScenario(t, "Should return not found when calling POST on", "/api/admin/users/200/logout",
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error { "/api/admin/users/:id/logout", func(sc *scenarioContext) {
userId = cmd.Id userID := int64(0)
return models.ErrUserNotFound
})
adminLogoutUserScenario("Should return not found when calling POST on", "/api/admin/users/200/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) { bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() userID = cmd.Id
So(sc.resp.Code, ShouldEqual, 404) return models.ErrUserNotFound
So(userId, ShouldEqual, 200) })
}) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
assert.Equal(t, int64(200), userID)
})
}) })
Convey("When a server admin attempts to revoke an auth token for a non-existing user", t, func() { t.Run("When a server admin attempts to revoke an auth token for a non-existing user", func(t *testing.T) {
userId := int64(0)
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
userId = cmd.Id
return models.ErrUserNotFound
})
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
adminRevokeUserAuthTokenScenario("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) { adminRevokeUserAuthTokenScenario(t, "Should return not found when calling POST on",
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() "/api/admin/users/200/revoke-auth-token", "/api/admin/users/:id/revoke-auth-token", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 404) var userID int64
So(userId, ShouldEqual, 200) bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
}) userID = cmd.Id
}) return models.ErrUserNotFound
})
Convey("When a server admin gets auth tokens for a non-existing user", t, func() {
userId := int64(0)
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
userId = cmd.Id
return models.ErrUserNotFound
})
adminGetUserAuthTokensScenario("Should return not found when calling GET on", "/api/admin/users/200/auth-tokens", "/api/admin/users/:id/auth-tokens", func(sc *scenarioContext) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404)
So(userId, ShouldEqual, 200)
})
})
Convey("When a server admin attempts to enable/disable a nonexistent user", t, func() {
var userId int64
isDisabled := false
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
return models.ErrUserNotFound
})
bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
userId = cmd.UserId
isDisabled = cmd.IsDisabled
return models.ErrUserNotFound
})
adminDisableUserScenario("Should return user not found on a POST request", "enable", "/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldEqual, "user not found")
So(userId, ShouldEqual, 42)
So(isDisabled, ShouldEqual, false)
})
adminDisableUserScenario("Should return user not found on a POST request", "disable", "/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldEqual, "user not found")
So(userId, ShouldEqual, 42)
So(isDisabled, ShouldEqual, true)
})
})
Convey("When a server admin attempts to disable/enable external user", t, func() {
userId := int64(0)
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
userId = cmd.UserId
return nil
})
adminDisableUserScenario("Should return Could not disable external user error", "disable", "/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 500)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldEqual, "Could not disable external user")
So(userId, ShouldEqual, 42)
})
adminDisableUserScenario("Should return Could not enable external user error", "enable", "/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 500)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldEqual, "Could not enable external user")
So(userId, ShouldEqual, 42)
})
})
Convey("When a server admin attempts to delete a nonexistent user", t, func() {
var userId int64
bus.AddHandler("test", func(cmd *models.DeleteUserCommand) error {
userId = cmd.UserId
return models.ErrUserNotFound
})
adminDeleteUserScenario("Should return user not found error", "/api/admin/users/42", "/api/admin/users/:id", func(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldEqual, "user not found")
So(userId, ShouldEqual, 42)
})
})
Convey("When a server admin attempts to create a user", t, func() {
var userLogin string
var orgId int64
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
userLogin = cmd.Login
orgId = cmd.OrgId
if orgId == nonExistingOrgID {
return models.ErrOrgNotFound
}
cmd.Result = models.User{Id: TestUserID}
return nil
})
Convey("Without an organization", func() {
createCmd := dtos.AdminCreateUserForm{
Login: TestLogin,
Password: TestPassword,
}
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 404, sc.resp.Code)
assert.Equal(t, int64(200), userID)
})
})
t.Run("When a server admin gets auth tokens for a non-existing user", func(t *testing.T) {
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(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)
})
})
t.Run("When a server admin attempts to enable/disable a nonexistent user", func(t *testing.T) {
adminDisableUserScenario(t, "Should return user not found on a POST request", "enable",
"/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
var userID int64
isDisabled := false
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
return models.ErrUserNotFound
})
bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
userID = cmd.UserId
isDisabled = cmd.IsDisabled
return models.ErrUserNotFound
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
assert.Equal(t, int64(42), userID)
assert.Equal(t, false, isDisabled)
})
adminDisableUserScenario(t, "Should return user not found on a POST request", "disable",
"/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
var userID int64
isDisabled := false
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
return models.ErrUserNotFound
})
bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
userID = cmd.UserId
isDisabled = cmd.IsDisabled
return models.ErrUserNotFound
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
assert.Equal(t, int64(42), userID)
assert.Equal(t, true, isDisabled)
})
})
t.Run("When a server admin attempts to disable/enable external user", func(t *testing.T) {
adminDisableUserScenario(t, "Should return Could not disable external user error", "disable",
"/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
var userID int64
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
userID = cmd.UserId
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 500, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil) require.NoError(t, err)
So(respJSON.Get("id").MustInt64(), ShouldEqual, TestUserID) assert.Equal(t, "Could not disable external user", respJSON.Get("message").MustString())
So(respJSON.Get("message").MustString(), ShouldEqual, "User created")
// test that userLogin and orgId were transmitted correctly to the handler assert.Equal(t, int64(42), userID)
So(userLogin, ShouldEqual, TestLogin) })
So(orgId, ShouldEqual, 0)
adminDisableUserScenario(t, "Should return Could not enable external user error", "enable",
"/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
var userID int64
bus.AddHandler("test", func(cmd *models.GetAuthInfoQuery) error {
userID = cmd.UserId
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 500, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, "Could not enable external user", respJSON.Get("message").MustString())
assert.Equal(t, int64(42), userID)
})
})
t.Run("When a server admin attempts to delete a nonexistent user", func(t *testing.T) {
adminDeleteUserScenario(t, "Should return user not found error", "/api/admin/users/42",
"/api/admin/users/:id", func(sc *scenarioContext) {
var userID int64
bus.AddHandler("test", func(cmd *models.DeleteUserCommand) error {
userID = cmd.UserId
return models.ErrUserNotFound
})
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
assert.Equal(t, int64(42), userID)
})
})
t.Run("When a server admin attempts to create a user", func(t *testing.T) {
t.Run("Without an organization", func(t *testing.T) {
createCmd := dtos.AdminCreateUserForm{
Login: testLogin,
Password: testPassword,
}
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
var userLogin string
var orgID int64
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
userLogin = cmd.Login
orgID = cmd.OrgId
if orgID == nonExistingOrgID {
return models.ErrOrgNotFound
}
cmd.Result = models.User{Id: testUserID}
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, testUserID, respJSON.Get("id").MustInt64())
assert.Equal(t, "User created", respJSON.Get("message").MustString())
// Verify that userLogin and orgID were transmitted correctly to the handler
assert.Equal(t, testLogin, userLogin)
assert.Equal(t, int64(0), orgID)
}) })
}) })
Convey("With an organization", func() { t.Run("With an organization", func(t *testing.T) {
createCmd := dtos.AdminCreateUserForm{ createCmd := dtos.AdminCreateUserForm{
Login: TestLogin, Login: testLogin,
Password: TestPassword, Password: testPassword,
OrgId: TestOrgID, OrgId: testOrgID,
} }
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) { adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
var userLogin string
var orgID int64
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
userLogin = cmd.Login
orgID = cmd.OrgId
if orgID == nonExistingOrgID {
return models.ErrOrgNotFound
}
cmd.Result = models.User{Id: testUserID}
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil) require.NoError(t, err)
So(respJSON.Get("id").MustInt64(), ShouldEqual, TestUserID) assert.Equal(t, testUserID, respJSON.Get("id").MustInt64())
So(respJSON.Get("message").MustString(), ShouldEqual, "User created") assert.Equal(t, "User created", respJSON.Get("message").MustString())
So(userLogin, ShouldEqual, TestLogin) assert.Equal(t, testLogin, userLogin)
So(orgId, ShouldEqual, TestOrgID) assert.Equal(t, testOrgID, orgID)
}) })
}) })
Convey("With a nonexistent organization", func() { t.Run("With a nonexistent organization", func(t *testing.T) {
createCmd := dtos.AdminCreateUserForm{ createCmd := dtos.AdminCreateUserForm{
Login: TestLogin, Login: testLogin,
Password: TestPassword, Password: testPassword,
OrgId: nonExistingOrgID, OrgId: nonExistingOrgID,
} }
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) { adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
var userLogin string
var orgID int64
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
userLogin = cmd.Login
orgID = cmd.OrgId
if orgID == nonExistingOrgID {
return models.ErrOrgNotFound
}
cmd.Result = models.User{Id: testUserID}
return nil
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 400) assert.Equal(t, 400, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil) require.NoError(t, err)
So(respJSON.Get("message").MustString(), ShouldEqual, "organization not found") assert.Equal(t, "organization not found", respJSON.Get("message").MustString())
So(userLogin, ShouldEqual, TestLogin) assert.Equal(t, testLogin, userLogin)
So(orgId, ShouldEqual, 1000) assert.Equal(t, int64(1000), orgID)
}) })
}) })
}) })
Convey("When a server admin attempts to create a user with an already existing email/login", t, func() { t.Run("When a server admin attempts to create a user with an already existing email/login", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
return models.ErrUserAlreadyExists
})
createCmd := dtos.AdminCreateUserForm{ createCmd := dtos.AdminCreateUserForm{
Login: TestLogin, Login: testLogin,
Password: TestPassword, Password: testPassword,
} }
adminCreateUserScenario("Should return an error", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) { adminCreateUserScenario(t, "Should return an error", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
bus.ClearBusHandlers()
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
return models.ErrUserAlreadyExists
})
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 412) assert.Equal(t, 412, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil) require.NoError(t, err)
So(respJSON.Get("error").MustString(), ShouldEqual, "user already exists") assert.Equal(t, "user already exists", respJSON.Get("error").MustString())
}) })
}) })
} }
func putAdminScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) { func putAdminScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
Convey(desc+" "+url, func() { cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return AdminUpdateUserPermissions(c, cmd) return AdminUpdateUserPermissions(c, cmd)
@@ -301,20 +370,22 @@ func putAdminScenario(desc string, url string, routePattern string, role models.
}) })
} }
func adminLogoutUserScenario(desc string, url string, routePattern string, fn scenarioFunc) { func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{ hs := HTTPServer{
Bus: bus.GetBus(), Bus: bus.GetBus(),
AuthTokenService: auth.NewFakeUserAuthTokenService(), AuthTokenService: auth.NewFakeUserAuthTokenService(),
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
t.Log("Route handler invoked", "url", c.Req.URL)
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.AdminLogoutUser(c) return hs.AdminLogoutUser(c)
@@ -326,9 +397,9 @@ func adminLogoutUserScenario(desc string, url string, routePattern string, fn sc
}) })
} }
func adminRevokeUserAuthTokenScenario(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) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -337,12 +408,12 @@ func adminRevokeUserAuthTokenScenario(desc string, url string, routePattern stri
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.AdminRevokeUserAuthToken(c, cmd) return hs.AdminRevokeUserAuthToken(c, cmd)
@@ -354,9 +425,9 @@ func adminRevokeUserAuthTokenScenario(desc string, url string, routePattern stri
}) })
} }
func adminGetUserAuthTokensScenario(desc string, url string, routePattern string, fn scenarioFunc) { func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -365,12 +436,12 @@ func adminGetUserAuthTokensScenario(desc string, url string, routePattern string
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.AdminGetUserAuthTokens(c) return hs.AdminGetUserAuthTokens(c)
@@ -382,9 +453,9 @@ func adminGetUserAuthTokensScenario(desc string, url string, routePattern string
}) })
} }
func adminDisableUserScenario(desc string, action string, url string, routePattern string, fn scenarioFunc) { func adminDisableUserScenario(t *testing.T, desc string, action string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -393,10 +464,10 @@ func adminDisableUserScenario(desc string, action string, url string, routePatte
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
if action == "enable" { if action == "enable" {
return AdminEnableUser(c) return AdminEnableUser(c)
@@ -411,14 +482,14 @@ func adminDisableUserScenario(desc string, action string, url string, routePatte
}) })
} }
func adminDeleteUserScenario(desc string, url string, routePattern string, fn scenarioFunc) { func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
return AdminDeleteUser(c) return AdminDeleteUser(c)
}) })
@@ -429,14 +500,14 @@ func adminDeleteUserScenario(desc string, url string, routePattern string, fn sc
}) })
} }
func adminCreateUserScenario(desc string, url string, routePattern string, cmd dtos.AdminCreateUserForm, fn scenarioFunc) { func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.AdminCreateUserForm, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
return AdminCreateUser(c, cmd) return AdminCreateUser(c, cmd)
}) })

View File

@@ -1,29 +1,38 @@
package api package api
import ( import (
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
func TestAlertingApiEndpoint(t *testing.T) { type setUpConf struct {
Convey("Given an alert in a dashboard with an acl", t, func() { aclMockResp []*models.DashboardAclInfoDTO
singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"} }
func TestAlertingAPIEndpoint(t *testing.T) {
singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"}
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
setUp := func(confs ...setUpConf) {
bus.AddHandler("test", func(query *models.GetAlertByIdQuery) error { bus.AddHandler("test", func(query *models.GetAlertByIdQuery) error {
query.Result = singleAlert query.Result = singleAlert
return nil return nil
}) })
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{} aclMockResp := []*models.DashboardAclInfoDTO{}
for _, c := range confs {
if c.aclMockResp != nil {
aclMockResp = c.aclMockResp
}
}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp query.Result = aclMockResp
return nil return nil
@@ -33,39 +42,45 @@ func TestAlertingApiEndpoint(t *testing.T) {
query.Result = []*models.TeamDTO{} query.Result = []*models.TeamDTO{}
return nil return nil
}) })
}
Convey("When user is editor and not in the ACL", func() { t.Run("When user is editor and not in the ACL", func(t *testing.T) {
Convey("Should not be able to pause the alert", func() { cmd := dtos.PauseAlertCommand{
cmd := dtos.PauseAlertCommand{ AlertId: 1,
AlertId: 1, Paused: true,
Paused: true, }
} postAlertScenario(t, "When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause",
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) { models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
CallPauseAlert(sc) setUp()
So(sc.resp.Code, ShouldEqual, 403)
}) callPauseAlert(sc)
assert.Equal(t, 403, sc.resp.Code)
}) })
}) })
Convey("When user is editor and dashboard has default ACL", func() { t.Run("When user is editor and dashboard has default ACL", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{ cmd := dtos.PauseAlertCommand{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW}, AlertId: 1,
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, Paused: true,
} }
postAlertScenario(t, "When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause",
Convey("Should be able to pause the alert", func() { models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
cmd := dtos.PauseAlertCommand{ setUp(setUpConf{
AlertId: 1, aclMockResp: []*models.DashboardAclInfoDTO{
Paused: true, {Role: &viewerRole, Permission: models.PERMISSION_VIEW},
} {Role: &editorRole, Permission: models.PERMISSION_EDIT},
postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) { },
CallPauseAlert(sc)
So(sc.resp.Code, ShouldEqual, 200)
}) })
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) { callPauseAlert(sc)
assert.Equal(t, 200, sc.resp.Code)
})
})
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts",
models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
var searchQuery *search.Query var searchQuery *search.Query
bus.AddHandler("test", func(query *search.Query) error { bus.AddHandler("test", func(query *search.Query) error {
searchQuery = query searchQuery = query
@@ -81,11 +96,15 @@ func TestAlertingApiEndpoint(t *testing.T) {
sc.handlerFunc = GetAlerts sc.handlerFunc = GetAlerts
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(searchQuery, ShouldBeNil) require.Nil(t, searchQuery)
So(getAlertsQuery, ShouldNotBeNil) assert.NotNil(t, getAlertsQuery)
}) })
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET",
"/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery",
"/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
var searchQuery *search.Query var searchQuery *search.Query
bus.AddHandler("test", func(query *search.Query) error { bus.AddHandler("test", func(query *search.Query) error {
searchQuery = query searchQuery = query
@@ -105,29 +124,31 @@ func TestAlertingApiEndpoint(t *testing.T) {
sc.handlerFunc = GetAlerts sc.handlerFunc = GetAlerts
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(searchQuery, ShouldNotBeNil) require.NotNil(t, searchQuery)
So(searchQuery.DashboardIds[0], ShouldEqual, 1) assert.Equal(t, int64(1), searchQuery.DashboardIds[0])
So(searchQuery.DashboardIds[1], ShouldEqual, 2) assert.Equal(t, int64(2), searchQuery.DashboardIds[1])
So(searchQuery.FolderIds[0], ShouldEqual, 3) assert.Equal(t, int64(3), searchQuery.FolderIds[0])
So(searchQuery.Tags[0], ShouldEqual, "abc") assert.Equal(t, "abc", searchQuery.Tags[0])
So(searchQuery.Title, ShouldEqual, "dbQuery") assert.Equal(t, "dbQuery", searchQuery.Title)
So(getAlertsQuery, ShouldNotBeNil) require.NotNil(t, getAlertsQuery)
So(getAlertsQuery.DashboardIDs[0], ShouldEqual, 1) assert.Equal(t, int64(1), getAlertsQuery.DashboardIDs[0])
So(getAlertsQuery.DashboardIDs[1], ShouldEqual, 2) assert.Equal(t, int64(2), getAlertsQuery.DashboardIDs[1])
So(getAlertsQuery.Limit, ShouldEqual, 5) assert.Equal(t, int64(5), getAlertsQuery.Limit)
So(getAlertsQuery.Query, ShouldEqual, "alertQuery") assert.Equal(t, "alertQuery", getAlertsQuery.Query)
}) })
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alert-notifications/1",
"/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) {
setUp()
sc.handlerFunc = GetAlertNotificationByID sc.handlerFunc = GetAlertNotificationByID
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404) assert.Equal(t, 404, sc.resp.Code)
}) })
})
} }
func CallPauseAlert(sc *scenarioContext) { func callPauseAlert(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.PauseAlertCommand) error { bus.AddHandler("test", func(cmd *models.PauseAlertCommand) error {
return nil return nil
}) })
@@ -135,15 +156,16 @@ func CallPauseAlert(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
func postAlertScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PauseAlertCommand, fn scenarioFunc) { func postAlertScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
Convey(desc+" "+url, func() { cmd dtos.PauseAlertCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return PauseAlert(c, cmd) return PauseAlert(c, cmd)

View File

@@ -247,7 +247,6 @@ func DeleteAnnotationByID(c *models.ReqContext) Response {
OrgId: c.OrgId, OrgId: c.OrgId,
Id: annotationID, Id: annotationID,
}) })
if err != nil { if err != nil {
return Error(500, "Failed to delete annotation", err) return Error(500, "Failed to delete annotation", err)
} }
@@ -272,7 +271,6 @@ func canSaveByDashboardID(c *models.ReqContext, dashboardID int64) (bool, error)
func canSave(c *models.ReqContext, repo annotations.Repository, annotationID int64) Response { func canSave(c *models.ReqContext, repo annotations.Repository, annotationID int64) Response {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId}) items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 { if err != nil || len(items) == 0 {
return Error(500, "Could not find annotation to update", err) return Error(500, "Could not find annotation to update", err)
} }

View File

@@ -1,18 +1,18 @@
package api package api
import ( import (
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey"
) )
func TestAnnotationsApiEndpoint(t *testing.T) { func TestAnnotationsAPIEndpoint(t *testing.T) {
Convey("Given an annotation without a dashboard id", t, func() { t.Run("Given an annotation without a dashboard ID", func(t *testing.T) {
cmd := dtos.PostAnnotationsCmd{ cmd := dtos.PostAnnotationsCmd{
Time: 1000, Time: 1000,
Text: "annotation text", Text: "annotation text",
@@ -31,60 +31,70 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
Tags: []string{"tag1", "tag2"}, Tags: []string{"tag1", "tag2"},
} }
Convey("When user is an Org Viewer", func() { t.Run("When user is an Org Viewer", func(t *testing.T) {
role := models.ROLE_VIEWER role := models.ROLE_VIEWER
Convey("Should not be allowed to save an annotation", func() { t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 403) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}) assert.Equal(t, 403, sc.resp.Code)
})
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) { putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId",
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() role, updateCmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 403) sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
}) assert.Equal(t, 403, sc.resp.Code)
})
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) { patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1",
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 403) sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
}) assert.Equal(t, 403, sc.resp.Code)
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
sc.handlerFunc = DeleteAnnotationByID "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() fakeAnnoRepo = &fakeAnnotationsRepo{}
So(sc.resp.Code, ShouldEqual, 403) annotations.SetRepository(fakeAnnoRepo)
}) sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
}) })
}) })
Convey("When user is an Org Editor", func() { t.Run("When user is an Org Editor", func(t *testing.T) {
role := models.ROLE_EDITOR role := models.ROLE_EDITOR
Convey("Should be able to save an annotation", func() { t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 200) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}) assert.Equal(t, 200, sc.resp.Code)
})
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) { putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) { patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
sc.handlerFunc = DeleteAnnotationByID "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() fakeAnnoRepo = &fakeAnnotationsRepo{}
So(sc.resp.Code, ShouldEqual, 200) annotations.SetRepository(fakeAnnoRepo)
}) sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
}) })
}) })
}) })
Convey("Given an annotation with a dashboard id and the dashboard does not have an acl", t, func() { t.Run("Given an annotation with a dashboard ID and the dashboard does not have an ACL", func(t *testing.T) {
cmd := dtos.PostAnnotationsCmd{ cmd := dtos.PostAnnotationsCmd{
Time: 1000, Time: 1000,
Text: "annotation text", Text: "annotation text",
@@ -120,90 +130,111 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, {Role: &editorRole, Permission: models.PERMISSION_EDIT},
} }
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { setUp := func() {
query.Result = aclMockResp bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
return nil query.Result = aclMockResp
}) return nil
})
bus.AddHandler("test", func(query *models.GetTeamsByUserQuery) error { bus.AddHandler("test", func(query *models.GetTeamsByUserQuery) error {
query.Result = []*models.TeamDTO{} query.Result = []*models.TeamDTO{}
return nil return nil
}) })
}
Convey("When user is an Org Viewer", func() { t.Run("When user is an Org Viewer", func(t *testing.T) {
role := models.ROLE_VIEWER role := models.ROLE_VIEWER
Convey("Should not be allowed to save an annotation", func() { t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403) assert.Equal(t, 403, sc.resp.Code)
}) })
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) { putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403) assert.Equal(t, 403, sc.resp.Code)
}) })
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) { patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403) assert.Equal(t, 403, sc.resp.Code)
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
sc.handlerFunc = DeleteAnnotationByID "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() setUp()
So(sc.resp.Code, ShouldEqual, 403) fakeAnnoRepo = &fakeAnnotationsRepo{}
}) annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
}) })
}) })
Convey("When user is an Org Editor", func() { t.Run("When user is an Org Editor", func(t *testing.T) {
role := models.ROLE_EDITOR role := models.ROLE_EDITOR
Convey("Should be able to save an annotation", func() { t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) { putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) { patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
sc.handlerFunc = DeleteAnnotationByID "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec() setUp()
So(sc.resp.Code, ShouldEqual, 200) fakeAnnoRepo = &fakeAnnotationsRepo{}
}) annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
}) })
}) })
Convey("When user is an Admin", func() { t.Run("When user is an Admin", func(t *testing.T) {
role := models.ROLE_ADMIN role := models.ROLE_ADMIN
Convey("Should be able to do anything", func() { t.Run("Should be able to do anything", func(t *testing.T) {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) { postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
putAnnotationScenario("When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) { putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) { patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) { deleteAnnotationsScenario(t, "When calling POST on", "/api/annotations/mass-delete",
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 200) setUp()
}) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
}) })
}) })
}) })
@@ -229,15 +260,16 @@ func (repo *fakeAnnotationsRepo) Find(query *annotations.ItemQuery) ([]*annotati
var fakeAnnoRepo *fakeAnnotationsRepo var fakeAnnoRepo *fakeAnnotationsRepo
func postAnnotationScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PostAnnotationsCmd, fn scenarioFunc) { func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
Convey(desc+" "+url, func() { cmd dtos.PostAnnotationsCmd, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return PostAnnotation(c, cmd) return PostAnnotation(c, cmd)
@@ -252,15 +284,16 @@ func postAnnotationScenario(desc string, url string, routePattern string, role m
}) })
} }
func putAnnotationScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.UpdateAnnotationsCmd, fn scenarioFunc) { func putAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
Convey(desc+" "+url, func() { cmd dtos.UpdateAnnotationsCmd, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return UpdateAnnotation(c, cmd) return UpdateAnnotation(c, cmd)
@@ -275,15 +308,15 @@ func putAnnotationScenario(desc string, url string, routePattern string, role mo
}) })
} }
func patchAnnotationScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) { func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return PatchAnnotation(c, cmd) return PatchAnnotation(c, cmd)
@@ -298,15 +331,16 @@ func patchAnnotationScenario(desc string, url string, routePattern string, role
}) })
} }
func deleteAnnotationsScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) { func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
Convey(desc+" "+url, func() { cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
return DeleteAnnotations(c, cmd) return DeleteAnnotations(c, cmd)

View File

@@ -11,24 +11,23 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
) )
func loggedInUserScenario(desc string, url string, fn scenarioFunc) { func loggedInUserScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
loggedInUserScenarioWithRole(desc, "GET", url, url, models.ROLE_EDITOR, fn) loggedInUserScenarioWithRole(t, desc, "GET", url, url, models.ROLE_EDITOR, fn)
} }
func loggedInUserScenarioWithRole(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) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = role sc.context.OrgRole = role
if sc.handlerFunc != nil { if sc.handlerFunc != nil {
return sc.handlerFunc(sc.context) return sc.handlerFunc(sc.context)
@@ -48,11 +47,11 @@ func loggedInUserScenarioWithRole(desc string, method string, url string, routeP
}) })
} }
func anonymousUserScenario(desc string, method string, url string, routePattern string, fn scenarioFunc) { func anonymousUserScenario(t *testing.T, desc string, method string, url string, routePattern string, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
if sc.handlerFunc != nil { if sc.handlerFunc != nil {
@@ -76,7 +75,7 @@ func anonymousUserScenario(desc string, method string, url string, routePattern
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext { func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder() sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil) req, err := http.NewRequest(method, url, nil)
So(err, ShouldBeNil) require.NoError(sc.t, err)
sc.req = req sc.req = req
return sc return sc
@@ -141,9 +140,10 @@ func (sc *scenarioContext) exec() {
type scenarioFunc func(c *scenarioContext) type scenarioFunc func(c *scenarioContext)
type handlerFunc func(c *models.ReqContext) Response type handlerFunc func(c *models.ReqContext) Response
func setupScenarioContext(url string) *scenarioContext { func setupScenarioContext(t *testing.T, url string) *scenarioContext {
sc := &scenarioContext{ sc := &scenarioContext{
url: url, url: url,
t: t,
} }
viewsPath, _ := filepath.Abs("../../public/views") viewsPath, _ := filepath.Abs("../../public/views")

View File

@@ -48,7 +48,9 @@ func dashboardGuardianResponse(err error) Response {
} }
func (hs *HTTPServer) GetDashboard(c *models.ReqContext) Response { func (hs *HTTPServer) GetDashboard(c *models.ReqContext) Response {
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, c.Params(":uid")) slug := c.Params(":slug")
uid := c.Params(":uid")
dash, rsp := getDashboardHelper(c.OrgId, slug, 0, uid)
if rsp != nil { if rsp != nil {
return rsp return rsp
} }

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
@@ -8,21 +9,25 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
func TestDashboardPermissionApiEndpoint(t *testing.T) { func TestDashboardPermissionAPIEndpoint(t *testing.T) {
Convey("Dashboard permissions test", t, func() { t.Run("Dashboard permissions test", func(t *testing.T) {
Convey("Given dashboard not exists", func() { t.Run("Given dashboard not exists", func(t *testing.T) {
bus.AddHandler("test", func(query *models.GetDashboardQuery) error { setUp := func() {
return models.ErrDashboardNotFound bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
}) return models.ErrDashboardNotFound
})
}
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
callGetDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 404) setUp()
}) callGetDashboardPermissions(sc)
assert.Equal(t, 404, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{ cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{ Items: []dtos.DashboardAclUpdateItem{
@@ -30,26 +35,37 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
} }
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) { updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
callUpdateDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 404) setUp()
}) callUpdateDashboardPermissions(sc)
assert.Equal(t, 404, sc.resp.Code)
})
}) })
Convey("Given user has no admin permissions", func() { t.Run("Given user has no admin permissions", func(t *testing.T) {
origNewGuardian := guardian.New origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
getDashboardQueryResult := models.NewDashboard("Dash") getDashboardQueryResult := models.NewDashboard("Dash")
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { setUp := func() {
callGetDashboardPermissions(sc) bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
So(sc.resp.Code, ShouldEqual, 403) query.Result = getDashboardQueryResult
}) return nil
})
}
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
"/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
callGetDashboardPermissions(sc)
assert.Equal(t, 403, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{ cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{ Items: []dtos.DashboardAclUpdateItem{
@@ -57,18 +73,20 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
} }
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) { updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
callUpdateDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 403) setUp()
}) callUpdateDashboardPermissions(sc)
assert.Equal(t, 403, sc.resp.Code)
Reset(func() { })
guardian.New = origNewGuardian
})
}) })
Convey("Given user has admin permissions and permissions to update", func() { t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
origNewGuardian := guardian.New origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true, CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true, CheckPermissionBeforeUpdateValue: true,
@@ -81,21 +99,25 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
}) })
getDashboardQueryResult := models.NewDashboard("Dash") setUp := func() {
bus.AddHandler("test", func(query *models.GetDashboardQuery) error { getDashboardQueryResult := models.NewDashboard("Dash")
query.Result = getDashboardQueryResult bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
return nil query.Result = getDashboardQueryResult
}) return nil
})
}
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
callGetDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 200) setUp()
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) callGetDashboardPermissions(sc)
So(err, ShouldBeNil) assert.Equal(t, 200, sc.resp.Code)
So(len(respJSON.MustArray()), ShouldEqual, 5) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2) require.NoError(t, err)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, models.PERMISSION_VIEW) assert.Equal(t, 5, len(respJSON.MustArray()))
}) assert.Equal(t, 2, respJSON.GetIndex(0).Get("userId").MustInt())
assert.Equal(t, int(models.PERMISSION_VIEW), respJSON.GetIndex(0).Get("permission").MustInt())
})
cmd := dtos.UpdateDashboardAclCommand{ cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{ Items: []dtos.DashboardAclUpdateItem{
@@ -103,29 +125,32 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
} }
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) { updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
callUpdateDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 200) setUp()
}) callUpdateDashboardPermissions(sc)
assert.Equal(t, 200, sc.resp.Code)
Reset(func() { })
guardian.New = origNewGuardian
})
}) })
Convey("When trying to update permissions with duplicate permissions", func() { t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) {
origNewGuardian := guardian.New origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true, CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false, CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists, CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
}) })
getDashboardQueryResult := models.NewDashboard("Dash") setUp := func() {
bus.AddHandler("test", func(query *models.GetDashboardQuery) error { getDashboardQueryResult := models.NewDashboard("Dash")
query.Result = getDashboardQueryResult bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
return nil query.Result = getDashboardQueryResult
}) return nil
})
}
cmd := dtos.UpdateDashboardAclCommand{ cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{ Items: []dtos.DashboardAclUpdateItem{
@@ -133,29 +158,33 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
} }
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) { updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
callUpdateDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 400) setUp()
}) callUpdateDashboardPermissions(sc)
assert.Equal(t, 400, sc.resp.Code)
Reset(func() { })
guardian.New = origNewGuardian
})
}) })
Convey("When trying to override inherited permissions with lower precedence", func() { t.Run("When trying to override inherited permissions with lower precedence", func(t *testing.T) {
origNewGuardian := guardian.New origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true, CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false, CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride}, CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
) )
getDashboardQueryResult := models.NewDashboard("Dash") setUp := func() {
bus.AddHandler("test", func(query *models.GetDashboardQuery) error { getDashboardQueryResult := models.NewDashboard("Dash")
query.Result = getDashboardQueryResult bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
return nil query.Result = getDashboardQueryResult
}) return nil
})
}
cmd := dtos.UpdateDashboardAclCommand{ cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{ Items: []dtos.DashboardAclUpdateItem{
@@ -163,14 +192,12 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
}, },
} }
updateDashboardPermissionScenario("When calling POST on", "/api/dashboards/id/1/permissions", "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) { updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
callUpdateDashboardPermissions(sc) "/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 400) setUp()
}) callUpdateDashboardPermissions(sc)
assert.Equal(t, 400, sc.resp.Code)
Reset(func() { })
guardian.New = origNewGuardian
})
}) })
}) })
} }
@@ -188,16 +215,16 @@ func callUpdateDashboardPermissions(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
func updateDashboardPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) { func updateDashboardPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.UserId = TestUserID sc.context.UserId = testUserID
return UpdateDashboardPermissions(c, cmd) return UpdateDashboardPermissions(c, cmd)
}) })

View File

@@ -236,10 +236,10 @@ func DeleteDashboardSnapshot(c *models.ReqContext) Response {
if err != nil { if err != nil {
return Error(500, "Failed to get dashboard snapshot", err) return Error(500, "Failed to get dashboard snapshot", err)
} }
if query.Result == nil { if query.Result == nil {
return Error(404, "Failed to get dashboard snapshot", nil) return Error(404, "Failed to get dashboard snapshot", nil)
} }
dashboard, err := query.Result.DashboardJSON() dashboard, err := query.Result.DashboardJSON()
if err != nil { if err != nil {
return Error(500, "Failed to get dashboard data for dashboard snapshot", err) return Error(500, "Failed to get dashboard data for dashboard snapshot", err)

View File

@@ -4,23 +4,38 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/components/securedata" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securedata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
) )
func TestDashboardSnapshotApiEndpoint(t *testing.T) { func TestDashboardSnapshotAPIEndpoint_singleSnapshot(t *testing.T) {
Convey("Given a single snapshot", t, func() { setupRemoteServer := func(fn func(http.ResponseWriter, *http.Request)) *httptest.Server {
var externalRequest *http.Request s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
jsonModel, _ := simplejson.NewJson([]byte(`{"id":100}`)) fn(rw, r)
}))
t.Cleanup(s.Close)
return s
}
jsonModel, err := simplejson.NewJson([]byte(`{"id":100}`))
require.NoError(t, err)
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{}
setUpSnapshotTest := func(t *testing.T) *models.DashboardSnapshot {
t.Helper()
mockSnapshotResult := &models.DashboardSnapshot{ mockSnapshotResult := &models.DashboardSnapshot{
Id: 1, Id: 1,
@@ -41,9 +56,6 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
return nil return nil
}) })
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp query.Result = aclMockResp
return nil return nil
@@ -55,185 +67,199 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
return nil return nil
}) })
setupRemoteServer := func(fn func(http.ResponseWriter, *http.Request)) *httptest.Server { return mockSnapshotResult
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { }
fn(rw, r)
})) t.Run("When user has editor role and is not in the ACL", func(t *testing.T) {
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)
var externalRequest *http.Request
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
externalRequest = req
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
assert.Equal(t, 403, sc.resp.Code)
require.Nil(t, externalRequest)
})
})
t.Run("When user is anonymous", func(t *testing.T) {
anonymousUserScenario(t, "Should be able to delete a snapshot when calling GET on", "GET",
"/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) {
mockSnapshotResult := setUpSnapshotTest(t)
var externalRequest *http.Request
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
externalRequest = req
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey
sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec()
require.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(respJSON.Get("message").MustString(), "Snapshot deleted"))
assert.Equal(t, http.MethodGet, externalRequest.Method)
assert.Equal(t, ts.URL, fmt.Sprintf("http://%s", externalRequest.Host))
assert.Equal(t, "/", externalRequest.URL.EscapedPath())
})
})
t.Run("When user is editor and dashboard has default ACL", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
} }
Convey("When user has editor role and is not in the ACL", func() { loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on", "DELETE",
Convey("Should not be able to delete snapshot", func() { "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { mockSnapshotResult := setUpSnapshotTest(t)
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
externalRequest = req
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL var externalRequest *http.Request
sc.handlerFunc = DeleteDashboardSnapshot ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec() rw.WriteHeader(200)
externalRequest = req
So(sc.resp.Code, ShouldEqual, 403)
So(externalRequest, ShouldBeNil)
}) })
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
assert.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.True(t, strings.HasPrefix(respJSON.Get("message").MustString(), "Snapshot deleted"))
assert.Equal(t, ts.URL, fmt.Sprintf("http://%s", externalRequest.Host))
assert.Equal(t, "/", externalRequest.URL.EscapedPath())
}) })
}) })
Convey("When user is anonymous", func() { t.Run("When user is editor and creator of the snapshot", func(t *testing.T) {
Convey("Should be able to delete snapshot by deleteKey", func() { aclMockResp = []*models.DashboardAclInfoDTO{}
anonymousUserScenario("When calling GET on", "GET", "/api/snapshots-delete/12345", "/api/snapshots-delete/:deleteKey", func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "Should be able to delete a snapshot when calling DELETE on",
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
rw.WriteHeader(200) mockSnapshotResult := setUpSnapshotTest(t)
externalRequest = req
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL mockSnapshotResult.UserId = testUserID
sc.handlerFunc = DeleteDashboardSnapshotByDeleteKey mockSnapshotResult.External = false
sc.fakeReqWithParams("GET", sc.url, map[string]string{"deleteKey": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200) sc.handlerFunc = DeleteDashboardSnapshot
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted") assert.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
So(externalRequest.Method, ShouldEqual, http.MethodGet) assert.True(t, strings.HasPrefix(respJSON.Get("message").MustString(), "Snapshot deleted"))
So(fmt.Sprintf("http://%s", externalRequest.Host), ShouldEqual, ts.URL) })
So(externalRequest.URL.EscapedPath(), ShouldEqual, "/") })
t.Run("When deleting an external snapshot", func(t *testing.T) {
aclMockResp = []*models.DashboardAclInfoDTO{}
var writeErr error
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) {
mockSnapshotResult := setUpSnapshotTest(t)
mockSnapshotResult.UserId = testUserID
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
_, writeErr = rw.Write([]byte(`{"message":"Failed to get dashboard snapshot"}`))
rw.WriteHeader(500)
}) })
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
require.NoError(t, writeErr)
assert.Equal(t, 200, sc.resp.Code)
}) })
})
Convey("When user is editor and dashboard has default ACL", func() { loggedInUserScenarioWithRole(t,
aclMockResp = []*models.DashboardAclInfoDTO{ "Should fail to delete local snapshot when an unexpected 500 error occurs when calling DELETE on", "DELETE",
{Role: &viewerRole, Permission: models.PERMISSION_VIEW}, "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
{Role: &editorRole, Permission: models.PERMISSION_EDIT}, mockSnapshotResult := setUpSnapshotTest(t)
} mockSnapshotResult.UserId = testUserID
Convey("Should be able to delete a snapshot", func() {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(200)
externalRequest = req
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted")
So(fmt.Sprintf("http://%s", externalRequest.Host), ShouldEqual, ts.URL)
So(externalRequest.URL.EscapedPath(), ShouldEqual, "/")
})
})
})
Convey("When user is editor and is the creator of the snapshot", func() {
aclMockResp = []*models.DashboardAclInfoDTO{}
mockSnapshotResult.UserId = TestUserID
mockSnapshotResult.External = false
Convey("Should be able to delete a snapshot", func() {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("message").MustString(), ShouldStartWith, "Snapshot deleted")
})
})
})
Convey("When deleting an external snapshot", func() {
aclMockResp = []*models.DashboardAclInfoDTO{}
mockSnapshotResult.UserId = TestUserID
Convey("Should gracefully delete local snapshot when remote snapshot has already been removed", func() {
var writeErr error var writeErr error
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(500)
_, writeErr = rw.Write([]byte(`{"message":"Failed to get dashboard snapshot"}`)) _, writeErr = rw.Write([]byte(`{"message":"Unexpected"}`))
rw.WriteHeader(500)
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(writeErr, ShouldBeNil)
So(sc.resp.Code, ShouldEqual, 200)
}) })
t.Log("Setting external delete URL", "url", ts.URL)
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
require.NoError(t, writeErr)
assert.Equal(t, 500, sc.resp.Code)
}) })
Convey("Should fail to delete local snapshot when an unexpected 500 error occurs", func() { loggedInUserScenarioWithRole(t,
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { "Should fail to delete local snapshot when an unexpected remote error occurs when calling DELETE on",
var writeErr error "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { mockSnapshotResult := setUpSnapshotTest(t)
rw.WriteHeader(500) mockSnapshotResult.UserId = testUserID
_, writeErr = rw.Write([]byte(`{"message":"Unexpected"}`))
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) {
sc.handlerFunc = DeleteDashboardSnapshot rw.WriteHeader(404)
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(writeErr, ShouldBeNil)
So(sc.resp.Code, ShouldEqual, 500)
}) })
mockSnapshotResult.ExternalDeleteUrl = ts.URL
sc.handlerFunc = DeleteDashboardSnapshot
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
assert.Equal(t, 500, sc.resp.Code)
}) })
Convey("Should fail to delete local snapshot when an unexpected remote error occurs", func() { loggedInUserScenarioWithRole(t, "Should be able to read a snapshot's unencrypted data when calling GET on",
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
ts := setupRemoteServer(func(rw http.ResponseWriter, req *http.Request) { setUpSnapshotTest(t)
rw.WriteHeader(404)
})
mockSnapshotResult.ExternalDeleteUrl = ts.URL sc.handlerFunc = GetDashboardSnapshot
sc.handlerFunc = DeleteDashboardSnapshot sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 500) assert.Equal(t, 200, sc.resp.Code)
}) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
dashboard := respJSON.Get("dashboard")
id := dashboard.Get("id")
assert.Equal(t, int64(100), id.MustInt64())
}) })
Convey("Should be able to read a snapshot's un-encrypted data", func() { loggedInUserScenarioWithRole(t, "Should be able to read a snapshot's encrypted data When calling GET on",
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboardSnapshot
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
dashboard := respJSON.Get("dashboard")
id := dashboard.Get("id")
So(id.MustInt64(), ShouldEqual, 100)
})
})
Convey("Should be able to read a snapshot's encrypted data", func() {
origSecret := setting.SecretKey origSecret := setting.SecretKey
setting.SecretKey = "dashboard_snapshot_api_test" setting.SecretKey = "dashboard_snapshot_api_test"
t.Cleanup(func() { t.Cleanup(func() {
setting.SecretKey = origSecret setting.SecretKey = origSecret
}) })
dashboardId := 123 const dashboardID int64 = 123
jsonModel, err := simplejson.NewJson([]byte(fmt.Sprintf(`{"id":%d}`, dashboardId))) jsonModel, err := simplejson.NewJson([]byte(fmt.Sprintf(`{"id":%d}`, dashboardID)))
So(err, ShouldBeNil) require.NoError(t, err)
jsonModelEncoded, err := jsonModel.Encode() jsonModelEncoded, err := jsonModel.Encode()
So(err, ShouldBeNil) require.NoError(t, err)
encrypted, err := securedata.Encrypt(jsonModelEncoded) encrypted, err := securedata.Encrypt(jsonModelEncoded)
So(err, ShouldBeNil) require.NoError(t, err)
// mock snapshot with encrypted dashboard info // mock snapshot with encrypted dashboard info
mockSnapshotResult := &models.DashboardSnapshot{ mockSnapshotResult := &models.DashboardSnapshot{
@@ -242,21 +268,20 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
Expires: time.Now().Add(time.Duration(1000) * time.Second), Expires: time.Now().Add(time.Duration(1000) * time.Second),
} }
setUpSnapshotTest(t)
bus.AddHandler("test", func(query *models.GetDashboardSnapshotQuery) error { bus.AddHandler("test", func(query *models.GetDashboardSnapshotQuery) error {
query.Result = mockSnapshotResult query.Result = mockSnapshotResult
return nil return nil
}) })
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/snapshots/12345", "/api/snapshots/:key", models.ROLE_EDITOR, func(sc *scenarioContext) { sc.handlerFunc = GetDashboardSnapshot
sc.handlerFunc = GetDashboardSnapshot sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
sc.fakeReqWithParams("GET", sc.url, map[string]string{"key": "12345"}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil) require.NoError(t, err)
So(respJSON.Get("dashboard").Get("id").MustInt64(), ShouldEqual, dashboardId) assert.Equal(t, dashboardID, respJSON.Get("dashboard").Get("id").MustInt64())
})
}) })
})
}) })
} }

File diff suppressed because it is too large Load Diff

View File

@@ -6,64 +6,57 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
. "github.com/smartystreets/goconvey/convey"
) )
const ( const (
TestOrgID = 1 testOrgID int64 = 1
TestUserID = 1 testUserID int64 = 1
) )
func TestDataSourcesProxy(t *testing.T) { func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
Convey("Given a user is logged in", t, func() { loggedInUserScenario(t, "When calling GET on", "/api/datasources/", func(sc *scenarioContext) {
loggedInUserScenario("When calling GET on", "/api/datasources/", func(sc *scenarioContext) { // Stubs the database query
// Stubs the database query bus.AddHandler("test", func(query *models.GetDataSourcesQuery) error {
bus.AddHandler("test", func(query *models.GetDataSourcesQuery) error { assert.Equal(t, testOrgID, query.OrgId)
So(query.OrgId, ShouldEqual, TestOrgID) query.Result = []*models.DataSource{
query.Result = []*models.DataSource{ {Name: "mmm"},
{Name: "mmm"}, {Name: "ZZZ"},
{Name: "ZZZ"}, {Name: "BBB"},
{Name: "BBB"}, {Name: "aaa"},
{Name: "aaa"}, }
} return nil
return nil
})
// handler func being tested
sc.handlerFunc = GetDataSources
sc.fakeReq("GET", "/api/datasources").exec()
respJSON := []map[string]interface{}{}
err := json.NewDecoder(sc.resp.Body).Decode(&respJSON)
So(err, ShouldBeNil)
Convey("should return list of datasources for org sorted alphabetically and case insensitively", func() {
So(respJSON[0]["name"], ShouldEqual, "aaa")
So(respJSON[1]["name"], ShouldEqual, "BBB")
So(respJSON[2]["name"], ShouldEqual, "mmm")
So(respJSON[3]["name"], ShouldEqual, "ZZZ")
})
}) })
Convey("Should be able to save a data source", func() { // handler func being tested
loggedInUserScenario("When calling DELETE on non-existing", "/api/datasources/name/12345", func(sc *scenarioContext) { sc.handlerFunc = GetDataSources
sc.handlerFunc = DeleteDataSourceByName sc.fakeReq("GET", "/api/datasources").exec()
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404) respJSON := []map[string]interface{}{}
}) err := json.NewDecoder(sc.resp.Body).Decode(&respJSON)
}) require.NoError(t, err)
assert.Equal(t, "aaa", respJSON[0]["name"])
assert.Equal(t, "BBB", respJSON[1]["name"])
assert.Equal(t, "mmm", respJSON[2]["name"])
assert.Equal(t, "ZZZ", respJSON[3]["name"])
}) })
loggedInUserScenario(t, "Should be able to save a data source when calling DELETE on non-existing",
"/api/datasources/name/12345", func(sc *scenarioContext) {
sc.handlerFunc = DeleteDataSourceByName
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
assert.Equal(t, 404, sc.resp.Code)
})
} }
// Adding data sources with invalid URLs should lead to an error. // Adding data sources with invalid URLs should lead to an error.
func TestAddDataSource_InvalidURL(t *testing.T) { func TestAddDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext("/api/datasources") sc := setupScenarioContext(t, "/api/datasources")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response { sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{ return AddDataSource(c, models.AddDataSourceCommand{
@@ -93,9 +86,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
return nil return nil
}) })
sc := setupScenarioContext("/api/datasources") sc := setupScenarioContext(t, "/api/datasources")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response { sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{ return AddDataSource(c, models.AddDataSourceCommand{
@@ -113,9 +104,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
func TestUpdateDataSource_InvalidURL(t *testing.T) { func TestUpdateDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext("/api/datasources/1234") sc := setupScenarioContext(t, "/api/datasources/1234")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response { sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{ return AddDataSource(c, models.AddDataSourceCommand{
@@ -145,9 +134,7 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
return nil return nil
}) })
sc := setupScenarioContext("/api/datasources/1234") sc := setupScenarioContext(t, "/api/datasources/1234")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response { sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{ return AddDataSource(c, models.AddDataSourceCommand{

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"fmt"
"testing" "testing"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
@@ -9,204 +10,201 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
func TestFolderPermissionApiEndpoint(t *testing.T) { func TestFolderPermissionAPIEndpoint(t *testing.T) {
Convey("Folder permissions test", t, func() { t.Run("Given folder not exists", func(t *testing.T) {
Convey("Given folder not exists", func() { mock := &fakeFolderService{
mock := &fakeFolderService{ GetFolderByUIDError: models.ErrFolderNotFound,
GetFolderByUIDError: models.ErrFolderNotFound, }
}
origNewFolderService := dashboards.NewFolderService origNewFolderService := dashboards.NewFolderService
mockFolderService(mock) t.Cleanup(func() {
dashboards.NewFolderService = origNewFolderService
})
mockFolderService(mock)
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc) callGetFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404) assert.Equal(t, 404, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
})
Reset(func() {
dashboards.NewFolderService = origNewFolderService
})
}) })
Convey("Given user has no admin permissions", func() { cmd := dtos.UpdateDashboardAclCommand{
origNewGuardian := guardian.New Items: []dtos.DashboardAclUpdateItem{
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) {UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
mock := &fakeFolderService{ updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
GetFolderByUIDResult: &models.Folder{ callUpdateFolderPermissions(sc)
Id: 1, assert.Equal(t, 404, sc.resp.Code)
Uid: "uid", })
Title: "Folder", })
},
}
origNewFolderService := dashboards.NewFolderService t.Run("Given user has no admin permissions", func(t *testing.T) {
mockFolderService(mock) origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { t.Cleanup(func() {
callGetFolderPermissions(sc) guardian.New = origNewGuardian
So(sc.resp.Code, ShouldEqual, 403) dashboards.NewFolderService = origNewFolderService
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 403)
})
Reset(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
}) })
Convey("Given user has admin permissions and permissions to update", func() { guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true,
GetAclValue: []*models.DashboardAclInfoDTO{
{OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_EDIT},
{OrgId: 1, DashboardId: 1, UserId: 4, Permission: models.PERMISSION_ADMIN},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: models.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: models.PERMISSION_ADMIN},
},
})
mock := &fakeFolderService{ mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{ GetFolderByUIDResult: &models.Folder{
Id: 1, Id: 1,
Uid: "uid", Uid: "uid",
Title: "Folder", Title: "Folder",
}, },
} }
origNewFolderService := dashboards.NewFolderService mockFolderService(mock)
mockFolderService(mock)
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc) callGetFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 403, sc.resp.Code)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 5)
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, models.PERMISSION_VIEW)
})
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("id").MustInt(), ShouldEqual, 1)
So(respJSON.Get("title").MustString(), ShouldEqual, "Folder")
})
Reset(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
}) })
Convey("When trying to update permissions with duplicate permissions", func() { cmd := dtos.UpdateDashboardAclCommand{
origNewGuardian := guardian.New Items: []dtos.DashboardAclUpdateItem{
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ {UserId: 1000, Permission: models.PERMISSION_ADMIN},
CanAdminValue: true, },
CheckPermissionBeforeUpdateValue: false, }
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
mock := &fakeFolderService{ updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
GetFolderByUIDResult: &models.Folder{ callUpdateFolderPermissions(sc)
Id: 1, assert.Equal(t, 403, sc.resp.Code)
Uid: "uid", })
Title: "Folder", })
},
}
origNewFolderService := dashboards.NewFolderService t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
mockFolderService(mock) origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
cmd := dtos.UpdateDashboardAclCommand{ t.Cleanup(func() {
Items: []dtos.DashboardAclUpdateItem{ guardian.New = origNewGuardian
{UserId: 1000, Permission: models.PERMISSION_ADMIN}, dashboards.NewFolderService = origNewFolderService
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
}) })
Convey("When trying to override inherited permissions with lower precedence", func() { guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
origNewGuardian := guardian.New CanAdminValue: true,
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ CheckPermissionBeforeUpdateValue: true,
CanAdminValue: true, GetAclValue: []*models.DashboardAclInfoDTO{
CheckPermissionBeforeUpdateValue: false, {OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_VIEW},
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride}, {OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_EDIT},
) {OrgId: 1, DashboardId: 1, UserId: 4, Permission: models.PERMISSION_ADMIN},
{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: models.PERMISSION_VIEW},
{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: models.PERMISSION_ADMIN},
},
})
mock := &fakeFolderService{ mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{ GetFolderByUIDResult: &models.Folder{
Id: 1, Id: 1,
Uid: "uid", Uid: "uid",
Title: "Folder", Title: "Folder",
}, },
} }
origNewFolderService := dashboards.NewFolderService mockFolderService(mock)
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{ loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
Items: []dtos.DashboardAclUpdateItem{ callGetFolderPermissions(sc)
{UserId: 1000, Permission: models.PERMISSION_ADMIN}, assert.Equal(t, 200, sc.resp.Code)
}, respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
} require.NoError(t, err)
assert.Equal(t, 5, len(respJSON.MustArray()))
assert.Equal(t, 2, respJSON.GetIndex(0).Get("userId").MustInt())
assert.Equal(t, int(models.PERMISSION_VIEW), respJSON.GetIndex(0).Get("permission").MustInt())
})
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) { cmd := dtos.UpdateDashboardAclCommand{
callUpdateFolderPermissions(sc) Items: []dtos.DashboardAclUpdateItem{
So(sc.resp.Code, ShouldEqual, 400) {UserId: 1000, Permission: models.PERMISSION_ADMIN},
}) },
}
Reset(func() { updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
guardian.New = origNewGuardian callUpdateFolderPermissions(sc)
dashboards.NewFolderService = origNewFolderService assert.Equal(t, 200, sc.resp.Code)
}) respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 1, respJSON.Get("id").MustInt())
assert.Equal(t, "Folder", respJSON.Get("title").MustString())
})
})
t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
assert.Equal(t, 400, sc.resp.Code)
})
})
t.Run("When trying to override inherited permissions with lower precedence", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
)
mock := &fakeFolderService{
GetFolderByUIDResult: &models.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
},
}
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
{UserId: 1000, Permission: models.PERMISSION_ADMIN},
},
}
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
assert.Equal(t, 400, sc.resp.Code)
}) })
}) })
} }
@@ -224,16 +222,16 @@ func callUpdateFolderPermissions(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
func updateFolderPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) { func updateFolderPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.UserId = TestUserID sc.context.UserId = testUserID
return UpdateFolderPermissions(c, cmd) return UpdateFolderPermissions(c, cmd)
}) })

View File

@@ -10,127 +10,121 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require"
) )
func TestFoldersApiEndpoint(t *testing.T) { func TestFoldersAPIEndpoint(t *testing.T) {
Convey("Create/update folder response tests", t, func() { t.Run("Given a correct request for creating a folder", func(t *testing.T) {
Convey("Given a correct request for creating a folder", func() { cmd := models.CreateFolderCommand{
cmd := models.CreateFolderCommand{ Uid: "uid",
Uid: "uid", Title: "Folder",
Title: "Folder", }
}
mock := &fakeFolderService{ mock := &fakeFolderService{
CreateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}, CreateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder"},
} }
createFolderScenario("When calling POST on", "/api/folders", "/api/folders", mock, cmd, func(sc *scenarioContext) { createFolderScenario(t, "When calling POST on", "/api/folders", "/api/folders", mock, cmd,
func(sc *scenarioContext) {
callCreateFolder(sc) callCreateFolder(sc)
Convey("It should return correct response data", func() { folder := dtos.Folder{}
folder := dtos.Folder{} err := json.NewDecoder(sc.resp.Body).Decode(&folder)
err := json.NewDecoder(sc.resp.Body).Decode(&folder) require.NoError(t, err)
So(err, ShouldBeNil) assert.Equal(t, int64(1), folder.Id)
So(folder.Id, ShouldEqual, 1) assert.Equal(t, "uid", folder.Uid)
So(folder.Uid, ShouldEqual, "uid") assert.Equal(t, "Folder", folder.Title)
So(folder.Title, ShouldEqual, "Folder")
})
}) })
}) })
Convey("Given incorrect requests for creating a folder", func() { t.Run("Given incorrect requests for creating a folder", func(t *testing.T) {
testCases := []struct { testCases := []struct {
Error error Error error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 400}, {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 400},
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400}, {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
} }
cmd := models.CreateFolderCommand{ cmd := models.CreateFolderCommand{
Uid: "uid", Uid: "uid",
Title: "Folder", Title: "Folder",
} }
for _, tc := range testCases {
mock := &fakeFolderService{
CreateFolderError: tc.Error,
}
createFolderScenario(fmt.Sprintf("Expect '%s' error when calling POST on", tc.Error.Error()), "/api/folders", "/api/folders", mock, cmd, func(sc *scenarioContext) {
callCreateFolder(sc)
if sc.resp.Code != tc.ExpectedStatusCode {
t.Errorf("For error '%s' expected status code %d, actual %d", tc.Error, tc.ExpectedStatusCode, sc.resp.Code)
}
})
}
})
Convey("Given a correct request for updating a folder", func() {
cmd := models.UpdateFolderCommand{
Title: "Folder upd",
}
for _, tc := range testCases {
mock := &fakeFolderService{ mock := &fakeFolderService{
UpdateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"}, CreateFolderError: tc.Error,
} }
updateFolderScenario("When calling PUT on", "/api/folders/uid", "/api/folders/:uid", mock, cmd, func(sc *scenarioContext) { createFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.Error.Error()),
"/api/folders", "/api/folders", mock, cmd, func(sc *scenarioContext) {
callCreateFolder(sc)
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for error %s", tc.Error)
})
}
})
t.Run("Given a correct request for updating a folder", func(t *testing.T) {
cmd := models.UpdateFolderCommand{
Title: "Folder upd",
}
mock := &fakeFolderService{
UpdateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"},
}
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", mock, cmd,
func(sc *scenarioContext) {
callUpdateFolder(sc) callUpdateFolder(sc)
Convey("It should return correct response data", func() { folder := dtos.Folder{}
folder := dtos.Folder{} err := json.NewDecoder(sc.resp.Body).Decode(&folder)
err := json.NewDecoder(sc.resp.Body).Decode(&folder) require.NoError(t, err)
So(err, ShouldBeNil) assert.Equal(t, int64(1), folder.Id)
So(folder.Id, ShouldEqual, 1) assert.Equal(t, "uid", folder.Uid)
So(folder.Uid, ShouldEqual, "uid") assert.Equal(t, "Folder upd", folder.Title)
So(folder.Title, ShouldEqual, "Folder upd")
})
}) })
}) })
Convey("Given incorrect requests for updating a folder", func() { t.Run("Given incorrect requests for updating a folder", func(t *testing.T) {
testCases := []struct { testCases := []struct {
Error error Error error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 400}, {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 400},
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400}, {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
}
cmd := models.UpdateFolderCommand{
Title: "Folder upd",
}
for _, tc := range testCases {
mock := &fakeFolderService{
UpdateFolderError: tc.Error,
} }
cmd := models.UpdateFolderCommand{ updateFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling PUT on", tc.Error.Error()),
Title: "Folder upd", "/api/folders/uid", "/api/folders/:uid", mock, cmd, func(sc *scenarioContext) {
}
for _, tc := range testCases {
mock := &fakeFolderService{
UpdateFolderError: tc.Error,
}
updateFolderScenario(fmt.Sprintf("Expect '%s' error when calling PUT on", tc.Error.Error()), "/api/folders/uid", "/api/folders/:uid", mock, cmd, func(sc *scenarioContext) {
callUpdateFolder(sc) callUpdateFolder(sc)
if sc.resp.Code != tc.ExpectedStatusCode { assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for %s", tc.Error)
t.Errorf("For error '%s' expected status code %d, actual %d", tc.Error, tc.ExpectedStatusCode, sc.resp.Code)
}
}) })
} }
})
}) })
} }
@@ -138,19 +132,20 @@ func callCreateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
} }
func createFolderScenario(desc string, url string, routePattern string, mock *fakeFolderService, cmd models.CreateFolderCommand, fn scenarioFunc) { func createFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
Convey(desc+" "+url, func() { cmd models.CreateFolderCommand, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{ hs := HTTPServer{
Bus: bus.GetBus(), Bus: bus.GetBus(),
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.SignedInUser = &models.SignedInUser{OrgId: TestOrgID, UserId: TestUserID} sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
return hs.CreateFolder(c, cmd) return hs.CreateFolder(c, cmd)
}) })
@@ -172,27 +167,27 @@ func callUpdateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
} }
func updateFolderScenario(desc string, url string, routePattern string, mock *fakeFolderService, cmd models.UpdateFolderCommand, fn scenarioFunc) { func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
Convey(desc+" "+url, func() { cmd models.UpdateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.SignedInUser = &models.SignedInUser{OrgId: TestOrgID, UserId: TestUserID} sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
return UpdateFolder(c, cmd) return UpdateFolder(c, cmd)
}) })
origNewFolderService := dashboards.NewFolderService origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
dashboards.NewFolderService = origNewFolderService
})
mockFolderService(mock) mockFolderService(mock)
sc.m.Put(routePattern, sc.defaultHandler) sc.m.Put(routePattern, sc.defaultHandler)
defer func() {
dashboards.NewFolderService = origNewFolderService
}()
fn(sc) fn(sc)
}) })
} }

View File

@@ -27,7 +27,7 @@ func logSentryEventScenario(t *testing.T, desc string, event frontendSentryEvent
frontendLogger.SetHandler(origHandler) frontendLogger.SetHandler(origHandler)
}) })
sc := setupScenarioContext("/log") sc := setupScenarioContext(t, "/log")
hs := HTTPServer{} hs := HTTPServer{}
handler := Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response { handler := Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response {

View File

@@ -158,7 +158,6 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) Response {
} }
ldapConfig, err := getLDAPConfig() ldapConfig, err := getLDAPConfig()
if err != nil { if err != nil {
return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err) return Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
} }

View File

@@ -50,11 +50,11 @@ func (m *LDAPMock) User(login string) (*models.ExternalUserInfo, ldap.ServerConf
func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext { func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
t.Helper() t.Helper()
sc := setupScenarioContext(requestURL) sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled origLDAP := setting.LDAPEnabled
setting.LDAPEnabled = true setting.LDAPEnabled = true
defer func() { setting.LDAPEnabled = ldap }() t.Cleanup(func() { setting.LDAPEnabled = origLDAP })
hs := &HTTPServer{Cfg: setting.NewCfg()} hs := &HTTPServer{Cfg: setting.NewCfg()}
@@ -73,7 +73,7 @@ func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
return sc return sc
} }
func TestGetUserFromLDAPApiEndpoint_UserNotFound(t *testing.T) { func TestGetUserFromLDAPAPIEndpoint_UserNotFound(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) { getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil return &ldap.Config{}, nil
} }
@@ -90,7 +90,7 @@ func TestGetUserFromLDAPApiEndpoint_UserNotFound(t *testing.T) {
assert.JSONEq(t, "{\"message\":\"No user was found in the LDAP server(s) with that username\"}", sc.resp.Body.String()) assert.JSONEq(t, "{\"message\":\"No user was found in the LDAP server(s) with that username\"}", sc.resp.Body.String())
} }
func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) { func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
isAdmin := true isAdmin := true
userSearchResult = &models.ExternalUserInfo{ userSearchResult = &models.ExternalUserInfo{
Name: "John Doe", Name: "John Doe",
@@ -152,7 +152,7 @@ func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) {
assert.JSONEq(t, expected, sc.resp.Body.String()) assert.JSONEq(t, expected, sc.resp.Body.String())
} }
func TestGetUserFromLDAPApiEndpoint(t *testing.T) { func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
isAdmin := true isAdmin := true
userSearchResult = &models.ExternalUserInfo{ userSearchResult = &models.ExternalUserInfo{
Name: "John Doe", Name: "John Doe",
@@ -232,7 +232,7 @@ func TestGetUserFromLDAPApiEndpoint(t *testing.T) {
assert.JSONEq(t, expected, sc.resp.Body.String()) assert.JSONEq(t, expected, sc.resp.Body.String())
} }
func TestGetUserFromLDAPApiEndpoint_WithTeamHandler(t *testing.T) { func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
isAdmin := true isAdmin := true
userSearchResult = &models.ExternalUserInfo{ userSearchResult = &models.ExternalUserInfo{
Name: "John Doe", Name: "John Doe",
@@ -319,11 +319,11 @@ func getLDAPStatusContext(t *testing.T) *scenarioContext {
t.Helper() t.Helper()
requestURL := "/api/admin/ldap/status" requestURL := "/api/admin/ldap/status"
sc := setupScenarioContext(requestURL) sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled ldap := setting.LDAPEnabled
setting.LDAPEnabled = true setting.LDAPEnabled = true
defer func() { setting.LDAPEnabled = ldap }() t.Cleanup(func() { setting.LDAPEnabled = ldap })
hs := &HTTPServer{Cfg: setting.NewCfg()} hs := &HTTPServer{Cfg: setting.NewCfg()}
@@ -342,7 +342,7 @@ func getLDAPStatusContext(t *testing.T) *scenarioContext {
return sc return sc
} }
func TestGetLDAPStatusApiEndpoint(t *testing.T) { func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
pingResult = []*multildap.ServerStatus{ pingResult = []*multildap.ServerStatus{
{Host: "10.0.0.3", Port: 361, Available: true, Error: nil}, {Host: "10.0.0.3", Port: 361, Available: true, Error: nil},
{Host: "10.0.0.3", Port: 362, Available: true, Error: nil}, {Host: "10.0.0.3", Port: 362, Available: true, Error: nil},
@@ -375,16 +375,21 @@ func TestGetLDAPStatusApiEndpoint(t *testing.T) {
// PostSyncUserWithLDAP tests // PostSyncUserWithLDAP tests
// *** // ***
func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioContext { func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(t *testing.T)) *scenarioContext {
t.Helper() t.Helper()
sc := setupScenarioContext(requestURL) sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled ldap := setting.LDAPEnabled
t.Cleanup(func() {
setting.LDAPEnabled = ldap
})
setting.LDAPEnabled = true setting.LDAPEnabled = true
defer func() { setting.LDAPEnabled = ldap }()
hs := &HTTPServer{Cfg: setting.NewCfg(), AuthTokenService: auth.NewFakeUserAuthTokenService()} hs := &HTTPServer{
Cfg: setting.NewCfg(),
AuthTokenService: auth.NewFakeUserAuthTokenService(),
}
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
@@ -394,7 +399,11 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioConte
sc.m.Post("/api/admin/ldap/sync/:id", sc.defaultHandler) sc.m.Post("/api/admin/ldap/sync/:id", sc.defaultHandler)
sc.resp = httptest.NewRecorder() sc.resp = httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, requestURL, nil) req, err := http.NewRequest(http.MethodPost, requestURL, nil)
require.NoError(t, err)
preHook(t)
sc.req = req sc.req = req
sc.exec() sc.exec()
@@ -402,39 +411,39 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioConte
} }
func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) { func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) { sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
return &ldap.Config{}, nil getLDAPConfig = func() (*ldap.Config, error) {
} return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP { newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{} return &LDAPMock{}
} }
userSearchResult = &models.ExternalUserInfo{ userSearchResult = &models.ExternalUserInfo{
Login: "ldap-daniel", Login: "ldap-daniel",
} }
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error { bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login) require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
return nil return nil
})
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34))
q.Result = &models.User{Login: "ldap-daniel", Id: 34}
return nil
})
bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
require.Equal(t, q.UserId, int64(34))
require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
return nil
})
}) })
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34))
q.Result = &models.User{Login: "ldap-daniel", Id: 34}
return nil
})
bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
require.Equal(t, q.UserId, int64(34))
require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
assert.Equal(t, http.StatusOK, sc.resp.Code) assert.Equal(t, http.StatusOK, sc.resp.Code)
expected := ` expected := `
@@ -447,22 +456,22 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
} }
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) { func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) { sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
return &ldap.Config{}, nil getLDAPConfig = func() (*ldap.Config, error) {
} return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP { newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{} return &LDAPMock{}
} }
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error { bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34)) require.Equal(t, q.Id, int64(34))
return models.ErrUserNotFound return models.ErrUserNotFound
})
}) })
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
assert.Equal(t, http.StatusNotFound, sc.resp.Code) assert.Equal(t, http.StatusNotFound, sc.resp.Code)
expected := ` expected := `
@@ -475,36 +484,36 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
} }
func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) { func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) { sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
return &ldap.Config{}, nil getLDAPConfig = func() (*ldap.Config, error) {
} return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP { newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{} return &LDAPMock{}
} }
userSearchError = multildap.ErrDidNotFindUser userSearchError = multildap.ErrDidNotFindUser
admin := setting.AdminUser admin := setting.AdminUser
setting.AdminUser = "ldap-daniel" t.Cleanup(func() { setting.AdminUser = admin })
defer func() { setting.AdminUser = admin }() setting.AdminUser = "ldap-daniel"
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error { bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34)) require.Equal(t, q.Id, int64(34))
q.Result = &models.User{Login: "ldap-daniel", Id: 34} q.Result = &models.User{Login: "ldap-daniel", Id: 34}
return nil return nil
})
bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
require.Equal(t, q.UserId, int64(34))
require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
return nil
})
}) })
bus.AddHandler("test", func(q *models.GetAuthInfoQuery) error {
require.Equal(t, q.UserId, int64(34))
require.Equal(t, q.AuthModule, models.AuthModuleLDAP)
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
assert.Equal(t, http.StatusBadRequest, sc.resp.Code) assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
expected := ` expected := `
@@ -518,42 +527,42 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
} }
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) { func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) { sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
return &ldap.Config{}, nil getLDAPConfig = func() (*ldap.Config, error) {
} return &ldap.Config{}, nil
}
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP { newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
return &LDAPMock{} return &LDAPMock{}
} }
userSearchResult = nil userSearchResult = nil
bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error { bus.AddHandler("test", func(cmd *models.UpsertUserCommand) error {
require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login) require.Equal(t, "ldap-daniel", cmd.ExternalUser.Login)
return nil return nil
})
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34))
q.Result = &models.User{Login: "ldap-daniel", Id: 34}
return nil
})
bus.AddHandler("test", func(q *models.GetExternalUserInfoByLoginQuery) error {
assert.Equal(t, "ldap-daniel", q.LoginOrEmail)
q.Result = &models.ExternalUserInfo{IsDisabled: true, UserId: 34}
return nil
})
bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
assert.Equal(t, 34, cmd.UserId)
return nil
})
}) })
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34))
q.Result = &models.User{Login: "ldap-daniel", Id: 34}
return nil
})
bus.AddHandler("test", func(q *models.GetExternalUserInfoByLoginQuery) error {
assert.Equal(t, "ldap-daniel", q.LoginOrEmail)
q.Result = &models.ExternalUserInfo{IsDisabled: true, UserId: 34}
return nil
})
bus.AddHandler("test", func(cmd *models.DisableUserCommand) error {
assert.Equal(t, 34, cmd.UserId)
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
assert.Equal(t, http.StatusBadRequest, sc.resp.Code) assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
expected := ` expected := `

View File

@@ -53,7 +53,7 @@ func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
// when using a subUrl, the redirect_to should start with the subUrl (which contains the leading slash), otherwise the redirect // when using a subUrl, the redirect_to should start with the subUrl (which contains the leading slash), otherwise the redirect
// will send the user to the wrong location // will send the user to the wrong location
if hs.Cfg.AppSubUrl != "" && !strings.HasPrefix(to.Path, hs.Cfg.AppSubUrl+"/") { if hs.Cfg.AppSubURL != "" && !strings.HasPrefix(to.Path, hs.Cfg.AppSubURL+"/") {
return login.ErrInvalidRedirectTo return login.ErrInvalidRedirectTo
} }
@@ -62,8 +62,8 @@ func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
func (hs *HTTPServer) CookieOptionsFromCfg() middleware.CookieOptions { func (hs *HTTPServer) CookieOptionsFromCfg() middleware.CookieOptions {
path := "/" path := "/"
if len(hs.Cfg.AppSubUrl) > 0 { if len(hs.Cfg.AppSubURL) > 0 {
path = hs.Cfg.AppSubUrl path = hs.Cfg.AppSubURL
} }
return middleware.CookieOptions{ return middleware.CookieOptions{
Path: path, Path: path,
@@ -126,7 +126,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
// the user is already logged so instead of rendering the login page with error // the user is already logged so instead of rendering the login page with error
// it should be redirected to the home page. // it should be redirected to the home page.
log.Debugf("Ignored invalid redirect_to cookie value: %v", redirectTo) log.Debugf("Ignored invalid redirect_to cookie value: %v", redirectTo)
redirectTo = hs.Cfg.AppSubUrl + "/" redirectTo = hs.Cfg.AppSubURL + "/"
} }
middleware.DeleteCookie(c.Resp, "redirect_to", hs.CookieOptionsFromCfg) middleware.DeleteCookie(c.Resp, "redirect_to", hs.CookieOptionsFromCfg)
c.Redirect(redirectTo) c.Redirect(redirectTo)

View File

@@ -84,7 +84,7 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) {
mockViewIndex() mockViewIndex()
defer resetViewIndex() defer resetViewIndex()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
@@ -138,7 +138,7 @@ func TestLoginViewRedirect(t *testing.T) {
mockViewIndex() mockViewIndex()
defer resetViewIndex() defer resetViewIndex()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
@@ -254,12 +254,12 @@ func TestLoginViewRedirect(t *testing.T) {
} }
for _, c := range redirectCases { for _, c := range redirectCases {
hs.Cfg.AppUrl = c.appURL hs.Cfg.AppURL = c.appURL
hs.Cfg.AppSubUrl = c.appSubURL hs.Cfg.AppSubURL = c.appSubURL
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
expCookiePath := "/" expCookiePath := "/"
if len(hs.Cfg.AppSubUrl) > 0 { if len(hs.Cfg.AppSubURL) > 0 {
expCookiePath = hs.Cfg.AppSubUrl expCookiePath = hs.Cfg.AppSubURL
} }
cookie := http.Cookie{ cookie := http.Cookie{
Name: "redirect_to", Name: "redirect_to",
@@ -314,7 +314,7 @@ func TestLoginPostRedirect(t *testing.T) {
mockViewIndex() mockViewIndex()
defer resetViewIndex() defer resetViewIndex()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
log: &FakeLogger{}, log: &FakeLogger{},
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
@@ -423,12 +423,12 @@ func TestLoginPostRedirect(t *testing.T) {
} }
for _, c := range redirectCases { for _, c := range redirectCases {
hs.Cfg.AppUrl = c.appURL hs.Cfg.AppURL = c.appURL
hs.Cfg.AppSubUrl = c.appSubURL hs.Cfg.AppSubURL = c.appSubURL
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
expCookiePath := "/" expCookiePath := "/"
if len(hs.Cfg.AppSubUrl) > 0 { if len(hs.Cfg.AppSubURL) > 0 {
expCookiePath = hs.Cfg.AppSubUrl expCookiePath = hs.Cfg.AppSubURL
} }
cookie := http.Cookie{ cookie := http.Cookie{
Name: "redirect_to", Name: "redirect_to",
@@ -472,7 +472,7 @@ func TestLoginOAuthRedirect(t *testing.T) {
mockSetIndexViewData() mockSetIndexViewData()
defer resetSetIndexViewData() defer resetSetIndexViewData()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
@@ -507,7 +507,7 @@ func TestLoginInternal(t *testing.T) {
mockViewIndex() mockViewIndex()
defer resetViewIndex() defer resetViewIndex()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
@@ -537,7 +537,7 @@ func TestLoginInternal(t *testing.T) {
} }
func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) { func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) {
sc := setupAuthProxyLoginTest(false) sc := setupAuthProxyLoginTest(t, false)
assert.Equal(t, sc.resp.Code, 302) assert.Equal(t, sc.resp.Code, 302)
location, ok := sc.resp.Header()["Location"] location, ok := sc.resp.Header()["Location"]
@@ -549,7 +549,7 @@ func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) {
} }
func TestAuthProxyLoginWithEnableLoginToken(t *testing.T) { func TestAuthProxyLoginWithEnableLoginToken(t *testing.T) {
sc := setupAuthProxyLoginTest(true) sc := setupAuthProxyLoginTest(t, true)
assert.Equal(t, sc.resp.Code, 302) assert.Equal(t, sc.resp.Code, 302)
location, ok := sc.resp.Header()["Location"] location, ok := sc.resp.Header()["Location"]
@@ -561,11 +561,11 @@ func TestAuthProxyLoginWithEnableLoginToken(t *testing.T) {
assert.Equal(t, "grafana_session=; Path=/; Max-Age=0; HttpOnly", setCookie[0]) assert.Equal(t, "grafana_session=; Path=/; Max-Age=0; HttpOnly", setCookie[0])
} }
func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext { func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioContext {
mockSetIndexViewData() mockSetIndexViewData()
defer resetSetIndexViewData() defer resetSetIndexViewData()
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{}, License: &licensing.OSSLicensingService{},
@@ -601,7 +601,7 @@ func (r *loginHookTest) LoginHook(loginInfo *models.LoginInfo, req *models.ReqCo
} }
func TestLoginPostRunLokingHook(t *testing.T) { func TestLoginPostRunLokingHook(t *testing.T) {
sc := setupScenarioContext("/login") sc := setupScenarioContext(t, "/login")
hookService := &hooks.HooksService{} hookService := &hooks.HooksService{}
hs := &HTTPServer{ hs := &HTTPServer{
log: log.New("test"), log: log.New("test"),

View File

@@ -13,7 +13,7 @@ import (
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
) )
// ApplyRoute should use the plugin route data to set auth headers and custom headers // ApplyRoute should use the plugin route data to set auth headers and custom headers.
func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute, ds *models.DataSource) { func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute, ds *models.DataSource) {
proxyPath = strings.TrimPrefix(proxyPath, route.Path) proxyPath = strings.TrimPrefix(proxyPath, route.Path)

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
macaron "gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
@@ -12,9 +13,8 @@ import (
"net/http" "net/http"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
macaron "gopkg.in/macaron.v1" "github.com/stretchr/testify/require"
) )
type testLogger struct { type testLogger struct {
@@ -28,8 +28,8 @@ func (stub *testLogger) Warn(testMessage string, ctx ...interface{}) {
stub.warnMessage = testMessage stub.warnMessage = testMessage
} }
func TestTeamApiEndpoint(t *testing.T) { func TestTeamAPIEndpoint(t *testing.T) {
Convey("Given two teams", t, func() { t.Run("Given two teams", func(t *testing.T) {
mockResult := models.SearchTeamQueryResult{ mockResult := models.SearchTeamQueryResult{
Teams: []*models.TeamDTO{ Teams: []*models.TeamDTO{
{Name: "team1"}, {Name: "team1"},
@@ -42,56 +42,52 @@ func TestTeamApiEndpoint(t *testing.T) {
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
} }
Convey("When searching with no parameters", func() { loggedInUserScenario(t, "When calling GET on", "/api/teams/search", func(sc *scenarioContext) {
loggedInUserScenario("When calling GET on", "/api/teams/search", func(sc *scenarioContext) { var sentLimit int
var sentLimit int var sendPage int
var sendPage int bus.AddHandler("test", func(query *models.SearchTeamsQuery) error {
bus.AddHandler("test", func(query *models.SearchTeamsQuery) error { query.Result = mockResult
query.Result = mockResult
sentLimit = query.Limit sentLimit = query.Limit
sendPage = query.Page sendPage = query.Page
return nil return nil
})
sc.handlerFunc = hs.SearchTeams
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sentLimit, ShouldEqual, 1000)
So(sendPage, ShouldEqual, 1)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(respJSON.Get("totalCount").MustInt(), ShouldEqual, 2)
So(len(respJSON.Get("teams").MustArray()), ShouldEqual, 2)
}) })
sc.handlerFunc = hs.SearchTeams
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
assert.Equal(t, 1000, sentLimit)
assert.Equal(t, 1, sendPage)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 2, respJSON.Get("totalCount").MustInt())
assert.Equal(t, 2, len(respJSON.Get("teams").MustArray()))
}) })
Convey("When searching with page and perpage parameters", func() { loggedInUserScenario(t, "When calling GET on", "/api/teams/search", func(sc *scenarioContext) {
loggedInUserScenario("When calling GET on", "/api/teams/search", func(sc *scenarioContext) { var sentLimit int
var sentLimit int var sendPage int
var sendPage int bus.AddHandler("test", func(query *models.SearchTeamsQuery) error {
bus.AddHandler("test", func(query *models.SearchTeamsQuery) error { query.Result = mockResult
query.Result = mockResult
sentLimit = query.Limit sentLimit = query.Limit
sendPage = query.Page sendPage = query.Page
return nil return nil
})
sc.handlerFunc = hs.SearchTeams
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
So(sentLimit, ShouldEqual, 10)
So(sendPage, ShouldEqual, 2)
}) })
sc.handlerFunc = hs.SearchTeams
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
assert.Equal(t, 10, sentLimit)
assert.Equal(t, 2, sendPage)
}) })
}) })
t.Run("When creating team with api key", func(t *testing.T) { t.Run("When creating team with API key", func(t *testing.T) {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
hs := &HTTPServer{ hs := &HTTPServer{

View File

@@ -10,50 +10,49 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestUserApiEndpoint(t *testing.T) { func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
Convey("Given a user is logged in", t, func() { mockResult := models.SearchUserQueryResult{
mockResult := models.SearchUserQueryResult{ Users: []*models.UserSearchHitDTO{
Users: []*models.UserSearchHitDTO{ {Name: "user1"},
{Name: "user1"}, {Name: "user2"},
{Name: "user2"}, },
}, TotalCount: 2,
TotalCount: 2, }
}
loggedInUserScenario("When calling GET on", "api/users/:id", func(sc *scenarioContext) { loggedInUserScenario(t, "When calling GET on", "api/users/:id", func(sc *scenarioContext) {
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC) fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC)
bus.AddHandler("test", func(query *models.GetUserProfileQuery) error { bus.AddHandler("test", func(query *models.GetUserProfileQuery) error {
query.Result = models.UserProfileDTO{ query.Result = models.UserProfileDTO{
Id: int64(1), Id: int64(1),
Email: "daniel@grafana.com", Email: "daniel@grafana.com",
Name: "Daniel", Name: "Daniel",
Login: "danlee", Login: "danlee",
OrgId: int64(2), OrgId: int64(2),
IsGrafanaAdmin: true, IsGrafanaAdmin: true,
IsDisabled: false, IsDisabled: false,
IsExternal: false, IsExternal: false,
UpdatedAt: fakeNow, UpdatedAt: fakeNow,
CreatedAt: fakeNow, CreatedAt: fakeNow,
} }
return nil return nil
}) })
bus.AddHandler("test", func(query *models.GetAuthInfoQuery) error { bus.AddHandler("test", func(query *models.GetAuthInfoQuery) error {
query.Result = &models.UserAuth{ query.Result = &models.UserAuth{
AuthModule: models.AuthModuleLDAP, AuthModule: models.AuthModuleLDAP,
} }
return nil return nil
}) })
sc.handlerFunc = GetUserByID sc.handlerFunc = GetUserByID
avatarUrl := dtos.GetGravatarUrl("daniel@grafana.com") avatarUrl := dtos.GetGravatarUrl("daniel@grafana.com")
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
expected := fmt.Sprintf(` expected := fmt.Sprintf(`
{ {
"id": 1, "id": 1,
"email": "daniel@grafana.com", "email": "daniel@grafana.com",
@@ -73,35 +72,35 @@ func TestUserApiEndpoint(t *testing.T) {
} }
`, avatarUrl) `, avatarUrl)
require.Equal(t, http.StatusOK, sc.resp.Code) require.Equal(t, http.StatusOK, sc.resp.Code)
require.JSONEq(t, expected, sc.resp.Body.String()) require.JSONEq(t, expected, sc.resp.Body.String())
})
loggedInUserScenario(t, "When calling GET on", "/api/users/lookup", func(sc *scenarioContext) {
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC)
bus.AddHandler("test", func(query *models.GetUserByLoginQuery) error {
require.Equal(t, "danlee", query.LoginOrEmail)
query.Result = &models.User{
Id: int64(1),
Email: "daniel@grafana.com",
Name: "Daniel",
Login: "danlee",
Theme: "light",
IsAdmin: true,
OrgId: int64(2),
IsDisabled: false,
Updated: fakeNow,
Created: fakeNow,
}
return nil
}) })
loggedInUserScenario("When calling GET on", "/api/users/lookup", func(sc *scenarioContext) { sc.handlerFunc = GetUserByLoginOrEmail
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC) sc.fakeReqWithParams("GET", sc.url, map[string]string{"loginOrEmail": "danlee"}).exec()
bus.AddHandler("test", func(query *models.GetUserByLoginQuery) error {
require.Equal(t, "danlee", query.LoginOrEmail)
query.Result = &models.User{ expected := `
Id: int64(1),
Email: "daniel@grafana.com",
Name: "Daniel",
Login: "danlee",
Theme: "light",
IsAdmin: true,
OrgId: int64(2),
IsDisabled: false,
Updated: fakeNow,
Created: fakeNow,
}
return nil
})
sc.handlerFunc = GetUserByLoginOrEmail
sc.fakeReqWithParams("GET", sc.url, map[string]string{"loginOrEmail": "danlee"}).exec()
expected := `
{ {
"id": 1, "id": 1,
"email": "daniel@grafana.com", "email": "daniel@grafana.com",
@@ -119,94 +118,93 @@ func TestUserApiEndpoint(t *testing.T) {
} }
` `
require.Equal(t, http.StatusOK, sc.resp.Code) require.Equal(t, http.StatusOK, sc.resp.Code)
require.JSONEq(t, expected, sc.resp.Body.String()) require.JSONEq(t, expected, sc.resp.Body.String())
})
loggedInUserScenario(t, "When calling GET on", "/api/users", func(sc *scenarioContext) {
var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sentLimit = query.Limit
sendPage = query.Page
return nil
}) })
loggedInUserScenario("When calling GET on", "/api/users", func(sc *scenarioContext) { sc.handlerFunc = SearchUsers
var sentLimit int sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sentLimit = query.Limit assert.Equal(t, 1000, sentLimit)
sendPage = query.Page assert.Equal(t, 1, sendPage)
return nil respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
}) require.NoError(t, err)
assert.Equal(t, 2, len(respJSON.MustArray()))
})
sc.handlerFunc = SearchUsers loggedInUserScenario(t, "When calling GET with page and limit querystring parameters on", "/api/users", func(sc *scenarioContext) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
So(sentLimit, ShouldEqual, 1000) sentLimit = query.Limit
So(sendPage, ShouldEqual, 1) sendPage = query.Page
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) return nil
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 2)
}) })
loggedInUserScenario("When calling GET with page and limit querystring parameters on", "/api/users", func(sc *scenarioContext) { sc.handlerFunc = SearchUsers
var sentLimit int sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sentLimit = query.Limit assert.Equal(t, 10, sentLimit)
sendPage = query.Page assert.Equal(t, 2, sendPage)
})
return nil loggedInUserScenario(t, "When calling GET on", "/api/users/search", func(sc *scenarioContext) {
}) var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sc.handlerFunc = SearchUsers sentLimit = query.Limit
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec() sendPage = query.Page
So(sentLimit, ShouldEqual, 10) return nil
So(sendPage, ShouldEqual, 2)
}) })
loggedInUserScenario("When calling GET on", "/api/users/search", func(sc *scenarioContext) { sc.handlerFunc = SearchUsersWithPaging
var sentLimit int sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sentLimit = query.Limit assert.Equal(t, 1000, sentLimit)
sendPage = query.Page assert.Equal(t, 1, sendPage)
return nil respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
}) require.NoError(t, err)
sc.handlerFunc = SearchUsersWithPaging assert.Equal(t, 2, respJSON.Get("totalCount").MustInt())
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() assert.Equal(t, 2, len(respJSON.Get("users").MustArray()))
})
So(sentLimit, ShouldEqual, 1000) loggedInUserScenario(t, "When calling GET with page and perpage querystring parameters on", "/api/users/search", func(sc *scenarioContext) {
So(sendPage, ShouldEqual, 1) var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes()) sentLimit = query.Limit
So(err, ShouldBeNil) sendPage = query.Page
So(respJSON.Get("totalCount").MustInt(), ShouldEqual, 2) return nil
So(len(respJSON.Get("users").MustArray()), ShouldEqual, 2)
}) })
loggedInUserScenario("When calling GET with page and perpage querystring parameters on", "/api/users/search", func(sc *scenarioContext) { sc.handlerFunc = SearchUsersWithPaging
var sentLimit int sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
query.Result = mockResult
sentLimit = query.Limit assert.Equal(t, 10, sentLimit)
sendPage = query.Page assert.Equal(t, 2, sendPage)
return nil
})
sc.handlerFunc = SearchUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
So(sentLimit, ShouldEqual, 10)
So(sendPage, ShouldEqual, 2)
})
}) })
} }

View File

@@ -2,115 +2,117 @@ package api
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/stretchr/testify/assert"
. "github.com/smartystreets/goconvey/convey"
) )
func TestUserTokenApiEndpoint(t *testing.T) { func TestUserTokenAPIEndpoint(t *testing.T) {
Convey("When current user attempts to revoke an auth token for a non-existing user", t, func() { t.Run("When current user attempts to revoke an auth token for a non-existing user", func(t *testing.T) {
userId := int64(0)
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
userId = cmd.Id
return models.ErrUserNotFound
})
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
revokeUserAuthTokenScenario("Should return not found when calling POST on", "/api/user/revoke-auth-token", "/api/user/revoke-auth-token", cmd, 200, func(sc *scenarioContext) { revokeUserAuthTokenScenario(t, "Should return not found when calling POST on", "/api/user/revoke-auth-token",
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() "/api/user/revoke-auth-token", cmd, 200, func(sc *scenarioContext) {
So(sc.resp.Code, ShouldEqual, 404) var userID int64
So(userId, ShouldEqual, 200) bus.AddHandler("test", func(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)
})
}) })
Convey("When current user gets auth tokens for a non-existing user", t, func() { t.Run("When current user gets auth tokens for a non-existing user", func(t *testing.T) {
userId := int64(0) getUserAuthTokensScenario(t, "Should return not found when calling GET on", "/api/user/auth-tokens", "/api/user/auth-tokens", 200, func(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error { var userID int64
userId = cmd.Id bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
return models.ErrUserNotFound userID = cmd.Id
}) return models.ErrUserNotFound
})
getUserAuthTokensScenario("Should return not found when calling GET on", "/api/user/auth-tokens", "/api/user/auth-tokens", 200, func(sc *scenarioContext) {
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404) assert.Equal(t, 404, sc.resp.Code)
So(userId, ShouldEqual, 200) assert.Equal(t, int64(200), userID)
}) })
}) })
Convey("When logout an existing user from all devices", t, func() { t.Run("When logging out an existing user from all devices", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error { logoutUserFromAllDevicesInternalScenario(t, "Should be successful", 1, func(sc *scenarioContext) {
cmd.Result = &models.User{Id: 200} const userID int64 = 200
return nil bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
}) cmd.Result = &models.User{Id: userID}
return nil
})
logoutUserFromAllDevicesInternalScenario("Should be successful", 1, func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
}) })
Convey("When logout a non-existing user from all devices", t, func() { t.Run("When logout a non-existing user from all devices", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error { logoutUserFromAllDevicesInternalScenario(t, "Should return not found", testUserID, func(sc *scenarioContext) {
return models.ErrUserNotFound bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
}) return models.ErrUserNotFound
})
logoutUserFromAllDevicesInternalScenario("Should return not found", TestUserID, func(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 404) assert.Equal(t, 404, sc.resp.Code)
}) })
}) })
Convey("When revoke an auth token for a user", t, func() { t.Run("When revoke an auth token for a user", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: 200}
return nil
})
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
token := &models.UserToken{Id: 1} token := &models.UserToken{Id: 1}
revokeUserAuthTokenInternalScenario("Should be successful", cmd, 200, token, func(sc *scenarioContext) { revokeUserAuthTokenInternalScenario(t, "Should be successful", cmd, 200, token, func(sc *scenarioContext) {
bus.AddHandler("test", func(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) { sc.userAuthTokenService.GetUserTokenProvider = func(ctx context.Context, userId, userTokenId int64) (*models.UserToken, error) {
return &models.UserToken{Id: 2}, nil return &models.UserToken{Id: 2}, nil
} }
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
}) })
}) })
Convey("When revoke the active auth token used by himself", t, func() { t.Run("When revoke the active auth token used by himself", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: TestUserID}
return nil
})
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2} cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
token := &models.UserToken{Id: 2} token := &models.UserToken{Id: 2}
revokeUserAuthTokenInternalScenario("Should not be successful", cmd, TestUserID, token, func(sc *scenarioContext) { revokeUserAuthTokenInternalScenario(t, "Should not be successful", cmd, testUserID, token, func(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: testUserID}
return nil
})
sc.userAuthTokenService.GetUserTokenProvider = func(ctx context.Context, userId, userTokenId int64) (*models.UserToken, error) { sc.userAuthTokenService.GetUserTokenProvider = func(ctx context.Context, userId, userTokenId int64) (*models.UserToken, error) {
return token, nil return token, nil
} }
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 400) assert.Equal(t, 400, sc.resp.Code)
}) })
}) })
Convey("When gets auth tokens for a user", t, func() { t.Run("When gets auth tokens for a user", func(t *testing.T) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: TestUserID}
return nil
})
currentToken := &models.UserToken{Id: 1} currentToken := &models.UserToken{Id: 1}
getUserAuthTokensInternalScenario("Should be successful", currentToken, func(sc *scenarioContext) { getUserAuthTokensInternalScenario(t, "Should be successful", currentToken, func(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.GetUserByIdQuery) error {
cmd.Result = &models.User{Id: testUserID}
return nil
})
tokens := []*models.UserToken{ tokens := []*models.UserToken{
{ {
Id: 1, Id: 1,
@@ -132,42 +134,43 @@ func TestUserTokenApiEndpoint(t *testing.T) {
} }
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200) assert.Equal(t, 200, sc.resp.Code)
result := sc.ToJSON() result := sc.ToJSON()
So(result.MustArray(), ShouldHaveLength, 2) assert.Len(t, result.MustArray(), 2)
resultOne := result.GetIndex(0) resultOne := result.GetIndex(0)
So(resultOne.Get("id").MustInt64(), ShouldEqual, tokens[0].Id) assert.Equal(t, tokens[0].Id, resultOne.Get("id").MustInt64())
So(resultOne.Get("isActive").MustBool(), ShouldBeTrue) assert.True(t, resultOne.Get("isActive").MustBool())
So(resultOne.Get("clientIp").MustString(), ShouldEqual, "127.0.0.1") assert.Equal(t, "127.0.0.1", resultOne.Get("clientIp").MustString())
So(resultOne.Get("createdAt").MustString(), ShouldEqual, time.Unix(tokens[0].CreatedAt, 0).Format(time.RFC3339)) assert.Equal(t, time.Unix(tokens[0].CreatedAt, 0).Format(time.RFC3339), resultOne.Get("createdAt").MustString())
So(resultOne.Get("seenAt").MustString(), ShouldEqual, time.Unix(tokens[0].SeenAt, 0).Format(time.RFC3339)) assert.Equal(t, time.Unix(tokens[0].SeenAt, 0).Format(time.RFC3339), resultOne.Get("seenAt").MustString())
So(resultOne.Get("device").MustString(), ShouldEqual, "Other") assert.Equal(t, "Other", resultOne.Get("device").MustString())
So(resultOne.Get("browser").MustString(), ShouldEqual, "Chrome") assert.Equal(t, "Chrome", resultOne.Get("browser").MustString())
So(resultOne.Get("browserVersion").MustString(), ShouldEqual, "72.0") assert.Equal(t, "72.0", resultOne.Get("browserVersion").MustString())
So(resultOne.Get("os").MustString(), ShouldEqual, "Linux") assert.Equal(t, "Linux", resultOne.Get("os").MustString())
So(resultOne.Get("osVersion").MustString(), ShouldEqual, "") assert.Empty(t, resultOne.Get("osVersion").MustString())
resultTwo := result.GetIndex(1) resultTwo := result.GetIndex(1)
So(resultTwo.Get("id").MustInt64(), ShouldEqual, tokens[1].Id) assert.Equal(t, tokens[1].Id, resultTwo.Get("id").MustInt64())
So(resultTwo.Get("isActive").MustBool(), ShouldBeFalse) assert.False(t, resultTwo.Get("isActive").MustBool())
So(resultTwo.Get("clientIp").MustString(), ShouldEqual, "127.0.0.2") assert.Equal(t, "127.0.0.2", resultTwo.Get("clientIp").MustString())
So(resultTwo.Get("createdAt").MustString(), ShouldEqual, time.Unix(tokens[1].CreatedAt, 0).Format(time.RFC3339)) assert.Equal(t, time.Unix(tokens[1].CreatedAt, 0).Format(time.RFC3339), resultTwo.Get("createdAt").MustString())
So(resultTwo.Get("seenAt").MustString(), ShouldEqual, time.Unix(tokens[1].CreatedAt, 0).Format(time.RFC3339)) assert.Equal(t, time.Unix(tokens[1].CreatedAt, 0).Format(time.RFC3339), resultTwo.Get("seenAt").MustString())
So(resultTwo.Get("device").MustString(), ShouldEqual, "iPhone") assert.Equal(t, "iPhone", resultTwo.Get("device").MustString())
So(resultTwo.Get("browser").MustString(), ShouldEqual, "Mobile Safari") assert.Equal(t, "Mobile Safari", resultTwo.Get("browser").MustString())
So(resultTwo.Get("browserVersion").MustString(), ShouldEqual, "11.0") assert.Equal(t, "11.0", resultTwo.Get("browserVersion").MustString())
So(resultTwo.Get("os").MustString(), ShouldEqual, "iOS") assert.Equal(t, "iOS", resultTwo.Get("os").MustString())
So(resultTwo.Get("osVersion").MustString(), ShouldEqual, "11.0") assert.Equal(t, "11.0", resultTwo.Get("osVersion").MustString())
}) })
}) })
} }
func revokeUserAuthTokenScenario(desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, userId int64, fn scenarioFunc) { func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd,
Convey(desc+" "+url, func() { userId int64, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -176,12 +179,12 @@ func revokeUserAuthTokenScenario(desc string, url string, routePattern string, c
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = userId sc.context.UserId = userId
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.RevokeUserAuthToken(c, cmd) return hs.RevokeUserAuthToken(c, cmd)
@@ -193,9 +196,9 @@ func revokeUserAuthTokenScenario(desc string, url string, routePattern string, c
}) })
} }
func getUserAuthTokensScenario(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) {
Convey(desc+" "+url, func() { t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -204,12 +207,12 @@ func getUserAuthTokensScenario(desc string, url string, routePattern string, use
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext(url) sc := setupScenarioContext(t, url)
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = userId sc.context.UserId = userId
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.GetUserAuthTokens(c) return hs.GetUserAuthTokens(c)
@@ -221,20 +224,20 @@ func getUserAuthTokensScenario(desc string, url string, routePattern string, use
}) })
} }
func logoutUserFromAllDevicesInternalScenario(desc string, userId int64, fn scenarioFunc) { func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId int64, fn scenarioFunc) {
Convey(desc, func() { t.Run(desc, func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{ hs := HTTPServer{
Bus: bus.GetBus(), Bus: bus.GetBus(),
AuthTokenService: auth.NewFakeUserAuthTokenService(), AuthTokenService: auth.NewFakeUserAuthTokenService(),
} }
sc := setupScenarioContext("/") sc := setupScenarioContext(t, "/")
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
return hs.logoutUserFromAllDevicesInternal(context.Background(), userId) return hs.logoutUserFromAllDevicesInternal(context.Background(), userId)
@@ -246,9 +249,10 @@ func logoutUserFromAllDevicesInternalScenario(desc string, userId int64, fn scen
}) })
} }
func revokeUserAuthTokenInternalScenario(desc string, cmd models.RevokeAuthTokenCmd, userId int64, token *models.UserToken, fn scenarioFunc) { func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.RevokeAuthTokenCmd, userId int64,
Convey(desc, func() { token *models.UserToken, fn scenarioFunc) {
defer bus.ClearBusHandlers() t.Run(desc, func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -257,12 +261,12 @@ func revokeUserAuthTokenInternalScenario(desc string, cmd models.RevokeAuthToken
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext("/") sc := setupScenarioContext(t, "/")
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
sc.context.UserToken = token sc.context.UserToken = token
@@ -275,9 +279,9 @@ func revokeUserAuthTokenInternalScenario(desc string, cmd models.RevokeAuthToken
}) })
} }
func getUserAuthTokensInternalScenario(desc string, token *models.UserToken, fn scenarioFunc) { func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models.UserToken, fn scenarioFunc) {
Convey(desc, func() { t.Run(desc, func(t *testing.T) {
defer bus.ClearBusHandlers() t.Cleanup(bus.ClearBusHandlers)
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
@@ -286,16 +290,16 @@ func getUserAuthTokensInternalScenario(desc string, token *models.UserToken, fn
AuthTokenService: fakeAuthTokenService, AuthTokenService: fakeAuthTokenService,
} }
sc := setupScenarioContext("/") sc := setupScenarioContext(t, "/")
sc.userAuthTokenService = fakeAuthTokenService sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c sc.context = c
sc.context.UserId = TestUserID sc.context.UserId = testUserID
sc.context.OrgId = TestOrgID sc.context.OrgId = testOrgID
sc.context.OrgRole = models.ROLE_ADMIN sc.context.OrgRole = models.ROLE_ADMIN
sc.context.UserToken = token sc.context.UserToken = token
return hs.getUserAuthTokensInternal(c, TestUserID) return hs.getUserAuthTokensInternal(c, testUserID)
}) })
sc.m.Get("/", sc.defaultHandler) sc.m.Get("/", sc.defaultHandler)

View File

@@ -34,7 +34,7 @@ func (*OSSLicensingService) StateInfo() string {
func (l *OSSLicensingService) LicenseURL(user *models.SignedInUser) string { func (l *OSSLicensingService) LicenseURL(user *models.SignedInUser) string {
if user.IsGrafanaAdmin { if user.IsGrafanaAdmin {
return l.Cfg.AppSubUrl + "/admin/upgrading" return l.Cfg.AppSubURL + "/admin/upgrading"
} }
return "https://grafana.com/products/enterprise/?utm_source=grafana_footer" return "https://grafana.com/products/enterprise/?utm_source=grafana_footer"

View File

@@ -235,7 +235,7 @@ func (rs *RenderingService) getURL(path string) string {
subPath := "" subPath := ""
if rs.Cfg.ServeFromSubPath { if rs.Cfg.ServeFromSubPath {
subPath = rs.Cfg.AppSubUrl subPath = rs.Cfg.AppSubURL
} }
// &render=1 signals to the legacy redirect layer to // &render=1 signals to the legacy redirect layer to

View File

@@ -27,14 +27,14 @@ func TestGetUrl(t *testing.T) {
t.Run("And protocol HTTP configured should return expected path", func(t *testing.T) { t.Run("And protocol HTTP configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = "" rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTPScheme setting.Protocol = setting.HTTPScheme
url := rs.getURL(path) url := rs.getURL(path)
require.Equal(t, "http://localhost:3000/"+path+"&render=1", url) require.Equal(t, "http://localhost:3000/"+path+"&render=1", url)
t.Run("And serve from sub path should return expected path", func(t *testing.T) { t.Run("And serve from sub path should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = true rs.Cfg.ServeFromSubPath = true
rs.Cfg.AppSubUrl = "/grafana" rs.Cfg.AppSubURL = "/grafana"
url := rs.getURL(path) url := rs.getURL(path)
require.Equal(t, "http://localhost:3000/grafana/"+path+"&render=1", url) require.Equal(t, "http://localhost:3000/grafana/"+path+"&render=1", url)
}) })
@@ -42,7 +42,7 @@ func TestGetUrl(t *testing.T) {
t.Run("And protocol HTTPS configured should return expected path", func(t *testing.T) { t.Run("And protocol HTTPS configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = "" rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTPSScheme setting.Protocol = setting.HTTPSScheme
url := rs.getURL(path) url := rs.getURL(path)
require.Equal(t, "https://localhost:3000/"+path+"&render=1", url) require.Equal(t, "https://localhost:3000/"+path+"&render=1", url)
@@ -50,7 +50,7 @@ func TestGetUrl(t *testing.T) {
t.Run("And protocol HTTP2 configured should return expected path", func(t *testing.T) { t.Run("And protocol HTTP2 configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = "" rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTP2Scheme setting.Protocol = setting.HTTP2Scheme
url := rs.getURL(path) url := rs.getURL(path)
require.Equal(t, "https://localhost:3000/"+path+"&render=1", url) require.Equal(t, "https://localhost:3000/"+path+"&render=1", url)

View File

@@ -78,7 +78,7 @@ var (
// Log settings. // Log settings.
LogConfigs []util.DynMap LogConfigs []util.DynMap
// Http server options // HTTP server options
Protocol Scheme Protocol Scheme
Domain string Domain string
HttpAddr, HttpPort string HttpAddr, HttpPort string
@@ -193,7 +193,7 @@ var (
LDAPAllowSignup bool LDAPAllowSignup bool
LDAPActiveSyncEnabled bool LDAPActiveSyncEnabled bool
// QUOTA // Quota
Quota QuotaSettings Quota QuotaSettings
// Alerting // Alerting
@@ -228,8 +228,8 @@ type Cfg struct {
Logger log.Logger Logger log.Logger
// HTTP Server Settings // HTTP Server Settings
AppUrl string AppURL string
AppSubUrl string AppSubURL string
ServeFromSubPath bool ServeFromSubPath bool
StaticRootPath string StaticRootPath string
Protocol Scheme Protocol Scheme
@@ -707,7 +707,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
cfg.Raw = iniFile cfg.Raw = iniFile
// Temporary keep global, to make refactor in steps // Temporarily keep global, to make refactor in steps
Raw = cfg.Raw Raw = cfg.Raw
cfg.BuildVersion = BuildVersion cfg.BuildVersion = BuildVersion
@@ -1203,8 +1203,8 @@ func readServerSettings(iniFile *ini.File, cfg *Cfg) error {
} }
ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false) ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false)
cfg.AppUrl = AppUrl cfg.AppURL = AppUrl
cfg.AppSubUrl = AppSubUrl cfg.AppSubURL = AppSubUrl
cfg.ServeFromSubPath = ServeFromSubPath cfg.ServeFromSubPath = ServeFromSubPath
Protocol = HTTPScheme Protocol = HTTPScheme