The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/user/userimpl/verifier_test.go

266 lines
9.0 KiB

package userimpl
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/auth/idtest"
"github.com/grafana/grafana/pkg/services/notifications"
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
"github.com/grafana/grafana/pkg/services/temp_user/tempusertest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
)
func TestVerifier_Start(t *testing.T) {
ts := &tempusertest.FakeTempUserService{}
us := &usertest.FakeUserService{}
ns := notifications.MockNotificationService()
is := &idtest.MockService{}
type calls struct {
expireCalled bool
createCalled bool
updateCalled bool
}
verifier := ProvideVerifier(setting.NewCfg(), us, ts, ns, is)
t.Run("should error if email already exist for other user", func(t *testing.T) {
us.ExpectedUser = &user.User{ID: 1}
err := verifier.Start(context.Background(), user.StartVerifyEmailCommand{
User: user.User{ID: 2},
Email: "some@email.com",
Action: user.EmailUpdateAction,
})
assert.ErrorIs(t, err, user.ErrEmailConflict)
})
t.Run("should succeed when no user has the email", func(t *testing.T) {
us.ExpectedUser = nil
var c calls
ts.ExpirePreviousVerificationsFN = func(ctx context.Context, cmd *tempuser.ExpirePreviousVerificationsCommand) error {
c.expireCalled = true
return nil
}
ts.CreateTempUserFN = func(ctx context.Context, cmd *tempuser.CreateTempUserCommand) (*tempuser.TempUser, error) {
c.createCalled = true
return &tempuser.TempUser{
OrgID: cmd.OrgID,
Email: cmd.Email,
Name: cmd.Name,
InvitedByUserID: cmd.InvitedByUserID,
Code: cmd.Code,
}, nil
}
ts.UpdateTempUserWithEmailSentFN = func(ctx context.Context, cmd *tempuser.UpdateTempUserWithEmailSentCommand) error {
c.updateCalled = true
return nil
}
err := verifier.Start(context.Background(), user.StartVerifyEmailCommand{
User: user.User{ID: 2},
Email: "some@email.com",
Action: user.EmailUpdateAction,
})
assert.NoError(t, err)
assert.True(t, c.expireCalled)
assert.True(t, c.createCalled)
assert.True(t, c.updateCalled)
})
t.Run("should succeed when the user holding the email is the same user that want to verify it", func(t *testing.T) {
us.ExpectedUser = &user.User{ID: 2}
var c calls
ts.ExpirePreviousVerificationsFN = func(ctx context.Context, cmd *tempuser.ExpirePreviousVerificationsCommand) error {
c.expireCalled = true
return nil
}
ts.CreateTempUserFN = func(ctx context.Context, cmd *tempuser.CreateTempUserCommand) (*tempuser.TempUser, error) {
c.createCalled = true
return &tempuser.TempUser{
OrgID: cmd.OrgID,
Email: cmd.Email,
Name: cmd.Name,
InvitedByUserID: cmd.InvitedByUserID,
Code: cmd.Code,
}, nil
}
ts.UpdateTempUserWithEmailSentFN = func(ctx context.Context, cmd *tempuser.UpdateTempUserWithEmailSentCommand) error {
c.updateCalled = true
return nil
}
err := verifier.Start(context.Background(), user.StartVerifyEmailCommand{
User: user.User{ID: 2},
Email: "some@email.com",
Action: user.EmailUpdateAction,
})
assert.ErrorIs(t, err, nil)
assert.True(t, c.expireCalled)
assert.True(t, c.createCalled)
assert.True(t, c.updateCalled)
})
}
func TestVerifier_Complete(t *testing.T) {
ts := &tempusertest.FakeTempUserService{}
us := &usertest.FakeUserService{}
ns := notifications.MockNotificationService()
is := &idtest.MockService{}
type calls struct {
updateCalled bool
updateStatusCalled bool
removeTokenCalled bool
}
cfg := setting.NewCfg()
cfg.VerificationEmailMaxLifetime = 1 * time.Hour
verifier := ProvideVerifier(cfg, us, ts, ns, is)
t.Run("should return error for invalid code", func(t *testing.T) {
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return nil, tempuser.ErrTempUserNotFound
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.ErrorIs(t, err, errInvalidCode)
})
t.Run("should return error when verification has wrong status", func(t *testing.T) {
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateCompleted,
}, nil
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.ErrorIs(t, err, errInvalidCode)
})
t.Run("should return error when verification email was never sent", func(t *testing.T) {
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateStarted,
EmailSent: false,
}, nil
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.ErrorIs(t, err, errInvalidCode)
})
t.Run("should return error when verification code has expired", func(t *testing.T) {
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateStarted,
EmailSent: true,
EmailSentOn: time.Now().Add(-10 * time.Hour),
}, nil
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.ErrorIs(t, err, errExpiredCode)
})
t.Run("should return error user connect to code don't exists", func(t *testing.T) {
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateStarted,
EmailSent: true,
EmailSentOn: time.Now(),
}, nil
}
us.ExpectedError = user.ErrUserNotFound
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.ErrorIs(t, err, user.ErrUserNotFound)
})
t.Run("should update user email on valid code", func(t *testing.T) {
var c calls
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateStarted,
Name: string(user.EmailUpdateAction),
InvitedByID: 1,
Email: "updated@email.com",
EmailSent: true,
EmailSentOn: time.Now(),
}, nil
}
ts.UpdateTempUserStatusFN = func(ctx context.Context, cmd *tempuser.UpdateTempUserStatusCommand) error {
c.updateStatusCalled = true
return nil
}
is.RemoveIDTokenFn = func(ctx context.Context, identity identity.Requester) error {
c.removeTokenCalled = true
return nil
}
us.ExpectedUser = &user.User{Email: "initial@email.com"}
us.ExpectedError = nil
us.UpdateFn = func(ctx context.Context, cmd *user.UpdateUserCommand) error {
c.updateCalled = true
assert.True(t, *cmd.EmailVerified)
assert.Equal(t, int64(1), cmd.UserID)
assert.Equal(t, "", cmd.Login)
assert.Equal(t, "updated@email.com", cmd.Email)
return nil
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.NoError(t, err)
assert.True(t, c.updateCalled)
assert.True(t, c.updateStatusCalled)
assert.True(t, c.removeTokenCalled)
})
t.Run("should update user email and login if login is an email on valid code", func(t *testing.T) {
var c calls
ts.GetTempUserByCodeFN = func(ctx context.Context, query *tempuser.GetTempUserByCodeQuery) (*tempuser.TempUserDTO, error) {
return &tempuser.TempUserDTO{
Status: tempuser.TmpUserEmailUpdateStarted,
Name: string(user.EmailUpdateAction),
InvitedByID: 1,
Email: "updated@email.com",
EmailSent: true,
EmailSentOn: time.Now(),
}, nil
}
ts.UpdateTempUserStatusFN = func(ctx context.Context, cmd *tempuser.UpdateTempUserStatusCommand) error {
c.updateStatusCalled = true
return nil
}
is.RemoveIDTokenFn = func(ctx context.Context, identity identity.Requester) error {
c.removeTokenCalled = true
return nil
}
us.ExpectedUser = &user.User{Email: "initial@email.com", Login: "other@email.com"}
us.ExpectedError = nil
us.UpdateFn = func(ctx context.Context, cmd *user.UpdateUserCommand) error {
c.updateCalled = true
assert.True(t, *cmd.EmailVerified)
assert.Equal(t, int64(1), cmd.UserID)
assert.Equal(t, "updated@email.com", cmd.Email)
assert.Equal(t, "updated@email.com", cmd.Login)
return nil
}
err := verifier.Complete(context.Background(), user.CompleteEmailVerifyCommand{Code: "some-code"})
assert.NoError(t, err)
assert.True(t, c.updateCalled)
assert.True(t, c.updateStatusCalled)
assert.True(t, c.removeTokenCalled)
})
}