add: update handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -22,12 +24,9 @@ import (
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/logger"
|
||||
)
|
||||
|
||||
type loginInput struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type RegisterUserTest struct {
|
||||
WantDBData map[string]interface{}
|
||||
@@ -37,6 +36,8 @@ type RegisterUserTest struct {
|
||||
Assert bool
|
||||
}
|
||||
|
||||
var jwtSigningMethod = jwt.SigningMethodHS256
|
||||
|
||||
func (rt *RegisterUserTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
|
||||
return GetMockedJSONContext([]byte(rt.Input), "register")
|
||||
}
|
||||
@@ -61,17 +62,23 @@ func testUserController(t *testing.T) {
|
||||
|
||||
tests := getTestUsers()
|
||||
for _, tt := range tests {
|
||||
logger.Error.Print("==============================================================")
|
||||
logger.Error.Printf("Register User Testing : %v", tt.Name)
|
||||
logger.Error.Print("==============================================================")
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
if err := runSingleTest(&tt); err != nil {
|
||||
t.Fatalf("Test failed: %v", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
testCurrentUserHandler(t)
|
||||
|
||||
loginEmail, loginCookie := testLoginHandler(t)
|
||||
logoutCookie := testCurrentUserHandler(t, loginEmail, loginCookie)
|
||||
testUpdateUser(t, loginEmail, loginCookie)
|
||||
testLogoutHandler(t, logoutCookie)
|
||||
}
|
||||
|
||||
func testLogoutHandler(t *testing.T) {
|
||||
loginCookie := testCurrentUserHandler(t)
|
||||
func testLogoutHandler(t *testing.T, loginCookie http.Cookie) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -93,6 +100,9 @@ func testLogoutHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
logger.Error.Print("==============================================================")
|
||||
logger.Error.Printf("Logout User Testing : %v", tt.name)
|
||||
logger.Error.Print("==============================================================")
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
@@ -125,11 +135,11 @@ func testLogoutHandler(t *testing.T) {
|
||||
|
||||
// Verify that the user can no longer access protected routes
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/current-user", nil)
|
||||
req, _ = http.NewRequest("GET", "/current", nil)
|
||||
if logoutCookie != nil {
|
||||
req.AddCookie(logoutCookie)
|
||||
}
|
||||
router.GET("/current-user", middlewares.AuthMiddleware(), Uc.CurrentUserHandler)
|
||||
router.GET("/current", middlewares.AuthMiddleware(), Uc.CurrentUserHandler)
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code, "User should not be able to access protected routes after logout")
|
||||
})
|
||||
@@ -196,9 +206,8 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
if tt.wantToken {
|
||||
logger.Info.Printf("Response: %#v", response)
|
||||
assert.Contains(t, response, "set-token")
|
||||
assert.NotEmpty(t, response["set-token"])
|
||||
assert.Contains(t, response, "message")
|
||||
assert.Equal(t, "Login successful", response["message"])
|
||||
for _, cookie := range w.Result().Cookies() {
|
||||
if cookie.Name == "jwt" {
|
||||
loginCookie = *cookie
|
||||
@@ -211,7 +220,8 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
||||
}
|
||||
assert.NotEmpty(t, loginCookie)
|
||||
} else {
|
||||
assert.NotContains(t, response, "set-token")
|
||||
assert.Contains(t, response, "error")
|
||||
assert.NotEmpty(t, response["error"])
|
||||
}
|
||||
})
|
||||
|
||||
@@ -220,8 +230,7 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
|
||||
return loginInput.Email, loginCookie
|
||||
}
|
||||
|
||||
func testCurrentUserHandler(t *testing.T) http.Cookie {
|
||||
loginEmail, loginCookie := testLoginHandler(t)
|
||||
func testCurrentUserHandler(t *testing.T, loginEmail string, loginCookie http.Cookie) http.Cookie {
|
||||
// This test should run after the user login test
|
||||
invalidCookie := http.Cookie{
|
||||
Name: "jwt",
|
||||
@@ -232,6 +241,7 @@ func testCurrentUserHandler(t *testing.T) http.Cookie {
|
||||
setupCookie func(*http.Request)
|
||||
expectedUserMail string
|
||||
expectedStatus int
|
||||
expectNewCookie bool
|
||||
}{
|
||||
{
|
||||
name: "With valid cookie",
|
||||
@@ -241,6 +251,24 @@ func testCurrentUserHandler(t *testing.T) http.Cookie {
|
||||
expectedUserMail: loginEmail,
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "With valid expired cookie",
|
||||
setupCookie: func(req *http.Request) {
|
||||
sessionID := "test-session"
|
||||
token := jwt.NewWithClaims(jwtSigningMethod, jwt.MapClaims{
|
||||
"user_id": 1,
|
||||
"role_id": 0,
|
||||
"session_id": sessionID,
|
||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||
})
|
||||
tokenString, _ := token.SignedString([]byte(config.Auth.JWTSecret))
|
||||
req.AddCookie(&http.Cookie{Name: "jwt", Value: tokenString})
|
||||
middlewares.UpdateSession(sessionID, 1) // Add a valid session
|
||||
},
|
||||
expectedUserMail: config.Recipients.AdminEmail,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectNewCookie: true,
|
||||
},
|
||||
{
|
||||
name: "Without cookie",
|
||||
setupCookie: func(req *http.Request) {},
|
||||
@@ -259,18 +287,15 @@ func testCurrentUserHandler(t *testing.T) http.Cookie {
|
||||
logger.Error.Print("==============================================================")
|
||||
logger.Error.Printf("Testing : %v", tt.name)
|
||||
logger.Error.Print("==============================================================")
|
||||
if tt.expectedStatus == http.StatusOK {
|
||||
time.Sleep(time.Second) // Small delay to ensure different timestamps to get a different JWT token
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.Use(middlewares.AuthMiddleware())
|
||||
router.GET("/current-user", Uc.CurrentUserHandler)
|
||||
router.GET("/current", Uc.CurrentUserHandler)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/current-user", nil)
|
||||
req, _ := http.NewRequest("GET", "/current", nil)
|
||||
tt.setupCookie(req)
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
@@ -290,9 +315,13 @@ func testCurrentUserHandler(t *testing.T) http.Cookie {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, newCookie, "Cookie should be renewed")
|
||||
assert.NotEqual(t, loginCookie.Value, newCookie.Value, "Cookie value should be different")
|
||||
assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
|
||||
if tt.expectNewCookie {
|
||||
assert.NotNil(t, newCookie, "New cookie should be set for expired token")
|
||||
assert.NotEqual(t, loginCookie.Value, newCookie.Value, "Cookie value should be different")
|
||||
assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
|
||||
} else {
|
||||
assert.Nil(t, newCookie, "No new cookie should be set for non-expired token")
|
||||
}
|
||||
} else {
|
||||
// For unauthorized requests, check for an error message
|
||||
var errorResponse map[string]string
|
||||
@@ -316,29 +345,32 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
||||
if assert != (len(*users) != 0) {
|
||||
return fmt.Errorf("User entry query didn't met expectation: %v != %#v", assert, *users)
|
||||
}
|
||||
|
||||
if assert {
|
||||
//check for email delivery
|
||||
messages := utils.SMTPGetMessages()
|
||||
for _, message := range messages {
|
||||
mail, err := utils.DecodeMail(message.MsgRequest())
|
||||
if err != nil {
|
||||
logger.Error.Printf("Error in validateUser: %#v", err)
|
||||
return err
|
||||
}
|
||||
if strings.Contains(mail.Subject, constants.MailRegistrationSubject) {
|
||||
if err := checkRegistrationMail(mail, &(*users)[0]); err != nil {
|
||||
logger.Error.Printf("Error in checkRegistrationMail: %#v", err)
|
||||
return err
|
||||
}
|
||||
} else if strings.Contains(mail.Subject, constants.MailVerificationSubject) {
|
||||
if err := checkVerificationMail(mail, &(*users)[0]); err != nil {
|
||||
logger.Error.Printf("Error in checkVerificationMail: %#v", err)
|
||||
return err
|
||||
}
|
||||
verifiedUsers, err := Uc.Service.GetUsers(wantDBData)
|
||||
if err != nil {
|
||||
logger.Error.Printf("Error in GetUsers: %#v", err)
|
||||
return err
|
||||
}
|
||||
if (*verifiedUsers)[0].Status != constants.VerifiedStatus {
|
||||
return fmt.Errorf("Users status isn't verified after email verification. Status is: %#v", (*verifiedUsers)[0].Status)
|
||||
return fmt.Errorf("Users(%v) status isn't verified after email verification. Status is: %v", (*verifiedUsers)[0].Email, (*verifiedUsers)[0].Status)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Subject not expected: %v", mail.Subject)
|
||||
@@ -348,6 +380,168 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func testUpdateUser(t *testing.T, loginEmail string, loginCookie http.Cookie) {
|
||||
|
||||
invalidCookie := http.Cookie{
|
||||
Name: "jwt",
|
||||
Value: "invalid.token.here",
|
||||
}
|
||||
// Get the user we just created
|
||||
users, err := Uc.Service.GetUsers(map[string]interface{}{"email": "john.doe@example.com"})
|
||||
if err != nil || len(*users) == 0 {
|
||||
t.Fatalf("Failed to get test user: %v", err)
|
||||
}
|
||||
user := (*users)[0]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupCookie func(*http.Request)
|
||||
updateFunc func(*models.User)
|
||||
expectedStatus int
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Valid Update",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(&loginCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = ""
|
||||
u.FirstName = "John Updated"
|
||||
u.LastName = "Doe Updated"
|
||||
u.Phone = "01738484994"
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "Password Update",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(&loginCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = "NewPassword"
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "Valid Update, invalid cookie",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(&invalidCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = ""
|
||||
u.FirstName = "John Updated"
|
||||
u.LastName = "Doe Updated"
|
||||
u.Phone = "01738484994"
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Auth token invalid",
|
||||
},
|
||||
{
|
||||
name: "Invalid Email Update",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(&loginCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = ""
|
||||
u.Email = "invalid-email"
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Invalid user data",
|
||||
},
|
||||
{
|
||||
name: "User ID mismatch while not admin",
|
||||
setupCookie: func(req *http.Request) {
|
||||
req.AddCookie(&loginCookie)
|
||||
},
|
||||
updateFunc: func(u *models.User) {
|
||||
u.Password = ""
|
||||
u.ID = 1
|
||||
u.FirstName = "John Missing ID"
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedError: "You are not authorized to update this user",
|
||||
},
|
||||
// {
|
||||
// name: "Non-existent User",
|
||||
// setupCookie: func(req *http.Request) {
|
||||
// req.AddCookie(&loginCookie)
|
||||
// },
|
||||
// updateFunc: func(u *models.User) {
|
||||
// u.Password = ""
|
||||
// u.ID = 99999
|
||||
// u.FirstName = "Non-existent"
|
||||
// },
|
||||
// expectedStatus: http.StatusNotFound,
|
||||
// expectedError: "User not found",
|
||||
// },
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
logger.Error.Print("==============================================================")
|
||||
logger.Error.Printf("Update Testing : %v", tt.name)
|
||||
logger.Error.Print("==============================================================")
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a copy of the user and apply the updates
|
||||
updatedUser := user
|
||||
tt.updateFunc(&updatedUser)
|
||||
|
||||
// Convert user to JSON
|
||||
jsonData, err := json.Marshal(updatedUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal user data: %v", err)
|
||||
}
|
||||
|
||||
// Create request
|
||||
req, _ := http.NewRequest("PUT", "/users/"+strconv.FormatInt(user.ID, 10), bytes.NewBuffer(jsonData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
tt.setupCookie(req)
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Set up router and add middleware
|
||||
router := gin.New()
|
||||
router.Use(middlewares.AuthMiddleware())
|
||||
router.PUT("/users/:id", Uc.UpdateHandler)
|
||||
|
||||
// Perform request
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// Check status code
|
||||
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||
|
||||
// Parse response
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
assert.Equal(t, tt.expectedError, response["error"])
|
||||
} else {
|
||||
assert.Equal(t, "User updated successfully", response["message"])
|
||||
|
||||
// Verify the update in the database
|
||||
updatedUserFromDB, err := Uc.Service.GetUserByID(user.ID)
|
||||
updatedUserFromDB.UpdatedAt = updatedUser.UpdatedAt
|
||||
updatedUserFromDB.Membership.UpdatedAt = updatedUser.Membership.UpdatedAt
|
||||
updatedUserFromDB.BankAccount.UpdatedAt = updatedUser.BankAccount.UpdatedAt
|
||||
updatedUserFromDB.Verification.UpdatedAt = updatedUser.Verification.UpdatedAt
|
||||
updatedUserFromDB.Membership.SubscriptionModel.UpdatedAt = updatedUser.Membership.SubscriptionModel.UpdatedAt
|
||||
if updatedUser.Password == "" {
|
||||
assert.Equal(t, user.Password, (*updatedUserFromDB).Password)
|
||||
} else {
|
||||
assert.NotEqual(t, user.Password, (*updatedUserFromDB).Password)
|
||||
updatedUser.Password = ""
|
||||
}
|
||||
updatedUserFromDB.Password = ""
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, updatedUser, *updatedUserFromDB, "Updated user in DB does not match expected user")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkWelcomeMail(message *utils.Email, user *models.User) error {
|
||||
|
||||
if !strings.Contains(message.To, user.Email) {
|
||||
@@ -506,23 +700,6 @@ func getVerificationURL(mailBody string) (string, error) {
|
||||
}
|
||||
|
||||
// TEST DATA:
|
||||
func getBaseUser() models.User {
|
||||
return models.User{
|
||||
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "john.doe@example.com",
|
||||
Address: "Pablo Escobar Str. 4",
|
||||
ZipCode: "25474",
|
||||
City: "Hasloh",
|
||||
Phone: "01738484993",
|
||||
BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"},
|
||||
Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
|
||||
ProfilePicture: "",
|
||||
Password: "password123",
|
||||
Company: "",
|
||||
}
|
||||
}
|
||||
|
||||
func customizeInput(customize func(models.User) models.User) *RegistrationData {
|
||||
user := getBaseUser()
|
||||
|
||||
Reference in New Issue
Block a user