Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
620 lines
18 KiB
Go
620 lines
18 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func TestLoad(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalDBPassword := os.Getenv("DB_PASSWORD")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalAppPort := os.Getenv("APP_PORT")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalDBPassword != "" {
|
|
os.Setenv("DB_PASSWORD", originalDBPassword)
|
|
} else {
|
|
os.Unsetenv("DB_PASSWORD")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalAppPort != "" {
|
|
os.Setenv("APP_PORT", originalAppPort)
|
|
} else {
|
|
os.Unsetenv("APP_PORT")
|
|
}
|
|
}()
|
|
|
|
// Définir les variables requises
|
|
os.Setenv("DB_PASSWORD", "test_password")
|
|
os.Setenv("JWT_SECRET", "test_secret")
|
|
|
|
config, err := Load()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, config)
|
|
|
|
// Vérifier les valeurs par défaut
|
|
assert.Equal(t, 8080, config.AppPort)
|
|
assert.Equal(t, "development", config.AppEnv)
|
|
assert.Equal(t, "localhost", config.DBHost)
|
|
assert.Equal(t, 5432, config.DBPort)
|
|
assert.Equal(t, "veza", config.DBUser)
|
|
assert.Equal(t, "veza_db", config.DBName)
|
|
assert.Equal(t, "redis://localhost:6379", config.RedisURL)
|
|
|
|
// Vérifier les valeurs requises
|
|
assert.Equal(t, "test_password", config.DBPassword)
|
|
assert.Equal(t, "test_secret", config.JWTSecret)
|
|
}
|
|
|
|
func TestLoad_WithCustomValues(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalDBPassword := os.Getenv("DB_PASSWORD")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalAppPort := os.Getenv("APP_PORT")
|
|
originalDBHost := os.Getenv("DB_HOST")
|
|
originalDBPort := os.Getenv("DB_PORT")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalDBPassword != "" {
|
|
os.Setenv("DB_PASSWORD", originalDBPassword)
|
|
} else {
|
|
os.Unsetenv("DB_PASSWORD")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalAppPort != "" {
|
|
os.Setenv("APP_PORT", originalAppPort)
|
|
} else {
|
|
os.Unsetenv("APP_PORT")
|
|
}
|
|
if originalDBHost != "" {
|
|
os.Setenv("DB_HOST", originalDBHost)
|
|
} else {
|
|
os.Unsetenv("DB_HOST")
|
|
}
|
|
if originalDBPort != "" {
|
|
os.Setenv("DB_PORT", originalDBPort)
|
|
} else {
|
|
os.Unsetenv("DB_PORT")
|
|
}
|
|
}()
|
|
|
|
// Définir des valeurs personnalisées
|
|
os.Setenv("DB_PASSWORD", "custom_password")
|
|
os.Setenv("JWT_SECRET", "custom_secret")
|
|
os.Setenv("APP_PORT", "9090")
|
|
os.Setenv("DB_HOST", "custom_host")
|
|
os.Setenv("DB_PORT", "3306")
|
|
|
|
config, err := Load()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 9090, config.AppPort)
|
|
assert.Equal(t, "custom_host", config.DBHost)
|
|
assert.Equal(t, 3306, config.DBPort)
|
|
assert.Equal(t, "custom_password", config.DBPassword)
|
|
assert.Equal(t, "custom_secret", config.JWTSecret)
|
|
}
|
|
|
|
func TestLoad_MissingRequiredVariable_DBPassword(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalDBPassword := os.Getenv("DB_PASSWORD")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalDBPassword != "" {
|
|
os.Setenv("DB_PASSWORD", originalDBPassword)
|
|
} else {
|
|
os.Unsetenv("DB_PASSWORD")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
}()
|
|
|
|
// Supprimer les variables requises
|
|
os.Unsetenv("DB_PASSWORD")
|
|
os.Setenv("JWT_SECRET", "test_secret")
|
|
|
|
// Devrait paniquer
|
|
assert.Panics(t, func() {
|
|
_, _ = Load()
|
|
}, "Should panic when DB_PASSWORD is missing")
|
|
}
|
|
|
|
func TestLoad_MissingRequiredVariable_JWTSecret(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalDBPassword := os.Getenv("DB_PASSWORD")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalDBPassword != "" {
|
|
os.Setenv("DB_PASSWORD", originalDBPassword)
|
|
} else {
|
|
os.Unsetenv("DB_PASSWORD")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
}()
|
|
|
|
// Supprimer les variables requises
|
|
os.Setenv("DB_PASSWORD", "test_password")
|
|
os.Unsetenv("JWT_SECRET")
|
|
|
|
// Devrait paniquer
|
|
assert.Panics(t, func() {
|
|
_, _ = Load()
|
|
}, "Should panic when JWT_SECRET is missing")
|
|
}
|
|
|
|
func TestGetEnv(t *testing.T) {
|
|
// Sauvegarder la valeur originale
|
|
originalValue := os.Getenv("TEST_VAR")
|
|
|
|
defer func() {
|
|
if originalValue != "" {
|
|
os.Setenv("TEST_VAR", originalValue)
|
|
} else {
|
|
os.Unsetenv("TEST_VAR")
|
|
}
|
|
}()
|
|
|
|
// Test avec valeur définie
|
|
os.Setenv("TEST_VAR", "test_value")
|
|
assert.Equal(t, "test_value", getEnv("TEST_VAR", "default"))
|
|
|
|
// Test sans valeur (devrait retourner défaut)
|
|
os.Unsetenv("TEST_VAR")
|
|
assert.Equal(t, "default", getEnv("TEST_VAR", "default"))
|
|
}
|
|
|
|
func TestGetEnvInt(t *testing.T) {
|
|
// Sauvegarder la valeur originale
|
|
originalValue := os.Getenv("TEST_INT")
|
|
|
|
defer func() {
|
|
if originalValue != "" {
|
|
os.Setenv("TEST_INT", originalValue)
|
|
} else {
|
|
os.Unsetenv("TEST_INT")
|
|
}
|
|
}()
|
|
|
|
// Test avec valeur entière valide
|
|
os.Setenv("TEST_INT", "42")
|
|
assert.Equal(t, 42, getEnvInt("TEST_INT", 10))
|
|
|
|
// Test sans valeur (devrait retourner défaut)
|
|
os.Unsetenv("TEST_INT")
|
|
assert.Equal(t, 10, getEnvInt("TEST_INT", 10))
|
|
|
|
// Test avec valeur invalide (devrait retourner défaut)
|
|
os.Setenv("TEST_INT", "not_a_number")
|
|
assert.Equal(t, 10, getEnvInt("TEST_INT", 10))
|
|
}
|
|
|
|
func TestGetEnvRequired(t *testing.T) {
|
|
// Sauvegarder la valeur originale
|
|
originalValue := os.Getenv("TEST_REQUIRED")
|
|
|
|
defer func() {
|
|
if originalValue != "" {
|
|
os.Setenv("TEST_REQUIRED", originalValue)
|
|
} else {
|
|
os.Unsetenv("TEST_REQUIRED")
|
|
}
|
|
}()
|
|
|
|
// Test avec valeur définie
|
|
os.Setenv("TEST_REQUIRED", "required_value")
|
|
assert.Equal(t, "required_value", getEnvRequired("TEST_REQUIRED"))
|
|
|
|
// Test sans valeur (devrait paniquer)
|
|
os.Unsetenv("TEST_REQUIRED")
|
|
assert.Panics(t, func() {
|
|
_ = getEnvRequired("TEST_REQUIRED")
|
|
}, "Should panic when required variable is missing")
|
|
}
|
|
|
|
func TestLoad_DefaultValues(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalDBPassword := os.Getenv("DB_PASSWORD")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalAppEnv := os.Getenv("APP_ENV")
|
|
originalRedisURL := os.Getenv("REDIS_URL")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalDBPassword != "" {
|
|
os.Setenv("DB_PASSWORD", originalDBPassword)
|
|
} else {
|
|
os.Unsetenv("DB_PASSWORD")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalAppEnv != "" {
|
|
os.Setenv("APP_ENV", originalAppEnv)
|
|
} else {
|
|
os.Unsetenv("APP_ENV")
|
|
}
|
|
if originalRedisURL != "" {
|
|
os.Setenv("REDIS_URL", originalRedisURL)
|
|
} else {
|
|
os.Unsetenv("REDIS_URL")
|
|
}
|
|
}()
|
|
|
|
// Définir seulement les variables requises
|
|
os.Setenv("DB_PASSWORD", "test")
|
|
os.Setenv("JWT_SECRET", "secret")
|
|
|
|
// Supprimer les variables optionnelles pour tester les valeurs par défaut
|
|
os.Unsetenv("APP_ENV")
|
|
os.Unsetenv("REDIS_URL")
|
|
|
|
config, err := Load()
|
|
require.NoError(t, err)
|
|
|
|
// Vérifier que les valeurs par défaut sont utilisées
|
|
assert.Equal(t, "development", config.AppEnv)
|
|
assert.Equal(t, "redis://localhost:6379", config.RedisURL)
|
|
}
|
|
|
|
// TestNewConfig_RequiresJWTSecret vérifie que NewConfig() refuse de démarrer sans JWT_SECRET
|
|
// Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut hardcodée
|
|
func TestNewConfig_RequiresJWTSecret(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalDatabaseURL := os.Getenv("DATABASE_URL")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalDatabaseURL != "" {
|
|
os.Setenv("DATABASE_URL", originalDatabaseURL)
|
|
} else {
|
|
os.Unsetenv("DATABASE_URL")
|
|
}
|
|
}()
|
|
|
|
// Supprimer JWT_SECRET - devrait causer un panic
|
|
os.Unsetenv("JWT_SECRET")
|
|
// Définir DATABASE_URL pour éviter un panic sur cette variable (on teste seulement JWT_SECRET)
|
|
os.Setenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test_db")
|
|
|
|
// Devrait paniquer car JWT_SECRET est requis
|
|
assert.Panics(t, func() {
|
|
_, _ = NewConfig()
|
|
}, "NewConfig should panic when JWT_SECRET is missing")
|
|
}
|
|
|
|
// TestNewConfig_RequiresDatabaseURL vérifie que NewConfig() refuse de démarrer sans DATABASE_URL
|
|
// Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut avec credentials
|
|
func TestNewConfig_RequiresDatabaseURL(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalDatabaseURL := os.Getenv("DATABASE_URL")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalDatabaseURL != "" {
|
|
os.Setenv("DATABASE_URL", originalDatabaseURL)
|
|
} else {
|
|
os.Unsetenv("DATABASE_URL")
|
|
}
|
|
}()
|
|
|
|
// Définir JWT_SECRET (minimum 32 caractères pour passer la validation)
|
|
os.Setenv("JWT_SECRET", "test-jwt-secret-key-minimum-32-characters-long")
|
|
// Supprimer DATABASE_URL - devrait causer un panic
|
|
os.Unsetenv("DATABASE_URL")
|
|
|
|
// Devrait paniquer car DATABASE_URL est requis
|
|
assert.Panics(t, func() {
|
|
_, _ = NewConfig()
|
|
}, "NewConfig should panic when DATABASE_URL is missing")
|
|
}
|
|
|
|
// ============================================================================
|
|
// P0-SECURITY: Tests pour la sécurisation de la configuration CORS
|
|
// ============================================================================
|
|
|
|
// TestLoadConfig_DevDefaults vérifie que les defaults dev sont corrects (P0-SECURITY)
|
|
func TestLoadConfig_DevDefaults(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalEnv := os.Getenv("APP_ENV")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalDatabaseURL := os.Getenv("DATABASE_URL")
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalEnv != "" {
|
|
os.Setenv("APP_ENV", originalEnv)
|
|
} else {
|
|
os.Unsetenv("APP_ENV")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalDatabaseURL != "" {
|
|
os.Setenv("DATABASE_URL", originalDatabaseURL)
|
|
} else {
|
|
os.Unsetenv("DATABASE_URL")
|
|
}
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// Configuration pour développement
|
|
os.Setenv("APP_ENV", "development")
|
|
os.Setenv("JWT_SECRET", "test-jwt-secret-key-minimum-32-characters-long")
|
|
os.Setenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test_db")
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS") // Pas défini pour tester les defaults
|
|
|
|
// Note: NewConfig() nécessite Redis et DB, donc on teste seulement getCORSOrigins
|
|
origins := getCORSOrigins("development")
|
|
require.NotEmpty(t, origins, "Development should have default CORS origins")
|
|
assert.Contains(t, origins, "http://localhost:3000", "Should include localhost:3000")
|
|
assert.Contains(t, origins, "http://127.0.0.1:3000", "Should include 127.0.0.1:3000")
|
|
assert.NotContains(t, origins, "*", "Should not contain wildcard")
|
|
}
|
|
|
|
// TestLoadConfig_ProdMissingCritical vérifie que prod refuse si CORS manquant (P0-SECURITY)
|
|
func TestLoadConfig_ProdMissingCritical(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalEnv := os.Getenv("APP_ENV")
|
|
originalJWTSecret := os.Getenv("JWT_SECRET")
|
|
originalDatabaseURL := os.Getenv("DATABASE_URL")
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalEnv != "" {
|
|
os.Setenv("APP_ENV", originalEnv)
|
|
} else {
|
|
os.Unsetenv("APP_ENV")
|
|
}
|
|
if originalJWTSecret != "" {
|
|
os.Setenv("JWT_SECRET", originalJWTSecret)
|
|
} else {
|
|
os.Unsetenv("JWT_SECRET")
|
|
}
|
|
if originalDatabaseURL != "" {
|
|
os.Setenv("DATABASE_URL", originalDatabaseURL)
|
|
} else {
|
|
os.Unsetenv("DATABASE_URL")
|
|
}
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// Configuration pour production sans CORS
|
|
os.Setenv("APP_ENV", "production")
|
|
os.Setenv("JWT_SECRET", "test-jwt-secret-key-minimum-32-characters-long")
|
|
os.Setenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test_db")
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS") // Manquant intentionnellement
|
|
|
|
// Créer une config minimale pour tester la validation
|
|
cfg := &Config{
|
|
Env: "production",
|
|
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
|
DatabaseURL: "postgresql://test:test@localhost:5432/test_db",
|
|
RedisURL: "redis://localhost:6379",
|
|
AppPort: 8080,
|
|
LogLevel: "INFO",
|
|
RateLimitLimit: 100, // Valeur valide pour passer Validate()
|
|
RateLimitWindow: 60, // Valeur valide pour passer Validate()
|
|
CORSOrigins: []string{}, // Vide - devrait échouer en prod
|
|
}
|
|
|
|
// Créer un logger minimal pour la config
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg.Logger = logger
|
|
|
|
// La validation devrait échouer
|
|
err := cfg.ValidateForEnvironment()
|
|
require.Error(t, err, "Production config should fail validation when CORS_ALLOWED_ORIGINS is empty")
|
|
assert.Contains(t, err.Error(), "CORS_ALLOWED_ORIGINS is required", "Error should mention CORS requirement")
|
|
}
|
|
|
|
// TestLoadConfig_ProdWildcard vérifie que prod refuse le wildcard (P0-SECURITY)
|
|
func TestLoadConfig_ProdWildcard(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalEnv := os.Getenv("APP_ENV")
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalEnv != "" {
|
|
os.Setenv("APP_ENV", originalEnv)
|
|
} else {
|
|
os.Unsetenv("APP_ENV")
|
|
}
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// Configuration pour production avec wildcard
|
|
os.Setenv("APP_ENV", "production")
|
|
|
|
// Créer une config minimale avec wildcard
|
|
cfg := &Config{
|
|
Env: "production",
|
|
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
|
DatabaseURL: "postgresql://test:test@localhost:5432/test_db",
|
|
RedisURL: "redis://localhost:6379",
|
|
AppPort: 8080,
|
|
LogLevel: "INFO",
|
|
RateLimitLimit: 100, // Valeur valide pour passer Validate()
|
|
RateLimitWindow: 60, // Valeur valide pour passer Validate()
|
|
CORSOrigins: []string{"*"}, // Wildcard - devrait échouer en prod
|
|
}
|
|
|
|
// Créer un logger minimal pour la config
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg.Logger = logger
|
|
|
|
// La validation devrait échouer
|
|
err := cfg.ValidateForEnvironment()
|
|
require.Error(t, err, "Production config should fail validation when CORS contains wildcard")
|
|
assert.Contains(t, err.Error(), "wildcard", "Error should mention wildcard prohibition")
|
|
}
|
|
|
|
// TestLoadConfig_ProdValid vérifie qu'une config prod valide passe (P0-SECURITY)
|
|
func TestLoadConfig_ProdValid(t *testing.T) {
|
|
// Sauvegarder les valeurs originales
|
|
originalEnv := os.Getenv("APP_ENV")
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
|
|
// Nettoyer après le test
|
|
defer func() {
|
|
if originalEnv != "" {
|
|
os.Setenv("APP_ENV", originalEnv)
|
|
} else {
|
|
os.Unsetenv("APP_ENV")
|
|
}
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// Configuration pour production valide
|
|
os.Setenv("APP_ENV", "production")
|
|
|
|
// Créer une config minimale valide
|
|
cfg := &Config{
|
|
Env: "production",
|
|
JWTSecret: "test-jwt-secret-key-minimum-32-characters-long",
|
|
DatabaseURL: "postgresql://test:test@localhost:5432/test_db",
|
|
RedisURL: "redis://localhost:6379",
|
|
AppPort: 8080,
|
|
LogLevel: "INFO",
|
|
RateLimitLimit: 100, // Valeur valide pour passer Validate()
|
|
RateLimitWindow: 60, // Valeur valide pour passer Validate()
|
|
CORSOrigins: []string{"https://app.veza.com", "https://www.veza.com"}, // Valide - pas de wildcard
|
|
}
|
|
|
|
// Créer un logger minimal pour la config
|
|
logger, _ := zap.NewDevelopment()
|
|
cfg.Logger = logger
|
|
|
|
// La validation devrait passer
|
|
err := cfg.ValidateForEnvironment()
|
|
assert.NoError(t, err, "Valid production config should pass validation")
|
|
}
|
|
|
|
// TestGetCORSOrigins_EnvironmentDefaults teste les defaults selon l'environnement (P0-SECURITY)
|
|
func TestGetCORSOrigins_EnvironmentDefaults(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "development defaults",
|
|
env: "development",
|
|
expected: []string{"http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173", "http://127.0.0.1:5173"},
|
|
},
|
|
{
|
|
name: "staging defaults",
|
|
env: "staging",
|
|
expected: []string{"http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173", "http://127.0.0.1:5173"},
|
|
},
|
|
{
|
|
name: "production no defaults",
|
|
env: "production",
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "test no defaults",
|
|
env: "test",
|
|
expected: []string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Sauvegarder CORS_ALLOWED_ORIGINS
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
defer func() {
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// S'assurer que CORS_ALLOWED_ORIGINS n'est pas défini
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
|
|
origins := getCORSOrigins(tt.env)
|
|
assert.Equal(t, tt.expected, origins, "CORS origins should match expected defaults for %s", tt.env)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetCORSOrigins_ExplicitValue teste que les valeurs explicites sont utilisées (P0-SECURITY)
|
|
func TestGetCORSOrigins_ExplicitValue(t *testing.T) {
|
|
// Sauvegarder CORS_ALLOWED_ORIGINS
|
|
originalCORSOrigins := os.Getenv("CORS_ALLOWED_ORIGINS")
|
|
defer func() {
|
|
if originalCORSOrigins != "" {
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", originalCORSOrigins)
|
|
} else {
|
|
os.Unsetenv("CORS_ALLOWED_ORIGINS")
|
|
}
|
|
}()
|
|
|
|
// Définir explicitement CORS_ALLOWED_ORIGINS
|
|
os.Setenv("CORS_ALLOWED_ORIGINS", "https://example.com,https://app.example.com")
|
|
|
|
origins := getCORSOrigins("production")
|
|
assert.Equal(t, []string{"https://example.com", "https://app.example.com"}, origins, "Should use explicit CORS_ALLOWED_ORIGINS value")
|
|
}
|