From 066419e5463c7891b6b6742a0e510f7664a5dd5a Mon Sep 17 00:00:00 2001 From: "$(pass /github/name)" <$(pass /github/email)> Date: Sat, 7 Sep 2024 08:55:39 +0200 Subject: [PATCH] fix: user model json handling; user_controller_test debug logging, user_controller --- internal/controllers/user_controller.go | 17 ++- internal/controllers/user_controller_test.go | 137 +++++++++++++++++-- internal/models/user.go | 4 +- 3 files changed, 136 insertions(+), 22 deletions(-) diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index cde8915..97c20b5 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -24,17 +24,19 @@ type UserController struct { } type RegistrationData struct { - User models.User `json:"user"` + User models.User `json:"user"` + Password string `json:"password"` } func (uc *UserController) CurrentUserHandler(c *gin.Context) { - userID, err := middlewares.GetUserIDFromContext(c) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to authenticate user"}) - c.Abort() - return + userIDString, ok := c.Get("user_id") + if !ok || userIDString == nil { + logger.Error.Printf("Error getting user_id from header") } - user, err := uc.Service.GetUserByID(userID) + userID := userIDString.(float64) + logger.Error.Printf("UserIDINt64: %v", userID) + logger.Error.Printf("c.Get Value: %v", userIDString) + user, err := uc.Service.GetUserByID(int64(userID)) if err != nil { logger.Error.Printf("Error retrieving valid user: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving user."}) @@ -124,6 +126,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) { // logger.Info.Printf("REGISTERING user: %#v", regData.User) regData.User.RoleID = constants.Roles.Member + regData.User.Password = regData.Password // Register User id, token, err := uc.Service.RegisterUser(®Data.User) diff --git a/internal/controllers/user_controller_test.go b/internal/controllers/user_controller_test.go index 588d5c4..b2be844 100644 --- a/internal/controllers/user_controller_test.go +++ b/internal/controllers/user_controller_test.go @@ -18,11 +18,17 @@ import ( "GoMembership/internal/config" "GoMembership/internal/constants" + "GoMembership/internal/middlewares" "GoMembership/internal/models" "GoMembership/internal/utils" "GoMembership/pkg/logger" ) +type loginInput struct { + Email string `json:"email"` + Password string `json:"password"` +} + type RegisterUserTest struct { WantDBData map[string]interface{} Name string @@ -61,11 +67,13 @@ func testUserController(t *testing.T) { } }) } - testLoginUser(t) + testCurrentUserHandler(t) } -func testLoginUser(t *testing.T) { +func testLoginUser(t *testing.T) (string, http.Cookie) { // This test should run after the user registration test + var loginCookie http.Cookie + var loginInput loginInput t.Run("LoginUser", func(t *testing.T) { // Test cases tests := []struct { @@ -104,6 +112,9 @@ func testLoginUser(t *testing.T) { } for _, tt := range tests { + logger.Error.Print("==============================================================") + logger.Error.Printf("Testing : %v", tt.name) + logger.Error.Print("==============================================================") t.Run(tt.name, func(t *testing.T) { // Setup c, w, _ := GetMockedJSONContext([]byte(tt.input), "/login") @@ -122,13 +133,113 @@ func testLoginUser(t *testing.T) { logger.Info.Printf("Response: %#v", response) assert.Contains(t, response, "set-token") assert.NotEmpty(t, response["set-token"]) + for _, cookie := range w.Result().Cookies() { + if cookie.Name == "jwt" { + loginCookie = *cookie + + err = json.Unmarshal([]byte(tt.input), &loginInput) + assert.NoError(t, err, "Failed to unmarshal input JSON") + + break + } + } + assert.NotEmpty(t, loginCookie) } else { assert.NotContains(t, response, "set-token") } }) + } }) + return loginInput.Email, loginCookie } + +func testCurrentUserHandler(t *testing.T) { + loginEmail, loginCookie := testLoginUser(t) + // This test should run after the user login test + invalidCookie := http.Cookie{ + Name: "jwt", + Value: "invalid.token.here", + } + tests := []struct { + name string + setupCookie func(*http.Request) + expectedUserMail string + expectedStatus int + }{ + { + name: "With valid cookie", + setupCookie: func(req *http.Request) { + req.AddCookie(&loginCookie) + }, + expectedUserMail: loginEmail, + expectedStatus: http.StatusOK, + }, + { + name: "Without cookie", + setupCookie: func(req *http.Request) {}, + expectedStatus: http.StatusUnauthorized, + }, + { + name: "With invalid cookie", + setupCookie: func(req *http.Request) { + req.AddCookie(&invalidCookie) + }, + expectedStatus: http.StatusUnauthorized, + }, + } + + for _, tt := range tests { + 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) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/current-user", nil) + tt.setupCookie(req) + + router.ServeHTTP(w, req) + + assert.Equal(t, tt.expectedStatus, w.Code) + + if tt.expectedStatus == http.StatusOK { + var response models.User + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, tt.expectedUserMail, response.Email) + var newCookie *http.Cookie + for _, cookie := range w.Result().Cookies() { + if cookie.Name == "jwt" { + newCookie = 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") + } else { + // For unauthorized requests, check for an error message + var errorResponse map[string]string + err := json.Unmarshal(w.Body.Bytes(), &errorResponse) + assert.NoError(t, err) + assert.Contains(t, errorResponse, "error") + assert.NotEmpty(t, errorResponse["error"]) + } + + }) + } +} + func validateUser(assert bool, wantDBData map[string]interface{}) error { users, err := Uc.Service.GetUsers(wantDBData) if err != nil { @@ -195,14 +306,14 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error { if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnummer: %v", user.Membership.ID)) { return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID) } - if !strings.Contains(message.Body, config.BaseURL) { - return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL) + if !strings.Contains(message.Body, config.Site.BaseURL) { + return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL) } - if !strings.Contains(message.Body, config.BaseURL+config.Templates.LogoURI) { - return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.BaseURL+config.WebsiteTitle) + if !strings.Contains(message.Body, config.Site.BaseURL+config.Templates.LogoURI) { + return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.Site.BaseURL+config.Site.WebsiteTitle) } - if !strings.Contains(message.Body, config.WebsiteTitle) { - return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.WebsiteTitle) + if !strings.Contains(message.Body, config.Site.WebsiteTitle) { + return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.Site.WebsiteTitle) } return nil } @@ -249,8 +360,8 @@ func checkRegistrationMail(message *utils.Email, user *models.User) error { if !strings.Contains(message.Body, user.BankAccount.IBAN) { return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN) } - if !strings.Contains(message.Body, config.BaseURL) { - return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL) + if !strings.Contains(message.Body, config.Site.BaseURL) { + return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL) } return nil } @@ -267,8 +378,8 @@ func checkVerificationMail(message *utils.Email, user *models.User) error { return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL) } - if !strings.Contains(message.Body, config.BaseURL) { - return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.BaseURL) + if !strings.Contains(message.Body, config.Site.BaseURL) { + return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.Site.BaseURL) } // open the provided link: if err := verifyMail(verificationURL); err != nil { @@ -349,7 +460,7 @@ func getBaseUser() models.User { func customizeInput(customize func(models.User) models.User) *RegistrationData { user := getBaseUser() user = customize(user) // Apply the customization - return &RegistrationData{User: user} + return &RegistrationData{User: user, Password: user.Password} } func getTestUsers() []RegisterUserTest { diff --git a/internal/models/user.go b/internal/models/user.go index 911c398..f82ed91 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -16,7 +16,7 @@ type User struct { Phone string `json:"phone" validate:"omitempty,omitnil"` Notes *string `json:"notes"` FirstName string `gorm:"not null" json:"first_name" validate:"required"` - Password string `json:"password" required_unless=RoleID 0` + Password string `json:"-" required_unless=RoleID 0` Email string `gorm:"unique;not null" json:"email" validate:"required,email"` LastName string `gorm:"not null" json:"last_name" validate:"required"` ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image"` @@ -27,7 +27,7 @@ type User struct { BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"` Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"` - ID int64 `gorm:"primaryKey"` + ID int64 `gorm:"primaryKey" json:"id"` PaymentStatus int8 `json:"payment_status"` Status int8 `json:"status"` RoleID int8 `json:"role_id"`