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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token, ttl) 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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token, ttl) require.NoError(t, err) // Validate the token err = service.Validate(user.ID, token) 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 err := service.Validate(user.ID, "non-existent-token") assert.Error(t, err) assert.Equal(t, "refresh token not found", err.Error()) } 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" ttl := -1 * time.Hour // Expired 1 hour ago err := service.Store(user.ID, token, ttl) require.NoError(t, err) // Validate the expired token err = service.Validate(user.ID, token) assert.Error(t, err) assert.Equal(t, "refresh token expired", err.Error()) } 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" ttl := 30 * 24 * time.Hour // Store token for first user err := service.Store(user.ID, token, ttl) require.NoError(t, err) // Try to validate with wrong user ID err = service.Validate(otherUser.ID, token) assert.Error(t, err) assert.Equal(t, "refresh token not found", err.Error()) } 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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token, ttl) require.NoError(t, err) // Verify token exists err = service.Validate(user.ID, token) require.NoError(t, err) // Revoke the token err = service.Revoke(user.ID, token) assert.NoError(t, err) // Verify token is no longer valid err = service.Validate(user.ID, token) assert.Error(t, err) assert.Equal(t, "refresh token not found", err.Error()) } 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") assert.NoError(t, err) // assert.Contains(t, err.Error(), "not found") // Service returns nil (idempotent) } 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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token1, ttl) require.NoError(t, err) err = service.Store(user.ID, token2, ttl) require.NoError(t, err) err = service.Store(user.ID, token3, ttl) require.NoError(t, err) // Verify all tokens are valid 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) // Revoke all tokens err = service.RevokeAll(user.ID) assert.NoError(t, err) // Verify all tokens are revoked 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) } 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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token1, ttl) assert.NoError(t, err) err = service.Store(user.ID, token2, ttl) assert.NoError(t, err) // Both tokens should be valid err = service.Validate(user.ID, token1) assert.NoError(t, err) err = service.Validate(user.ID, token2) 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" ttl := 30 * 24 * time.Hour err := service.Store(user.ID, token1, ttl) require.NoError(t, err) err = service.Store(user.ID, token2, ttl) require.NoError(t, err) // Revoke only token1 err = service.Revoke(user.ID, token1) assert.NoError(t, err) // token1 should be invalid err = service.Validate(user.ID, token1) assert.Error(t, err) // token2 should still be valid err = service.Validate(user.ID, token2) assert.NoError(t, err) }