2025-12-03 19:29:37 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"gorm.io/driver/sqlite"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// setupTestRefreshTokenService crée un RefreshTokenService de test avec une base de données en mémoire
|
|
|
|
|
func setupTestRefreshTokenService(t *testing.T) (*RefreshTokenService, *gorm.DB) {
|
|
|
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to open test database: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-migrate
|
|
|
|
|
err = db.AutoMigrate(&models.User{}, &models.RefreshToken{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Failed to migrate: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a test user
|
|
|
|
|
user := &models.User{
|
|
|
|
|
Email: "test@example.com",
|
|
|
|
|
Username: "testuser",
|
|
|
|
|
Role: "user",
|
|
|
|
|
IsActive: true,
|
|
|
|
|
}
|
|
|
|
|
db.Create(user)
|
|
|
|
|
|
|
|
|
|
service := NewRefreshTokenService(db)
|
|
|
|
|
return service, db
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Store(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
token := "test-refresh-token-123"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify token was stored (check by hash)
|
|
|
|
|
var storedToken models.RefreshToken
|
|
|
|
|
tokenHash := service.hashToken(token)
|
|
|
|
|
err = db.Where("user_id = ? AND token_hash = ?", user.ID, tokenHash).First(&storedToken).Error
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, user.ID, storedToken.UserID)
|
|
|
|
|
assert.Equal(t, tokenHash, storedToken.TokenHash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Validate_ValidToken(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
token := "valid-refresh-token"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Validate the token
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Validate_InvalidToken(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
// Try to validate a token that doesn't exist
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Validate(user.ID, "non-existent-token")
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Equal(t, "refresh token not found", err.Error())
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Validate_ExpiredToken(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
token := "expired-refresh-token"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := -1 * time.Hour // Expired 1 hour ago
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Validate the expired token
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Equal(t, "refresh token expired", err.Error())
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Validate_WrongUser(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
// Create another user
|
|
|
|
|
otherUser := &models.User{
|
|
|
|
|
Email: "other@example.com",
|
|
|
|
|
Username: "otheruser",
|
|
|
|
|
Role: "user",
|
|
|
|
|
IsActive: true,
|
|
|
|
|
}
|
|
|
|
|
db.Create(otherUser)
|
|
|
|
|
|
|
|
|
|
token := "user-specific-token"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
|
|
|
|
// Store token for first user
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Try to validate with wrong user ID
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(otherUser.ID, token)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Equal(t, "refresh token not found", err.Error())
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Revoke(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
token := "token-to-revoke"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify token exists
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Revoke the token
|
|
|
|
|
err = service.Revoke(user.ID, token)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify token is no longer valid
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Equal(t, "refresh token not found", err.Error())
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Revoke_NonExistentToken(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
// Try to revoke a token that doesn't exist
|
|
|
|
|
err := service.Revoke(user.ID, "non-existent-token")
|
2025-12-08 18:57:54 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
// assert.Contains(t, err.Error(), "not found") // Service returns nil (idempotent)
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_RevokeAll(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
// Store multiple tokens
|
|
|
|
|
token1 := "token-1"
|
|
|
|
|
token2 := "token-2"
|
|
|
|
|
token3 := "token-3"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token1, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Store(user.ID, token2, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Store(user.ID, token3, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify all tokens are valid
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token1)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
err = service.Validate(user.ID, token2)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
err = service.Validate(user.ID, token3)
|
|
|
|
|
assert.NoError(t, err)
|
2025-12-03 19:29:37 +00:00
|
|
|
|
|
|
|
|
// Revoke all tokens
|
|
|
|
|
err = service.RevokeAll(user.ID)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify all tokens are revoked
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token1)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
err = service.Validate(user.ID, token2)
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
err = service.Validate(user.ID, token3)
|
|
|
|
|
assert.Error(t, err)
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_hashToken(t *testing.T) {
|
|
|
|
|
service, _ := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
token := "test-token"
|
|
|
|
|
hash1 := service.hashToken(token)
|
|
|
|
|
hash2 := service.hashToken(token)
|
|
|
|
|
|
|
|
|
|
// Same token should produce same hash
|
|
|
|
|
assert.Equal(t, hash1, hash2)
|
|
|
|
|
assert.Len(t, hash1, 64) // SHA-256 produces 64 hex characters
|
|
|
|
|
|
|
|
|
|
// Different tokens should produce different hashes
|
|
|
|
|
hash3 := service.hashToken("different-token")
|
|
|
|
|
assert.NotEqual(t, hash1, hash3)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_StoreMultipleTokens(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
// Store multiple tokens for the same user
|
|
|
|
|
token1 := "token-1"
|
|
|
|
|
token2 := "token-2"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token1, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Store(user.ID, token2, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Both tokens should be valid
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token1)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token2)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Verify both tokens are stored in database
|
|
|
|
|
var count int64
|
|
|
|
|
db.Model(&models.RefreshToken{}).Where("user_id = ?", user.ID).Count(&count)
|
|
|
|
|
assert.Equal(t, int64(2), count)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRefreshTokenService_Validate_AfterRevokeOne(t *testing.T) {
|
|
|
|
|
service, db := setupTestRefreshTokenService(t)
|
|
|
|
|
|
|
|
|
|
var user models.User
|
|
|
|
|
db.Where("email = ?", "test@example.com").First(&user)
|
|
|
|
|
|
|
|
|
|
token1 := "token-1"
|
|
|
|
|
token2 := "token-2"
|
2025-12-06 16:21:59 +00:00
|
|
|
ttl := 30 * 24 * time.Hour
|
2025-12-03 19:29:37 +00:00
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
err := service.Store(user.ID, token1, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Store(user.ID, token2, ttl)
|
2025-12-03 19:29:37 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Revoke only token1
|
|
|
|
|
err = service.Revoke(user.ID, token1)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// token1 should be invalid
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token1)
|
|
|
|
|
assert.Error(t, err)
|
2025-12-03 19:29:37 +00:00
|
|
|
|
|
|
|
|
// token2 should still be valid
|
2025-12-06 16:21:59 +00:00
|
|
|
err = service.Validate(user.ID, token2)
|
2025-12-03 19:29:37 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|