2019-06-22 17:48:44 -08:00
package account
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-24 23:02:43 -08:00
"github.com/lib/pq"
2019-06-22 17:48:44 -08:00
"github.com/pborman/uuid"
"github.com/pkg/errors"
)
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 ( )
}
2019-06-24 04:26:48 -08:00
// TestApplyClaimsSelect validates applyClaimsSelect
func TestApplyClaimsSelect ( t * testing . T ) {
2019-06-22 17:48:44 -08:00
var claimTests = [ ] struct {
name string
claims auth . Claims
expectedSql string
error error
} {
{ "EmptyClaims" ,
auth . Claims { } ,
"SELECT " + accountMapColumns + " FROM " + accountTableName ,
nil ,
} ,
{ "RoleAccount" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "user1" ,
Audience : "acc1" ,
} ,
} ,
"SELECT " + accountMapColumns + " FROM " + accountTableName + " WHERE id IN (SELECT account_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 " + accountMapColumns + " FROM " + accountTableName + " WHERE id IN (SELECT account_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\tapplyClaimsSelect 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\tapplyClaimsSelect 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\tapplyClaimsSelect ok." , tests . Success )
}
}
}
}
// TestCreateValidation ensures all the validation tags work on Create
func TestCreateValidation ( t * testing . T ) {
invalidStatus := AccountStatus ( "xxxxxx" )
var accountTests = [ ] struct {
name string
2019-06-24 22:41:21 -08:00
req AccountCreateRequest
expected func ( req AccountCreateRequest , res * Account ) * Account
2019-06-22 17:48:44 -08:00
error error
} {
{ "Required Fields" ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest { } ,
func ( req AccountCreateRequest , res * Account ) * Account {
2019-06-22 17:48:44 -08:00
return nil
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'AccountCreateRequest.name' Error:Field validation for 'name' failed on the 'required' tag\n" +
"Key: 'AccountCreateRequest.address1' Error:Field validation for 'address1' failed on the 'required' tag\n" +
"Key: 'AccountCreateRequest.city' Error:Field validation for 'city' failed on the 'required' tag\n" +
"Key: 'AccountCreateRequest.region' Error:Field validation for 'region' failed on the 'required' tag\n" +
"Key: 'AccountCreateRequest.country' Error:Field validation for 'country' failed on the 'required' tag\n" +
"Key: 'AccountCreateRequest.zipcode' Error:Field validation for 'zipcode' failed on the 'required' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
{ "Default Timezone & Status" ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( req AccountCreateRequest , res * Account ) * Account {
2019-06-22 17:48:44 -08:00
return & Account {
Name : req . Name ,
Address1 : req . Address1 ,
Address2 : req . Address2 ,
City : req . City ,
Region : req . Region ,
Country : req . Country ,
Zipcode : req . Zipcode ,
Timezone : "America/Anchorage" ,
Status : AccountStatus_Pending ,
// Copy this fields from the result.
ID : res . ID ,
CreatedAt : res . CreatedAt ,
UpdatedAt : res . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} ,
{ "Valid Status" ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
Status : & invalidStatus ,
} ,
2019-06-24 22:41:21 -08:00
func ( req AccountCreateRequest , res * Account ) * Account {
2019-06-22 17:48:44 -08:00
return nil
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'AccountCreateRequest.status' Error:Field validation for 'status' failed on the 'oneof' 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 account create." )
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
res , err := Create ( ctx , auth . Claims { } , test . MasterDB , tt . req , now )
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 {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
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 )
}
}
}
}
// TestCreateValidationNameUnique validates names must be unique on Create.
func TestCreateValidationNameUnique ( t * testing . T ) {
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure duplicate names are not allowed for account create." )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
req1 := AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
}
account1 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req1 , now )
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 := AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : account1 . Name ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
}
2019-06-27 04:48:18 -08:00
expectedErr := errors . New ( "Key: 'AccountCreateRequest.name' Error:Field validation for 'name' failed on the 'unique' tag" )
2019-06-22 17:48:44 -08:00
_ , err = Create ( ctx , auth . Claims { } , test . MasterDB , req2 , now )
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 accountTests = [ ] struct {
name string
claims auth . Claims
2019-06-24 22:41:21 -08:00
req AccountCreateRequest
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
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
nil ,
} ,
// Role of account, only admins can create new accounts.
{ "RoleAccount" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "account1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
nil ,
} ,
// Role of admin, can create accounts.
{ "RoleAdmin" ,
auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Subject : "account1" ,
Audience : "acc1" ,
} ,
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
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 account." )
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
_ , err := Create ( ctx , auth . Claims { } , test . MasterDB , tt . req , now )
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 account so can test the output of findbyId
type accountTest struct {
name string
2019-06-24 22:41:21 -08:00
req AccountUpdateRequest
2019-06-22 17:48:44 -08:00
error error
}
var accountTests = [ ] accountTest {
{ "Required Fields" ,
2019-06-24 22:41:21 -08:00
AccountUpdateRequest { } ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'AccountUpdateRequest.id' Error:Field validation for 'id' failed on the 'required' tag" ) ,
2019-06-22 17:48:44 -08:00
} ,
}
invalidStatus := AccountStatus ( "xxxxxx" )
accountTests = append ( accountTests , accountTest { "Valid Status" ,
2019-06-24 22:41:21 -08:00
AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : uuid . NewRandom ( ) . String ( ) ,
Status : & invalidStatus ,
} ,
2019-06-27 04:48:18 -08:00
errors . New ( "Key: 'AccountUpdateRequest.status' Error:Field validation for 'status' failed on the 'oneof' 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 account update." )
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
err := Update ( ctx , auth . Claims { } , test . MasterDB , tt . req , now )
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 {
t . Logf ( "\t\tGot : %+v" , err )
t . Logf ( "\t\tWant: %+v" , tt . error )
t . Fatalf ( "\t%s\tUpdate failed." , tests . Failed )
}
}
t . Logf ( "\t%s\tUpdate ok." , tests . Success )
}
}
}
}
// TestUpdateValidationNameUnique validates names must be unique on Update.
func TestUpdateValidationNameUnique ( t * testing . T ) {
now := time . Date ( 2018 , time . October , 1 , 0 , 0 , 0 , 0 , time . UTC )
t . Log ( "Given the need ensure duplicate names are not allowed for account update." )
{
ctx := tests . Context ( )
2019-06-24 22:41:21 -08:00
req1 := AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
}
account1 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req1 , now )
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 := AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
}
account2 , err := Create ( ctx , auth . Claims { } , test . MasterDB , req2 , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
// Try to set the email for account 1 on account 2
2019-06-24 22:41:21 -08:00
updateReq := AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account2 . ID ,
Name : & account1 . Name ,
}
2019-06-27 04:48:18 -08:00
expectedErr := errors . New ( "Key: 'AccountUpdateRequest.name' Error:Field validation for 'name' failed on the 'unique' tag" )
2019-06-22 17:48:44 -08:00
err = Update ( ctx , auth . Claims { } , test . MasterDB , updateReq , now )
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 )
}
}
// TestCrud validates the full set of CRUD operations for accounts and ensures ACLs are correctly applied by claims.
func TestCrud ( t * testing . T ) {
defer tests . Recover ( t )
type accountTest struct {
name string
claims func ( * Account , string ) auth . Claims
2019-06-24 22:41:21 -08:00
create AccountCreateRequest
update func ( * Account ) AccountUpdateRequest
2019-06-22 17:48:44 -08:00
updateErr error
2019-06-24 22:41:21 -08:00
expected func ( * Account , AccountUpdateRequest ) * Account
2019-06-22 17:48:44 -08:00
findErr error
}
var accountTests [ ] accountTest
// Internal request, should bypass ACL.
accountTests = append ( accountTests , accountTest { "EmptyClaims" ,
func ( account * Account , userId string ) auth . Claims {
return auth . Claims { }
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( account * Account ) AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
name := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
return AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account . ID ,
Name : & name ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( account * Account , req AccountUpdateRequest ) * Account {
2019-06-22 17:48:44 -08:00
return & Account {
Name : * req . Name ,
// Copy this fields from the created account.
ID : account . ID ,
Address1 : account . Address1 ,
Address2 : account . Address2 ,
City : account . City ,
Region : account . Region ,
Country : account . Country ,
Zipcode : account . Zipcode ,
Status : account . Status ,
Timezone : account . Timezone ,
SignupUserID : account . SignupUserID ,
BillingUserID : account . BillingUserID ,
CreatedAt : account . CreatedAt ,
UpdatedAt : account . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
// Role of account but claim account does not match update account so forbidden.
accountTests = append ( accountTests , accountTest { "RoleAccountDiffAccount" ,
func ( account * Account , userId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Audience : uuid . NewRandom ( ) . String ( ) ,
Subject : userId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( account * Account ) AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
name := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
return AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account . ID ,
Name : & name ,
}
} ,
ErrForbidden ,
2019-06-24 22:41:21 -08:00
func ( account * Account , req AccountUpdateRequest ) * Account {
2019-06-22 17:48:44 -08:00
return account
} ,
ErrNotFound ,
} )
// Role of account AND claim account matches update account so OK.
accountTests = append ( accountTests , accountTest { "RoleAccountSameAccount" ,
func ( account * Account , userId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Audience : account . ID ,
Subject : userId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( account * Account ) AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
name := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
return AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account . ID ,
Name : & name ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( account * Account , req AccountUpdateRequest ) * Account {
2019-06-22 17:48:44 -08:00
return & Account {
Name : * req . Name ,
// Copy this fields from the created account.
ID : account . ID ,
Address1 : account . Address1 ,
Address2 : account . Address2 ,
City : account . City ,
Region : account . Region ,
Country : account . Country ,
Zipcode : account . Zipcode ,
Status : account . Status ,
Timezone : account . Timezone ,
SignupUserID : account . SignupUserID ,
BillingUserID : account . BillingUserID ,
CreatedAt : account . CreatedAt ,
UpdatedAt : account . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
// Role of admin but claim account does not match update account so forbidden.
accountTests = append ( accountTests , accountTest { "RoleAdminDiffAccount" ,
func ( account * Account , accountId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Audience : uuid . NewRandom ( ) . String ( ) ,
Subject : uuid . NewRandom ( ) . String ( ) ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( account * Account ) AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
name := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
return AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account . ID ,
Name : & name ,
}
} ,
ErrForbidden ,
2019-06-24 22:41:21 -08:00
func ( account * Account , req AccountUpdateRequest ) * Account {
2019-06-22 17:48:44 -08:00
return nil
} ,
ErrNotFound ,
} )
// Role of admin and claim account matches update account so ok.
accountTests = append ( accountTests , accountTest { "RoleAdminSameAccount" ,
func ( account * Account , userId string ) auth . Claims {
return auth . Claims {
Roles : [ ] string { auth . RoleAdmin } ,
StandardClaims : jwt . StandardClaims {
Audience : uuid . NewRandom ( ) . String ( ) ,
Subject : userId ,
} ,
}
} ,
2019-06-24 22:41:21 -08:00
AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} ,
2019-06-24 22:41:21 -08:00
func ( account * Account ) AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
name := uuid . NewRandom ( ) . String ( )
2019-06-24 22:41:21 -08:00
return AccountUpdateRequest {
2019-06-22 17:48:44 -08:00
ID : account . ID ,
Name : & name ,
}
} ,
nil ,
2019-06-24 22:41:21 -08:00
func ( account * Account , req AccountUpdateRequest ) * Account {
2019-06-22 17:48:44 -08:00
return & Account {
Name : * req . Name ,
// Copy this fields from the created account.
ID : account . ID ,
Address1 : account . Address1 ,
Address2 : account . Address2 ,
City : account . City ,
Region : account . Region ,
Country : account . Country ,
Zipcode : account . Zipcode ,
Status : account . Status ,
Timezone : account . Timezone ,
SignupUserID : account . SignupUserID ,
BillingUserID : account . BillingUserID ,
CreatedAt : account . CreatedAt ,
UpdatedAt : account . UpdatedAt ,
//ArchivedAt: nil,
}
} ,
nil ,
} )
t . Log ( "Given the need to ensure claims are applied as ACL for update account." )
{
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 )
{
ctx := tests . Context ( )
// Always create the new account with empty claims, testing claims for create account
// will be handled separately.
account , err := Create ( ctx , auth . Claims { } , test . MasterDB , tt . create , now )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
// Create a new random account and associate that with the account.
userId := uuid . NewRandom ( ) . String ( )
err = mockUserAccount ( account . ID , userId , account . CreatedAt , auth . RoleAdmin )
if err != nil {
t . Log ( "\t\tGot :" , err )
t . Fatalf ( "\t%s\tAdd user account failed." , tests . Failed )
}
// Update the account.
updateReq := tt . update ( account )
err = Update ( ctx , tt . claims ( account , userId ) , test . MasterDB , updateReq , now )
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 account and make sure the updates where made.
2019-08-04 14:48:43 -08:00
findRes , err := ReadByID ( ctx , tt . claims ( account , userId ) , test . MasterDB , account . 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 account.
2019-08-04 14:48:43 -08:00
err = Archive ( ctx , tt . claims ( account , userId ) , test . MasterDB , AccountArchiveRequest { ID : account . ID } , 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 account with the includeArchived false should result in not found.
2019-08-04 14:48:43 -08:00
_ , err = ReadByID ( ctx , tt . claims ( account , userId ) , test . MasterDB , account . 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 account with the includeArchived true should result no error.
2019-08-04 14:48:43 -08:00
_ , err = Read ( ctx , tt . claims ( account , userId ) , test . MasterDB ,
AccountReadRequest { ID : account . 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 )
// Delete (hard-delete) the account.
2019-08-04 14:48:43 -08:00
err = Delete ( ctx , tt . claims ( account , userId ) , test . MasterDB , AccountDeleteRequest { ID : account . ID } )
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 account with the includeArchived true should result in not found.
2019-08-04 14:48:43 -08:00
_ , err = ReadByID ( ctx , tt . claims ( account , userId ) , test . MasterDB , account . 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 accounts [ ] * Account
for i := 0 ; i <= 4 ; i ++ {
2019-06-24 22:41:21 -08:00
account , err := Create ( tests . Context ( ) , auth . Claims { } , test . MasterDB , AccountCreateRequest {
2019-06-22 17:48:44 -08:00
Name : uuid . NewRandom ( ) . String ( ) ,
Address1 : "103 East Main St" ,
Address2 : "Unit 546" ,
City : "Valdez" ,
Region : "AK" ,
Country : "USA" ,
Zipcode : "99686" ,
} , now . Add ( time . Second * time . Duration ( i ) ) )
if err != nil {
t . Logf ( "\t\tGot : %+v" , err )
t . Fatalf ( "\t%s\tCreate failed." , tests . Failed )
}
accounts = append ( accounts , account )
endTime = account . CreatedAt
}
type accountTest struct {
name string
req AccountFindRequest
2019-08-05 17:12:28 -08:00
expected Accounts
2019-06-22 17:48:44 -08:00
error error
}
var accountTests [ ] accountTest
createdFilter := "created_at BETWEEN ? AND ?"
// Test sort accounts.
accountTests = append ( accountTests , accountTest { "Find all order by created_at asc" ,
AccountFindRequest {
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" } ,
} ,
accounts ,
nil ,
} )
// Test reverse sorted accounts.
var expected [ ] * Account
for i := len ( accounts ) - 1 ; i >= 0 ; i -- {
expected = append ( expected , accounts [ i ] )
}
accountTests = append ( accountTests , accountTest { "Find all order by created_at desc" ,
AccountFindRequest {
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
accountTests = append ( accountTests , accountTest { "Find limit" ,
AccountFindRequest {
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 ,
} ,
accounts [ 0 : 2 ] ,
nil ,
} )
// Test offset.
var offset uint = 3
accountTests = append ( accountTests , accountTest { "Find limit, offset" ,
AccountFindRequest {
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 ,
} ,
accounts [ 3 : 5 ] ,
nil ,
} )
// Test where filter.
whereParts := [ ] string { }
whereArgs := [ ] interface { } { startTime , endTime }
expected = [ ] * Account { }
for i := 0 ; i <= len ( accounts ) ; i ++ {
if rand . Intn ( 100 ) < 50 {
continue
}
u := * accounts [ i ]
whereParts = append ( whereParts , "name = ?" )
whereArgs = append ( whereArgs , u . Name )
expected = append ( expected , & u )
}
accountTests = append ( accountTests , accountTest { "Find where" ,
AccountFindRequest {
2019-08-05 17:12:28 -08:00
Where : createdFilter + " AND (" + strings . Join ( whereParts , " OR " ) + ")" ,
2019-06-22 17:48:44 -08:00
Args : whereArgs ,
Order : [ ] string { "created_at" } ,
} ,
expected ,
nil ,
} )
t . Log ( "Given the need to ensure find accounts returns the expected results." )
{
for i , tt := range accountTests {
t . Logf ( "\tTest: %d\tWhen running test: %s" , i , tt . name )
{
ctx := tests . Context ( )
res , err := Find ( ctx , auth . Claims { } , test . MasterDB , tt . req )
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 )
}
}
}
}
func mockUserAccount ( accountId , userId string , now time . Time , roles ... string ) error {
var roleArr pq . StringArray
for _ , r := range roles {
roleArr = append ( roleArr , r )
}
2019-06-24 22:41:21 -08:00
err := mockUser ( userId , now )
if err != nil {
return err
}
2019-06-22 17:48:44 -08:00
// 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 )
2019-06-24 22:41:21 -08:00
// 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 )
2019-06-22 17:48:44 -08:00
// 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
}