- 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>
348 lines
12 KiB
Python
348 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Utilitaires pour tests de fonctionnalité réelle - Documentation Tab
|
|
|
|
Fournit des fonctions pour tester avec de vraies données et services
|
|
sans simulation ni mocking.
|
|
"""
|
|
|
|
import json
|
|
import requests
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any
|
|
|
|
|
|
class RealDocumentationService:
|
|
"""Service pour interagir avec la vraie API de documentation"""
|
|
|
|
def __init__(self, base_url: str = "http://localhost:5000"):
|
|
self.base_url = base_url.rstrip("/")
|
|
self.session = requests.Session()
|
|
self.session.timeout = 10
|
|
|
|
def get_node_types(self) -> Optional[List[Dict[str, Any]]]:
|
|
"""Récupérer les vrais types de nœuds depuis l'API"""
|
|
try:
|
|
response = self.session.get(f"{self.base_url}/api/node-types")
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
print(f"Erreur API node-types: {e}")
|
|
return None
|
|
|
|
def get_tool_documentation(self, tool_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Récupérer la documentation d'un outil spécifique"""
|
|
try:
|
|
response = self.session.get(f"{self.base_url}/api/tools/{tool_id}/documentation")
|
|
if response.status_code == 200:
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
print(f"Erreur API documentation {tool_id}: {e}")
|
|
return None
|
|
|
|
def validate_documentation_structure(self, doc_data: Dict[str, Any]) -> bool:
|
|
"""Valider que la structure de documentation est correcte"""
|
|
required_fields = ["title", "description", "parameters"]
|
|
return all(field in doc_data for field in required_fields)
|
|
|
|
|
|
class RealWorkflowService:
|
|
"""Service pour interagir avec les vrais workflows"""
|
|
|
|
def __init__(self, base_url: str = "http://localhost:5000"):
|
|
self.base_url = base_url.rstrip("/")
|
|
self.session = requests.Session()
|
|
self.session.timeout = 10
|
|
|
|
def create_test_workflow(self) -> Optional[str]:
|
|
"""Créer un vrai workflow de test"""
|
|
test_workflow = {
|
|
"name": "Test Documentation Workflow",
|
|
"description": "Workflow créé pour tester la documentation",
|
|
"nodes": [
|
|
{
|
|
"id": "node_1",
|
|
"type": "action",
|
|
"data": {
|
|
"label": "Test Action",
|
|
"action_type": "click",
|
|
"target": "button"
|
|
},
|
|
"position": {"x": 100, "y": 100}
|
|
}
|
|
],
|
|
"edges": []
|
|
}
|
|
|
|
try:
|
|
response = self.session.post(
|
|
f"{self.base_url}/api/workflows",
|
|
json=test_workflow
|
|
)
|
|
if response.status_code == 201:
|
|
return response.json().get("id")
|
|
except requests.RequestException as e:
|
|
print(f"Erreur création workflow: {e}")
|
|
return None
|
|
|
|
def delete_test_workflow(self, workflow_id: str) -> bool:
|
|
"""Supprimer un workflow de test"""
|
|
try:
|
|
response = self.session.delete(f"{self.base_url}/api/workflows/{workflow_id}")
|
|
return response.status_code == 204
|
|
except requests.RequestException as e:
|
|
print(f"Erreur suppression workflow: {e}")
|
|
return False
|
|
|
|
|
|
class RealDataProvider:
|
|
"""Fournisseur de données réelles pour les tests"""
|
|
|
|
@staticmethod
|
|
def get_local_tool_documentation() -> Optional[Dict[str, Any]]:
|
|
"""Charger la vraie documentation locale depuis les fichiers"""
|
|
doc_file = Path("visual_workflow_builder/frontend/src/data/toolDocumentation.ts")
|
|
|
|
if not doc_file.exists():
|
|
return None
|
|
|
|
try:
|
|
content = doc_file.read_text(encoding='utf-8')
|
|
|
|
# Parser le contenu TypeScript (simple extraction)
|
|
# Chercher les définitions d'outils
|
|
tools = {}
|
|
lines = content.split('\n')
|
|
|
|
current_tool = None
|
|
for line in lines:
|
|
line = line.strip()
|
|
|
|
# Détecter le début d'une définition d'outil
|
|
if line.startswith('"') and '": {' in line:
|
|
tool_name = line.split('"')[1]
|
|
current_tool = tool_name
|
|
tools[tool_name] = {}
|
|
|
|
# Extraire les propriétés
|
|
elif current_tool and ':' in line and not line.startswith('//'):
|
|
if 'title:' in line:
|
|
title = line.split('"')[1] if '"' in line else ""
|
|
tools[current_tool]['title'] = title
|
|
elif 'description:' in line:
|
|
desc = line.split('"')[1] if '"' in line else ""
|
|
tools[current_tool]['description'] = desc
|
|
|
|
return tools if tools else None
|
|
|
|
except Exception as e:
|
|
print(f"Erreur lecture documentation locale: {e}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_real_node_configurations() -> List[Dict[str, Any]]:
|
|
"""Récupérer les vraies configurations de nœuds"""
|
|
return [
|
|
{
|
|
"type": "action",
|
|
"label": "Action Node",
|
|
"category": "basic",
|
|
"properties": {
|
|
"action_type": {"type": "select", "options": ["click", "type", "wait"]},
|
|
"target": {"type": "string", "required": True},
|
|
"value": {"type": "string", "required": False}
|
|
}
|
|
},
|
|
{
|
|
"type": "condition",
|
|
"label": "Condition Node",
|
|
"category": "logic",
|
|
"properties": {
|
|
"condition_type": {"type": "select", "options": ["exists", "visible", "text_contains"]},
|
|
"target": {"type": "string", "required": True},
|
|
"expected_value": {"type": "string", "required": False}
|
|
}
|
|
},
|
|
{
|
|
"type": "loop",
|
|
"label": "Loop Node",
|
|
"category": "control",
|
|
"properties": {
|
|
"loop_type": {"type": "select", "options": ["for", "while", "foreach"]},
|
|
"iterations": {"type": "number", "default": 1},
|
|
"condition": {"type": "string", "required": False}
|
|
}
|
|
}
|
|
]
|
|
|
|
|
|
class RealBrowserInteractions:
|
|
"""Interactions réelles avec le navigateur pour les tests"""
|
|
|
|
@staticmethod
|
|
def simulate_real_user_behavior(driver, duration_seconds: int = 5):
|
|
"""Simuler un comportement utilisateur réaliste"""
|
|
import random
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
|
|
actions = ActionChains(driver)
|
|
|
|
# Mouvements de souris réalistes
|
|
for _ in range(duration_seconds):
|
|
# Mouvement aléatoire de la souris
|
|
x_offset = random.randint(-50, 50)
|
|
y_offset = random.randint(-50, 50)
|
|
actions.move_by_offset(x_offset, y_offset)
|
|
|
|
# Pause réaliste
|
|
time.sleep(random.uniform(0.5, 1.5))
|
|
|
|
# Parfois cliquer
|
|
if random.random() < 0.3:
|
|
actions.click()
|
|
|
|
actions.perform()
|
|
actions = ActionChains(driver) # Reset actions
|
|
|
|
@staticmethod
|
|
def verify_real_dom_state(driver, expected_elements: List[str]) -> Dict[str, bool]:
|
|
"""Vérifier l'état réel du DOM"""
|
|
results = {}
|
|
|
|
for selector in expected_elements:
|
|
try:
|
|
elements = driver.find_elements("css selector", selector)
|
|
results[selector] = len(elements) > 0
|
|
except Exception as e:
|
|
print(f"Erreur vérification {selector}: {e}")
|
|
results[selector] = False
|
|
|
|
return results
|
|
|
|
@staticmethod
|
|
def capture_real_browser_logs(driver) -> List[Dict[str, Any]]:
|
|
"""Capturer les vrais logs du navigateur"""
|
|
try:
|
|
logs = driver.get_log('browser')
|
|
return [
|
|
{
|
|
"level": log['level'],
|
|
"message": log['message'],
|
|
"timestamp": log['timestamp']
|
|
}
|
|
for log in logs
|
|
]
|
|
except Exception as e:
|
|
print(f"Erreur capture logs: {e}")
|
|
return []
|
|
|
|
|
|
class RealTestValidator:
|
|
"""Validateur pour s'assurer que les tests utilisent de vraies données"""
|
|
|
|
@staticmethod
|
|
def validate_no_mocks_used(test_function) -> bool:
|
|
"""Vérifier qu'aucun mock n'est utilisé dans le test"""
|
|
import inspect
|
|
|
|
source = inspect.getsource(test_function)
|
|
mock_indicators = [
|
|
"unittest.mock",
|
|
"mock.patch",
|
|
"MagicMock",
|
|
"Mock()",
|
|
"@patch",
|
|
"mock_"
|
|
]
|
|
|
|
for indicator in mock_indicators:
|
|
if indicator in source:
|
|
print(f"⚠️ Mock détecté: {indicator}")
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def validate_real_data_usage(data: Any) -> bool:
|
|
"""Vérifier que les données utilisées sont réelles"""
|
|
if isinstance(data, dict):
|
|
# Vérifier qu'il n'y a pas de données factices
|
|
fake_indicators = ["fake", "mock", "test_", "dummy", "placeholder"]
|
|
|
|
for key, value in data.items():
|
|
key_str = str(key).lower()
|
|
value_str = str(value).lower()
|
|
|
|
for indicator in fake_indicators:
|
|
if indicator in key_str or indicator in value_str:
|
|
print(f"⚠️ Données factices détectées: {key}={value}")
|
|
return False
|
|
|
|
return True
|
|
|
|
@staticmethod
|
|
def validate_real_service_calls(service_calls: List[str]) -> bool:
|
|
"""Vérifier que les appels de service sont réels"""
|
|
for call in service_calls:
|
|
if "localhost" not in call and "127.0.0.1" not in call:
|
|
if not call.startswith("http"):
|
|
print(f"⚠️ Appel de service suspect: {call}")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
# Fonctions utilitaires pour l'intégration
|
|
def setup_real_test_environment():
|
|
"""Configurer un environnement de test avec de vraies données"""
|
|
doc_service = RealDocumentationService()
|
|
workflow_service = RealWorkflowService()
|
|
|
|
# Vérifier que les services sont disponibles
|
|
node_types = doc_service.get_node_types()
|
|
if node_types:
|
|
print(f"✅ Service documentation disponible ({len(node_types)} types)")
|
|
else:
|
|
print("⚠️ Service documentation non disponible, utilisation données locales")
|
|
|
|
return {
|
|
"documentation_service": doc_service,
|
|
"workflow_service": workflow_service,
|
|
"data_provider": RealDataProvider(),
|
|
"browser_interactions": RealBrowserInteractions(),
|
|
"validator": RealTestValidator()
|
|
}
|
|
|
|
|
|
def cleanup_real_test_environment(test_env: Dict[str, Any], created_resources: List[str]):
|
|
"""Nettoyer l'environnement de test"""
|
|
workflow_service = test_env.get("workflow_service")
|
|
|
|
if workflow_service:
|
|
for resource_id in created_resources:
|
|
try:
|
|
workflow_service.delete_test_workflow(resource_id)
|
|
print(f"✅ Ressource nettoyée: {resource_id}")
|
|
except Exception as e:
|
|
print(f"⚠️ Erreur nettoyage {resource_id}: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Test des utilitaires
|
|
print("🧪 Test des utilitaires de fonctionnalité réelle")
|
|
|
|
test_env = setup_real_test_environment()
|
|
|
|
# Test du fournisseur de données locales
|
|
local_docs = RealDataProvider.get_local_tool_documentation()
|
|
if local_docs:
|
|
print(f"✅ Documentation locale chargée: {len(local_docs)} outils")
|
|
else:
|
|
print("❌ Impossible de charger la documentation locale")
|
|
|
|
# Test des configurations de nœuds
|
|
node_configs = RealDataProvider.get_real_node_configurations()
|
|
print(f"✅ Configurations de nœuds: {len(node_configs)} types")
|
|
|
|
print("✅ Utilitaires de test réel validés") |