2026-02-27 08:58:53 +00:00
//go:build integration
// +build integration
package integration
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"veza-backend-api/internal/config"
"veza-backend-api/internal/core/auth"
"veza-backend-api/internal/database"
"veza-backend-api/internal/dto"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/handlers"
"veza-backend-api/internal/models"
"veza-backend-api/internal/repositories"
"veza-backend-api/internal/services"
"veza-backend-api/internal/validators"
"github.com/google/uuid"
)
// setupTokenRefreshTestRouter creates a test router for token refresh E2E (cookie-based)
func setupTokenRefreshTestRouter ( t * testing . T ) ( * gin . Engine , * auth . AuthService , * config . Config , * gorm . DB , func ( ) ) {
gin . SetMode ( gin . TestMode )
logger := zaptest . NewLogger ( t )
db , err := gorm . Open ( sqlite . Open ( ":memory:" ) , & gorm . Config { } )
require . NoError ( t , err )
db . Exec ( "PRAGMA foreign_keys = ON" )
err = db . AutoMigrate (
& models . User { } ,
& models . RefreshToken { } ,
& models . Session { } ,
& models . Role { } ,
& models . Permission { } ,
& models . UserRole { } ,
& models . RolePermission { } ,
)
require . NoError ( t , err )
err = db . Exec ( `
CREATE TABLE IF NOT EXISTS email_verification_tokens (
id TEXT PRIMARY KEY ,
user_id TEXT NOT NULL REFERENCES users ( id ) ON DELETE CASCADE ,
token TEXT NOT NULL UNIQUE ,
token_hash TEXT NOT NULL ,
email TEXT NOT NULL ,
verified INTEGER NOT NULL DEFAULT 0 ,
used INTEGER NOT NULL DEFAULT 0 ,
verified_at TIMESTAMP ,
expires_at TIMESTAMP NOT NULL ,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)
` ) . Error
require . NoError ( t , err )
sqlDB , err := db . DB ( )
require . NoError ( t , err )
dbWrapper := & database . Database { DB : sqlDB , GormDB : db , Logger : logger }
emailValidator := validators . NewEmailValidator ( db )
passwordValidator := validators . NewPasswordValidator ( )
passwordService := services . NewPasswordService ( dbWrapper , logger )
2026-03-05 18:22:31 +00:00
jwtService , err := services . NewJWTService ( "" , "" , "test-secret-key-must-be-32-chars-long" , "test-issuer" , "test-audience" )
2026-02-27 08:58:53 +00:00
require . NoError ( t , err )
refreshTokenService := services . NewRefreshTokenService ( db )
emailVerificationService := services . NewEmailVerificationService ( dbWrapper , logger )
passwordResetService := services . NewPasswordResetService ( dbWrapper , logger )
emailService := services . NewEmailService ( dbWrapper , logger )
authService := auth . NewAuthService (
db , emailValidator , passwordValidator , passwordService ,
jwtService , refreshTokenService , emailVerificationService ,
passwordResetService , emailService , nil , nil , logger ,
)
sessionService := services . NewSessionService ( dbWrapper , logger )
twoFactorService := services . NewTwoFactorService ( dbWrapper , logger )
userRepo := repositories . NewGormUserRepository ( db )
userService := services . NewUserServiceWithDB ( userRepo , db )
cfg := & config . Config {
CookiePath : "/" ,
CookieDomain : "" ,
CookieHttpOnly : true ,
CookieSecure : false ,
CookieSameSite : "lax" ,
JWTService : jwtService ,
}
router := gin . New ( )
authGroup := router . Group ( "/auth" )
{
authGroup . POST ( "/login" , handlers . Login ( authService , sessionService , twoFactorService , logger , cfg ) )
authGroup . POST ( "/register" , handlers . Register ( authService , sessionService , logger , cfg ) )
authGroup . POST ( "/refresh" , handlers . Refresh ( authService , sessionService , logger , cfg ) )
authGroup . POST ( "/logout" , handlers . Logout ( authService , sessionService , logger , cfg ) )
authGroup . POST ( "/verify-email" , handlers . VerifyEmail ( authService ) )
authGroup . GET ( "/check-username" , handlers . CheckUsername ( authService ) )
protected := authGroup . Group ( "" )
protected . Use ( func ( c * gin . Context ) {
authHeader := c . GetHeader ( "Authorization" )
if authHeader == "" {
handlers . RespondWithAppError ( c , apperrors . NewUnauthorizedError ( "Unauthorized" ) )
c . Abort ( )
return
}
if ! strings . HasPrefix ( authHeader , "Bearer " ) {
handlers . RespondWithAppError ( c , apperrors . NewUnauthorizedError ( "Invalid authorization header" ) )
c . Abort ( )
return
}
token := strings . TrimPrefix ( authHeader , "Bearer " )
claims , err := authService . JWTService . ValidateToken ( token )
if err != nil {
handlers . RespondWithAppError ( c , apperrors . NewUnauthorizedError ( "Invalid token" ) )
c . Abort ( )
return
}
if claims . UserID == uuid . Nil {
handlers . RespondWithAppError ( c , apperrors . NewUnauthorizedError ( "Invalid token claims" ) )
c . Abort ( )
return
}
c . Set ( "user_id" , claims . UserID )
c . Next ( )
} )
protected . GET ( "/me" , handlers . GetMe ( userService ) )
}
return router , authService , cfg , db , func ( ) { }
}
// TestTokenRefreshViaCookies tests refresh flow using httpOnly cookies only (no body)
func TestTokenRefreshViaCookies ( t * testing . T ) {
router , _ , _ , db , cleanup := setupTokenRefreshTestRouter ( t )
defer cleanup ( )
// 1. Register
registerBody , _ := json . Marshal ( dto . RegisterRequest {
Email : "refresh@test.com" ,
Username : "refreshtest" ,
Password : "SecurePassword123!" ,
PasswordConfirm : "SecurePassword123!" ,
} )
registerReq := httptest . NewRequest ( http . MethodPost , "/auth/register" , bytes . NewBuffer ( registerBody ) )
registerReq . Header . Set ( "Content-Type" , "application/json" )
registerW := httptest . NewRecorder ( )
router . ServeHTTP ( registerW , registerReq )
require . Equal ( t , http . StatusCreated , registerW . Code )
// 2. Verify email
var user models . User
require . NoError ( t , db . Where ( "email = ?" , "refresh@test.com" ) . First ( & user ) . Error )
var token string
err := db . Raw ( "SELECT token FROM email_verification_tokens WHERE user_id = ? AND used = 0 ORDER BY created_at DESC LIMIT 1" , user . ID . String ( ) ) . Scan ( & token ) . Error
if err != nil {
t . Skip ( "email verification token not found" )
return
}
verifyReq := httptest . NewRequest ( http . MethodPost , "/auth/verify-email?token=" + token , nil )
verifyW := httptest . NewRecorder ( )
router . ServeHTTP ( verifyW , verifyReq )
require . Equal ( t , http . StatusOK , verifyW . Code )
// 3. Login
loginBody , _ := json . Marshal ( dto . LoginRequest {
Email : "refresh@test.com" ,
Password : "SecurePassword123!" ,
RememberMe : false ,
} )
loginReq := httptest . NewRequest ( http . MethodPost , "/auth/login" , bytes . NewBuffer ( loginBody ) )
loginReq . Header . Set ( "Content-Type" , "application/json" )
loginW := httptest . NewRecorder ( )
router . ServeHTTP ( loginW , loginReq )
require . Equal ( t , http . StatusOK , loginW . Code )
// Extract refresh_token from cookies
loginCookies := loginW . Result ( ) . Cookies ( )
var refreshCookie * http . Cookie
for _ , c := range loginCookies {
if c . Name == "refresh_token" {
refreshCookie = c
break
}
}
require . NotNil ( t , refreshCookie , "refresh_token cookie should be set after login" )
require . NotEmpty ( t , refreshCookie . Value )
assert . True ( t , refreshCookie . HttpOnly )
// 4. Refresh using ONLY cookie (no body)
refreshReq := httptest . NewRequest ( http . MethodPost , "/auth/refresh" , nil )
refreshReq . AddCookie ( refreshCookie )
refreshW := httptest . NewRecorder ( )
router . ServeHTTP ( refreshW , refreshReq )
require . Equal ( t , http . StatusOK , refreshW . Code )
// 5. Verify new cookies are set
refreshCookies := refreshW . Result ( ) . Cookies ( )
var newAccessCookie * http . Cookie
for _ , c := range refreshCookies {
if c . Name == "access_token" {
newAccessCookie = c
break
}
}
require . NotNil ( t , newAccessCookie , "access_token cookie should be updated" )
require . NotEmpty ( t , newAccessCookie . Value )
assert . True ( t , newAccessCookie . HttpOnly )
// 6. Use new access token for protected route
meReq := httptest . NewRequest ( http . MethodGet , "/auth/me" , nil )
meReq . Header . Set ( "Authorization" , "Bearer " + newAccessCookie . Value )
meW := httptest . NewRecorder ( )
router . ServeHTTP ( meW , meReq )
assert . Equal ( t , http . StatusOK , meW . Code )
}