2019-06-22 17:48:44 -08:00
package user
import (
"math/rand"
"os"
"strings"
"testing"
"time"
2019-07-13 12:16:28 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
"geeks-accelerator/oss/saas-starter-kit/internal/platform/tests"
2019-06-22 17:48:44 -08:00
"github.com/dgrijalva/jwt-go"
"github.com/google/go-cmp/cmp"
"github.com/huandu/go-sqlbuilder"
2019-06-27 04:48:18 -08:00
"github.com/lib/pq"
2019-06-22 17:48:44 -08:00
"github.com/pborman/uuid"
"github.com/pkg/errors"
)
2019-08-13 23:41:06 -08:00
var (
test * tests . Test
repo * Repository
)
2019-06-22 17:48:44 -08:00
// TestMain is the entry point for testing.
func TestMain ( m * testing . M ) {
os . Exit ( testMain ( m ) )
}
func testMain ( m * testing . M ) int {
test = tests . New ( )
defer test . TearDown ( )
2019-08-13 23:41:06 -08:00
2019-08-14 11:40:26 -08:00
repo = MockRepository ( test . MasterDB )
2019-08-13 23:41:06 -08:00
2019-06-22 17:48:44 -08:00
return m . Run ( )
}
// TestFindRequestQuery validates findRequestQuery
func TestFindRequestQuery ( t * testing . T ) {
var (
limit uint = 12
offset uint = 34
)
req := UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : "first_name = ? or email = ?" ,
2019-06-22 17:48:44 -08:00
Args : [ ] interface { } {
2019-07-31 13:47:30 -08:00
"lee" ,
2019-06-22 17:48:44 -08:00
"lee@geeksinthewoods.com" ,
} ,
Order : [ ] string {
"id asc" ,
"created_at desc" ,
} ,
Limit : & limit ,
Offset : & offset ,
}
2019-07-31 13:47:30 -08:00
expected := "SELECT " + userMapColumns + " FROM " + userTableName + " WHERE (first_name = ? or email = ?) ORDER BY id asc, created_at desc LIMIT 12 OFFSET 34"
2019-06-22 17:48:44 -08:00
res , args := findRequestQuery ( req )
if diff := cmp . Diff ( res . String ( ) , expected ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected result query to match. Diff:\n%s" , tests . Failed , diff )
}
if diff := cmp . Diff ( args , req . Args ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected result query to match. Diff:\n%s" , tests . Failed , diff )
}
}
// TestApplyClaimsSelect validates applyClaimsSelect
func TestApplyClaimsSelect ( t * testing . T ) {
var claimTests = [ ] struct {
name string
claims auth . Claims
expectedSql string
error error
} {
{ "EmptyClaims" ,
auth . Claims { } ,
"SELECT " + userMapColumns + " FROM " + userTableName ,
nil ,
} ,
{ "RoleUser" ,
auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
"SELECT " + userMapColumns + " FROM " + userTableName + " WHERE id IN (SELECT user_id FROM " + userAccountTableName + " WHERE (account_id = 'acc1' OR user_id = 'user1'))" ,
nil ,
} ,
{ "RoleAdmin" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
"SELECT " + userMapColumns + " FROM " + userTableName + " WHERE id IN (SELECT user_id FROM " + userAccountTableName + " WHERE (account_id = 'acc1' OR user_id = 'user1'))" ,
nil ,
} ,
}
t . Log ( "Given the need to validate ACLs are enforced by claims to a select query." )
{
for i , tt := range claimTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
query := selectQuery ( )
err := applyClaimsSelect ( ctx , tt . claims , query )
if err != tt . error {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
t . Fatalf ( "\t%s\tapplyClaimsUserSelect failed." , tests . Failed )
}
sql , args := query . Build ( )
// Use mysql flavor so placeholders will get replaced for comparison.
sql , err = sqlbuilder . MySQL . Interpolate ( sql , args )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tapplyClaimsUserSelect failed." , tests . Failed )
}
if diff := cmp . Diff ( sql , tt . expectedSql ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected result query to match. Diff:\n%s" , tests . Failed , diff )
}
t . Logf ( "\t%s\tapplyClaimsUserSelect ok." , tests . Success )
}
}
}
}
// TestCreateValidation ensures all the validation tags work on Create
func TestCreateValidation ( t * testing . T ) {
var userTests = [ ] struct {
name string
2019-06-24 22:41:21 -08:00
req UserCreateRequest
expected func ( req UserCreateRequest , res * User ) * User
2019-06-22 17:48:44 -08:00
error error
} {
{ "Required Fields" ,
2019-06-24 22:41:21 -08:00
UserCreateRequest { } ,
func ( req UserCreateRequest , res * User ) * User {
2019-06-22 17:48:44 -08:00
return nil
} ,
2019-07-31 13:47:30 -08:00
errors . New ( "Key: 'UserCreateRequest.first_name' Error:Field validation for 'first_name' failed on the 'required' tag\n" +
"Key: 'UserCreateRequest.last_name' Error:Field validation for 'last_name' failed on the 'required' tag\n" +
2019-06-27 04:48:18 -08:00
"Key: 'UserCreateRequest.email' Error:Field validation for 'email' failed on the 'required' tag\n" +
2019-08-01 16:17:47 -08:00
"Key: 'UserCreateRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
"Key: 'UserCreateRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
{ "Valid Email" ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : "xxxxxxxxxx" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( req UserCreateRequest , res * User ) * User {
2019-06-22 17:48:44 -08:00
return nil
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'UserCreateRequest.email' Error:Field validation for 'email' failed on the 'email' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
{ "Passwords Match" ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "W0rkL1fe#" ,
} ,
2019-06-24 22:41:21 -08:00
func ( req UserCreateRequest , res * User ) * User {
2019-06-22 17:48:44 -08:00
return nil
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'UserCreateRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'eqfield' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
{ "Default Timezone" ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( req UserCreateRequest , res * User ) * User {
2019-06-22 17:48:44 -08:00
return & User {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
Email : req . Email ,
2019-08-05 17:12:28 -08:00
Timezone : nil ,
2019-06-22 17:48:44 -08:00
// Copy this fields from the result.
ID : res . ID ,
PasswordSalt : res . PasswordSalt ,
PasswordHash : res . PasswordHash ,
PasswordReset : res . PasswordReset ,
CreatedAt : res . CreatedAt ,
UpdatedAt : res . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} ,
}
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure all validation tags are working for user create." )
{
for i , tt := range userTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-08-13 23:41:06 -08:00
res , err := repo . Create ( ctx , auth . Claims { } , tt . req , now )
2019-06-22 17:48:44 -08:00
if err != tt . error {
// TODO: need a better way to handle validation errors as they are
// of type interface validator.ValidationErrorsTranslations
var errStr string
if err != nil {
2019-08-01 16:17:47 -08:00
errStr = strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
2019-06-22 17:48:44 -08:00
}
var expectStr string
if tt . error != nil {
expectStr = tt . error . Error ( )
}
if errStr != expectStr {
2019-08-01 16:17:47 -08:00
t . Logf ( "\t\tGot : %+v" , errStr )
t . Logf ( "\t\tWant: %+v" , expectStr )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
}
// If there was an error that was expected, then don't go any further
if tt . error != nil {
t . Logf ( "\t%s\tCreate ok." , tests . Success )
continue
}
expected := tt . expected ( tt . req , res )
if diff := cmp . Diff ( res , expected ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected result should match. Diff:\n%s" , tests . Failed , diff )
}
t . Logf ( "\t%s\tCreate ok." , tests . Success )
}
}
}
}
// TestCreateValidationEmailUnique validates emails must be unique on Create.
func TestCreateValidationEmailUnique ( t * testing . T ) {
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure duplicate emails are not allowed for user create." )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
req1 := UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
}
2019-08-13 23:41:06 -08:00
user1 , err := repo . Create ( ctx , auth . Claims { } , req1 , now )
2019-06-22 17:48:44 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
2019-06-24 22:41:21 -08:00
req2 := UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : user1 . Email ,
Password : "W0rkL1fe#" ,
PasswordConfirm : "W0rkL1fe#" ,
}
2019-06-27 04:48:18 -08:00
expectedErr := errors . New ( "Key: 'UserCreateRequest.email' Error:Field validation for 'email' failed on the 'unique' tag" )
2019-08-13 23:41:06 -08:00
_ , err = repo . Create ( ctx , auth . Claims { } , req2 , now )
2019-06-22 17:48:44 -08:00
if err == nil {
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
2019-08-01 16:17:47 -08:00
errStr := strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
if errStr != expectedErr . Error ( ) {
t . Logf ( "\t\tGot : %+v" , errStr )
2019-06-22 17:48:44 -08:00
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
t . Logf ( "\t%s\tCreate ok." , tests . Success )
}
}
// TestCreateClaims validates ACLs are correctly applied to Create by claims.
func TestCreateClaims ( t * testing . T ) {
defer tests . Recover ( t )
var userTests = [ ] struct {
name string
claims auth . Claims
2019-06-24 22:41:21 -08:00
req UserCreateRequest
2019-06-22 17:48:44 -08:00
error error
} {
// Internal request, should bypass ACL.
{ "EmptyClaims" ,
auth . Claims { } ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
nil ,
} ,
// Role of user, only admins can create new users.
{ "RoleUser" ,
auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
ErrForbidden ,
} ,
// Role of admin, can create users.
{ "RoleAdmin" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
nil ,
} ,
}
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need to ensure claims are applied as ACL for create user." )
{
for i , tt := range userTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-08-13 23:41:06 -08:00
_ , err := repo . Create ( ctx , tt . claims , tt . req , now )
2019-06-22 17:48:44 -08:00
if errors . Cause ( err ) != tt . error {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
t . Logf ( "\t%s\tCreate ok." , tests . Success )
}
}
}
}
// TestUpdateValidation ensures all the validation tags work on Update
func TestUpdateValidation ( t * testing . T ) {
// TODO: actually create the user so can test the output of findbyId
type userTest struct {
name string
2019-06-24 22:41:21 -08:00
req UserUpdateRequest
2019-06-22 17:48:44 -08:00
error error
}
var userTests = [ ] userTest {
{ "Required Fields" ,
2019-06-24 22:41:21 -08:00
UserUpdateRequest { } ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'UserUpdateRequest.id' Error:Field validation for 'id' failed on the 'required' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
}
invalidEmail := "xxxxxxxxxx"
userTests = append ( userTests , userTest { "Valid Email" ,
2019-06-24 22:41:21 -08:00
UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : uuid . NewRandom ( ) . String ( ) ,
Email : & invalidEmail ,
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'UserUpdateRequest.email' Error:Field validation for 'email' failed on the 'email' tag" ) ,
2019-06-22 17:48:44 -08:00
} )
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure all validation tags are working for user update." )
{
for i , tt := range userTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-08-13 23:41:06 -08:00
err := repo . Update ( ctx , auth . Claims { } , tt . req , now )
2019-06-22 17:48:44 -08:00
if err != tt . error {
// TODO: need a better way to handle validation errors as they are
// of type interface validator.ValidationErrorsTranslations
var errStr string
if err != nil {
2019-08-01 16:17:47 -08:00
errStr = strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
2019-06-22 17:48:44 -08:00
}
var expectStr string
if tt . error != nil {
expectStr = tt . error . Error ( )
}
if errStr != expectStr {
2019-08-01 16:17:47 -08:00
t . Logf ( "\t\tGot : %+v" , errStr )
t . Logf ( "\t\tWant: %+v" , expectStr )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
}
}
t . Logf ( "\t%s\tUpdate ok." , tests . Success )
}
}
}
}
// TestUpdateValidationEmailUnique validates emails must be unique on Update.
func TestUpdateValidationEmailUnique ( t * testing . T ) {
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure duplicate emails are not allowed for user update." )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
req1 := UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
}
2019-08-13 23:41:06 -08:00
user1 , err := repo . Create ( ctx , auth . Claims { } , req1 , now )
2019-06-22 17:48:44 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
2019-06-24 22:41:21 -08:00
req2 := UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "W0rkL1fe#" ,
PasswordConfirm : "W0rkL1fe#" ,
}
2019-08-13 23:41:06 -08:00
user2 , err := repo . Create ( ctx , auth . Claims { } , req2 , now )
2019-06-22 17:48:44 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
// Try to set the email for user 1 on user 2
2019-06-24 22:41:21 -08:00
updateReq := UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user2 . ID ,
Email : & user1 . Email ,
}
2019-06-27 04:48:18 -08:00
expectedErr := errors . New ( "Key: 'UserUpdateRequest.email' Error:Field validation for 'email' failed on the 'unique' tag" )
2019-08-13 23:41:06 -08:00
err = repo . Update ( ctx , auth . Claims { } , updateReq , now )
2019-06-22 17:48:44 -08:00
if err == nil {
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
}
2019-08-01 16:17:47 -08:00
errStr := strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
if errStr != expectedErr . Error ( ) {
t . Logf ( "\t\tGot : %+v" , errStr )
2019-06-22 17:48:44 -08:00
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
}
t . Logf ( "\t%s\tUpdate ok." , tests . Success )
}
}
// TestUpdatePassword validates update user password works.
func TestUpdatePassword ( t * testing . T ) {
t . Log ( "Given the need ensure a user password can be updated." )
{
ctx := tests . Context ( )
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
// Create a new user for testing.
initPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
user , err := repo . Create ( ctx , auth . Claims { } , UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : initPass ,
PasswordConfirm : initPass ,
} , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
// Create a new random account.
accountId := uuid . NewRandom ( ) . String ( )
err = mockAccount ( accountId , user . CreatedAt )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate account failed." , tests . Failed )
}
// Associate new random account with user.
err = mockUserAccount ( user . ID , accountId , user . CreatedAt , auth . RoleUser )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
}
// Ensure validation is working by trying UpdatePassword with an empty request.
2019-06-27 04:48:18 -08:00
expectedErr := errors . New ( "Key: 'UserUpdatePasswordRequest.id' Error:Field validation for 'id' failed on the 'required' tag\n" +
2019-08-01 16:17:47 -08:00
"Key: 'UserUpdatePasswordRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
"Key: 'UserUpdatePasswordRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag" )
2019-08-13 23:41:06 -08:00
err = repo . UpdatePassword ( ctx , auth . Claims { } , UserUpdatePasswordRequest { } , now )
2019-06-22 17:48:44 -08:00
if err == nil {
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
2019-08-01 16:17:47 -08:00
}
errStr := strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
if errStr != expectedErr . Error ( ) {
t . Logf ( "\t\tGot : %+v" , errStr )
2019-06-22 17:48:44 -08:00
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tValidation failed." , tests . Failed )
}
t . Logf ( "\t%s\tValidation ok." , tests . Success )
// Update the users password.
newPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
err = repo . UpdatePassword ( ctx , auth . Claims { } , UserUpdatePasswordRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Password : newPass ,
PasswordConfirm : newPass ,
} , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
2019-08-02 15:03:32 -08:00
t . Fatalf ( "\t%s\tUpdate password failed." , tests . Failed )
2019-06-22 17:48:44 -08:00
}
t . Logf ( "\t%s\tUpdatePassword ok." , tests . Success )
}
}
// TestCrud validates the full set of CRUD operations for users and ensures ACLs are correctly applied by claims.
func TestCrud ( t * testing . T ) {
defer tests . Recover ( t )
type userTest struct {
name string
claims func ( * User , string ) auth . Claims
2019-06-24 22:41:21 -08:00
create UserCreateRequest
update func ( * User ) UserUpdateRequest
2019-06-22 17:48:44 -08:00
updateErr error
2019-06-24 22:41:21 -08:00
expected func ( * User , UserUpdateRequest ) * User
2019-06-22 17:48:44 -08:00
findErr error
}
var userTests [ ] userTest
// Internal request, should bypass ACL.
userTests = append ( userTests , userTest { "EmptyClaims" ,
func ( user * User , accountId string ) auth . Claims {
return auth . Claims { }
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( user * User ) UserUpdateRequest {
2019-06-22 17:48:44 -08:00
email := uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com"
2019-06-24 22:41:21 -08:00
return UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Email : & email ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( user * User , req UserUpdateRequest ) * User {
2019-06-22 17:48:44 -08:00
return & User {
Email : * req . Email ,
// Copy this fields from the created user.
ID : user . ID ,
2019-07-31 13:47:30 -08:00
FirstName : user . FirstName ,
LastName : user . LastName ,
2019-06-22 17:48:44 -08:00
PasswordSalt : user . PasswordSalt ,
PasswordHash : user . PasswordHash ,
PasswordReset : user . PasswordReset ,
Timezone : user . Timezone ,
CreatedAt : user . CreatedAt ,
UpdatedAt : user . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
// Role of user but claim user does not match update user so forbidden.
userTests = append ( userTests , userTest { "RoleUserDiffUser" ,
func ( user * User , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : accountId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( user * User ) UserUpdateRequest {
2019-06-22 17:48:44 -08:00
email := uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com"
2019-06-24 22:41:21 -08:00
return UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Email : & email ,
}
} ,
ErrForbidden ,
2019-06-24 22:41:21 -08:00
func ( user * User , req UserUpdateRequest ) * User {
2019-06-22 17:48:44 -08:00
return user
} ,
ErrNotFound ,
} )
// Role of user AND claim user matches update user so OK.
userTests = append ( userTests , userTest { "RoleUserSameUser" ,
func ( user * User , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : user . ID ,
Audience : accountId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( user * User ) UserUpdateRequest {
2019-06-22 17:48:44 -08:00
email := uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com"
2019-06-24 22:41:21 -08:00
return UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Email : & email ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( user * User , req UserUpdateRequest ) * User {
2019-06-22 17:48:44 -08:00
return & User {
Email : * req . Email ,
// Copy this fields from the created user.
ID : user . ID ,
2019-07-31 13:47:30 -08:00
FirstName : user . FirstName ,
LastName : user . LastName ,
2019-06-22 17:48:44 -08:00
PasswordSalt : user . PasswordSalt ,
PasswordHash : user . PasswordHash ,
PasswordReset : user . PasswordReset ,
Timezone : user . Timezone ,
CreatedAt : user . CreatedAt ,
UpdatedAt : user . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
// Role of admin but claim account does not match update user so forbidden.
userTests = append ( userTests , userTest { "RoleAdminDiffUser" ,
func ( user * User , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : uuid . NewRandom ( ) . String ( ) ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( user * User ) UserUpdateRequest {
2019-06-22 17:48:44 -08:00
email := uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com"
2019-06-24 22:41:21 -08:00
return UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Email : & email ,
}
} ,
ErrForbidden ,
2019-06-24 22:41:21 -08:00
func ( user * User , req UserUpdateRequest ) * User {
2019-06-22 17:48:44 -08:00
return nil
} ,
ErrNotFound ,
} )
// Role of admin and claim account matches update user so ok.
userTests = append ( userTests , userTest { "RoleAdminSameAccount" ,
func ( user * User , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : accountId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
2019-06-24 22:41:21 -08:00
func ( user * User ) UserUpdateRequest {
2019-06-22 17:48:44 -08:00
email := uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com"
2019-06-24 22:41:21 -08:00
return UserUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : user . ID ,
Email : & email ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( user * User , req UserUpdateRequest ) * User {
2019-06-22 17:48:44 -08:00
return & User {
Email : * req . Email ,
// Copy this fields from the created user.
ID : user . ID ,
2019-07-31 13:47:30 -08:00
FirstName : user . FirstName ,
LastName : user . LastName ,
2019-06-22 17:48:44 -08:00
PasswordSalt : user . PasswordSalt ,
PasswordHash : user . PasswordHash ,
PasswordReset : user . PasswordReset ,
Timezone : user . Timezone ,
CreatedAt : user . CreatedAt ,
UpdatedAt : user . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
t . Log ( "Given the need to ensure claims are applied as ACL for update user." )
{
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
for i , tt := range userTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
// Always create the new user with empty claims, testing claims for create user
// will be handled separately.
2019-08-13 23:41:06 -08:00
user , err := repo . Create ( tests . Context ( ) , auth . Claims { } , tt . create , now )
2019-06-22 17:48:44 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
2019-06-24 22:41:21 -08:00
t . Fatalf ( "\t%s\tCreate user failed." , tests . Failed )
2019-06-22 17:48:44 -08:00
}
2019-06-24 22:41:21 -08:00
// Create a random account for the new user.
2019-06-22 17:48:44 -08:00
accountId := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
err = mockAccount ( accountId , user . CreatedAt )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate account failed." , tests . Failed )
}
// Associate the account with the new test user.
2019-06-22 17:48:44 -08:00
err = mockUserAccount ( user . ID , accountId , user . CreatedAt , auth . RoleAdmin )
if err != nil {
t . Log ( "\t\tGot :" , err )
2019-06-24 22:41:21 -08:00
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
2019-06-22 17:48:44 -08:00
}
// Update the user.
updateReq := tt . update ( user )
2019-08-13 23:41:06 -08:00
err = repo . Update ( ctx , tt . claims ( user , accountId ) , updateReq , now )
2019-06-22 17:48:44 -08:00
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
}
t . Logf ( "\t%s\tUpdate ok." , tests . Success )
// Find the user and make sure the updates where made.
2019-08-13 23:41:06 -08:00
findRes , err := repo . ReadByID ( ctx , tt . claims ( user , accountId ) , user . ID )
2019-06-22 17:48:44 -08:00
if err != nil && errors . Cause ( err ) != tt . findErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . findErr )
t . Fatalf ( "\t%s\tRead failed." , tests . Failed )
} else {
findExpected := tt . expected ( findRes , updateReq )
if diff := cmp . Diff ( findRes , findExpected ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected find result to match update. Diff:\n%s" , tests . Failed , diff )
}
t . Logf ( "\t%s\tRead ok." , tests . Success )
}
// Archive (soft-delete) the user.
2019-08-13 23:41:06 -08:00
err = repo . Archive ( ctx , tt . claims ( user , accountId ) , UserArchiveRequest { ID : user . ID , force : true } , now )
2019-06-22 17:48:44 -08:00
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
t . Fatalf ( "\t%s\tArchive failed." , tests . Failed )
} else if tt . updateErr == nil {
// Trying to find the archived user with the includeArchived false should result in not found.
2019-08-13 23:41:06 -08:00
_ , err = repo . ReadByID ( ctx , tt . claims ( user , accountId ) , user . ID )
2019-06-22 17:48:44 -08:00
if err != nil && errors . Cause ( err ) != ErrNotFound {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , ErrNotFound )
t . Fatalf ( "\t%s\tArchive Read failed." , tests . Failed )
}
// Trying to find the archived user with the includeArchived true should result no error.
2019-08-13 23:41:06 -08:00
_ , err = repo . Read ( ctx , tt . claims ( user , accountId ) ,
2019-08-04 14:48:43 -08:00
UserReadRequest { ID : user . ID , IncludeArchived : true } )
2019-06-22 17:48:44 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tArchive Read failed." , tests . Failed )
}
}
t . Logf ( "\t%s\tArchive ok." , tests . Success )
2019-08-04 14:48:43 -08:00
// Restore (un-delete) the user.
2019-08-13 23:41:06 -08:00
err = repo . Restore ( ctx , tt . claims ( user , accountId ) , UserRestoreRequest { ID : user . ID } , now )
2019-08-02 15:03:32 -08:00
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
t . Fatalf ( "\t%s\tUnarchive failed." , tests . Failed )
} else if tt . updateErr == nil {
// Trying to find the archived user with the includeArchived false should result no error.
2019-08-13 23:41:06 -08:00
_ , err = repo . ReadByID ( ctx , tt . claims ( user , accountId ) , user . ID )
2019-08-02 15:03:32 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tUnarchive Read failed." , tests . Failed )
}
}
t . Logf ( "\t%s\tUnarchive ok." , tests . Success )
2019-06-22 17:48:44 -08:00
// Delete (hard-delete) the user.
2019-08-13 23:41:06 -08:00
err = repo . Delete ( ctx , tt . claims ( user , accountId ) , UserDeleteRequest { ID : user . ID , force : true } )
2019-06-22 17:48:44 -08:00
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
} else if tt . updateErr == nil {
// Trying to find the deleted user with the includeArchived true should result in not found.
2019-08-13 23:41:06 -08:00
_ , err = repo . ReadByID ( ctx , tt . claims ( user , accountId ) , user . ID )
2019-06-22 17:48:44 -08:00
if errors . Cause ( err ) != ErrNotFound {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , ErrNotFound )
t . Fatalf ( "\t%s\tDelete Read failed." , tests . Failed )
}
}
t . Logf ( "\t%s\tDelete ok." , tests . Success )
}
}
}
}
// TestFind validates all the request params are correctly parsed into a select query.
func TestFind ( t * testing . T ) {
now := time . Now ( ) . Add ( time . Hour * - 1 ) . UTC ( )
startTime := now . Truncate ( time . Millisecond )
var endTime time . Time
var users [ ] * User
for i := 0 ; i <= 4 ; i ++ {
2019-08-14 11:40:26 -08:00
user , err := repo . Create ( tests . Context ( ) , auth . Claims { } , UserCreateRequest {
2019-07-31 13:47:30 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
2019-06-22 17:48:44 -08:00
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} , now . Add ( time . Second * time . Duration ( i ) ) )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
users = append ( users , user )
endTime = user . CreatedAt
}
type userTest struct {
name string
req UserFindRequest
2019-08-05 17:12:28 -08:00
expected Users
2019-06-22 17:48:44 -08:00
error error
}
var userTests [ ] userTest
createdFilter := "created_at BETWEEN ? AND ?"
// Test sort users.
userTests = append ( userTests , userTest { "Find all order by created_at asc" ,
UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : createdFilter ,
2019-06-22 17:48:44 -08:00
Args : [ ] interface { } { startTime , endTime } ,
Order : [ ] string { "created_at" } ,
} ,
users ,
nil ,
} )
// Test reverse sorted users.
2019-08-05 17:12:28 -08:00
var expected Users
2019-06-22 17:48:44 -08:00
for i := len ( users ) - 1 ; i >= 0 ; i -- {
expected = append ( expected , users [ i ] )
}
userTests = append ( userTests , userTest { "Find all order by created_at desc" ,
UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : createdFilter ,
2019-06-22 17:48:44 -08:00
Args : [ ] interface { } { startTime , endTime } ,
Order : [ ] string { "created_at desc" } ,
} ,
expected ,
nil ,
} )
// Test limit.
var limit uint = 2
userTests = append ( userTests , userTest { "Find limit" ,
UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : createdFilter ,
2019-06-22 17:48:44 -08:00
Args : [ ] interface { } { startTime , endTime } ,
Order : [ ] string { "created_at" } ,
Limit : & limit ,
} ,
users [ 0 : 2 ] ,
nil ,
} )
// Test offset.
var offset uint = 3
userTests = append ( userTests , userTest { "Find limit, offset" ,
UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : createdFilter ,
2019-06-22 17:48:44 -08:00
Args : [ ] interface { } { startTime , endTime } ,
Order : [ ] string { "created_at" } ,
Limit : & limit ,
Offset : & offset ,
} ,
users [ 3 : 5 ] ,
nil ,
} )
// Test where filter.
whereParts := [ ] string { }
whereArgs := [ ] interface { } { startTime , endTime }
expected = [ ] * User { }
for i := 0 ; i <= len ( users ) ; i ++ {
if rand . Intn ( 100 ) < 50 {
continue
}
u := * users [ i ]
whereParts = append ( whereParts , "email = ?" )
whereArgs = append ( whereArgs , u . Email )
expected = append ( expected , & u )
}
where := createdFilter + " AND (" + strings . Join ( whereParts , " OR " ) + ")"
userTests = append ( userTests , userTest { "Find where" ,
UserFindRequest {
2019-08-05 17:12:28 -08:00
Where : where ,
2019-06-22 17:48:44 -08:00
Args : whereArgs ,
Order : [ ] string { "created_at" } ,
} ,
expected ,
nil ,
} )
t . Log ( "Given the need to ensure find users returns the expected results." )
{
for i , tt := range userTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-08-14 11:40:26 -08:00
res , err := repo . Find ( ctx , auth . Claims { } , tt . req )
2019-06-22 17:48:44 -08:00
if errors . Cause ( err ) != tt . error {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
t . Fatalf ( "\t%s\tFind failed." , tests . Failed )
} else if diff := cmp . Diff ( res , tt . expected ) ; diff != "" {
t . Logf ( "\t\tGot: %d items" , len ( res ) )
t . Logf ( "\t\tWant: %d items" , len ( tt . expected ) )
for _ , u := range res {
t . Logf ( "\t\tGot: %s ID" , u . ID )
}
for _ , u := range tt . expected {
t . Logf ( "\t\tExpected: %s ID" , u . ID )
}
t . Fatalf ( "\t%s\tExpected find result to match expected. Diff:\n%s" , tests . Failed , diff )
}
t . Logf ( "\t%s\tFind ok." , tests . Success )
}
}
}
}
2019-08-02 15:03:32 -08:00
// TestResetPassword validates that reset password for a user works.
func TestResetPassword ( t * testing . T ) {
t . Log ( "Given the need ensure a user can reset their password." )
{
ctx := tests . Context ( )
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
// Create a new user for testing.
initPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
user , err := repo . Create ( ctx , auth . Claims { } , UserCreateRequest {
2019-08-02 15:03:32 -08:00
FirstName : "Lee" ,
LastName : "Brown" ,
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : initPass ,
PasswordConfirm : initPass ,
} , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
// Create a new random account.
accountId := uuid . NewRandom ( ) . String ( )
err = mockAccount ( accountId , user . CreatedAt )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate account failed." , tests . Failed )
}
// Associate new random account with user.
err = mockUserAccount ( user . ID , accountId , user . CreatedAt , auth . RoleUser )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
}
// Ensure validation is working by trying ResetPassword with an empty request.
{
expectedErr := errors . New ( "Key: 'UserResetPasswordRequest.email' Error:Field validation for 'email' failed on the 'required' tag" )
2019-08-13 23:41:06 -08:00
_ , err = repo . ResetPassword ( ctx , UserResetPasswordRequest { } , now )
2019-08-02 15:03:32 -08:00
if err == nil {
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tResetPassword failed." , tests . Failed )
}
errStr := strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
if errStr != expectedErr . Error ( ) {
t . Logf ( "\t\tGot : %+v" , errStr )
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tResetPassword Validation failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetPassword Validation ok." , tests . Success )
}
ttl := time . Hour
// Make the reset password request.
2019-08-13 23:41:06 -08:00
resetHash , err := repo . ResetPassword ( ctx , UserResetPasswordRequest {
2019-08-02 15:03:32 -08:00
Email : user . Email ,
TTL : ttl ,
2019-08-13 23:41:06 -08:00
} , now )
2019-08-02 15:03:32 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tResetPassword failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetPassword ok." , tests . Success )
// Read the user to ensure the password_reset field was set.
2019-08-13 23:41:06 -08:00
user , err = repo . ReadByID ( ctx , auth . Claims { } , user . ID )
2019-08-02 15:03:32 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tRead failed." , tests . Failed )
} else if user . PasswordReset == nil || user . PasswordReset . String == "" {
t . Fatalf ( "\t%s\tUser field password_reset is empty." , tests . Failed )
}
// Ensure validation is working by trying ResetConfirm with an empty request.
{
expectedErr := errors . New ( "Key: 'UserResetConfirmRequest.reset_hash' Error:Field validation for 'reset_hash' failed on the 'required' tag\n" +
"Key: 'UserResetConfirmRequest.password' Error:Field validation for 'password' failed on the 'required' tag\n" +
"Key: 'UserResetConfirmRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'required' tag" )
2019-08-13 23:41:06 -08:00
_ , err = repo . ResetConfirm ( ctx , UserResetConfirmRequest { } , now )
2019-08-02 15:03:32 -08:00
if err == nil {
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tResetConfirm failed." , tests . Failed )
}
errStr := strings . Replace ( err . Error ( ) , "{{" , "" , - 1 )
errStr = strings . Replace ( errStr , "}}" , "" , - 1 )
if errStr != expectedErr . Error ( ) {
t . Logf ( "\t\tGot : %+v" , errStr )
t . Logf ( "\t\tWant: %+v" , expectedErr )
t . Fatalf ( "\t%s\tResetConfirm Validation failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetConfirm Validation ok." , tests . Success )
}
// Ensure the TTL is enforced.
{
newPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
_ , err = repo . ResetConfirm ( ctx , UserResetConfirmRequest {
2019-08-02 15:03:32 -08:00
ResetHash : resetHash ,
Password : newPass ,
PasswordConfirm : newPass ,
2019-08-13 23:41:06 -08:00
} , now . UTC ( ) . Add ( ttl * 2 ) )
2019-08-02 15:03:32 -08:00
if errors . Cause ( err ) != ErrResetExpired {
t . Logf ( "\t\tGot : %+v" , errors . Cause ( err ) )
t . Logf ( "\t\tWant: %+v" , ErrResetExpired )
t . Fatalf ( "\t%s\tResetConfirm enforce TTL failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetConfirm enforce TTL ok." , tests . Success )
}
// Assuming we have received the email and clicked the link, we now can ensure confirm works.
newPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
reset , err := repo . ResetConfirm ( ctx , UserResetConfirmRequest {
2019-08-02 15:03:32 -08:00
ResetHash : resetHash ,
Password : newPass ,
PasswordConfirm : newPass ,
2019-08-13 23:41:06 -08:00
} , now )
2019-08-02 15:03:32 -08:00
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tResetConfirm failed." , tests . Failed )
} else if reset . ID != user . ID {
t . Logf ( "\t\tGot : %+v" , reset . ID )
t . Logf ( "\t\tWant: %+v" , user . ID )
t . Fatalf ( "\t%s\tResetConfirm failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetConfirm ok." , tests . Success )
// Ensure the reset hash does not work after its used.
{
newPass := uuid . NewRandom ( ) . String ( )
2019-08-13 23:41:06 -08:00
_ , err = repo . ResetConfirm ( ctx , UserResetConfirmRequest {
2019-08-02 15:03:32 -08:00
ResetHash : resetHash ,
Password : newPass ,
PasswordConfirm : newPass ,
2019-08-13 23:41:06 -08:00
} , now )
2019-08-02 15:03:32 -08:00
if errors . Cause ( err ) != ErrNotFound {
t . Logf ( "\t\tGot : %+v" , errors . Cause ( err ) )
t . Logf ( "\t\tWant: %+v" , ErrNotFound )
t . Fatalf ( "\t%s\tResetConfirm enforce TTL failed." , tests . Failed )
}
t . Logf ( "\t%s\tResetConfirm reuse disabled ok." , tests . Success )
}
}
}
2019-06-22 17:48:44 -08:00
func mockUserAccount ( userId , accountId string , now time . Time , roles ... string ) error {
var roleArr pq . StringArray
for _ , r := range roles {
roleArr = append ( roleArr , r )
}
// Build the insert SQL statement.
query := sqlbuilder . NewInsertBuilder ( )
query . InsertInto ( userAccountTableName )
query . Cols ( "id" , "user_id" , "account_id" , "roles" , "created_at" , "updated_at" )
query . Values ( uuid . NewRandom ( ) . String ( ) , userId , accountId , roleArr , now , now )
// Execute the query with the provided context.
sql , args := query . Build ( )
sql = test . MasterDB . Rebind ( sql )
_ , err := test . MasterDB . ExecContext ( tests . Context ( ) , sql , args ... )
if err != nil {
err = errors . Wrapf ( err , "query - %s" , query . String ( ) )
return err
}
return nil
}
func mockAccount ( accountId string , now time . Time ) error {
// Build the insert SQL statement.
query := sqlbuilder . NewInsertBuilder ( )
query . InsertInto ( accountTableName )
query . Cols ( "id" , "name" , "created_at" , "updated_at" )
query . Values ( accountId , uuid . NewRandom ( ) . String ( ) , now , now )
// Execute the query with the provided context.
sql , args := query . Build ( )
sql = test . MasterDB . Rebind ( sql )
_ , err := test . MasterDB . ExecContext ( tests . Context ( ) , sql , args ... )
if err != nil {
err = errors . Wrapf ( err , "query - %s" , query . String ( ) )
return err
}
return nil
}