""" Tests unitaires pour FusionEngine avec property-based testing. Property 17: State Embedding Component Weights Sum - Les poids de fusion doivent toujours sommer à 1.0 - Validates: Requirements 4.5 """ import sys from pathlib import Path # Ajouter le répertoire parent au path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) import pytest import numpy as np from core.embedding.fusion_engine import FusionEngine, FusionConfig class TestFusionEngine: """Tests pour FusionEngine.""" def setup_method(self): """Setup avant chaque test.""" self.engine = FusionEngine() def test_default_weights_sum_to_one(self): """Property 17: Les poids par défaut doivent sommer à 1.0.""" weights = self.engine.config.weights total = sum(weights.values()) assert abs(total - 1.0) < 1e-6, f"Weights sum to {total}, expected 1.0" def test_custom_weights_sum_to_one(self): """Property 17: Les poids personnalisés doivent sommer à 1.0.""" custom_weights = { 'image': 0.4, 'text': 0.3, 'title': 0.2, 'ui': 0.1 } config = FusionConfig(weights=custom_weights) engine = FusionEngine(config=config) total = sum(engine.config.weights.values()) assert abs(total - 1.0) < 1e-6, f"Custom weights sum to {total}, expected 1.0" def test_fusion_with_all_components(self): """Test fusion avec tous les composants présents.""" dim = 512 embeddings = { 'image': np.random.randn(dim), 'text': np.random.randn(dim), 'title': np.random.randn(dim), 'ui': np.random.randn(dim) } fused = self.engine.fuse(embeddings) # Vérifier dimensions assert fused.shape == (dim,), f"Expected shape ({dim},), got {fused.shape}" # Vérifier normalisation norm = np.linalg.norm(fused) assert abs(norm - 1.0) < 1e-5, f"Fused vector norm is {norm}, expected 1.0" def test_fusion_with_missing_components(self): """Test fusion avec composants manquants.""" dim = 512 embeddings = { 'image': np.random.randn(dim), 'text': np.random.randn(dim) # title et ui manquants } fused = self.engine.fuse(embeddings) # Doit quand même fonctionner assert fused.shape == (dim,) norm = np.linalg.norm(fused) assert abs(norm - 1.0) < 1e-5 def test_fusion_normalization(self): """Test que la fusion normalise toujours le résultat.""" dim = 512 # Créer des vecteurs non normalisés embeddings = { 'image': np.random.randn(dim) * 10, # Grande magnitude 'text': np.random.randn(dim) * 0.1, # Petite magnitude } fused = self.engine.fuse(embeddings) norm = np.linalg.norm(fused) assert abs(norm - 1.0) < 1e-5, "Fusion should normalize result" def test_fusion_weighted_combination(self): """Test que la fusion applique bien les poids.""" dim = 512 # Créer des vecteurs orthogonaux pour faciliter la vérification image_vec = np.zeros(dim) image_vec[0] = 1.0 text_vec = np.zeros(dim) text_vec[1] = 1.0 embeddings = { 'image': image_vec, 'text': text_vec } fused = self.engine.fuse(embeddings) # Le vecteur fusionné devrait avoir des composantes non nulles # aux positions 0 et 1 assert fused[0] != 0, "Image component should contribute" assert fused[1] != 0, "Text component should contribute" # Vérifier normalisation norm = np.linalg.norm(fused) assert abs(norm - 1.0) < 1e-5 def test_fusion_empty_embeddings(self): """Test fusion avec dictionnaire vide.""" with pytest.raises((ValueError, KeyError)): self.engine.fuse({}) def test_fusion_single_component(self): """Test fusion avec un seul composant.""" dim = 512 embeddings = { 'image': np.random.randn(dim) } fused = self.engine.fuse(embeddings) # Doit fonctionner et normaliser assert fused.shape == (dim,) norm = np.linalg.norm(fused) assert abs(norm - 1.0) < 1e-5 def test_weights_validation(self): """Test validation des poids.""" # Poids ne sommant pas à 1.0 with pytest.raises(ValueError): config = FusionConfig(weights={'image': 0.5, 'text': 0.3}) # Sum = 0.8 FusionEngine(config=config) if __name__ == '__main__': # Exécuter les tests manuellement test = TestFusionEngine() print("="*70) print("TESTS UNITAIRES - FusionEngine") print("Property 17: State Embedding Component Weights Sum") print("="*70) tests = [ ('test_default_weights_sum_to_one', test.test_default_weights_sum_to_one), ('test_custom_weights_sum_to_one', test.test_custom_weights_sum_to_one), ('test_fusion_with_all_components', test.test_fusion_with_all_components), ('test_fusion_with_missing_components', test.test_fusion_with_missing_components), ('test_fusion_normalization', test.test_fusion_normalization), ('test_fusion_weighted_combination', test.test_fusion_weighted_combination), ('test_fusion_empty_embeddings', test.test_fusion_empty_embeddings), ('test_fusion_single_component', test.test_fusion_single_component), ('test_weights_validation', test.test_weights_validation), ] passed = 0 failed = 0 for test_name, test_func in tests: try: test.setup_method() test_func() print(f"✓ {test_name}") passed += 1 except Exception as e: print(f"✗ {test_name}: {e}") failed += 1 print("\n" + "="*70) print(f"RÉSULTATS: {passed} passés, {failed} échoués") print("="*70) sys.exit(0 if failed == 0 else 1)