2019-06-24 22:41:21 -08:00
package user_account
2019-05-29 03:35:08 -05:00
import (
"github.com/lib/pq"
"math/rand"
2019-06-22 17:48:44 -08:00
"os"
2019-05-29 03:35:08 -05:00
"strings"
"testing"
"time"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
2019-05-29 15:05:17 -05:00
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
2019-05-29 03:35:08 -05:00
"github.com/dgrijalva/jwt-go"
2019-05-29 15:05:17 -05:00
"github.com/google/go-cmp/cmp"
2019-05-29 03:35:08 -05:00
"github.com/huandu/go-sqlbuilder"
"github.com/pborman/uuid"
"github.com/pkg/errors"
)
2019-06-22 17:48:44 -08:00
var test * tests . Test
// 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 ( )
return m . Run ( )
}
// TestFindRequestQuery validates findRequestQuery
func TestFindRequestQuery ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
where := "account_id = ? or user_id = ?"
var (
limit uint = 12
offset uint = 34
)
req := UserAccountFindRequest {
Where : & where ,
Args : [ ] interface { } {
"xy7" ,
"qwert" ,
} ,
Order : [ ] string {
"id asc" ,
"created_at desc" ,
} ,
Limit : & limit ,
Offset : & offset ,
}
2019-06-22 17:48:44 -08:00
expected := "SELECT " + userAccountMapColumns + " FROM " + userAccountTableName + " WHERE (account_id = ? or user_id = ?) ORDER BY id asc, created_at desc LIMIT 12 OFFSET 34"
2019-05-29 03:35:08 -05:00
2019-06-22 17:48:44 -08:00
res , args := findRequestQuery ( req )
2019-05-29 03:35:08 -05:00
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 )
}
}
2019-06-22 17:48:44 -08:00
// TestApplyClaimsSelectvalidates applyClaimsSelect
func TestApplyClaimsSelectvalidates ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
var claimTests = [ ] struct {
name string
claims auth . Claims
expectedSql string
error error
} {
{ "EmptyClaims" ,
auth . Claims { } ,
2019-06-22 17:48:44 -08:00
"SELECT " + userAccountMapColumns + " FROM " + userAccountTableName ,
2019-05-29 03:35:08 -05:00
nil ,
} ,
{ "RoleUser" ,
auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-22 17:48:44 -08:00
"SELECT " + userAccountMapColumns + " FROM " + userAccountTableName + " WHERE id IN (SELECT id FROM " + userAccountTableName + " WHERE (account_id = 'acc1' OR user_id = 'user1'))" ,
2019-05-29 03:35:08 -05:00
nil ,
} ,
{ "RoleAdmin" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-22 17:48:44 -08:00
"SELECT " + userAccountMapColumns + " FROM " + userAccountTableName + " WHERE id IN (SELECT id FROM " + userAccountTableName + " WHERE (account_id = 'acc1' OR user_id = 'user1'))" ,
2019-05-29 03:35:08 -05:00
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 ( )
2019-06-22 17:48:44 -08:00
query := selectQuery ( )
2019-05-29 03:35:08 -05:00
2019-06-22 17:48:44 -08:00
err := applyClaimsSelect ( ctx , tt . claims , query )
2019-05-29 03:35:08 -05:00
if err != tt . error {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tapplyClaimsSelect failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
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 )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tapplyClaimsSelect failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
if diff := cmp . Diff ( sql , tt . expectedSql ) ; diff != "" {
t . Fatalf ( "\t%s\tExpected result query to match. Diff:\n%s" , tests . Failed , diff )
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tapplyClaimsSelect ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
}
}
2019-06-22 17:48:44 -08:00
// TestCreateValidation ensures all the validation tags work on user account create.
func TestCreateValidation ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
invalidRole := UserAccountRole ( "moon" )
invalidStatus := UserAccountStatus ( "moon" )
var accountTests = [ ] struct {
name string
2019-06-22 17:48:44 -08:00
req CreateUserAccountRequest
expected func ( req CreateUserAccountRequest , res * UserAccount ) * UserAccount
2019-05-29 03:35:08 -05:00
error error
} {
{ "Required Fields" ,
2019-06-22 17:48:44 -08:00
CreateUserAccountRequest { } ,
func ( req CreateUserAccountRequest , res * UserAccount ) * UserAccount {
2019-05-29 03:35:08 -05:00
return nil
} ,
2019-06-22 17:48:44 -08:00
errors . New ( "Key: 'CreateUserAccountRequest.UserID' Error:Field validation for 'UserID' failed on the 'required' tag\n" +
"Key: 'CreateUserAccountRequest.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag\n" +
"Key: 'CreateUserAccountRequest.Roles' Error:Field validation for 'Roles' failed on the 'required' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
{ "Valid Role" ,
2019-06-22 17:48:44 -08:00
CreateUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 03:35:08 -05:00
AccountID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { invalidRole } ,
2019-05-29 03:35:08 -05:00
} ,
2019-06-22 17:48:44 -08:00
func ( req CreateUserAccountRequest , res * UserAccount ) * UserAccount {
2019-05-29 03:35:08 -05:00
return nil
} ,
2019-06-22 17:48:44 -08:00
errors . New ( "Key: 'CreateUserAccountRequest.Roles[0]' Error:Field validation for 'Roles[0]' failed on the 'oneof' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
{ "Valid Status" ,
2019-06-22 17:48:44 -08:00
CreateUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 03:35:08 -05:00
AccountID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
Status : & invalidStatus ,
2019-05-29 03:35:08 -05:00
} ,
2019-06-22 17:48:44 -08:00
func ( req CreateUserAccountRequest , res * UserAccount ) * UserAccount {
2019-05-29 03:35:08 -05:00
return nil
} ,
2019-06-22 17:48:44 -08:00
errors . New ( "Key: 'CreateUserAccountRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
{ "Default Status" ,
2019-06-22 17:48:44 -08:00
CreateUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 03:35:08 -05:00
AccountID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
2019-05-29 03:35:08 -05:00
} ,
2019-06-22 17:48:44 -08:00
func ( req CreateUserAccountRequest , res * UserAccount ) * UserAccount {
2019-05-29 03:35:08 -05:00
return & UserAccount {
2019-05-29 15:05:17 -05:00
UserID : req . UserID ,
AccountID : req . AccountID ,
Roles : req . Roles ,
Status : UserAccountStatus_Active ,
2019-05-29 03:35:08 -05:00
// Copy this fields from the result.
2019-05-29 15:05:17 -05:00
ID : res . ID ,
CreatedAt : res . CreatedAt ,
UpdatedAt : res . UpdatedAt ,
2019-05-29 03:35:08 -05:00
//ArchivedAt: nil,
}
} ,
nil ,
} ,
}
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
2019-06-22 17:48:44 -08:00
t . Log ( "Given the need ensure all validation tags are working for create user account." )
2019-05-29 03:35:08 -05:00
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
// Generate a new random user.
err := mockUser ( tt . req . UserID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock user failed." , tests . Failed )
}
// Generate a new random account.
err = mockAccount ( tt . req . AccountID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock account failed." , tests . Failed )
}
2019-06-22 17:48:44 -08:00
res , err := Create ( ctx , auth . Claims { } , test . MasterDB , tt . req , now )
2019-05-29 03:35:08 -05: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 {
errStr = err . Error ( )
}
var expectStr string
if tt . error != nil {
expectStr = tt . error . Error ( )
}
if errStr != expectStr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
}
// If there was an error that was expected, then don't go any further
if tt . error != nil {
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tCreate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
continue
}
expected := tt . expected ( tt . req , res )
if diff := cmp . Diff ( res , expected ) ; diff != "" {
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate user account result should match. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tCreate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
}
}
2019-06-22 17:48:44 -08:00
// TestCreateExistingEntry ensures that if an archived user account exist,
// the entry is updated rather than erroring on duplicate constraint.
func TestCreateExistingEntry ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure duplicate entries for the same user ID + account ID are updated and does not throw a duplicate key error." )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
// Generate a new random user.
userID := uuid . NewRandom ( ) . String ( )
err := mockUser ( userID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock user failed." , tests . Failed )
}
// Generate a new random account.
accountID := uuid . NewRandom ( ) . String ( )
err = mockAccount ( accountID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock account failed." , tests . Failed )
}
2019-06-22 17:48:44 -08:00
req1 := CreateUserAccountRequest {
2019-06-24 22:41:21 -08:00
UserID : userID ,
AccountID : accountID ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
ua1 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req1 , now )
2019-05-29 03:35:08 -05:00
if err != nil {
t . Log ( "\t\tGot :" , err )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
} else if diff := cmp . Diff ( ua1 . Roles , req1 . Roles ) ; diff != "" {
t . Fatalf ( "\t%s\tCreate user account roles should match request. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
req2 := CreateUserAccountRequest {
UserID : req1 . UserID ,
AccountID : req1 . AccountID ,
Roles : [ ] UserAccountRole { UserAccountRole_Admin } ,
}
ua2 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req2 , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
} else if diff := cmp . Diff ( ua2 . Roles , req2 . Roles ) ; diff != "" {
t . Fatalf ( "\t%s\tCreate user account roles should match request. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
// Now archive the user account to test trying to create a new entry for an archived entry
err = Archive ( tests . Context ( ) , auth . Claims { } , test . MasterDB , ArchiveUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : req1 . UserID ,
2019-05-29 03:35:08 -05:00
AccountID : req1 . AccountID ,
2019-06-22 17:48:44 -08:00
} , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tArchive user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
// Find the archived user account
arcRes , err := Read ( tests . Context ( ) , auth . Claims { } , test . MasterDB , ua2 . ID , true )
if err != nil || arcRes == nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tFind user account failed." , tests . Failed )
} else if ! arcRes . ArchivedAt . Valid || arcRes . ArchivedAt . Time . IsZero ( ) {
t . Fatalf ( "\t%s\tExpected user account to have archived_at set" , tests . Failed )
}
// Attempt to create the duplicate user account which should set archived_at back to nil
req3 := CreateUserAccountRequest {
UserID : req1 . UserID ,
AccountID : req1 . AccountID ,
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
}
ua3 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req3 , now )
2019-05-29 03:35:08 -05:00
if err != nil {
t . Log ( "\t\tGot :" , err )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
} else if diff := cmp . Diff ( ua3 . Roles , req3 . Roles ) ; diff != "" {
t . Fatalf ( "\t%s\tCreate user account roles should match request. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
// Ensure the user account has archived_at empty
findRes , err := Read ( tests . Context ( ) , auth . Claims { } , test . MasterDB , ua3 . ID , false )
if err != nil || arcRes == nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tFind user account failed." , tests . Failed )
} else if findRes . ArchivedAt . Valid && ! findRes . ArchivedAt . Time . IsZero ( ) {
t . Fatalf ( "\t%s\tExpected user account to have archived_at empty" , tests . Failed )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tCreate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
2019-06-22 17:48:44 -08:00
// TestUpdateValidation ensures all the validation tags work on user account update.
func TestUpdateValidation ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
invalidRole := UserAccountRole ( "moon" )
invalidStatus := UserAccountStatus ( "xxxxxxxxx" )
var accountTests = [ ] struct {
2019-05-29 15:05:17 -05:00
name string
2019-06-22 17:48:44 -08:00
req UpdateUserAccountRequest
2019-05-29 15:05:17 -05:00
error error
2019-05-29 03:35:08 -05:00
} {
{ "Required Fields" ,
2019-06-22 17:48:44 -08:00
UpdateUserAccountRequest { } ,
errors . New ( "Key: 'UpdateUserAccountRequest.UserID' Error:Field validation for 'UserID' failed on the 'required' tag\n" +
"Key: 'UpdateUserAccountRequest.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag\n" +
"Key: 'UpdateUserAccountRequest.Roles' Error:Field validation for 'Roles' failed on the 'required' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
{ "Valid Role" ,
2019-06-22 17:48:44 -08:00
UpdateUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 03:35:08 -05:00
AccountID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 15:05:17 -05:00
Roles : & UserAccountRoles { invalidRole } ,
2019-05-29 03:35:08 -05:00
} ,
2019-06-22 17:48:44 -08:00
errors . New ( "Key: 'UpdateUserAccountRequest.Roles[0]' Error:Field validation for 'Roles[0]' failed on the 'oneof' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
{ "Valid Status" ,
2019-06-22 17:48:44 -08:00
UpdateUserAccountRequest {
2019-05-29 15:05:17 -05:00
UserID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 03:35:08 -05:00
AccountID : uuid . NewRandom ( ) . String ( ) ,
2019-05-29 15:05:17 -05:00
Roles : & UserAccountRoles { UserAccountRole_User } ,
Status : & invalidStatus ,
2019-05-29 03:35:08 -05:00
} ,
2019-06-22 17:48:44 -08:00
errors . New ( "Key: 'UpdateUserAccountRequest.Status' Error:Field validation for 'Status' failed on the 'oneof' tag" ) ,
2019-05-29 03:35:08 -05:00
} ,
}
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
2019-06-22 17:48:44 -08:00
t . Log ( "Given the need ensure all validation tags are working for update user account." )
2019-05-29 03:35:08 -05:00
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-06-22 17:48:44 -08:00
err := Update ( ctx , auth . Claims { } , test . MasterDB , tt . req , now )
2019-05-29 03:35:08 -05: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 {
errStr = err . Error ( )
}
var expectStr string
if tt . error != nil {
expectStr = tt . error . Error ( )
}
if errStr != expectStr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tUpdate user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
}
// If there was an error that was expected, then don't go any further
if tt . error != nil {
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tUpdate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
continue
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tUpdate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
}
}
2019-06-22 17:48:44 -08:00
// TestCrud validates the full set of CRUD operations for user accounts and
2019-05-29 03:35:08 -05:00
// ensures ACLs are correctly applied by claims.
2019-06-22 17:48:44 -08:00
func TestCrud ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
defer tests . Recover ( t )
type accountTest struct {
name string
claims func ( string , string ) auth . Claims
2019-06-22 17:48:44 -08:00
createErr error
2019-05-29 03:35:08 -05:00
updateErr error
findErr error
}
var accountTests [ ] accountTest
// Internal request, should bypass ACL.
accountTests = append ( accountTests , accountTest { "EmptyClaims" ,
func ( userID , accountId string ) auth . Claims {
return auth . Claims { }
} ,
nil ,
nil ,
2019-06-22 17:48:44 -08:00
nil ,
2019-05-29 03:35:08 -05:00
} )
2019-06-22 17:48:44 -08:00
// Role of user but claim user does not match update user so forbidden for update.
2019-05-29 03:35:08 -05:00
accountTests = append ( accountTests , accountTest { "RoleUserDiffUser" ,
func ( userID , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : accountId ,
} ,
}
} ,
ErrForbidden ,
2019-06-22 17:48:44 -08:00
ErrForbidden ,
ErrForbidden ,
2019-05-29 03:35:08 -05:00
} )
// Role of user AND claim user matches update user so OK.
accountTests = append ( accountTests , accountTest { "RoleUserSameUser" ,
func ( userID , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleUser } ,
StandardClaims : jwt . StandardClaims {
Subject : userID ,
Audience : accountId ,
} ,
}
} ,
2019-06-22 17:48:44 -08:00
ErrForbidden ,
ErrForbidden ,
2019-05-29 03:35:08 -05:00
nil ,
} )
// Role of admin but claim account does not match update user so forbidden.
accountTests = append ( accountTests , accountTest { "RoleAdminDiffUser" ,
func ( userID , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : uuid . NewRandom ( ) . String ( ) ,
} ,
}
} ,
ErrForbidden ,
2019-06-22 17:48:44 -08:00
ErrForbidden ,
2019-05-29 03:35:08 -05:00
ErrNotFound ,
} )
// Role of admin and claim account matches update user so ok.
accountTests = append ( accountTests , accountTest { "RoleAdminSameAccount" ,
func ( userID , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : uuid . NewRandom ( ) . String ( ) ,
Audience : accountId ,
} ,
}
} ,
nil ,
nil ,
2019-06-22 17:48:44 -08:00
nil ,
2019-05-29 03:35:08 -05:00
} )
t . Log ( "Given the need to validate CRUD functionality for user accounts and ensure claims are applied as ACL." )
{
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
2019-06-24 22:41:21 -08:00
// Generate a new random user.
2019-06-22 17:48:44 -08:00
userID := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
err := mockUser ( userID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock user failed." , tests . Failed )
}
// Generate a new random account.
2019-05-29 03:35:08 -05:00
accountID := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
err = mockAccount ( accountID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tMock account failed." , tests . Failed )
}
// Associate that with the user.
2019-06-22 17:48:44 -08:00
createReq := CreateUserAccountRequest {
UserID : userID ,
2019-05-29 03:35:08 -05:00
AccountID : accountID ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
ua , err := Create ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , createReq , now )
if err != nil && errors . Cause ( err ) != tt . createErr {
2019-05-29 03:35:08 -05:00
t . Logf ( "\t\tGot : %+v" , err )
2019-06-22 17:48:44 -08:00
t . Logf ( "\t\tWant: %+v" , tt . createErr )
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
} else if tt . createErr == nil {
2019-05-29 03:35:08 -05:00
if diff := cmp . Diff ( ua . Roles , createReq . Roles ) ; diff != "" {
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tExpected user account roles result to match for create. Diff:\n%s" , tests . Failed , diff )
}
t . Logf ( "\t%s\tCreate user account ok." , tests . Success )
}
if tt . createErr == ErrForbidden {
ua , err = Create ( tests . Context ( ) , auth . Claims { } , test . MasterDB , createReq , now )
if err != nil && errors . Cause ( err ) != tt . createErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
}
// Update the account.
2019-06-22 17:48:44 -08:00
updateReq := UpdateUserAccountRequest {
UserID : userID ,
2019-05-29 03:35:08 -05:00
AccountID : accountID ,
2019-05-29 15:05:17 -05:00
Roles : & UserAccountRoles { UserAccountRole_Admin } ,
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
err = Update ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , updateReq , now )
if err != nil {
if errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
t . Fatalf ( "\t%s\tUpdate user account failed." , tests . Failed )
}
} else {
ua . Roles = * updateReq . Roles
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tUpdate user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
// Find the account for the user to verify the updates where made. There should only
// be one account associated with the user for this test.
2019-06-22 17:48:44 -08:00
ff := "user_id = ? or account_id = ?"
findRes , err := Find ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , UserAccountFindRequest {
Where : & ff ,
Args : [ ] interface { } { userID , accountID } ,
Order : [ ] string { "created_at" } ,
} )
2019-05-29 03:35:08 -05:00
if err != nil && errors . Cause ( err ) != tt . findErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . findErr )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tVerify update user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
} else if tt . findErr == nil {
expected := [ ] * UserAccount {
& UserAccount {
2019-05-29 15:05:17 -05:00
ID : ua . ID ,
UserID : ua . UserID ,
2019-05-29 03:35:08 -05:00
AccountID : ua . AccountID ,
2019-06-22 17:48:44 -08:00
Roles : ua . Roles ,
2019-05-29 15:05:17 -05:00
Status : ua . Status ,
CreatedAt : ua . CreatedAt ,
2019-05-29 03:35:08 -05:00
UpdatedAt : now ,
} ,
}
if diff := cmp . Diff ( findRes , expected ) ; diff != "" {
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tExpected user account find result to match update. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tVerify update user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
// Archive (soft-delete) the user account.
2019-06-22 17:48:44 -08:00
err = Archive ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , ArchiveUserAccountRequest {
UserID : userID ,
2019-05-29 03:35:08 -05:00
AccountID : accountID ,
} , now )
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tArchive user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
} else if tt . updateErr == nil {
// Trying to find the archived user with the includeArchived false should result in not found.
2019-06-22 17:48:44 -08:00
_ , err = FindByUserID ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , userID , false )
2019-05-29 03:35:08 -05:00
if errors . Cause ( err ) != ErrNotFound {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , ErrNotFound )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tVerify archive user account failed when excluding archived." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
// Trying to find the archived user with the includeArchived true should result no error.
2019-06-22 17:48:44 -08:00
findRes , err = FindByUserID ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , userID , true )
2019-05-29 03:35:08 -05:00
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tVerify archive user account failed when including archived." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
expected := [ ] * UserAccount {
& UserAccount {
2019-05-29 15:05:17 -05:00
ID : ua . ID ,
UserID : ua . UserID ,
AccountID : ua . AccountID ,
Roles : * updateReq . Roles ,
Status : ua . Status ,
CreatedAt : ua . CreatedAt ,
UpdatedAt : now ,
ArchivedAt : pq . NullTime { Time : now , Valid : true } ,
2019-05-29 03:35:08 -05:00
} ,
}
if diff := cmp . Diff ( findRes , expected ) ; diff != "" {
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tExpected user account find result to be archived. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tArchive user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
// Delete (hard-delete) the user account.
2019-06-22 17:48:44 -08:00
err = Delete ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , DeleteUserAccountRequest {
UserID : userID ,
2019-05-29 03:35:08 -05:00
AccountID : accountID ,
} )
if err != nil && errors . Cause ( err ) != tt . updateErr {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . updateErr )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tDelete user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
} else if tt . updateErr == nil {
// Trying to find the deleted user with the includeArchived true should result in not found.
2019-06-22 17:48:44 -08:00
_ , err = FindByUserID ( tests . Context ( ) , tt . claims ( userID , accountID ) , test . MasterDB , userID , true )
2019-05-29 03:35:08 -05:00
if errors . Cause ( err ) != ErrNotFound {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , ErrNotFound )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tVerify delete user account failed when including archived." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tDelete user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
}
}
2019-06-22 17:48:44 -08:00
// TestFind validates all the request params are correctly parsed into a select query.
func TestFind ( t * testing . T ) {
2019-05-29 03:35:08 -05:00
2019-05-29 15:05:17 -05:00
now := time . Now ( ) . Add ( time . Hour * - 2 ) . UTC ( )
2019-05-29 03:35:08 -05:00
2019-05-29 15:05:17 -05:00
startTime := now . Truncate ( time . Millisecond )
var endTime time . Time
2019-05-29 03:35:08 -05:00
var userAccounts [ ] * UserAccount
for i := 0 ; i <= 4 ; i ++ {
2019-06-24 22:41:21 -08:00
// Generate a new random user.
2019-06-22 17:48:44 -08:00
userID := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
err := mockUser ( userID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tCreate user failed." , tests . Failed )
}
// Generate a new random account.
2019-05-29 03:35:08 -05:00
accountID := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
err = mockAccount ( accountID , now )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tCreate account failed." , tests . Failed )
}
// Execute Create that will associate the user with the account.
2019-06-22 17:48:44 -08:00
ua , err := Create ( tests . Context ( ) , auth . Claims { } , test . MasterDB , CreateUserAccountRequest {
UserID : userID ,
2019-05-29 03:35:08 -05:00
AccountID : accountID ,
2019-05-29 15:05:17 -05:00
Roles : [ ] UserAccountRole { UserAccountRole_User } ,
2019-05-29 03:35:08 -05:00
} , now . Add ( time . Second * time . Duration ( i ) ) )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tCreate user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
}
userAccounts = append ( userAccounts , ua )
2019-06-22 17:48:44 -08:00
endTime = ua . CreatedAt
2019-05-29 03:35:08 -05:00
}
type accountTest struct {
name string
req UserAccountFindRequest
expected [ ] * UserAccount
error error
}
var accountTests [ ] accountTest
2019-05-29 15:05:17 -05:00
createdFilter := "created_at BETWEEN ? AND ?"
2019-05-29 03:35:08 -05:00
// Test sort users.
accountTests = append ( accountTests , accountTest { "Find all order by created_at asx" ,
UserAccountFindRequest {
2019-05-29 15:05:17 -05:00
Where : & createdFilter ,
Args : [ ] interface { } { startTime , endTime } ,
2019-05-29 03:35:08 -05:00
Order : [ ] string { "created_at" } ,
} ,
userAccounts ,
nil ,
} )
// Test reverse sorted user accounts.
var expected [ ] * UserAccount
for i := len ( userAccounts ) - 1 ; i >= 0 ; i -- {
expected = append ( expected , userAccounts [ i ] )
}
accountTests = append ( accountTests , accountTest { "Find all order by created_at desc" ,
UserAccountFindRequest {
2019-05-29 15:05:17 -05:00
Where : & createdFilter ,
Args : [ ] interface { } { startTime , endTime } ,
2019-05-29 03:35:08 -05:00
Order : [ ] string { "created_at desc" } ,
} ,
expected ,
nil ,
} )
// Test limit.
var limit uint = 2
accountTests = append ( accountTests , accountTest { "Find limit" ,
UserAccountFindRequest {
2019-05-29 15:05:17 -05:00
Where : & createdFilter ,
Args : [ ] interface { } { startTime , endTime } ,
2019-05-29 03:35:08 -05:00
Order : [ ] string { "created_at" } ,
Limit : & limit ,
} ,
userAccounts [ 0 : 2 ] ,
nil ,
} )
// Test offset.
var offset uint = 3
accountTests = append ( accountTests , accountTest { "Find limit, offset" ,
UserAccountFindRequest {
2019-05-29 15:05:17 -05:00
Where : & createdFilter ,
Args : [ ] interface { } { startTime , endTime } ,
2019-05-29 03:35:08 -05:00
Order : [ ] string { "created_at" } ,
Limit : & limit ,
Offset : & offset ,
} ,
userAccounts [ 3 : 5 ] ,
nil ,
} )
// Test where filter.
whereParts := [ ] string { }
2019-05-29 15:05:17 -05:00
whereArgs := [ ] interface { } { startTime , endTime }
2019-05-29 03:35:08 -05:00
expected = [ ] * UserAccount { }
2019-05-29 15:05:17 -05:00
for i := 0 ; i <= len ( userAccounts ) ; i ++ {
if rand . Intn ( 100 ) < 50 {
2019-05-29 03:35:08 -05:00
continue
}
2019-05-29 15:05:17 -05:00
ua := * userAccounts [ i ]
2019-05-29 03:35:08 -05:00
whereParts = append ( whereParts , "id = ?" )
2019-05-29 15:05:17 -05:00
whereArgs = append ( whereArgs , ua . ID )
expected = append ( expected , & ua )
2019-05-29 03:35:08 -05:00
}
2019-05-29 15:05:17 -05:00
where := createdFilter + " AND (" + strings . Join ( whereParts , " OR " ) + ")"
2019-05-29 03:35:08 -05:00
accountTests = append ( accountTests , accountTest { "Find where" ,
UserAccountFindRequest {
Where : & where ,
Args : whereArgs ,
2019-05-29 15:05:17 -05:00
Order : [ ] string { "created_at" } ,
2019-05-29 03:35:08 -05:00
} ,
expected ,
nil ,
} )
2019-06-22 17:48:44 -08:00
t . Log ( "Given the need to ensure find user accounts returns the expected results." )
2019-05-29 03:35:08 -05:00
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
2019-06-22 17:48:44 -08:00
res , err := Find ( ctx , auth . Claims { } , test . MasterDB , tt . req )
if errors . Cause ( err ) != tt . error {
2019-05-29 03:35:08 -05:00
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tFind user account failed." , tests . Failed )
2019-05-29 03:35:08 -05:00
} 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 ) )
2019-06-22 17:48:44 -08:00
t . Fatalf ( "\t%s\tExpected user account find result to match expected. Diff:\n%s" , tests . Failed , diff )
2019-05-29 03:35:08 -05:00
}
2019-06-22 17:48:44 -08:00
t . Logf ( "\t%s\tFind user account ok." , tests . Success )
2019-05-29 03:35:08 -05:00
}
}
}
}
2019-06-24 22:41:21 -08:00
func mockAccount ( accountId string , now time . Time ) error {
// Build the insert SQL statement.
query := sqlbuilder . NewInsertBuilder ( )
query . InsertInto ( "accounts" )
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
}
func mockUser ( userId string , now time . Time ) error {
// Build the insert SQL statement.
query := sqlbuilder . NewInsertBuilder ( )
query . InsertInto ( "users" )
query . Cols ( "id" , "email" , "password_hash" , "password_salt" , "created_at" , "updated_at" )
query . Values ( userId , 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
}