feat(auth): align password reset flows

This commit is contained in:
2026-06-05 22:59:15 +08:00
parent 9db133697e
commit ad5d864944
8 changed files with 397 additions and 54 deletions
+73 -5
View File
@@ -22,7 +22,7 @@ import (
"github.com/gochat/gochat/pkg/crypto"
)
func setupChatwootAuthTest(t *testing.T) (*gin.Engine, *model.User) {
func setupChatwootAuthTest(t *testing.T) (*gin.Engine, *gorm.DB, *model.User) {
t.Helper()
gin.SetMode(gin.TestMode)
@@ -60,11 +60,11 @@ func setupChatwootAuthTest(t *testing.T) (*gin.Engine, *model.User) {
router := gin.New()
RegisterChatwootAuthRoutes(router.Group("/auth"), handler)
return router, user
return router, db, user
}
func TestChatwootAuthSignInReturnsDeviseHeadersAndUserPayload(t *testing.T) {
router, _ := setupChatwootAuthTest(t)
router, _, _ := setupChatwootAuthTest(t)
body, _ := json.Marshal(map[string]string{"email": " AUTH@example.com ", "password": "password123"})
req, _ := http.NewRequest(http.MethodPost, "/auth/sign_in", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
@@ -93,7 +93,7 @@ func TestChatwootAuthSignInReturnsDeviseHeadersAndUserPayload(t *testing.T) {
}
func TestChatwootAuthValidateTokenReturnsPayloadData(t *testing.T) {
router, _ := setupChatwootAuthTest(t)
router, _, _ := setupChatwootAuthTest(t)
token := signInAndReturnAccessToken(t, router)
req, _ := http.NewRequest(http.MethodGet, "/auth/validate_token", nil)
req.Header.Set("access-token", token)
@@ -111,7 +111,7 @@ func TestChatwootAuthValidateTokenReturnsPayloadData(t *testing.T) {
}
func TestChatwootAuthSignOutRevokesRefreshSession(t *testing.T) {
router, _ := setupChatwootAuthTest(t)
router, _, _ := setupChatwootAuthTest(t)
token := signInAndReturnAccessToken(t, router)
req, _ := http.NewRequest(http.MethodDelete, "/auth/sign_out", nil)
req.Header.Set("access-token", token)
@@ -125,6 +125,74 @@ func TestChatwootAuthSignOutRevokesRefreshSession(t *testing.T) {
require.Equal(t, true, body["success"])
}
func TestChatwootAuthResetPasswordReturnsReferenceMessage(t *testing.T) {
router, db, user := setupChatwootAuthTest(t)
body, _ := json.Marshal(map[string]string{"email": "auth@example.com"})
req, _ := http.NewRequest(http.MethodPost, "/auth/password", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Equal(t, service.ChatwootPasswordResetMessage, resp["message"])
var updated model.User
require.NoError(t, db.First(&updated, user.ID).Error)
require.NotEmpty(t, updated.ResetPasswordToken)
}
func TestChatwootAuthConfirmResetPasswordReturnsAuthPayload(t *testing.T) {
router, db, user := setupChatwootAuthTest(t)
require.NoError(t, db.Model(user).Updates(map[string]interface{}{
"reset_password_token": "raw-reset-token",
"reset_password_sent_at": time.Now().UTC(),
}).Error)
body, _ := json.Marshal(map[string]string{
"reset_password_token": "raw-reset-token",
"password": "newpassword",
"password_confirmation": "newpassword",
})
req, _ := http.NewRequest(http.MethodPut, "/auth/password", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
require.NotEmpty(t, w.Header().Get("access-token"))
var resp map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]any)
require.Equal(t, "auth@example.com", data["email"])
var updated model.User
require.NoError(t, db.First(&updated, user.ID).Error)
require.True(t, crypto.CheckPassword("newpassword", updated.PasswordDigest))
require.Empty(t, updated.ResetPasswordToken)
}
func TestChatwootAuthConfirmationReturnsAuthPayload(t *testing.T) {
router, db, user := setupChatwootAuthTest(t)
require.NoError(t, db.Model(user).Updates(map[string]interface{}{
"confirmation_token": "raw-confirm-token",
"confirmed_at": nil,
}).Error)
body, _ := json.Marshal(map[string]string{"confirmation_token": "raw-confirm-token"})
req, _ := http.NewRequest(http.MethodPost, "/auth/confirmation", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
require.NotEmpty(t, w.Header().Get("access-token"))
var updated model.User
require.NoError(t, db.First(&updated, user.ID).Error)
require.NotNil(t, updated.ConfirmedAt)
require.Empty(t, updated.ConfirmationToken)
}
func signInAndReturnAccessToken(t *testing.T, router *gin.Engine) string {
t.Helper()
body, _ := json.Marshal(map[string]string{"email": "auth@example.com", "password": "password123"})