2019-05-16 10:39:25 -04:00
package tests
import (
2019-06-26 20:21:00 -08:00
"context"
2019-05-16 10:39:25 -04:00
"encoding/json"
2019-06-26 20:21:00 -08:00
"fmt"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/mid"
2019-05-16 10:39:25 -04:00
"net/http"
2019-06-26 20:21:00 -08:00
"strconv"
2019-05-16 10:39:25 -04:00
"testing"
2019-06-26 20:21:00 -08:00
"time"
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user"
2019-05-16 10:39:25 -04:00
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
"github.com/google/go-cmp/cmp"
2019-06-26 20:21:00 -08:00
"github.com/pborman/uuid"
2019-05-16 10:39:25 -04:00
)
2019-06-26 20:21:00 -08:00
func mockUser ( ) * user . User {
req := user . UserCreateRequest {
Name : "Lee Brown" ,
Email : uuid . NewRandom ( ) . String ( ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
}
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
a , err := user . Create ( tests . Context ( ) , auth . Claims { } , test . MasterDB , req , time . Now ( ) . UTC ( ) . AddDate ( - 1 , - 1 , - 1 ) )
if err != nil {
panic ( err )
}
return a
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// TestUser is the entry point for the user endpoints.
func TestUser ( t * testing . T ) {
defer tests . Recover ( t )
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
t . Run ( "getUser" , getUser )
t . Run ( "createUser" , createUser )
t . Run ( "patchUser" , patchUser )
t . Run ( "patchUserPassword" , patchUserPassword )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// getUser validates get user by ID endpoint.
func getUser ( t * testing . T ) {
var rtests [ ] requestTest
forbiddenUser := mockUser ( )
// Both roles should be able to read the user.
for rn , tr := range roleTests {
usr := tr . SignupResult . User
// Test 200.
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s 200" , rn ) ,
http . MethodGet ,
fmt . Sprintf ( "/v1/users/%s" , usr . ID ) ,
nil ,
tr . Token ,
tr . Claims ,
http . StatusOK ,
nil ,
func ( treq requestTest , body [ ] byte ) bool {
var actual user . UserResponse
if err := json . Unmarshal ( body , & actual ) ; err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
// Add claims to the context so they can be retrieved later.
ctx := context . WithValue ( tests . Context ( ) , auth . Key , tr . Claims )
expectedMap := map [ string ] interface { } {
"updated_at" : web . NewTimeResponse ( ctx , usr . UpdatedAt ) ,
"id" : usr . ID ,
"email" : usr . Email ,
"timezone" : usr . Timezone ,
"created_at" : web . NewTimeResponse ( ctx , usr . CreatedAt ) ,
"name" : usr . Name ,
}
expectedJson , err := json . Marshal ( expectedMap )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
var expected user . UserResponse
if err := json . Unmarshal ( [ ] byte ( expectedJson ) , & expected ) ; err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
printResultMap ( ctx , body )
return false
}
if diff := cmp . Diff ( actual , expected ) ; diff != "" {
actualJSON , err := json . MarshalIndent ( actual , "" , " " )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
t . Logf ( "\t\tGot : %s\n" , actualJSON )
expectedJSON , err := json . MarshalIndent ( expected , "" , " " )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
t . Logf ( "\t\tExpected : %s\n" , expectedJSON )
t . Logf ( "\t\tDiff : %s\n" , diff )
if len ( expectedMap ) == 0 {
printResultMap ( ctx , body )
}
return false
}
return true
} ,
} )
// Test 404.
invalidID := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s 404 w/invalid ID" , rn ) ,
http . MethodGet ,
fmt . Sprintf ( "/v1/users/%s" , invalidID ) ,
nil ,
tr . Token ,
tr . Claims ,
http . StatusNotFound ,
web . ErrorResponse {
Error : fmt . Sprintf ( "user %s not found: Entity not found" , invalidID ) ,
} ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
// Test 404 - User exists but not allowed.
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s 404 w/random user ID" , rn ) ,
http . MethodGet ,
fmt . Sprintf ( "/v1/users/%s" , forbiddenUser . ID ) ,
nil ,
tr . Token ,
tr . Claims ,
http . StatusNotFound ,
web . ErrorResponse {
Error : fmt . Sprintf ( "user %s not found: Entity not found" , forbiddenUser . ID ) ,
} ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
}
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
runRequestTests ( t , rtests )
}
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
// createUser validates create user endpoint.
func createUser ( t * testing . T ) {
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
var rtests [ ] requestTest
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
// Test create user.
// Admin role: 201
// User role 403
for rn , tr := range roleTests {
var expectedStatus int
var expectedErr interface { }
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
// Test 201.
if rn == auth . RoleAdmin {
expectedStatus = http . StatusCreated
} else {
expectedStatus = http . StatusForbidden
expectedErr = web . ErrorResponse {
Error : mid . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
}
2019-06-26 20:21:00 -08:00
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d" , rn , expectedStatus ) ,
http . MethodPost ,
"/v1/users" ,
user . UserCreateRequest {
Name : "Lee Brown" ,
Email : uuid . NewRandom ( ) . String ( ) + rn + strconv . Itoa ( len ( rtests ) ) + "@geeksinthewoods.com" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
if treq . error != nil {
return true
}
var actual user . UserResponse
if err := json . Unmarshal ( body , & actual ) ; err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
// Add claims to the context so they can be retrieved later.
ctx := context . WithValue ( tests . Context ( ) , auth . Key , tr . Claims )
req := treq . request . ( user . UserCreateRequest )
expectedMap := map [ string ] interface { } {
"updated_at" : web . NewTimeResponse ( ctx , actual . UpdatedAt . Value ) ,
"id" : actual . ID ,
"email" : req . Email ,
"timezone" : actual . Timezone ,
"created_at" : web . NewTimeResponse ( ctx , actual . CreatedAt . Value ) ,
"name" : req . Name ,
}
expectedJson , err := json . Marshal ( expectedMap )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
var expected user . UserResponse
if err := json . Unmarshal ( [ ] byte ( expectedJson ) , & expected ) ; err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
printResultMap ( ctx , body )
return false
}
if diff := cmp . Diff ( actual , expected ) ; diff != "" {
actualJSON , err := json . MarshalIndent ( actual , "" , " " )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
t . Logf ( "\t\tGot : %s\n" , actualJSON )
expectedJSON , err := json . MarshalIndent ( expected , "" , " " )
if err != nil {
t . Logf ( "\t\tGot error : %+v" , err )
return false
}
t . Logf ( "\t\tExpected : %s\n" , expectedJSON )
t . Logf ( "\t\tDiff : %s\n" , diff )
if len ( expectedMap ) == 0 {
printResultMap ( ctx , body )
}
return false
}
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user with invalid data.
// Admin role: 400
// User role 403
for rn , tr := range roleTests {
var expectedStatus int
var expectedErr interface { }
// Test 201.
if rn == auth . RoleAdmin {
expectedStatus = http . StatusBadRequest
expectedErr = web . ErrorResponse {
2019-05-16 10:39:25 -04:00
Error : "field validation error" ,
Fields : [ ] web . FieldError {
2019-06-26 20:21:00 -08:00
{ Field : "email" , Error : "Key: 'UserCreateRequest.email' Error:Field validation for 'email' failed on the 'email' tag" } ,
2019-05-16 10:39:25 -04:00
} ,
}
2019-06-26 20:21:00 -08:00
} else {
expectedStatus = http . StatusForbidden
expectedErr = web . ErrorResponse {
Error : mid . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
}
2019-06-26 20:21:00 -08:00
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/invalid data" , rn , expectedStatus ) ,
http . MethodPost ,
"/v1/users" ,
user . UserCreateRequest {
Name : "Lee Brown" ,
Email : "invalid email address" ,
Password : "akTechFr0n!ier" ,
PasswordConfirm : "akTechFr0n!ier" ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
runRequestTests ( t , rtests )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// patchUser validates update user by ID endpoint.
func patchUser ( t * testing . T ) {
var rtests [ ] requestTest
// Test update a user
// Admin role: 204
// User role 204 - user ID matches claims so OK
for rn , tr := range roleTests {
expectedStatus := http . StatusNoContent
newName := rn + uuid . NewRandom ( ) . String ( ) + strconv . Itoa ( len ( rtests ) )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users" ,
user . UserUpdateRequest {
ID : tr . SignupResult . User . ID ,
Name : & newName ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
nil ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user with invalid data.
// Admin role: 400
// User role 400
for rn , tr := range roleTests {
expectedStatus := http . StatusBadRequest
expectedErr := web . ErrorResponse {
Error : "field validation error" ,
Fields : [ ] web . FieldError {
{ Field : "email" , Error : "Key: 'UserUpdateRequest.email' Error:Field validation for 'email' failed on the 'email' tag" } ,
} ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
invalidEmail := "invalid email address"
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/invalid data" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users" ,
user . UserUpdateRequest {
ID : tr . SignupResult . User . ID ,
Email : & invalidEmail ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user for with an invalid ID.
// Admin role: 403
// User role 403
for rn , tr := range roleTests {
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
expectedStatus := http . StatusForbidden
expectedErr := web . ErrorResponse {
Error : user . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
newName := rn + uuid . NewRandom ( ) . String ( ) + strconv . Itoa ( len ( rtests ) )
invalidID := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/invalid ID" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users" ,
user . UserUpdateRequest {
ID : invalidID ,
Name : & newName ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user for with random user ID.
// Admin role: 403
// User role 403
forbiddenUser := mockUser ( )
for rn , tr := range roleTests {
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
expectedStatus := http . StatusForbidden
expectedErr := web . ErrorResponse {
Error : user . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
newName := rn + uuid . NewRandom ( ) . String ( ) + strconv . Itoa ( len ( rtests ) )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/random user ID" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users" ,
user . UserUpdateRequest {
ID : forbiddenUser . ID ,
Name : & newName ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
runRequestTests ( t , rtests )
}
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
// patchUserPassword validates update user password by ID endpoint.
func patchUserPassword ( t * testing . T ) {
var rtests [ ] requestTest
// Test update a user
// Admin role: 204
// User role 204 - user ID matches claims so OK
for rn , tr := range roleTests {
expectedStatus := http . StatusNoContent
newPass := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users/password" ,
user . UserUpdatePasswordRequest {
ID : tr . SignupResult . User . ID ,
Password : newPass ,
PasswordConfirm : newPass ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
nil ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user password with invalid data.
// Admin role: 400
// User role 400
for rn , tr := range roleTests {
expectedStatus := http . StatusBadRequest
expectedErr := web . ErrorResponse {
Error : "field validation error" ,
Fields : [ ] web . FieldError {
{ Field : "password_confirm" , Error : "Key: 'UserUpdatePasswordRequest.password_confirm' Error:Field validation for 'password_confirm' failed on the 'eqfield' tag" } ,
} ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
newPass := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/invalid data" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users/password" ,
user . UserUpdatePasswordRequest {
ID : tr . SignupResult . User . ID ,
Password : newPass ,
PasswordConfirm : "different" ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user password for with an invalid ID.
// Admin role: 403
// User role 403
for rn , tr := range roleTests {
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
expectedStatus := http . StatusForbidden
expectedErr := web . ErrorResponse {
Error : user . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
newPass := uuid . NewRandom ( ) . String ( )
invalidID := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/invalid ID" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users/password" ,
user . UserUpdatePasswordRequest {
ID : invalidID ,
Password : newPass ,
PasswordConfirm : newPass ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
// Test update a user password for with random user ID.
// Admin role: 403
// User role 403
forbiddenUser := mockUser ( )
for rn , tr := range roleTests {
2019-05-16 10:39:25 -04:00
2019-06-26 20:21:00 -08:00
expectedStatus := http . StatusForbidden
expectedErr := web . ErrorResponse {
Error : user . ErrForbidden . Error ( ) ,
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
newPass := uuid . NewRandom ( ) . String ( )
rtests = append ( rtests , requestTest {
fmt . Sprintf ( "Role %s %d w/random user ID" , rn , expectedStatus ) ,
http . MethodPatch ,
"/v1/users/password" ,
user . UserUpdatePasswordRequest {
ID : forbiddenUser . ID ,
Password : newPass ,
PasswordConfirm : newPass ,
} ,
tr . Token ,
tr . Claims ,
expectedStatus ,
expectedErr ,
func ( treq requestTest , body [ ] byte ) bool {
return true
} ,
} )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00
runRequestTests ( t , rtests )
2019-05-16 10:39:25 -04:00
}
2019-06-26 20:21:00 -08:00