MEDIUM-002: Remove manual X-Forwarded-For parsing in metrics_protection.go, use c.ClientIP() only (respects SetTrustedProxies) MEDIUM-003: Pin ClamAV Docker image to 1.4 across all compose files MEDIUM-004: Add clampLimit(100) to 15+ handlers that parsed limit directly MEDIUM-006: Remove unsafe-eval from CSP script-src on Swagger routes MEDIUM-007: Pin all GitHub Actions to SHA in 11 workflow files MEDIUM-008: Replace rabbitmq:3-management-alpine with rabbitmq:3-alpine in prod MEDIUM-009: Add trial-already-used check in subscription service MEDIUM-010: Add 60s periodic token re-validation to WebSocket connections MEDIUM-011: Mask email in auth handler logs with maskEmail() helper MEDIUM-012: Add k-anonymity threshold (k=5) to playback analytics stats LOW-001: Align frontend password policy to 12 chars (matching backend) LOW-003: Replace deprecated dotenv with dotenvy crate in Rust stream server LOW-004: Enable xpack.security in Elasticsearch dev/local compose files LOW-005: Accept context.Context in CleanupExpiredSessions instead of Background() LOW-002: Noted — Hyperswitch version update deferred (requires payment integration tests) 29/30 findings remediated. 1 noted (LOW-002). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
245 lines
6.9 KiB
Go
245 lines
6.9 KiB
Go
package jobs
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/database"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// MockSessionServiceForCleanup pour les tests
|
|
type MockSessionServiceForCleanup struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockSessionServiceForCleanup) CleanupExpiredSessions(ctx context.Context) (int64, error) {
|
|
args := m.Called(ctx)
|
|
return args.Get(0).(int64), args.Error(1)
|
|
}
|
|
|
|
// TestCleanupExpiredSessions_Success teste le nettoyage réussi des sessions expirées
|
|
func TestCleanupExpiredSessions_Success(t *testing.T) {
|
|
// Créer une base de données de test
|
|
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
|
|
// Créer la table sessions
|
|
err = gormDB.Exec(`
|
|
CREATE TABLE sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
revoked_at TIMESTAMP,
|
|
last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`).Error
|
|
require.NoError(t, err)
|
|
|
|
// Insérer des sessions expirées et non expirées
|
|
now := time.Now()
|
|
expiredTime := now.Add(-1 * time.Hour)
|
|
futureTime := now.Add(24 * time.Hour)
|
|
|
|
err = gormDB.Exec(`
|
|
INSERT INTO sessions (user_id, token_hash, expires_at, created_at)
|
|
VALUES
|
|
(1, 'hash1', ?, ?),
|
|
(1, 'hash2', ?, ?),
|
|
(2, 'hash3', ?, ?)
|
|
`, expiredTime, now, expiredTime, now, futureTime, now).Error
|
|
require.NoError(t, err)
|
|
|
|
sqlDB, err := gormDB.DB()
|
|
require.NoError(t, err)
|
|
|
|
testDB := &database.Database{
|
|
DB: sqlDB,
|
|
}
|
|
|
|
logger := zap.NewNop()
|
|
|
|
// Exécuter le nettoyage
|
|
err = CleanupExpiredSessions(context.Background(), testDB, logger)
|
|
assert.NoError(t, err)
|
|
|
|
// Vérifier que les sessions expirées ont été supprimées
|
|
var count int64
|
|
err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), count, "Only one non-expired session should remain")
|
|
}
|
|
|
|
// TestCleanupExpiredSessions_NoExpiredSessions teste le cas où il n'y a pas de sessions expirées
|
|
func TestCleanupExpiredSessions_NoExpiredSessions(t *testing.T) {
|
|
// Créer une base de données de test
|
|
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
|
|
// Créer la table sessions
|
|
err = gormDB.Exec(`
|
|
CREATE TABLE sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
revoked_at TIMESTAMP,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`).Error
|
|
require.NoError(t, err)
|
|
|
|
// Insérer seulement des sessions non expirées
|
|
now := time.Now()
|
|
futureTime1 := now.Add(24 * time.Hour)
|
|
futureTime2 := now.Add(48 * time.Hour)
|
|
|
|
err = gormDB.Exec(`
|
|
INSERT INTO sessions (user_id, token_hash, expires_at, created_at)
|
|
VALUES
|
|
(1, 'hash1', ?, ?),
|
|
(1, 'hash2', ?, ?)
|
|
`, futureTime1, now, futureTime2, now).Error
|
|
require.NoError(t, err)
|
|
|
|
sqlDB, err := gormDB.DB()
|
|
require.NoError(t, err)
|
|
|
|
testDB := &database.Database{
|
|
DB: sqlDB,
|
|
}
|
|
|
|
logger := zap.NewNop()
|
|
|
|
// Exécuter le nettoyage
|
|
err = CleanupExpiredSessions(context.Background(), testDB, logger)
|
|
assert.NoError(t, err)
|
|
|
|
// Vérifier que toutes les sessions sont toujours là
|
|
var count int64
|
|
err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), count, "All sessions should remain")
|
|
}
|
|
|
|
// TestCleanupExpiredSessions_EmptyDatabase teste le cas où la base de données est vide
|
|
func TestCleanupExpiredSessions_EmptyDatabase(t *testing.T) {
|
|
// Créer une base de données de test
|
|
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
|
|
// Créer la table sessions
|
|
err = gormDB.Exec(`
|
|
CREATE TABLE sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
revoked_at TIMESTAMP,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`).Error
|
|
require.NoError(t, err)
|
|
|
|
sqlDB, err := gormDB.DB()
|
|
require.NoError(t, err)
|
|
|
|
testDB := &database.Database{
|
|
DB: sqlDB,
|
|
}
|
|
|
|
logger := zap.NewNop()
|
|
|
|
// Exécuter le nettoyage
|
|
err = CleanupExpiredSessions(context.Background(), testDB, logger)
|
|
assert.NoError(t, err)
|
|
|
|
// Vérifier qu'il n'y a pas de sessions
|
|
var count int64
|
|
err = gormDB.Raw("SELECT COUNT(*) FROM sessions").Scan(&count).Error
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), count, "No sessions should exist")
|
|
}
|
|
|
|
// TestScheduleCleanupJob_Execution teste que le job est programmé correctement
|
|
func TestScheduleCleanupJob_Execution(t *testing.T) {
|
|
// Créer une base de données de test
|
|
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
|
|
// Créer la table sessions
|
|
err = gormDB.Exec(`
|
|
CREATE TABLE sessions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
token_hash TEXT NOT NULL,
|
|
expires_at TIMESTAMP NOT NULL,
|
|
revoked_at TIMESTAMP,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`).Error
|
|
require.NoError(t, err)
|
|
|
|
sqlDB, err := gormDB.DB()
|
|
require.NoError(t, err)
|
|
|
|
testDB := &database.Database{
|
|
DB: sqlDB,
|
|
}
|
|
|
|
logger := zap.NewNop()
|
|
|
|
// Programmer le job avec un ticker très court pour les tests (1 seconde au lieu de 24 heures)
|
|
// Note: Dans un vrai test, on pourrait utiliser un mock ticker, mais pour simplifier
|
|
// on teste juste que la fonction s'exécute sans erreur
|
|
ticker := time.NewTicker(100 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
executed := make(chan bool, 1)
|
|
go func() {
|
|
// Exécuter immédiatement au démarrage
|
|
if err := CleanupExpiredSessions(context.Background(), testDB, logger); err != nil {
|
|
logger.Error("Initial sessions cleanup job failed", zap.Error(err))
|
|
}
|
|
executed <- true
|
|
|
|
// Attendre un tick
|
|
<-ticker.C
|
|
if err := CleanupExpiredSessions(context.Background(), testDB, logger); err != nil {
|
|
logger.Error("Scheduled sessions cleanup job failed", zap.Error(err))
|
|
}
|
|
executed <- true
|
|
}()
|
|
|
|
// Attendre que le job initial soit exécuté
|
|
select {
|
|
case <-executed:
|
|
assert.True(t, true, "Initial cleanup job should execute")
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("Initial cleanup job did not execute in time")
|
|
}
|
|
|
|
// Attendre que le job programmé soit exécuté
|
|
select {
|
|
case <-executed:
|
|
assert.True(t, true, "Scheduled cleanup job should execute")
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Scheduled cleanup job did not execute in time")
|
|
}
|
|
|
|
// Vérifier que la fonction ScheduleCleanupJob peut être appelée sans erreur
|
|
// Note: On ne peut pas vraiment tester qu'elle s'exécute en continu sans bloquer le test
|
|
ScheduleSessionCleanupJob(testDB, logger)
|
|
time.Sleep(100 * time.Millisecond) // Attendre un peu pour que la goroutine démarre
|
|
}
|