Files
gochat/backend/internal/handler/api/v1/auth_handler_test.go
T

269 lines
10 KiB
Go

package v1
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
"github.com/gochat/gochat/pkg/crypto"
)
func setupChatwootAuthTest(t *testing.T) (*gin.Engine, *gorm.DB, *model.User) {
t.Helper()
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&model.Account{}, &model.User{}, &model.AccountUser{}, &model.AccessToken{}, &model.UserSession{}))
account := &model.Account{Name: "Auth Account", Status: "active", OnboardingStep: "invite_team"}
require.NoError(t, db.Create(account).Error)
passwordDigest, err := crypto.HashPassword("password123")
require.NoError(t, err)
confirmedAt := time.Now().UTC()
user := &model.User{
AccountID: account.ID,
Name: "Auth User",
Email: "auth@example.com",
UID: "auth@example.com",
PasswordDigest: passwordDigest,
Provider: "email",
DisplayName: "Auth Display",
MessageSignature: "Cheers",
PubsubToken: "pubsub-auth-user",
ConfirmedAt: &confirmedAt,
Active: true,
}
require.NoError(t, db.Create(user).Error)
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "administrator", Availability: "online", AutoOffline: true}).Error)
require.NoError(t, db.Create(&model.AccessToken{OwnerType: model.AccessTokenOwnerTypeUser, OwnerID: user.ID, Token: "auth-profile-token", TokenPrefix: "auth-pro", Name: "Personal Access Token"}).Error)
jwtCfg := &config.JWTConfig{Secret: "auth-test-secret", ExpiryHours: 1, RefreshExpiryHours: 24}
jwtSvc := auth.NewJWTService(jwtCfg)
refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg)
authSvc := service.NewAuthService(db, jwtSvc, refreshStore, nil, nil)
profileSvc := service.NewProfileService(repository.NewUserRepo(db), repository.NewAccountUserRepo(db), repository.NewAccessTokenRepo(db))
handler := NewAuthHandler(authSvc, nil, profileSvc)
router := gin.New()
RegisterChatwootAuthRoutes(router.Group("/auth"), handler)
return router, db, user
}
func TestRegisterAuthRoutesDoesNotExposeSelfRegistration(t *testing.T) {
router := gin.New()
RegisterAuthRoutes(router.Group("/api/v1"), &AuthHandler{})
for _, route := range router.Routes() {
require.NotEqual(t, "/api/v1/auth/register", route.Path)
}
}
func TestChatwootAuthSignInReturnsDeviseHeadersAndUserPayload(t *testing.T) {
router, db, user := 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")
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) Chrome/126.0.0.0 Safari/537.36")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
require.NotEmpty(t, w.Header().Get("access-token"))
require.Equal(t, "Bearer", w.Header().Get("token-type"))
require.NotEmpty(t, w.Header().Get("client"))
require.NotEmpty(t, w.Header().Get("expiry"))
require.Equal(t, "auth@example.com", w.Header().Get("uid"))
var payload map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &payload))
data := payload["data"].(map[string]any)
assertChatwootAuthUserFixture(t, data)
var session model.UserSession
require.NoError(t, db.Where("user_id = ? AND client_id = ?", user.ID, w.Header().Get("client")).First(&session).Error)
require.Equal(t, "Chrome", session.BrowserName)
require.Equal(t, "Linux", session.PlatformName)
}
func assertChatwootAuthUserFixture(t *testing.T, data map[string]any) {
t.Helper()
require.Equal(t, "Auth User", data["name"])
require.Equal(t, "auth@example.com", data["email"])
require.Equal(t, "auth@example.com", data["uid"])
require.Equal(t, "Auth Display", data["available_name"])
require.Equal(t, "Auth Display", data["display_name"])
require.Equal(t, "", data["avatar_url"])
require.Equal(t, "User", data["type"])
require.Equal(t, "email", data["provider"])
require.Equal(t, "pubsub-auth-user", data["pubsub_token"])
require.Equal(t, "Cheers", data["message_signature"])
require.Equal(t, "auth-profile-token", data["access_token"])
require.Equal(t, "administrator", data["role"])
require.Equal(t, map[string]any{}, data["custom_attributes"])
require.Equal(t, map[string]any{}, data["ui_settings"])
require.Equal(t, true, data["confirmed"])
require.NotNil(t, data["account_id"])
accounts := data["accounts"].([]any)
require.Len(t, accounts, 1)
account := accounts[0].(map[string]any)
require.Equal(t, "Auth Account", account["name"])
require.Equal(t, "active", account["status"])
require.Equal(t, "invite_team", account["onboarding_step"])
require.Equal(t, "administrator", account["role"])
require.Equal(t, "online", account["availability"])
require.Equal(t, "online", account["availability_status"])
require.Equal(t, true, account["auto_offline"])
require.Equal(t, []any{"administrator"}, account["permissions"])
}
func TestChatwootAuthValidateTokenReturnsPayloadData(t *testing.T) {
router, _, _ := setupChatwootAuthTest(t)
token := signInAndReturnAccessToken(t, router)
req, _ := http.NewRequest(http.MethodGet, "/auth/validate_token", nil)
req.Header.Set("access-token", token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var body map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
payload := body["payload"].(map[string]any)
require.Equal(t, true, payload["success"])
data := payload["data"].(map[string]any)
assertChatwootAuthUserFixture(t, data)
}
func TestChatwootAuthValidateTokenSerializesPlatformAdminType(t *testing.T) {
router, db, user := setupChatwootAuthTest(t)
require.NoError(t, db.Model(user).Updates(map[string]any{
"role": "super_admin",
"type": "User",
}).Error)
token := signInAndReturnAccessToken(t, router)
req, _ := http.NewRequest(http.MethodGet, "/auth/validate_token", nil)
req.Header.Set("access-token", token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var body map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
data := body["payload"].(map[string]any)["data"].(map[string]any)
require.Equal(t, "SuperAdmin", data["type"])
require.Equal(t, "administrator", data["role"])
}
func TestChatwootAuthSignOutRevokesRefreshSession(t *testing.T) {
router, _, _ := setupChatwootAuthTest(t)
token := signInAndReturnAccessToken(t, router)
req, _ := http.NewRequest(http.MethodDelete, "/auth/sign_out", nil)
req.Header.Set("access-token", token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
var body map[string]any
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body))
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"})
req, _ := http.NewRequest(http.MethodPost, "/auth/sign_in", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
return w.Header().Get("access-token")
}