1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-08 23:56:37 +02:00
2019-06-26 20:21:00 -08:00

563 lines
14 KiB
Go

package tests
import (
"context"
"encoding/json"
"fmt"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/mid"
"net/http"
"strconv"
"testing"
"time"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/user"
"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"
"github.com/pborman/uuid"
)
func mockUser() *user.User {
req := user.UserCreateRequest{
Name: "Lee Brown",
Email: uuid.NewRandom().String() + "@geeksinthewoods.com",
Password: "akTechFr0n!ier",
PasswordConfirm: "akTechFr0n!ier",
}
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
}
// TestUser is the entry point for the user endpoints.
func TestUser(t *testing.T) {
defer tests.Recover(t)
t.Run("getUser", getUser)
t.Run("createUser", createUser)
t.Run("patchUser", patchUser)
t.Run("patchUserPassword", patchUserPassword)
}
// 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
},
})
}
runRequestTests(t, rtests)
}
// createUser validates create user endpoint.
func createUser(t *testing.T) {
var rtests []requestTest
// Test create user.
// Admin role: 201
// User role 403
for rn, tr := range roleTests {
var expectedStatus int
var expectedErr interface{}
// Test 201.
if rn == auth.RoleAdmin {
expectedStatus = http.StatusCreated
} else {
expectedStatus = http.StatusForbidden
expectedErr = web.ErrorResponse{
Error: mid.ErrForbidden.Error(),
}
}
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
},
})
}
// 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{
Error: "field validation error",
Fields: []web.FieldError{
{Field: "email", Error: "Key: 'UserCreateRequest.email' Error:Field validation for 'email' failed on the 'email' tag"},
},
}
} else {
expectedStatus = http.StatusForbidden
expectedErr = web.ErrorResponse{
Error: mid.ErrForbidden.Error(),
}
}
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
},
})
}
runRequestTests(t, rtests)
}
// 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
},
})
}
// 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"},
},
}
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
},
})
}
// Test update a user for with an invalid ID.
// Admin role: 403
// User role 403
for rn, tr := range roleTests {
expectedStatus := http.StatusForbidden
expectedErr := web.ErrorResponse{
Error: user.ErrForbidden.Error(),
}
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
},
})
}
// Test update a user for with random user ID.
// Admin role: 403
// User role 403
forbiddenUser := mockUser()
for rn, tr := range roleTests {
expectedStatus := http.StatusForbidden
expectedErr := web.ErrorResponse{
Error: user.ErrForbidden.Error(),
}
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
},
})
}
runRequestTests(t, rtests)
}
// 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
},
})
}
// 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"},
},
}
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
},
})
}
// Test update a user password for with an invalid ID.
// Admin role: 403
// User role 403
for rn, tr := range roleTests {
expectedStatus := http.StatusForbidden
expectedErr := web.ErrorResponse{
Error: user.ErrForbidden.Error(),
}
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
},
})
}
// Test update a user password for with random user ID.
// Admin role: 403
// User role 403
forbiddenUser := mockUser()
for rn, tr := range roleTests {
expectedStatus := http.StatusForbidden
expectedErr := web.ErrorResponse{
Error: user.ErrForbidden.Error(),
}
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
},
})
}
runRequestTests(t, rtests)
}