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>
pull/29101/head
Arve Knudsen 5 years ago committed by GitHub
parent df8f63de7f
commit cb62e69997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 387
      pkg/api/admin_users_test.go
  2. 116
      pkg/api/alerting_test.go
  3. 2
      pkg/api/annotations.go
  4. 188
      pkg/api/annotations_test.go
  5. 26
      pkg/api/common_test.go
  6. 4
      pkg/api/dashboard.go
  7. 125
      pkg/api/dashboard_permission_test.go
  8. 2
      pkg/api/dashboard_snapshot.go
  9. 189
      pkg/api/dashboard_snapshot_test.go
  10. 886
      pkg/api/dashboard_test.go
  11. 49
      pkg/api/datasources_test.go
  12. 116
      pkg/api/folder_permission_test.go
  13. 83
      pkg/api/folder_test.go
  14. 2
      pkg/api/frontend_logging_test.go
  15. 1
      pkg/api/ldap_debug.go
  16. 57
      pkg/api/ldap_debug_test.go
  17. 8
      pkg/api/login.go
  18. 36
      pkg/api/login_test.go
  19. 2
      pkg/api/pluginproxy/ds_auth_provider.go
  20. 366
      pkg/api/pluginproxy/ds_proxy_test.go
  21. 32
      pkg/api/team_test.go
  22. 44
      pkg/api/user_test.go
  23. 194
      pkg/api/user_token_test.go
  24. 2
      pkg/services/licensing/oss.go
  25. 2
      pkg/services/rendering/rendering.go
  26. 8
      pkg/services/rendering/rendering_test.go
  27. 14
      pkg/setting/setting.go

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

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

@ -247,7 +247,6 @@ func DeleteAnnotationByID(c *models.ReqContext) Response {
OrgId: c.OrgId,
Id: annotationID,
})
if err != nil {
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 {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return Error(500, "Could not find annotation to update", err)
}

@ -1,18 +1,18 @@
package api
import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
)
func TestAnnotationsApiEndpoint(t *testing.T) {
Convey("Given an annotation without a dashboard id", t, func() {
func TestAnnotationsAPIEndpoint(t *testing.T) {
t.Run("Given an annotation without a dashboard ID", func(t *testing.T) {
cmd := dtos.PostAnnotationsCmd{
Time: 1000,
Text: "annotation text",
@ -31,60 +31,70 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
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
Convey("Should not be allowed to save an annotation", func() {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
cmd, func(sc *scenarioContext) {
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) {
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) {
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",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
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
Convey("Should be able to save an annotation", func() {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role,
cmd, func(sc *scenarioContext) {
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) {
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()
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",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
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{
Time: 1000,
Text: "annotation text",
@ -120,6 +130,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
setUp := func() {
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
@ -129,80 +140,100 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
query.Result = []*models.TeamDTO{}
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
Convey("Should not be allowed to save an annotation", func() {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
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()
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()
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()
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",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUp()
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
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
Convey("Should be able to save an annotation", func() {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
t.Run("Should be able to save an annotation", func(t *testing.T) {
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()
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()
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()
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",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUp()
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
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
Convey("Should be able to do anything", func() {
postAnnotationScenario("When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
t.Run("Should be able to do anything", func(t *testing.T) {
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()
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()
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()
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",
"/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
setUp()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
assert.Equal(t, 200, sc.resp.Code)
})
})
})
@ -229,15 +260,16 @@ func (repo *fakeAnnotationsRepo) Find(query *annotations.ItemQuery) ([]*annotati
var fakeAnnoRepo *fakeAnnotationsRepo
func postAnnotationScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PostAnnotationsCmd, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
func postAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.PostAnnotationsCmd, fn scenarioFunc) {
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.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
sc.context.OrgRole = role
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) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
func putAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.UpdateAnnotationsCmd, fn scenarioFunc) {
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.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
sc.context.OrgRole = role
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) {
Convey(desc+" "+url, func() {
func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
sc.context.OrgRole = role
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) {
Convey(desc+" "+url, func() {
func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePattern string, role models.RoleType,
cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c
sc.context.UserId = TestUserID
sc.context.OrgId = TestOrgID
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
sc.context.OrgRole = role
return DeleteAnnotations(c, cmd)

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

@ -48,7 +48,9 @@ func dashboardGuardianResponse(err error) 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 {
return rsp
}

@ -1,6 +1,7 @@
package api
import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
@ -8,20 +9,24 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDashboardPermissionApiEndpoint(t *testing.T) {
Convey("Dashboard permissions test", t, func() {
Convey("Given dashboard not exists", func() {
func TestDashboardPermissionAPIEndpoint(t *testing.T) {
t.Run("Dashboard permissions test", func(t *testing.T) {
t.Run("Given dashboard not exists", func(t *testing.T) {
setUp := func() {
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",
"/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
setUp()
callGetDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
assert.Equal(t, 404, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{
@ -30,25 +35,36 @@ 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",
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
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
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
getDashboardQueryResult := models.NewDashboard("Dash")
setUp := func() {
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) {
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)
So(sc.resp.Code, ShouldEqual, 403)
assert.Equal(t, 403, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{
@ -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",
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 403)
assert.Equal(t, 403, sc.resp.Code)
})
})
Reset(func() {
t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
})
Convey("Given user has admin permissions and permissions to update", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true,
@ -81,20 +99,24 @@ func TestDashboardPermissionApiEndpoint(t *testing.T) {
},
})
setUp := func() {
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_ADMIN, func(sc *scenarioContext) {
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
"/api/dashboards/id/:id/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
setUp()
callGetDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
assert.Equal(t, 200, 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)
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())
})
cmd := dtos.UpdateDashboardAclCommand{
@ -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",
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
})
Reset(func() {
guardian.New = origNewGuardian
assert.Equal(t, 200, sc.resp.Code)
})
})
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
t.Cleanup(func() {
guardian.New = origNewGuardian
})
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists,
})
setUp := func() {
getDashboardQueryResult := models.NewDashboard("Dash")
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
}
cmd := dtos.UpdateDashboardAclCommand{
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",
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
assert.Equal(t, 400, sc.resp.Code)
})
})
Reset(func() {
t.Run("When trying to override inherited permissions with lower precedence", func(t *testing.T) {
origNewGuardian := guardian.New
t.Cleanup(func() {
guardian.New = origNewGuardian
})
})
Convey("When trying to override inherited permissions with lower precedence", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride},
)
setUp := func() {
getDashboardQueryResult := models.NewDashboard("Dash")
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
query.Result = getDashboardQueryResult
return nil
})
}
cmd := dtos.UpdateDashboardAclCommand{
Items: []dtos.DashboardAclUpdateItem{
@ -163,13 +192,11 @@ 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",
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
setUp()
callUpdateDashboardPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
})
Reset(func() {
guardian.New = origNewGuardian
assert.Equal(t, 400, sc.resp.Code)
})
})
})
@ -188,16 +215,16 @@ func callUpdateDashboardPermissions(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func updateDashboardPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
func updateDashboardPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
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.context = c
sc.context.OrgId = TestOrgID
sc.context.UserId = TestUserID
sc.context.OrgId = testOrgID
sc.context.UserId = testUserID
return UpdateDashboardPermissions(c, cmd)
})

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

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

File diff suppressed because it is too large Load Diff

@ -6,22 +6,21 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
. "github.com/smartystreets/goconvey/convey"
)
const (
TestOrgID = 1
TestUserID = 1
testOrgID int64 = 1
testUserID int64 = 1
)
func TestDataSourcesProxy(t *testing.T) {
Convey("Given a user is logged in", t, func() {
loggedInUserScenario("When calling GET on", "/api/datasources/", func(sc *scenarioContext) {
func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
loggedInUserScenario(t, "When calling GET on", "/api/datasources/", func(sc *scenarioContext) {
// Stubs the database query
bus.AddHandler("test", func(query *models.GetDataSourcesQuery) error {
So(query.OrgId, ShouldEqual, TestOrgID)
assert.Equal(t, testOrgID, query.OrgId)
query.Result = []*models.DataSource{
{Name: "mmm"},
{Name: "ZZZ"},
@ -37,23 +36,19 @@ func TestDataSourcesProxy(t *testing.T) {
respJSON := []map[string]interface{}{}
err := json.NewDecoder(sc.resp.Body).Decode(&respJSON)
So(err, ShouldBeNil)
require.NoError(t, err)
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")
})
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"])
})
Convey("Should be able to save a data source", func() {
loggedInUserScenario("When calling DELETE on non-existing", "/api/datasources/name/12345", func(sc *scenarioContext) {
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()
So(sc.resp.Code, ShouldEqual, 404)
})
})
assert.Equal(t, 404, sc.resp.Code)
})
}
@ -61,9 +56,7 @@ func TestDataSourcesProxy(t *testing.T) {
func TestAddDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext("/api/datasources")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc := setupScenarioContext(t, "/api/datasources")
sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{
@ -93,9 +86,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
return nil
})
sc := setupScenarioContext("/api/datasources")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc := setupScenarioContext(t, "/api/datasources")
sc.m.Post(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{
@ -113,9 +104,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
func TestUpdateDataSource_InvalidURL(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext("/api/datasources/1234")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc := setupScenarioContext(t, "/api/datasources/1234")
sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{
@ -145,9 +134,7 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
return nil
})
sc := setupScenarioContext("/api/datasources/1234")
// TODO: Make this an argument to setupScenarioContext
sc.t = t
sc := setupScenarioContext(t, "/api/datasources/1234")
sc.m.Put(sc.url, Wrap(func(c *models.ReqContext) Response {
return AddDataSource(c, models.AddDataSourceCommand{

@ -1,6 +1,7 @@
package api
import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
@ -9,23 +10,25 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFolderPermissionApiEndpoint(t *testing.T) {
Convey("Folder permissions test", t, func() {
Convey("Given folder not exists", func() {
func TestFolderPermissionAPIEndpoint(t *testing.T) {
t.Run("Given folder not exists", func(t *testing.T) {
mock := &fakeFolderService{
GetFolderByUIDError: models.ErrFolderNotFound,
}
origNewFolderService := dashboards.NewFolderService
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)
So(sc.resp.Code, ShouldEqual, 404)
assert.Equal(t, 404, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{
@ -34,18 +37,20 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 404)
assert.Equal(t, 404, sc.resp.Code)
})
})
Reset(func() {
t.Run("Given user has no admin permissions", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
})
Convey("Given user has no admin permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
mock := &fakeFolderService{
@ -56,12 +61,11 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
origNewFolderService := dashboards.NewFolderService
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)
So(sc.resp.Code, ShouldEqual, 403)
assert.Equal(t, 403, sc.resp.Code)
})
cmd := dtos.UpdateDashboardAclCommand{
@ -70,19 +74,20 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 403)
assert.Equal(t, 403, sc.resp.Code)
})
})
Reset(func() {
t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
origNewGuardian := guardian.New
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
guardian.New = origNewGuardian
dashboards.NewFolderService = origNewFolderService
})
})
Convey("Given user has admin permissions and permissions to update", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: true,
@ -103,17 +108,16 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
origNewFolderService := dashboards.NewFolderService
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_ADMIN, func(sc *scenarioContext) {
callGetFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
assert.Equal(t, 200, 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)
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())
})
cmd := dtos.UpdateDashboardAclCommand{
@ -122,23 +126,24 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 200)
assert.Equal(t, 200, sc.resp.Code)
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")
require.NoError(t, err)
assert.Equal(t, 1, respJSON.Get("id").MustInt())
assert.Equal(t, "Folder", respJSON.Get("title").MustString())
})
})
Reset(func() {
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
})
})
Convey("When trying to update permissions with duplicate permissions", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
@ -153,7 +158,6 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
@ -162,19 +166,20 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
callUpdateFolderPermissions(sc)
So(sc.resp.Code, ShouldEqual, 400)
assert.Equal(t, 400, sc.resp.Code)
})
})
Reset(func() {
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
})
})
Convey("When trying to override inherited permissions with lower precedence", func() {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
CanAdminValue: true,
CheckPermissionBeforeUpdateValue: false,
@ -189,7 +194,6 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
origNewFolderService := dashboards.NewFolderService
mockFolderService(mock)
cmd := dtos.UpdateDashboardAclCommand{
@ -198,15 +202,9 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
},
}
updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
updateFolderPermissionScenario(t, "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
})
assert.Equal(t, 400, sc.resp.Code)
})
})
}
@ -224,16 +222,16 @@ func callUpdateFolderPermissions(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func updateFolderPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
func updateFolderPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
sc.context = c
sc.context.OrgId = TestOrgID
sc.context.UserId = TestUserID
sc.context.OrgId = testOrgID
sc.context.UserId = testUserID
return UpdateFolderPermissions(c, cmd)
})

@ -10,13 +10,12 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFoldersApiEndpoint(t *testing.T) {
Convey("Create/update folder response tests", t, func() {
Convey("Given a correct request for creating a folder", func() {
func TestFoldersAPIEndpoint(t *testing.T) {
t.Run("Given a correct request for creating a folder", func(t *testing.T) {
cmd := models.CreateFolderCommand{
Uid: "uid",
Title: "Folder",
@ -26,21 +25,20 @@ func TestFoldersApiEndpoint(t *testing.T) {
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)
Convey("It should return correct response data", func() {
folder := dtos.Folder{}
err := json.NewDecoder(sc.resp.Body).Decode(&folder)
So(err, ShouldBeNil)
So(folder.Id, ShouldEqual, 1)
So(folder.Uid, ShouldEqual, "uid")
So(folder.Title, ShouldEqual, "Folder")
})
require.NoError(t, err)
assert.Equal(t, int64(1), folder.Id)
assert.Equal(t, "uid", folder.Uid)
assert.Equal(t, "Folder", folder.Title)
})
})
Convey("Given incorrect requests for creating a folder", func() {
t.Run("Given incorrect requests for creating a folder", func(t *testing.T) {
testCases := []struct {
Error error
ExpectedStatusCode int
@ -66,16 +64,15 @@ func TestFoldersApiEndpoint(t *testing.T) {
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) {
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)
if sc.resp.Code != tc.ExpectedStatusCode {
t.Errorf("For error '%s' expected status code %d, actual %d", tc.Error, tc.ExpectedStatusCode, sc.resp.Code)
}
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for error %s", tc.Error)
})
}
})
Convey("Given a correct request for updating a folder", func() {
t.Run("Given a correct request for updating a folder", func(t *testing.T) {
cmd := models.UpdateFolderCommand{
Title: "Folder upd",
}
@ -84,21 +81,20 @@ func TestFoldersApiEndpoint(t *testing.T) {
UpdateFolderResult: &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"},
}
updateFolderScenario("When calling PUT on", "/api/folders/uid", "/api/folders/:uid", mock, cmd, func(sc *scenarioContext) {
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", mock, cmd,
func(sc *scenarioContext) {
callUpdateFolder(sc)
Convey("It should return correct response data", func() {
folder := dtos.Folder{}
err := json.NewDecoder(sc.resp.Body).Decode(&folder)
So(err, ShouldBeNil)
So(folder.Id, ShouldEqual, 1)
So(folder.Uid, ShouldEqual, "uid")
So(folder.Title, ShouldEqual, "Folder upd")
})
require.NoError(t, err)
assert.Equal(t, int64(1), folder.Id)
assert.Equal(t, "uid", folder.Uid)
assert.Equal(t, "Folder upd", folder.Title)
})
})
Convey("Given incorrect requests for updating a folder", func() {
t.Run("Given incorrect requests for updating a folder", func(t *testing.T) {
testCases := []struct {
Error error
ExpectedStatusCode int
@ -123,34 +119,33 @@ func TestFoldersApiEndpoint(t *testing.T) {
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) {
updateFolderScenario(t, 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)
if sc.resp.Code != tc.ExpectedStatusCode {
t.Errorf("For error '%s' expected status code %d, actual %d", tc.Error, tc.ExpectedStatusCode, sc.resp.Code)
}
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for %s", tc.Error)
})
}
})
})
}
func callCreateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func createFolderScenario(desc string, url string, routePattern string, mock *fakeFolderService, cmd models.CreateFolderCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
func createFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
cmd models.CreateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
t.Cleanup(bus.ClearBusHandlers)
hs := HTTPServer{
Bus: bus.GetBus(),
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(url)
sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
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)
})
@ -172,27 +167,27 @@ func callUpdateFolder(sc *scenarioContext) {
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
}
func updateFolderScenario(desc string, url string, routePattern string, mock *fakeFolderService, cmd models.UpdateFolderCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, mock *fakeFolderService,
cmd models.UpdateFolderCommand, fn scenarioFunc) {
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
defer bus.ClearBusHandlers()
sc := setupScenarioContext(url)
sc := setupScenarioContext(t, url)
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
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)
})
origNewFolderService := dashboards.NewFolderService
t.Cleanup(func() {
dashboards.NewFolderService = origNewFolderService
})
mockFolderService(mock)
sc.m.Put(routePattern, sc.defaultHandler)
defer func() {
dashboards.NewFolderService = origNewFolderService
}()
fn(sc)
})
}

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

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

@ -50,11 +50,11 @@ func (m *LDAPMock) User(login string) (*models.ExternalUserInfo, ldap.ServerConf
func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
t.Helper()
sc := setupScenarioContext(requestURL)
sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled
origLDAP := setting.LDAPEnabled
setting.LDAPEnabled = true
defer func() { setting.LDAPEnabled = ldap }()
t.Cleanup(func() { setting.LDAPEnabled = origLDAP })
hs := &HTTPServer{Cfg: setting.NewCfg()}
@ -73,7 +73,7 @@ func getUserFromLDAPContext(t *testing.T, requestURL string) *scenarioContext {
return sc
}
func TestGetUserFromLDAPApiEndpoint_UserNotFound(t *testing.T) {
func TestGetUserFromLDAPAPIEndpoint_UserNotFound(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
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())
}
func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) {
func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
isAdmin := true
userSearchResult = &models.ExternalUserInfo{
Name: "John Doe",
@ -152,7 +152,7 @@ func TestGetUserFromLDAPApiEndpoint_OrgNotfound(t *testing.T) {
assert.JSONEq(t, expected, sc.resp.Body.String())
}
func TestGetUserFromLDAPApiEndpoint(t *testing.T) {
func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
isAdmin := true
userSearchResult = &models.ExternalUserInfo{
Name: "John Doe",
@ -232,7 +232,7 @@ func TestGetUserFromLDAPApiEndpoint(t *testing.T) {
assert.JSONEq(t, expected, sc.resp.Body.String())
}
func TestGetUserFromLDAPApiEndpoint_WithTeamHandler(t *testing.T) {
func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
isAdmin := true
userSearchResult = &models.ExternalUserInfo{
Name: "John Doe",
@ -319,11 +319,11 @@ func getLDAPStatusContext(t *testing.T) *scenarioContext {
t.Helper()
requestURL := "/api/admin/ldap/status"
sc := setupScenarioContext(requestURL)
sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled
setting.LDAPEnabled = true
defer func() { setting.LDAPEnabled = ldap }()
t.Cleanup(func() { setting.LDAPEnabled = ldap })
hs := &HTTPServer{Cfg: setting.NewCfg()}
@ -342,7 +342,7 @@ func getLDAPStatusContext(t *testing.T) *scenarioContext {
return sc
}
func TestGetLDAPStatusApiEndpoint(t *testing.T) {
func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
pingResult = []*multildap.ServerStatus{
{Host: "10.0.0.3", Port: 361, 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
// ***
func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioContext {
func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(t *testing.T)) *scenarioContext {
t.Helper()
sc := setupScenarioContext(requestURL)
sc := setupScenarioContext(t, requestURL)
ldap := setting.LDAPEnabled
t.Cleanup(func() {
setting.LDAPEnabled = ldap
})
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.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.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.exec()
@ -402,6 +411,7 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string) *scenarioConte
}
func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
@ -432,8 +442,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
})
assert.Equal(t, http.StatusOK, sc.resp.Code)
@ -447,6 +456,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
}
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
@ -460,8 +470,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
return models.ErrUserNotFound
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
})
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
@ -475,6 +484,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
}
func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
@ -486,8 +496,8 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
userSearchError = multildap.ErrDidNotFindUser
admin := setting.AdminUser
t.Cleanup(func() { setting.AdminUser = admin })
setting.AdminUser = "ldap-daniel"
defer func() { setting.AdminUser = admin }()
bus.AddHandler("test", func(q *models.GetUserByIdQuery) error {
require.Equal(t, q.Id, int64(34))
@ -502,8 +512,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
})
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
@ -518,6 +527,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
}
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T) {
getLDAPConfig = func() (*ldap.Config, error) {
return &ldap.Config{}, nil
}
@ -551,8 +561,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
assert.Equal(t, 34, cmd.UserId)
return nil
})
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34")
})
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)

@ -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
// 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
}
@ -62,8 +62,8 @@ func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
func (hs *HTTPServer) CookieOptionsFromCfg() middleware.CookieOptions {
path := "/"
if len(hs.Cfg.AppSubUrl) > 0 {
path = hs.Cfg.AppSubUrl
if len(hs.Cfg.AppSubURL) > 0 {
path = hs.Cfg.AppSubURL
}
return middleware.CookieOptions{
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
// it should be redirected to the home page.
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)
c.Redirect(redirectTo)

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

@ -13,7 +13,7 @@ import (
"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) {
proxyPath = strings.TrimPrefix(proxyPath, route.Path)

@ -26,12 +26,10 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
. "github.com/smartystreets/goconvey/convey"
)
func TestDSRouteRule(t *testing.T) {
Convey("DataSourceProxy", t, func() {
Convey("Plugin with routes", func() {
func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("Plugin with routes", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{
Routes: []*plugins.AppPluginRoute{
{
@ -74,8 +72,14 @@ func TestDSRouteRule(t *testing.T) {
},
}
origSecretKey := setting.SecretKey
t.Cleanup(func() {
setting.SecretKey = origSecretKey
})
setting.SecretKey = "password" //nolint:goconst
key, _ := util.Encrypt([]byte("123"), "password")
key, err := util.Encrypt([]byte("123"), "password")
require.NoError(t, err)
ds := &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
@ -88,75 +92,79 @@ func TestDSRouteRule(t *testing.T) {
},
}
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
setUp := func() (*models.ReqContext, *http.Request) {
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
ctx := &models.ReqContext{
Context: &macaron.Context{
Req: macaron.Request{Request: req},
},
SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_EDITOR},
}
return ctx, req
}
Convey("When matching route path", func() {
t.Run("When matching route path", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.route = plugin.Routes[0]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
Convey("should add headers and update url", func() {
So(req.URL.String(), ShouldEqual, "https://www.google.com/some/method")
So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
})
assert.Equal(t, "https://www.google.com/some/method", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header"))
})
Convey("When matching route path and has dynamic url", func() {
t.Run("When matching route path and has dynamic url", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.route = plugin.Routes[3]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
Convey("should add headers and interpolate the url with query string parameters", func() {
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com/some/method?apiKey=123")
So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
})
assert.Equal(t, "https://dynamic.grafana.com/some/method?apiKey=123", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header"))
})
Convey("When matching route path with no url", func() {
t.Run("When matching route path with no url", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.route = plugin.Routes[4]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
Convey("Should not replace request url", func() {
So(req.URL.String(), ShouldEqual, "http://localhost/asd")
})
assert.Equal(t, "http://localhost/asd", req.URL.String())
})
Convey("Validating request", func() {
Convey("plugin route with valid role", func() {
t.Run("Validating request", func(t *testing.T) {
t.Run("plugin route with valid role", func(t *testing.T) {
ctx, _ := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
err = proxy.validateRequest()
So(err, ShouldBeNil)
require.NoError(t, err)
})
Convey("plugin route with admin role and user is editor", func() {
t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
ctx, _ := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
err = proxy.validateRequest()
So(err, ShouldNotBeNil)
require.Error(t, err)
})
Convey("plugin route with admin role and user is admin", func() {
t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
ctx, _ := setUp()
ctx.SignedInUser.OrgRole = models.ROLE_ADMIN
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
err = proxy.validateRequest()
So(err, ShouldBeNil)
require.NoError(t, err)
})
})
})
Convey("Plugin with multiple routes for token auth", func() {
t.Run("Plugin with multiple routes for token auth", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{
Routes: []*plugins.AppPluginRoute{
{
@ -188,8 +196,14 @@ func TestDSRouteRule(t *testing.T) {
},
}
origSecretKey := setting.SecretKey
t.Cleanup(func() {
setting.SecretKey = origSecretKey
})
setting.SecretKey = "password"
key, _ := util.Encrypt([]byte("123"), "password")
key, err := util.Encrypt([]byte("123"), "password")
require.NoError(t, err)
ds := &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
@ -201,7 +215,8 @@ func TestDSRouteRule(t *testing.T) {
},
}
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
ctx := &models.ReqContext{
Context: &macaron.Context{
Req: macaron.Request{Request: req},
@ -209,83 +224,87 @@ func TestDSRouteRule(t *testing.T) {
SignedInUser: &models.SignedInUser{OrgRole: models.ROLE_EDITOR},
}
Convey("When creating and caching access tokens", func() {
t.Run("When creating and caching access tokens", func(t *testing.T) {
var authorizationHeaderCall1 string
var authorizationHeaderCall2 string
Convey("first call should add authorization header with access token", func() {
t.Run("first call should add authorization header with access token", func(t *testing.T) {
json, err := ioutil.ReadFile("./test-data/access-token-1.json")
So(err, ShouldBeNil)
require.NoError(t, err)
client = newFakeHTTPClient(json)
proxy1, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", &setting.Cfg{})
So(err, ShouldBeNil)
proxy1.route = plugin.Routes[0]
ApplyRoute(proxy1.ctx.Req.Context(), req, proxy1.proxyPath, proxy1.route, proxy1.ds)
client = newFakeHTTPClient(t, json)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", &setting.Cfg{})
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds)
authorizationHeaderCall1 = req.Header.Get("Authorization")
So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String())
assert.True(t, strings.HasPrefix(authorizationHeaderCall1, "Bearer eyJ0e"))
Convey("second call to another route should add a different access token", func() {
t.Run("second call to another route should add a different access token", func(t *testing.T) {
json2, err := ioutil.ReadFile("./test-data/access-token-2.json")
So(err, ShouldBeNil)
require.NoError(t, err)
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
client = newFakeHTTPClient(json2)
proxy2, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", &setting.Cfg{})
So(err, ShouldBeNil)
proxy2.route = plugin.Routes[1]
ApplyRoute(proxy2.ctx.Req.Context(), req, proxy2.proxyPath, proxy2.route, proxy2.ds)
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
client = newFakeHTTPClient(t, json2)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", &setting.Cfg{})
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[1], proxy.ds)
authorizationHeaderCall2 = req.Header.Get("Authorization")
So(req.URL.String(), ShouldEqual, "https://api.nr2.io/some/path")
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
So(authorizationHeaderCall2, ShouldStartWith, "Bearer eyJ0e")
So(authorizationHeaderCall2, ShouldNotEqual, authorizationHeaderCall1)
assert.Equal(t, "https://api.nr2.io/some/path", req.URL.String())
assert.True(t, strings.HasPrefix(authorizationHeaderCall1, "Bearer eyJ0e"))
assert.True(t, strings.HasPrefix(authorizationHeaderCall2, "Bearer eyJ0e"))
assert.NotEqual(t, authorizationHeaderCall1, authorizationHeaderCall2)
Convey("third call to first route should add cached access token", func() {
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
t.Run("third call to first route should add cached access token", func(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
client = newFakeHTTPClient([]byte{})
proxy3, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", &setting.Cfg{})
So(err, ShouldBeNil)
proxy3.route = plugin.Routes[0]
ApplyRoute(proxy3.ctx.Req.Context(), req, proxy3.proxyPath, proxy3.route, proxy3.ds)
client = newFakeHTTPClient(t, []byte{})
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", &setting.Cfg{})
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds)
authorizationHeaderCall3 := req.Header.Get("Authorization")
So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
So(authorizationHeaderCall3, ShouldStartWith, "Bearer eyJ0e")
So(authorizationHeaderCall3, ShouldEqual, authorizationHeaderCall1)
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String())
assert.True(t, strings.HasPrefix(authorizationHeaderCall1, "Bearer eyJ0e"))
assert.True(t, strings.HasPrefix(authorizationHeaderCall3, "Bearer eyJ0e"))
assert.Equal(t, authorizationHeaderCall1, authorizationHeaderCall3)
})
})
})
})
})
Convey("When proxying graphite", func() {
t.Run("When proxying graphite", func(t *testing.T) {
origBuildVer := setting.BuildVersion
t.Cleanup(func() {
setting.BuildVersion = origBuildVer
})
setting.BuildVersion = "5.3.0"
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{Url: "htttp://graphite:8080", Type: models.DS_GRAPHITE}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)
Convey("Can translate request url and path", func() {
So(req.URL.Host, ShouldEqual, "graphite:8080")
So(req.URL.Path, ShouldEqual, "/render")
So(req.Header.Get("User-Agent"), ShouldEqual, "Grafana/5.3.0")
t.Run("Can translate request URL and path", func(t *testing.T) {
assert.Equal(t, "graphite:8080", req.URL.Host)
assert.Equal(t, "/render", req.URL.Path)
assert.Equal(t, "Grafana/5.3.0", req.Header.Get("User-Agent"))
})
})
Convey("When proxying InfluxDB", func() {
t.Run("When proxying InfluxDB", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{
@ -298,22 +317,20 @@ func TestDSRouteRule(t *testing.T) {
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)
Convey("Should add db to url", func() {
So(req.URL.Path, ShouldEqual, "/db/site/")
})
assert.Equal(t, "/db/site/", req.URL.Path)
})
Convey("When proxying a data source with no keepCookies specified", func() {
t.Run("When proxying a data source with no keepCookies specified", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{}
json, _ := simplejson.NewJson([]byte(`{"keepCookies": []}`))
json, err := simplejson.NewJson([]byte(`{"keepCookies": []}`))
require.NoError(t, err)
ds := &models.DataSource{
Type: models.DS_GRAPHITE,
@ -323,24 +340,24 @@ func TestDSRouteRule(t *testing.T) {
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
requestURL, _ := url.Parse("http://grafana.com/sub")
requestURL, err := url.Parse("http://grafana.com/sub")
require.NoError(t, err)
req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
proxy.getDirector()(&req)
Convey("Should clear all cookies", func() {
So(req.Header.Get("Cookie"), ShouldEqual, "")
})
assert.Equal(t, "", req.Header.Get("Cookie"))
})
Convey("When proxying a data source with keep cookies specified", func() {
t.Run("When proxying a data source with keep cookies specified", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{}
json, _ := simplejson.NewJson([]byte(`{"keepCookies": ["JSESSION_ID"]}`))
json, err := simplejson.NewJson([]byte(`{"keepCookies": ["JSESSION_ID"]}`))
require.NoError(t, err)
ds := &models.DataSource{
Type: models.DS_GRAPHITE,
@ -350,21 +367,20 @@ func TestDSRouteRule(t *testing.T) {
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
requestURL, _ := url.Parse("http://grafana.com/sub")
requestURL, err := url.Parse("http://grafana.com/sub")
require.NoError(t, err)
req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
proxy.getDirector()(&req)
Convey("Should keep named cookies", func() {
So(req.Header.Get("Cookie"), ShouldEqual, "JSESSION_ID=test")
})
assert.Equal(t, "JSESSION_ID=test", req.Header.Get("Cookie"))
})
Convey("When proxying a custom datasource", func() {
t.Run("When proxying a custom datasource", func(t *testing.T) {
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{
Type: "custom-datasource",
@ -372,32 +388,32 @@ func TestDSRouteRule(t *testing.T) {
}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req.Header.Add("Origin", "grafana.com")
req.Header.Add("Referer", "grafana.com")
req.Header.Add("X-Canary", "stillthere")
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)
Convey("Should keep user request (including trailing slash)", func() {
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
})
assert.Equal(t, "http://host/root/path/to/folder/", req.URL.String())
Convey("Origin and Referer headers should be dropped", func() {
So(req.Header.Get("Origin"), ShouldEqual, "")
So(req.Header.Get("Referer"), ShouldEqual, "")
So(req.Header.Get("X-Canary"), ShouldEqual, "stillthere")
})
assert.Empty(t, req.Header.Get("Origin"))
assert.Empty(t, req.Header.Get("Referer"))
assert.Equal(t, "stillthere", req.Header.Get("X-Canary"))
})
Convey("When proxying a datasource that has oauth token pass-through enabled", func() {
t.Run("When proxying a datasource that has oauth token pass-through enabled", func(t *testing.T) {
social.SocialMap["generic_oauth"] = &social.SocialGenericOAuth{
SocialBase: &social.SocialBase{
Config: &oauth2.Config{},
},
}
origAuthSvc := setting.OAuthService
t.Cleanup(func() {
setting.OAuthService = origAuthSvc
})
setting.OAuthService = &setting.OAuther{}
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
setting.OAuthService.OAuthInfos["generic_oauth"] = &setting.OAuthInfo{}
@ -424,7 +440,8 @@ func TestDSRouteRule(t *testing.T) {
}),
}
req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
ctx := &models.ReqContext{
SignedInUser: &models.SignedInUser{UserId: 1},
Context: &macaron.Context{
@ -432,19 +449,18 @@ func TestDSRouteRule(t *testing.T) {
},
}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)
Convey("Should have access token in header", func() {
So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("%s %s", "Bearer", "testtoken"))
})
assert.Equal(t, "Bearer testtoken", req.Header.Get("Authorization"))
})
Convey("When SendUserHeader config is enabled", func() {
t.Run("When SendUserHeader config is enabled", func(t *testing.T) {
req := getDatasourceProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
@ -452,13 +468,12 @@ func TestDSRouteRule(t *testing.T) {
},
&setting.Cfg{SendUserHeader: true},
)
Convey("Should add header with username", func() {
So(req.Header.Get("X-Grafana-User"), ShouldEqual, "test_user")
})
assert.Equal(t, "test_user", req.Header.Get("X-Grafana-User"))
})
Convey("When SendUserHeader config is disabled", func() {
t.Run("When SendUserHeader config is disabled", func(t *testing.T) {
req := getDatasourceProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
@ -466,54 +481,51 @@ func TestDSRouteRule(t *testing.T) {
},
&setting.Cfg{SendUserHeader: false},
)
Convey("Should not add header with username", func() {
// Get will return empty string even if header is not set
So(req.Header.Get("X-Grafana-User"), ShouldEqual, "")
})
assert.Empty(t, req.Header.Get("X-Grafana-User"))
})
Convey("When SendUserHeader config is enabled but user is anonymous", func() {
t.Run("When SendUserHeader config is enabled but user is anonymous", func(t *testing.T) {
req := getDatasourceProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{IsAnonymous: true},
},
&setting.Cfg{SendUserHeader: true},
)
Convey("Should not add header with username", func() {
// Get will return empty string even if header is not set
So(req.Header.Get("X-Grafana-User"), ShouldEqual, "")
})
assert.Empty(t, req.Header.Get("X-Grafana-User"))
})
Convey("When proxying data source proxy should handle authentication", func() {
tests := []*Test{
createAuthTest(models.DS_INFLUXDB_08, AUTHTYPE_PASSWORD, AUTHCHECK_QUERY, false),
createAuthTest(models.DS_INFLUXDB_08, AUTHTYPE_PASSWORD, AUTHCHECK_QUERY, true),
createAuthTest(models.DS_INFLUXDB, AUTHTYPE_PASSWORD, AUTHCHECK_HEADER, true),
createAuthTest(models.DS_INFLUXDB, AUTHTYPE_PASSWORD, AUTHCHECK_HEADER, false),
createAuthTest(models.DS_INFLUXDB, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true),
createAuthTest(models.DS_INFLUXDB, AUTHTYPE_BASIC, AUTHCHECK_HEADER, false),
t.Run("When proxying data source proxy should handle authentication", func(t *testing.T) {
tests := []*testCase{
createAuthTest(t, models.DS_INFLUXDB_08, authTypePassword, authCheckQuery, false),
createAuthTest(t, models.DS_INFLUXDB_08, authTypePassword, authCheckQuery, true),
createAuthTest(t, models.DS_INFLUXDB, authTypePassword, authCheckHeader, true),
createAuthTest(t, models.DS_INFLUXDB, authTypePassword, authCheckHeader, false),
createAuthTest(t, models.DS_INFLUXDB, authTypeBasic, authCheckHeader, true),
createAuthTest(t, models.DS_INFLUXDB, authTypeBasic, authCheckHeader, false),
// These two should be enough for any other datasource at the moment. Proxy has special handling
// only for Influx, others have the same path and only BasicAuth. Non BasicAuth datasources
// do not go through proxy but through TSDB API which is not tested here.
createAuthTest(models.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, false),
createAuthTest(models.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true),
createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, false),
createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, true),
}
for _, test := range tests {
models.ClearDSDecryptionCache()
runDatasourceAuthTest(test)
runDatasourceAuthTest(t, test)
}
})
Convey("HandleRequest()", func() {
t.Run("HandleRequest()", func(t *testing.T) {
var writeErr error
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"})
w.WriteHeader(200)
_, writeErr = w.Write([]byte("I am the backend"))
}))
defer backend.Close()
t.Cleanup(backend.Close)
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{Url: backend.URL, Type: models.DS_GRAPHITE}
@ -521,7 +533,7 @@ func TestDSRouteRule(t *testing.T) {
responseRecorder := &CloseNotifierResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
defer responseRecorder.Close()
t.Cleanup(responseRecorder.Close)
setupCtx := func(fn func(http.ResponseWriter)) *models.ReqContext {
responseWriter := macaron.NewResponseWriter("GET", responseRecorder)
@ -540,32 +552,30 @@ func TestDSRouteRule(t *testing.T) {
}
}
Convey("When response header Set-Cookie is not set should remove proxied Set-Cookie header", func() {
t.Run("When response header Set-Cookie is not set should remove proxied Set-Cookie header", func(t *testing.T) {
writeErr = nil
ctx := setupCtx(nil)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.HandleRequest()
So(writeErr, ShouldBeNil)
So(proxy.ctx.Resp.Header().Get("Set-Cookie"), ShouldBeEmpty)
require.NoError(t, writeErr)
assert.Empty(t, proxy.ctx.Resp.Header().Get("Set-Cookie"))
})
Convey("When response header Set-Cookie is set should remove proxied Set-Cookie header and restore the original Set-Cookie header", func() {
t.Run("When response header Set-Cookie is set should remove proxied Set-Cookie header and restore the original Set-Cookie header", func(t *testing.T) {
writeErr = nil
ctx := setupCtx(func(w http.ResponseWriter) {
w.Header().Set("Set-Cookie", "important_cookie=important_value")
})
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.HandleRequest()
So(writeErr, ShouldBeNil)
So(proxy.ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual,
"important_cookie=important_value")
})
require.NoError(t, writeErr)
assert.Equal(t, "important_cookie=important_value", proxy.ctx.Resp.Header().Get("Set-Cookie"))
})
})
}
@ -672,7 +682,7 @@ func (r *CloseNotifierResponseRecorder) Close() {
}
// getDatasourceProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
func getDatasourceProxiedRequest(ctx *models.ReqContext, cfg *setting.Cfg) *http.Request {
func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *setting.Cfg) *http.Request {
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{
@ -681,25 +691,28 @@ func getDatasourceProxiedRequest(ctx *models.ReqContext, cfg *setting.Cfg) *http
}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg)
So(err, ShouldBeNil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)
return req
}
type httpClientStub struct {
t *testing.T
fakeBody []byte
}
func (c *httpClientStub) Do(req *http.Request) (*http.Response, error) {
bodyJSON, _ := simplejson.NewJson(c.fakeBody)
bodyJSON, err := simplejson.NewJson(c.fakeBody)
require.NoError(c.t, err)
_, passedTokenCacheTest := bodyJSON.CheckGet("expires_on")
So(passedTokenCacheTest, ShouldBeTrue)
require.True(c.t, passedTokenCacheTest)
bodyJSON.Set("expires_on", fmt.Sprint(time.Now().Add(time.Second*60).Unix()))
body, _ := bodyJSON.MarshalJSON()
body, err := bodyJSON.MarshalJSON()
require.NoError(c.t, err)
resp := &http.Response{
Body: ioutil.NopCloser(bytes.NewReader(body)),
}
@ -707,39 +720,40 @@ func (c *httpClientStub) Do(req *http.Request) (*http.Response, error) {
return resp, nil
}
func newFakeHTTPClient(fakeBody []byte) httpClient {
func newFakeHTTPClient(t *testing.T, fakeBody []byte) httpClient {
return &httpClientStub{
t: t,
fakeBody: fakeBody,
}
}
type Test struct {
type testCase struct {
datasource *models.DataSource
checkReq func(req *http.Request)
}
const (
AUTHTYPE_PASSWORD = "password"
AUTHTYPE_BASIC = "basic"
authTypePassword = "password"
authTypeBasic = "basic"
)
const (
AUTHCHECK_QUERY = "query"
AUTHCHECK_HEADER = "header"
authCheckQuery = "query"
authCheckHeader = "header"
)
func createAuthTest(dsType string, authType string, authCheck string, useSecureJsonData bool) *Test {
func createAuthTest(t *testing.T, dsType string, authType string, authCheck string, useSecureJsonData bool) *testCase {
// Basic user:password
base64AthHeader := "Basic dXNlcjpwYXNzd29yZA=="
base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA=="
test := &Test{
test := &testCase{
datasource: &models.DataSource{
Type: dsType,
JsonData: simplejson.New(),
},
}
var message string
if authType == AUTHTYPE_PASSWORD {
if authType == authTypePassword {
message = fmt.Sprintf("%v should add username and password", dsType)
test.datasource.User = "user"
if useSecureJsonData {
@ -766,35 +780,31 @@ func createAuthTest(dsType string, authType string, authCheck string, useSecureJ
message += " from securejsondata"
}
if authCheck == AUTHCHECK_QUERY {
if authCheck == authCheckQuery {
message += " to query params"
test.checkReq = func(req *http.Request) {
Convey(message, func() {
queryVals := req.URL.Query()
So(queryVals["u"][0], ShouldEqual, "user")
So(queryVals["p"][0], ShouldEqual, "password")
})
assert.Equal(t, "user", queryVals["u"][0], message)
assert.Equal(t, "password", queryVals["p"][0], message)
}
} else {
message += " to auth header"
test.checkReq = func(req *http.Request) {
Convey(message, func() {
So(req.Header.Get("Authorization"), ShouldEqual, base64AthHeader)
})
assert.Equal(t, base64AuthHeader, req.Header.Get("Authorization"), message)
}
}
return test
}
func runDatasourceAuthTest(test *Test) {
func runDatasourceAuthTest(t *testing.T, test *testCase) {
plugin := &plugins.DataSourcePlugin{}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{})
So(err, ShouldBeNil)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
require.NoError(t, err)
proxy.getDirector()(req)

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

@ -10,12 +10,11 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserApiEndpoint(t *testing.T) {
Convey("Given a user is logged in", t, func() {
func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
mockResult := models.SearchUserQueryResult{
Users: []*models.UserSearchHitDTO{
{Name: "user1"},
@ -24,7 +23,7 @@ func TestUserApiEndpoint(t *testing.T) {
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)
bus.AddHandler("test", func(query *models.GetUserProfileQuery) error {
query.Result = models.UserProfileDTO{
@ -77,7 +76,7 @@ func TestUserApiEndpoint(t *testing.T) {
require.JSONEq(t, expected, sc.resp.Body.String())
})
loggedInUserScenario("When calling GET on", "/api/users/lookup", func(sc *scenarioContext) {
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)
@ -123,7 +122,7 @@ func TestUserApiEndpoint(t *testing.T) {
require.JSONEq(t, expected, sc.resp.Body.String())
})
loggedInUserScenario("When calling GET on", "/api/users", func(sc *scenarioContext) {
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 {
@ -138,15 +137,15 @@ func TestUserApiEndpoint(t *testing.T) {
sc.handlerFunc = SearchUsers
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sentLimit, ShouldEqual, 1000)
So(sendPage, ShouldEqual, 1)
assert.Equal(t, 1000, sentLimit)
assert.Equal(t, 1, sendPage)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 2)
require.NoError(t, err)
assert.Equal(t, 2, len(respJSON.MustArray()))
})
loggedInUserScenario("When calling GET with page and limit querystring parameters on", "/api/users", func(sc *scenarioContext) {
loggedInUserScenario(t, "When calling GET with page and limit querystring parameters on", "/api/users", func(sc *scenarioContext) {
var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
@ -161,11 +160,11 @@ func TestUserApiEndpoint(t *testing.T) {
sc.handlerFunc = SearchUsers
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
So(sentLimit, ShouldEqual, 10)
So(sendPage, ShouldEqual, 2)
assert.Equal(t, 10, sentLimit)
assert.Equal(t, 2, sendPage)
})
loggedInUserScenario("When calling GET on", "/api/users/search", func(sc *scenarioContext) {
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 {
@ -180,17 +179,17 @@ func TestUserApiEndpoint(t *testing.T) {
sc.handlerFunc = SearchUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
So(sentLimit, ShouldEqual, 1000)
So(sendPage, ShouldEqual, 1)
assert.Equal(t, 1000, sentLimit)
assert.Equal(t, 1, sendPage)
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
So(err, ShouldBeNil)
require.NoError(t, err)
So(respJSON.Get("totalCount").MustInt(), ShouldEqual, 2)
So(len(respJSON.Get("users").MustArray()), ShouldEqual, 2)
assert.Equal(t, 2, respJSON.Get("totalCount").MustInt())
assert.Equal(t, 2, len(respJSON.Get("users").MustArray()))
})
loggedInUserScenario("When calling GET with page and perpage querystring parameters on", "/api/users/search", func(sc *scenarioContext) {
loggedInUserScenario(t, "When calling GET with page and perpage querystring parameters on", "/api/users/search", func(sc *scenarioContext) {
var sentLimit int
var sendPage int
bus.AddHandler("test", func(query *models.SearchUsersQuery) error {
@ -205,8 +204,7 @@ func TestUserApiEndpoint(t *testing.T) {
sc.handlerFunc = SearchUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
So(sentLimit, ShouldEqual, 10)
So(sendPage, ShouldEqual, 2)
})
assert.Equal(t, 10, sentLimit)
assert.Equal(t, 2, sendPage)
})
}

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

@ -34,7 +34,7 @@ func (*OSSLicensingService) StateInfo() string {
func (l *OSSLicensingService) LicenseURL(user *models.SignedInUser) string {
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"

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

@ -27,14 +27,14 @@ func TestGetUrl(t *testing.T) {
t.Run("And protocol HTTP configured should return expected path", func(t *testing.T) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = ""
rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTPScheme
url := rs.getURL(path)
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) {
rs.Cfg.ServeFromSubPath = true
rs.Cfg.AppSubUrl = "/grafana"
rs.Cfg.AppSubURL = "/grafana"
url := rs.getURL(path)
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) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = ""
rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTPSScheme
url := rs.getURL(path)
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) {
rs.Cfg.ServeFromSubPath = false
rs.Cfg.AppSubUrl = ""
rs.Cfg.AppSubURL = ""
setting.Protocol = setting.HTTP2Scheme
url := rs.getURL(path)
require.Equal(t, "https://localhost:3000/"+path+"&render=1", url)

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

Loading…
Cancel
Save