""" Tests pour le système de Feature Flags Tests complets du système de feature flags utilisé pour la migration progressive. Couvre tous les cas d'usage critiques : activation/désactivation, configuration environnement, rollback, logging, et validation. """ import pytest import os from unittest.mock import patch from datetime import datetime from config.feature_flags import ( FeatureFlag, FeatureFlagConfig, FeatureFlagManager, feature_flags, is_feature_enabled ) class TestFeatureFlagConfig: """Tests pour la classe de configuration FeatureFlagConfig.""" def test_feature_flag_config_creation(self): """Test création d'une configuration de feature flag.""" config = FeatureFlagConfig( enabled=True, description="Test feature flag", migration_day=3, rollback_safe=True ) assert config.enabled is True assert config.description == "Test feature flag" assert config.migration_day == 3 assert config.rollback_safe is True assert config.created_at is not None assert config.updated_at is not None assert isinstance(config.created_at, datetime) assert isinstance(config.updated_at, datetime) def test_feature_flag_config_defaults(self): """Test valeurs par défaut de FeatureFlagConfig.""" config = FeatureFlagConfig(enabled=False, description="Test") assert config.migration_day is None assert config.rollback_safe is True # Défaut sécurisé assert config.created_at is not None assert config.updated_at is not None class TestFeatureFlagEnum: """Tests pour l'énumération des feature flags.""" def test_feature_flag_enum_values(self): """Test que tous les feature flags de migration sont définis.""" # Migration core (Jour 3-4) assert FeatureFlag.USE_STRATEGY_PATTERN.value == "use_strategy_pattern" assert FeatureFlag.USE_REFACTORED_ASSESSMENT.value == "use_refactored_assessment" # Migration avancée (Jour 5-6) assert FeatureFlag.USE_NEW_STUDENT_SCORE_CALCULATOR.value == "use_new_student_score_calculator" assert FeatureFlag.USE_NEW_ASSESSMENT_STATISTICS_SERVICE.value == "use_new_assessment_statistics_service" # Fonctionnalités avancées assert FeatureFlag.ENABLE_PERFORMANCE_MONITORING.value == "enable_performance_monitoring" assert FeatureFlag.ENABLE_QUERY_OPTIMIZATION.value == "enable_query_optimization" def test_feature_flag_enum_uniqueness(self): """Test que toutes les valeurs de feature flags sont uniques.""" values = [flag.value for flag in FeatureFlag] assert len(values) == len(set(values)) # Pas de doublons class TestFeatureFlagManager: """Tests pour la classe FeatureFlagManager.""" def test_manager_initialization(self): """Test initialisation du gestionnaire.""" manager = FeatureFlagManager() # Vérification que tous les flags sont initialisés for flag in FeatureFlag: config = manager.get_config(flag) assert config is not None assert isinstance(config, FeatureFlagConfig) # Par défaut, tous désactivés pour sécurité assert config.enabled is False def test_is_enabled_default_false(self): """Test que tous les flags sont désactivés par défaut.""" manager = FeatureFlagManager() for flag in FeatureFlag: assert manager.is_enabled(flag) is False def test_enable_flag(self): """Test activation d'un feature flag.""" manager = FeatureFlagManager() flag = FeatureFlag.USE_STRATEGY_PATTERN # Initialement désactivé assert manager.is_enabled(flag) is False # Activation success = manager.enable(flag, "Test activation") assert success is True assert manager.is_enabled(flag) is True # Vérification des métadonnées config = manager.get_config(flag) assert config.enabled is True assert config.updated_at is not None def test_disable_flag(self): """Test désactivation d'un feature flag.""" manager = FeatureFlagManager() flag = FeatureFlag.USE_STRATEGY_PATTERN # Activer d'abord manager.enable(flag, "Test") assert manager.is_enabled(flag) is True # Désactiver success = manager.disable(flag, "Test désactivation") assert success is True assert manager.is_enabled(flag) is False # Vérification des métadonnées config = manager.get_config(flag) assert config.enabled is False assert config.updated_at is not None def test_enable_unknown_flag(self): """Test activation d'un flag inexistant.""" manager = FeatureFlagManager() # Création d'un flag fictif pour le test class FakeFlag: value = "nonexistent_flag" fake_flag = FakeFlag() success = manager.enable(fake_flag, "Test") assert success is False def test_disable_unknown_flag(self): """Test désactivation d'un flag inexistant.""" manager = FeatureFlagManager() # Création d'un flag fictif pour le test class FakeFlag: value = "nonexistent_flag" fake_flag = FakeFlag() success = manager.disable(fake_flag, "Test") assert success is False def test_get_status_summary(self): """Test du résumé des statuts.""" manager = FeatureFlagManager() # Activer quelques flags manager.enable(FeatureFlag.USE_STRATEGY_PATTERN, "Test") manager.enable(FeatureFlag.ENABLE_PERFORMANCE_MONITORING, "Test") summary = manager.get_status_summary() # Structure du résumé assert 'flags' in summary assert 'migration_status' in summary assert 'total_enabled' in summary assert 'last_updated' in summary # Vérification du compte assert summary['total_enabled'] == 2 # Vérification des flags individuels assert summary['flags']['use_strategy_pattern']['enabled'] is True assert summary['flags']['enable_performance_monitoring']['enabled'] is True assert summary['flags']['use_refactored_assessment']['enabled'] is False def test_migration_day_status(self): """Test du statut de migration par jour.""" manager = FeatureFlagManager() summary = manager.get_status_summary() # Initialement, aucun jour n'est prêt assert summary['migration_status']['day_3_ready'] is False assert summary['migration_status']['day_4_ready'] is False assert summary['migration_status']['day_5_ready'] is False assert summary['migration_status']['day_6_ready'] is False # Activer le jour 3 manager.enable(FeatureFlag.USE_STRATEGY_PATTERN, "Test Jour 3") summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is True assert summary['migration_status']['day_4_ready'] is False def test_enable_migration_day(self): """Test activation des flags pour un jour de migration.""" manager = FeatureFlagManager() # Activer le jour 3 results = manager.enable_migration_day(3, "Test migration jour 3") assert 'use_strategy_pattern' in results assert results['use_strategy_pattern'] is True # Vérifier que le flag est effectivement activé assert manager.is_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is True # Vérifier le statut de migration summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is True def test_enable_migration_day_invalid(self): """Test activation d'un jour de migration invalide.""" manager = FeatureFlagManager() # Jour invalide results = manager.enable_migration_day(10, "Test invalide") assert results == {} # Jour 1 et 2 ne sont pas supportés (pas de flags associés) results = manager.enable_migration_day(1, "Test invalide") assert results == {} class TestEnvironmentConfiguration: """Tests pour la configuration par variables d'environnement.""" @patch.dict(os.environ, { 'FEATURE_FLAG_USE_STRATEGY_PATTERN': 'true', 'FEATURE_FLAG_ENABLE_PERFORMANCE_MONITORING': '1', 'FEATURE_FLAG_USE_REFACTORED_ASSESSMENT': 'false' }) def test_load_from_environment_variables(self): """Test chargement depuis variables d'environnement.""" manager = FeatureFlagManager() # Vérification des flags activés par env assert manager.is_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is True assert manager.is_enabled(FeatureFlag.ENABLE_PERFORMANCE_MONITORING) is True # Vérification du flag explicitement désactivé assert manager.is_enabled(FeatureFlag.USE_REFACTORED_ASSESSMENT) is False # Vérification des flags non définis (défaut: False) assert manager.is_enabled(FeatureFlag.USE_NEW_STUDENT_SCORE_CALCULATOR) is False @patch.dict(os.environ, { 'FEATURE_FLAG_USE_STRATEGY_PATTERN': 'yes', 'FEATURE_FLAG_ENABLE_QUERY_OPTIMIZATION': 'on', 'FEATURE_FLAG_ENABLE_BULK_OPERATIONS': 'enabled' }) def test_environment_boolean_parsing(self): """Test parsing des valeurs booléennes de l'environnement.""" manager = FeatureFlagManager() # Différentes formes de 'true' assert manager.is_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is True # 'yes' assert manager.is_enabled(FeatureFlag.ENABLE_QUERY_OPTIMIZATION) is True # 'on' assert manager.is_enabled(FeatureFlag.ENABLE_BULK_OPERATIONS) is True # 'enabled' @patch.dict(os.environ, { 'FEATURE_FLAG_USE_STRATEGY_PATTERN': 'false', 'FEATURE_FLAG_ENABLE_PERFORMANCE_MONITORING': '0', 'FEATURE_FLAG_ENABLE_QUERY_OPTIMIZATION': 'no', 'FEATURE_FLAG_ENABLE_BULK_OPERATIONS': 'disabled' }) def test_environment_false_values(self): """Test parsing des valeurs 'false' de l'environnement.""" manager = FeatureFlagManager() # Différentes formes de 'false' assert manager.is_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is False # 'false' assert manager.is_enabled(FeatureFlag.ENABLE_PERFORMANCE_MONITORING) is False # '0' assert manager.is_enabled(FeatureFlag.ENABLE_QUERY_OPTIMIZATION) is False # 'no' assert manager.is_enabled(FeatureFlag.ENABLE_BULK_OPERATIONS) is False # 'disabled' class TestGlobalFunctions: """Tests pour les fonctions globales utilitaires.""" def test_global_is_feature_enabled(self): """Test fonction globale is_feature_enabled.""" # Par défaut, tous désactivés assert is_feature_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is False # Activer via l'instance globale feature_flags.enable(FeatureFlag.USE_STRATEGY_PATTERN, "Test global") assert is_feature_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is True # Nettoyage pour les autres tests feature_flags.disable(FeatureFlag.USE_STRATEGY_PATTERN, "Nettoyage test") class TestMigrationScenarios: """Tests pour les scénarios de migration réels.""" def test_day_3_migration_scenario(self): """Test scénario complet migration Jour 3.""" manager = FeatureFlagManager() # État initial summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is False # Activation Jour 3 results = manager.enable_migration_day(3, "Migration Jour 3 - Grading Strategies") assert all(results.values()) # Tous les flags activés avec succès # Vérification post-migration summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is True assert manager.is_enabled(FeatureFlag.USE_STRATEGY_PATTERN) is True def test_progressive_migration_scenario(self): """Test scénario de migration progressive complète.""" manager = FeatureFlagManager() # Jour 3: Grading Strategies manager.enable_migration_day(3, "Jour 3") summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is True assert summary['total_enabled'] == 1 # Jour 4: Assessment Progress Service manager.enable_migration_day(4, "Jour 4") summary = manager.get_status_summary() assert summary['migration_status']['day_4_ready'] is True assert summary['total_enabled'] == 2 # Jour 5: Student Score Calculator manager.enable_migration_day(5, "Jour 5") summary = manager.get_status_summary() assert summary['migration_status']['day_5_ready'] is True assert summary['total_enabled'] == 3 # Jour 6: Assessment Statistics Service manager.enable_migration_day(6, "Jour 6") summary = manager.get_status_summary() assert summary['migration_status']['day_6_ready'] is True assert summary['total_enabled'] == 4 def test_rollback_scenario(self): """Test scénario de rollback complet.""" manager = FeatureFlagManager() # Activer plusieurs jours manager.enable_migration_day(3, "Migration") manager.enable_migration_day(4, "Migration") summary = manager.get_status_summary() assert summary['total_enabled'] == 2 # Rollback du Jour 4 seulement manager.disable(FeatureFlag.USE_REFACTORED_ASSESSMENT, "Rollback Jour 4") summary = manager.get_status_summary() assert summary['migration_status']['day_3_ready'] is True assert summary['migration_status']['day_4_ready'] is False assert summary['total_enabled'] == 1 class TestSafety: """Tests de sécurité et validation.""" def test_all_flags_rollback_safe_by_default(self): """Test que tous les flags sont rollback-safe par défaut.""" manager = FeatureFlagManager() for flag in FeatureFlag: config = manager.get_config(flag) assert config.rollback_safe is True, f"Flag {flag.value} n'est pas rollback-safe" def test_migration_flags_have_correct_days(self): """Test que les flags de migration ont les bons jours assignés.""" manager = FeatureFlagManager() # Jour 3 config = manager.get_config(FeatureFlag.USE_STRATEGY_PATTERN) assert config.migration_day == 3 # Jour 4 config = manager.get_config(FeatureFlag.USE_REFACTORED_ASSESSMENT) assert config.migration_day == 4 # Jour 5 config = manager.get_config(FeatureFlag.USE_NEW_STUDENT_SCORE_CALCULATOR) assert config.migration_day == 5 # Jour 6 config = manager.get_config(FeatureFlag.USE_NEW_ASSESSMENT_STATISTICS_SERVICE) assert config.migration_day == 6 def test_flag_descriptions_exist(self): """Test que tous les flags ont des descriptions significatives.""" manager = FeatureFlagManager() for flag in FeatureFlag: config = manager.get_config(flag) assert config.description, f"Flag {flag.value} n'a pas de description" assert len(config.description) > 10, f"Description trop courte pour {flag.value}"