446 lines
12 KiB
Go
446 lines
12 KiB
Go
package pwreset
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"src.dualinventive.com/go/authentication-service/internal/domain"
|
|
)
|
|
|
|
const (
|
|
userName string = "some-username"
|
|
email string = "some-email@some-domain.com"
|
|
invalidEmail string = "some$in!@valid-email@some-domain.com.tar.gz"
|
|
password string = "Some-password1"
|
|
passwordVerify string = "Some-password1"
|
|
passwordVerifyInvalid string = "Some-other-password2"
|
|
someGormError string = "some-gorm-error"
|
|
someSMTPError string = "some-smtp-error"
|
|
someResetCode string = "some-reset-code"
|
|
someOtherResetCode string = "some-other-reset-code"
|
|
)
|
|
|
|
var (
|
|
testCredentialRepo *TestCredentialRepository //nolint:gochecknoglobals
|
|
testCodeRepo *TestCodeRepository //nolint:gochecknoglobals
|
|
testTemplateRepo *TestTemplateRepository //nolint:gochecknoglobals
|
|
testEmailSender *TestEmailSender //nolint:gochecknoglobals
|
|
)
|
|
|
|
func before() {
|
|
testCredentialRepo = &TestCredentialRepository{returnErr: []error{nil, nil, nil}}
|
|
testCodeRepo = &TestCodeRepository{0, make(map[string]string), []error{nil, nil, nil}}
|
|
testTemplateRepo = &TestTemplateRepository{}
|
|
testEmailSender = &TestEmailSender{nil, []*sent{}}
|
|
}
|
|
|
|
func TestRequestPasswordResetWhenFailedFetch(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = nil
|
|
testCredentialRepo.returnErr = []error{errors.New(someGormError)}
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, &ErrUserFetchFailed{userName, errors.New(someGormError)}, err)
|
|
}
|
|
|
|
func TestRequestPasswordResetWithUnknownUsername(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = nil
|
|
testCredentialRepo.returnErr = []error{nil}
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, &ErrUserNotFound{userName}, err)
|
|
}
|
|
|
|
func TestRequestPasswordResetWithEmptyEmail(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: invalidEmail}
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, &ErrUserEmailInvalid{userName, invalidEmail}, err)
|
|
}
|
|
|
|
func TestRequestPasswordResetWithInvalidEmail(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: ""}
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, &ErrUserEmailAddressEmpty{userName}, err)
|
|
}
|
|
|
|
func TestRequestPasswordResetWhenFailedCodeCreation(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.returnErr = []error{errors.New("some-error")}
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.True(t, strings.HasPrefix(err.Error(), "failed to create password reset code for user 'some-username'"))
|
|
require.Equal(t, 0, len(testEmailSender.sent))
|
|
}
|
|
|
|
func TestRequestPasswordResetWhenFailedSendMail(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testEmailSender.returnErr = errors.New(someSMTPError)
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(
|
|
t,
|
|
"failed to send password reset code to email 'some-email@some-domain.com' "+
|
|
"for user 'some-username': some-smtp-error",
|
|
err.Error())
|
|
}
|
|
|
|
func TestRequestPasswordReset(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testTemplateRepo.returnMe = []byte("test template")
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
|
|
//then
|
|
require.Nil(t, err)
|
|
require.Equal(t, 1, len(testCodeRepo.codes))
|
|
require.NotNil(t, testCodeRepo.codes[userName])
|
|
|
|
require.Equal(t, 1, len(testEmailSender.sent))
|
|
sent := testEmailSender.sent[0]
|
|
require.NotNil(t, sent)
|
|
require.Equal(t, sent.body, []byte("test template"))
|
|
require.Equal(t, sent.to[0], email)
|
|
}
|
|
|
|
func TestRequestPasswordResetWhenPreviousCodeExistsThenOverwriteRestCode(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testTemplateRepo.returnMe = []byte("test template")
|
|
|
|
//when
|
|
err := RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
resetCode1 := testCodeRepo.codes[userName]
|
|
|
|
//then
|
|
require.Nil(t, err)
|
|
require.Equal(t, len(testCodeRepo.codes), 1)
|
|
require.NotNil(t, resetCode1)
|
|
|
|
//when
|
|
err = RequestPasswordReset(testCredentialRepo, testEmailSender, testTemplateRepo, testCodeRepo, userName)
|
|
resetCode2 := testCodeRepo.codes[userName]
|
|
|
|
//then
|
|
require.Nil(t, err)
|
|
require.Equal(t, len(testCodeRepo.codes), 1)
|
|
require.NotNil(t, resetCode2)
|
|
require.NotEqual(t, resetCode1, resetCode2)
|
|
|
|
require.Equal(t, len(testEmailSender.sent), 2)
|
|
}
|
|
|
|
func TestRedeemPasswordResetWhenUserFetchFailure(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnErr[0] = errors.New("some-database-error")
|
|
|
|
//when
|
|
err := RedeemPasswordReset(testCredentialRepo, testCodeRepo, userName, "", password, passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "failed to retrieve user 'some-username': some-database-error", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWithUnknownUsername(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = nil
|
|
testCredentialRepo.returnErr = []error{nil}
|
|
|
|
//when
|
|
err := RedeemPasswordReset(testCredentialRepo, testCodeRepo, userName, "", password, passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "user 'some-username' not found", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWithResetCodeFetchFailure(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.returnErr = []error{errors.New("some-redis-error")}
|
|
|
|
//when
|
|
err := RedeemPasswordReset(testCredentialRepo, testCodeRepo, userName, "", password, passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "failed to get password reset code for user 'some-username': some-redis-error", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWithUnknownResetCode(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = ""
|
|
|
|
//when
|
|
err := RedeemPasswordReset(testCredentialRepo, testCodeRepo, userName, "", password, passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "no password reset code found for user 'some-username'", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWithInvalidResetCode(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someOtherResetCode,
|
|
password,
|
|
passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "invalid password reset code 'some-other-reset-code' for user 'some-username'", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWithInvalidPasswordVerify(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someResetCode,
|
|
password,
|
|
passwordVerifyInvalid)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "password verify failed", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWhenPasswordChangeFailure(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCredentialRepo.returnErr = []error{nil, errors.New("some-database-error")}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someResetCode,
|
|
password,
|
|
passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "failed to change password for user 'some-username': some-database-error", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordResetWhenDeleteResetCodeFailed(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
testCodeRepo.returnErr = []error{nil, errors.New("some-redis-error")}
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someResetCode,
|
|
password,
|
|
passwordVerify)
|
|
|
|
//then
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "failed to delete password reset code for user 'some-username': some-redis-error", err.Error())
|
|
}
|
|
|
|
func TestRedeemPasswordReset(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someResetCode,
|
|
password,
|
|
passwordVerify)
|
|
|
|
//then
|
|
require.Nil(t, err)
|
|
setPassword := testCredentialRepo.password
|
|
require.Nil(t, bcrypt.CompareHashAndPassword(setPassword, []byte(password)))
|
|
require.Len(t, testCodeRepo.codes, 0)
|
|
}
|
|
|
|
func TestRedeemPasswordResetWhenInvalidPassword(t *testing.T) {
|
|
passwords := map[string]bool{
|
|
"aB1": true,
|
|
"Some-password1": true,
|
|
"#@!!%Ac1": true,
|
|
"aB": false,
|
|
"!@#@!#!@#!@%&&&$%$!": false,
|
|
"too-simple": false,
|
|
"too-simple1": false,
|
|
}
|
|
|
|
for pass, valid := range passwords {
|
|
t.Run(fmt.Sprintf("with password %s", pass), func(t *testing.T) {
|
|
before()
|
|
//given
|
|
testCredentialRepo.returnMe = &domain.User{Name: userName, Email: email}
|
|
testCodeRepo.codes[userName] = someResetCode
|
|
|
|
//when
|
|
err := RedeemPasswordReset(
|
|
testCredentialRepo,
|
|
testCodeRepo,
|
|
userName,
|
|
someResetCode,
|
|
pass, //nolint:scopelint
|
|
pass) //nolint:scopelint
|
|
|
|
//then
|
|
if valid { //nolint:scopelint
|
|
require.Nil(t, err)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
require.Equal(t, "invalid password", err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type TestCredentialRepository struct {
|
|
callCount int
|
|
returnMe *domain.User
|
|
returnErr []error
|
|
password []byte
|
|
}
|
|
|
|
func (r *TestCredentialRepository) GetUserByUserName(userName string, companyCode string) (*domain.User, error) {
|
|
err := r.returnErr[r.callCount]
|
|
r.callCount++
|
|
return r.returnMe, err
|
|
}
|
|
|
|
func (r *TestCredentialRepository) SetPassword(userName *domain.User, passwordHash []byte) error {
|
|
r.password = passwordHash
|
|
err := r.returnErr[r.callCount]
|
|
r.callCount++
|
|
return err
|
|
}
|
|
|
|
type TestCodeRepository struct {
|
|
callCount int
|
|
codes map[string]string
|
|
returnErr []error
|
|
}
|
|
|
|
func (r *TestCodeRepository) CreateCode(userName string, uuid string) error {
|
|
r.codes[userName] = uuid
|
|
err := r.returnErr[r.callCount]
|
|
r.callCount++
|
|
return err
|
|
}
|
|
|
|
func (r *TestCodeRepository) DeleteCode(userName string) error {
|
|
delete(r.codes, userName)
|
|
err := r.returnErr[r.callCount]
|
|
r.callCount++
|
|
return err
|
|
}
|
|
|
|
func (r *TestCodeRepository) GetCode(userName string) (string, error) {
|
|
err := r.returnErr[r.callCount]
|
|
r.callCount++
|
|
return r.codes[userName], err
|
|
}
|
|
|
|
type TestTemplateRepository struct {
|
|
returnMe []byte
|
|
returnErr error
|
|
}
|
|
|
|
func (t *TestTemplateRepository) Template(username, fromEmail, toEmail, code string) ([]byte, error) {
|
|
return t.returnMe, t.returnErr
|
|
}
|
|
|
|
type TestEmailSender struct {
|
|
returnErr error
|
|
sent []*sent
|
|
}
|
|
|
|
func (s *TestEmailSender) From() string {
|
|
return ""
|
|
}
|
|
|
|
func (s *TestEmailSender) Send(to []string, body []byte) error {
|
|
s.sent = append(s.sent, &sent{to, body})
|
|
return s.returnErr
|
|
}
|
|
|
|
type sent struct {
|
|
to []string
|
|
body []byte
|
|
}
|