v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
187
tests/unit/test_fusion_engine.py
Normal file
187
tests/unit/test_fusion_engine.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user