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:
348
test_documentation_real_functionality_utils.py
Normal file
348
test_documentation_real_functionality_utils.py
Normal file
@@ -0,0 +1,348 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user