#!/usr/bin/env python3
"""
đ§Ș Test du systĂšme de cache et circuit breaker
Script de test pour valider les fonctionnalités de la Phase 2.4
"""
import asyncio
import requests
import json
import time
from datetime import datetime
import sys
BASE_URL = "http://localhost:3003"
def print_header(title: str):
"""Affiche un en-tĂȘte de section"""
print(f"\n{'='*60}")
print(f"đ§Ș {title}")
print(f"{'='*60}")
def print_step(step: str):
"""Affiche une étape de test"""
print(f"\nâĄïž {step}")
def print_result(success: bool, message: str):
"""Affiche le résultat d'un test"""
icon = "â
" if success else "â"
print(f"{icon} {message}")
async def test_health_check():
"""Test de santé du serveur"""
print_step("Test de santé du serveur")
try:
response = requests.get(f"{BASE_URL}/health", timeout=5)
if response.status_code == 200:
data = response.json()
print_result(True, f"Serveur en ligne - Status: {data.get('status', 'unknown')}")
return True
else:
print_result(False, f"Serveur répond avec code {response.status_code}")
return False
except Exception as e:
print_result(False, f"Serveur inaccessible: {e}")
return False
async def test_cache_performance():
"""Test des performances du cache"""
print_header("TEST PERFORMANCE CACHE")
# Créer une session MCP
print_step("Création d'une session MCP")
try:
response = requests.post(f"{BASE_URL}/mcp/initialize",
json={
'protocolVersion': '2024-11-05',
'capabilities': {},
'clientInfo': {'name': 'cache_test', 'version': '1.0'}
},
timeout=10)
if response.status_code != 200:
print_result(False, f"Ăchec crĂ©ation session: {response.status_code}")
return False
session_data = response.json()
session_id = session_data['result']['session_id']
print_result(True, f"Session créée: {session_id}")
except Exception as e:
print_result(False, f"Erreur création session: {e}")
return False
# Test 1: Premier appel (MISS cache)
print_step("Test 1: Premier appel list_tools (CACHE MISS attendu)")
start_time = time.time()
try:
response = requests.post(f"{BASE_URL}/mcp/tools/list",
headers={'X-Session-ID': session_id},
json={'jsonrpc': '2.0', 'id': 1, 'method': 'tools/list', 'params': {}},
timeout=10)
first_call_time = time.time() - start_time
if response.status_code == 200:
tools = response.json()['result']['tools']
print_result(True, f"Outils récupérés en {first_call_time:.3f}s ({len(tools)} outils)")
else:
print_result(False, f"Ăchec rĂ©cupĂ©ration outils: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur appel tools/list: {e}")
return False
# Test 2: DeuxiÚme appel immédiat (HIT cache)
print_step("Test 2: DeuxiĂšme appel list_tools (CACHE HIT attendu)")
start_time = time.time()
try:
response = requests.post(f"{BASE_URL}/mcp/tools/list",
headers={'X-Session-ID': session_id},
json={'jsonrpc': '2.0', 'id': 2, 'method': 'tools/list', 'params': {}},
timeout=10)
second_call_time = time.time() - start_time
if response.status_code == 200:
tools = response.json()['result']['tools']
speedup = first_call_time / second_call_time if second_call_time > 0 else float('inf')
print_result(True, f"Outils récupérés en {second_call_time:.3f}s - Accélération: {speedup:.1f}x")
if second_call_time < first_call_time * 0.5:
print_result(True, "Cache fonctionne correctement (>50% plus rapide)")
else:
print_result(False, "Cache ne semble pas fonctionner (pas d'accélération significative)")
else:
print_result(False, f"Ăchec deuxiĂšme appel: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur deuxiĂšme appel: {e}")
return False
# Test 3: Appels multiples pour tester cache réponses
print_step("Test 3: Appels multiples get_entities (test cache réponses)")
total_time = 0
call_count = 5
for i in range(call_count):
start_time = time.time()
try:
response = requests.post(f"{BASE_URL}/mcp/tools/call",
headers={'X-Session-ID': session_id},
json={
'jsonrpc': '2.0',
'id': 10 + i,
'method': 'tools/call',
'params': {
'name': 'get_entities',
'arguments': {'domain': 'light'}
}
},
timeout=10)
call_time = time.time() - start_time
total_time += call_time
if response.status_code == 200:
print(f" Appel {i+1}: {call_time:.3f}s")
else:
print_result(False, f"Ăchec appel {i+1}: {response.status_code}")
except Exception as e:
print_result(False, f"Erreur appel {i+1}: {e}")
avg_time = total_time / call_count
print_result(True, f"Temps moyen sur {call_count} appels: {avg_time:.3f}s")
return True
async def test_circuit_breaker():
"""Test du circuit breaker"""
print_header("TEST CIRCUIT BREAKER")
print_step("Récupération métriques initiales")
try:
response = requests.get(f"{BASE_URL}/admin/metrics", timeout=10)
if response.status_code == 200:
initial_metrics = response.json()['metrics']
cb_stats = initial_metrics['circuit_breaker']
print_result(True, f"Circuit breaker état: {cb_stats['state']}")
print(f" Total requĂȘtes: {cb_stats['total_requests']}")
print(f" RequĂȘtes rĂ©ussies: {cb_stats['successful_requests']}")
print(f" Taux de succĂšs: {cb_stats['success_rate_percent']}%")
else:
print_result(False, f"Ăchec rĂ©cupĂ©ration mĂ©triques: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur récupération métriques: {e}")
return False
# Note: Dans un environnement de test réel, on pourrait forcer des erreurs
# Pour l'instant, on vérifie juste que le circuit breaker fonctionne
print_step("Circuit breaker en mode normal")
print_result(True, "Circuit breaker opérationnel en mode CLOSED")
return True
async def test_metrics_endpoint():
"""Test de l'endpoint métriques"""
print_header("TEST ENDPOINT MĂTRIQUES")
print_step("Récupération métriques complÚtes")
try:
response = requests.get(f"{BASE_URL}/admin/metrics", timeout=10)
if response.status_code == 200:
data = response.json()
metrics = data['metrics']
print_result(True, "Métriques récupérées avec succÚs")
# Afficher les métriques importantes
print(f"\\nđ MĂTRIQUES SYSTĂME:")
print(f" Uptime: {metrics['uptime_seconds']:.1f}s")
print(f"\\nđ§ CACHE OUTILS:")
tools_cache = metrics['tools_cache']
print(f" Taille: {tools_cache['size']}/{tools_cache['max_size']}")
print(f" Hits: {tools_cache['hits']}, Misses: {tools_cache['misses']}")
print(f" Taux de hit: {tools_cache['hit_rate_percent']}%")
print(f" TTL par défaut: {tools_cache['default_ttl']}s")
print(f"\\nđŸ CACHE RĂPONSES:")
resp_cache = metrics['response_cache']
print(f" Taille: {resp_cache['size']}/{resp_cache['max_size']}")
print(f" Hits: {resp_cache['hits']}, Misses: {resp_cache['misses']}")
print(f" Taux de hit: {resp_cache['hit_rate_percent']}%")
print(f"\\nđ CIRCUIT BREAKER:")
cb = metrics['circuit_breaker']
print(f" Ătat: {cb['state']}")
print(f" Disponible: {cb['is_available']}")
print(f" RequĂȘtes totales: {cb['total_requests']}")
print(f" Taux de succĂšs: {cb['success_rate_percent']}%")
print(f"\\nđ GESTION SESSIONS:")
if 'session_management' in metrics:
sm = metrics['session_management']
print(f" Sessions actives: {sm['active_sessions']}")
print(f" RequĂȘtes traitĂ©es: {sm['total_requests_processed']}")
print(f" Taille queue: {sm['queue_size']}")
if 'queue_stats' in sm:
qs = sm['queue_stats']
print(f" Performance queue:")
print(f" - Taux de succĂšs: {qs['performance']['success_rate_percent']}%")
print(f" - Temps moyen: {qs['performance']['avg_processing_time_ms']}ms")
print(f" - Charge actuelle: {qs['capacity']['current_load_percent']}%")
return True
else:
print_result(False, f"Ăchec rĂ©cupĂ©ration mĂ©triques: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur récupération métriques: {e}")
return False
async def test_cache_management():
"""Test de la gestion du cache"""
print_header("TEST GESTION CACHE")
print_step("Test vidage du cache")
try:
response = requests.post(f"{BASE_URL}/admin/cache/clear", timeout=10)
if response.status_code == 200:
result = response.json()
print_result(True, f"Cache vidé: {result['message']}")
else:
print_result(False, f"Ăchec vidage cache: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur vidage cache: {e}")
return False
# Vérifier que le cache est vide
print_step("Vérification cache vide")
try:
response = requests.get(f"{BASE_URL}/admin/metrics", timeout=10)
if response.status_code == 200:
metrics = response.json()['metrics']
tools_size = metrics['tools_cache']['size']
resp_size = metrics['response_cache']['size']
if tools_size == 0 and resp_size == 0:
print_result(True, "Cache correctement vidé")
else:
print_result(False, f"Cache non vide - tools: {tools_size}, responses: {resp_size}")
else:
print_result(False, f"Ăchec vĂ©rification cache: {response.status_code}")
return False
except Exception as e:
print_result(False, f"Erreur vérification cache: {e}")
return False
return True
async def main():
"""Fonction principale de test"""
print_header("TEST PHASE 2.4 - CACHE L1 & CIRCUIT BREAKER")
print(f"đ DĂ©marrage des tests: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# Test préliminaire
if not await test_health_check():
print_result(False, "â SERVEUR INACCESSIBLE - ARRĂT DES TESTS")
return False
# Tests principaux
tests = [
("Performance Cache", test_cache_performance),
("Circuit Breaker", test_circuit_breaker),
("Endpoint Métriques", test_metrics_endpoint),
("Gestion Cache", test_cache_management),
]
results = []
for test_name, test_func in tests:
try:
result = await test_func()
results.append((test_name, result))
except Exception as e:
print_result(False, f"ERREUR CRITIQUE dans {test_name}: {e}")
results.append((test_name, False))
# Résumé final
print_header("RĂSUMĂ DES TESTS")
total_tests = len(results)
passed_tests = sum(1 for _, result in results if result)
for test_name, result in results:
status = "â
PASSĂ" if result else "â ĂCHEC"
print(f"{status} - {test_name}")
print(f"\\nđ RĂSULTAT FINAL: {passed_tests}/{total_tests} tests rĂ©ussis")
if passed_tests == total_tests:
print_result(True, "đ TOUS LES TESTS DE LA PHASE 2.4 SONT PASSĂS!")
print("đ Phase 2.4 (Cache L1 & Circuit Breaker) - TERMINĂE AVEC SUCCĂS")
return True
else:
print_result(False, f"â {total_tests - passed_tests} tests ont Ă©chouĂ©")
return False
if __name__ == "__main__":
# Exécuter les tests
success = asyncio.run(main())
sys.exit(0 if success else 1)