veza/veza-backend-api/internal/database/pool_test.go

312 lines
7.6 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
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")
2025-12-03 19:29:37 +00:00
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")
2025-12-03 19:29:37 +00:00
// 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")
}