package database import ( "fmt" "os" "strconv" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/postgres" "gorm.io/gorm" ) // setupPoolTestDB crée une connexion de test à la base de données pour les tests de pool // Nécessite une base de données PostgreSQL en cours d'exécution func setupPoolTestDB(t *testing.T) *gorm.DB { // Récupérer les variables d'environnement ou utiliser des valeurs par défaut host := getEnv("DB_HOST", "localhost") port := getEnvInt("DB_PORT", 5432) user := getEnv("DB_USER", "veza") password := getEnv("DB_PASSWORD", "veza_password") dbname := getEnv("DB_NAME", "veza_db") dsn := buildDSN(host, port, user, password, dbname) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { t.Skipf("Skipping test: cannot connect to database: %v", err) return nil } // Configurer le pool de connexions pour les tests sqlDB, err := db.DB() if err != nil { t.Skipf("Skipping test: cannot get underlying sql.DB: %v", err) return nil } sqlDB.SetMaxOpenConns(5) // Moins de connexions pour les tests sqlDB.SetMaxIdleConns(2) sqlDB.SetConnMaxLifetime(1 * time.Minute) sqlDB.SetConnMaxIdleTime(30 * time.Second) // Tester la connexion if err := sqlDB.Ping(); err != nil { t.Skipf("Skipping test: cannot ping database: %v", err) return nil } return db } // Helper functions func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvInt(key string, defaultValue int) int { value := os.Getenv(key) if value != "" { if intValue, err := strconv.Atoi(value); err == nil { return intValue } } return defaultValue } func buildDSN(host string, port int, user, password, dbname string) string { return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable", host, user, password, dbname, port) } func TestNewDB(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } host := getEnv("DB_HOST", "localhost") port := getEnvInt("DB_PORT", 5432) user := getEnv("DB_USER", "veza") password := getEnv("DB_PASSWORD", "veza_password") dbname := getEnv("DB_NAME", "veza_db") // Test de création de connexion db, err := NewDB(host, port, user, password, dbname) if err != nil { t.Skipf("Skipping test: cannot connect to database: %v", err) return } require.NotNil(t, db) defer CloseDB(db) // Vérifier que la connexion fonctionne sqlDB, err := db.DB() require.NoError(t, err) require.NotNil(t, sqlDB) // Vérifier les paramètres du pool stats := sqlDB.Stats() assert.Equal(t, 25, stats.MaxOpenConnections, "MaxOpenConns should be 25") } func TestNewDB_InvalidCredentials(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } // Test avec des credentials invalides _, err := NewDB("localhost", 5432, "invalid_user", "invalid_password", "invalid_db") require.Error(t, err) assert.Contains(t, err.Error(), "failed to open database") } func TestCloseDB(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } // Fermer la connexion err := CloseDB(db) assert.NoError(t, err) // Vérifier que la connexion est fermée sqlDB, err := db.DB() require.NoError(t, err) err = sqlDB.Ping() assert.Error(t, err, "Connection should be closed") } func TestCloseDB_NilDB(t *testing.T) { // Test avec une DB nil err := CloseDB(nil) assert.NoError(t, err, "Closing nil DB should not return error") } func TestGetPoolStats(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) stats, err := GetPoolStats(db) require.NoError(t, err) require.NotNil(t, stats) // Vérifier que les statistiques contiennent des informations valides assert.GreaterOrEqual(t, stats.MaxOpenConnections, 0) assert.GreaterOrEqual(t, stats.OpenConnections, 0) assert.GreaterOrEqual(t, stats.InUse, 0) assert.GreaterOrEqual(t, stats.Idle, 0) } func TestGetPoolStats_NilDB(t *testing.T) { _, err := GetPoolStats(nil) require.Error(t, err) assert.Contains(t, err.Error(), "database connection is nil") } func TestIsConnectionHealthy(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) // Test avec un timeout suffisant err := IsConnectionHealthy(db, 5*time.Second) assert.NoError(t, err, "Healthy connection should not return error") } func TestIsConnectionHealthy_Timeout(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) // Test avec un timeout très court (devrait timeout) // Note: Ce test peut être flaky, mais il vérifie le comportement de timeout err := IsConnectionHealthy(db, 1*time.Nanosecond) // Le timeout peut ne pas se produire si la connexion est très rapide // Donc on accepte soit une erreur de timeout, soit pas d'erreur if err != nil { assert.Contains(t, err.Error(), "timeout") } } func TestIsConnectionHealthy_NilDB(t *testing.T) { err := IsConnectionHealthy(nil, 5*time.Second) require.Error(t, err) assert.Contains(t, err.Error(), "database connection is nil") } func TestDBPool_ConnectionPooling(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) sqlDB, err := db.DB() require.NoError(t, err) // Vérifier les paramètres du pool stats := sqlDB.Stats() _ = stats.OpenConnections // Vérification que le pool fonctionne // Simuler plusieurs requêtes pour utiliser le pool for i := 0; i < 10; i++ { var result int err := sqlDB.QueryRow("SELECT 1").Scan(&result) require.NoError(t, err) assert.Equal(t, 1, result) } // Vérifier que les connexions sont réutilisées (le nombre ne devrait pas augmenter significativement) stats = sqlDB.Stats() // Le nombre de connexions ouvertes ne devrait pas dépasser MaxOpenConns assert.LessOrEqual(t, stats.OpenConnections, 25, "Open connections should not exceed MaxOpenConns") } func TestDBPool_MaxConnections(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) sqlDB, err := db.DB() require.NoError(t, err) // Vérifier que MaxOpenConns est configuré stats := sqlDB.Stats() assert.Equal(t, 25, stats.MaxOpenConnections, "MaxOpenConns should be 25") } // Test de performance: vérifier que le pool peut gérer 100+ connexions simultanées func TestDBPool_Performance(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } db := setupPoolTestDB(t) if db == nil { return } defer CloseDB(db) sqlDB, err := db.DB() require.NoError(t, err) // Simuler 100 requêtes simultanées const numRequests = 100 results := make(chan error, numRequests) for i := 0; i < numRequests; i++ { go func() { var result int err := sqlDB.QueryRow("SELECT $1", 1).Scan(&result) results <- err }() } // Collecter tous les résultats var errors int for i := 0; i < numRequests; i++ { if err := <-results; err != nil { errors++ } } // Toutes les requêtes devraient réussir assert.Equal(t, 0, errors, "All requests should succeed") // Vérifier les statistiques du pool stats := sqlDB.Stats() assert.LessOrEqual(t, stats.OpenConnections, stats.MaxOpenConnections, "Open connections should not exceed MaxOpenConns") }