- 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>
188 lines
6.1 KiB
Python
188 lines
6.1 KiB
Python
"""
|
|
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)
|