311 lines
7.6 KiB
Go
311 lines
7.6 KiB
Go
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", "password")
|
|
dbname := getEnv("DB_NAME", "veza_db_test")
|
|
|
|
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", "password")
|
|
dbname := getEnv("DB_NAME", "veza_db_test")
|
|
|
|
// 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")
|
|
}
|