Files
rpa_vision_v3/tests/integration/test_vwb_frontend_stability_09jan2026.py
Dom a27b74cf22 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>
2026-01-29 11:23:51 +01:00

282 lines
12 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tests de stabilité de l'interface Visual Workflow Builder Frontend V2
Auteur : Dom, Alice, Kiro - 09 janvier 2026
Ce module vérifie que les corrections apportées pour résoudre la boucle
infinie de chargement sont correctement implémentées.
"""
import os
import re
import pytest
from pathlib import Path
# Chemin vers le frontend VWB
FRONTEND_PATH = Path(__file__).parent.parent.parent / "visual_workflow_builder" / "frontend"
SRC_PATH = FRONTEND_PATH / "src"
class TestApiClientStability:
"""Tests de stabilité du client API."""
def test_api_client_initial_state_is_offline(self):
"""Vérifie que l'état initial du client API est 'offline'."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
assert api_client_path.exists(), f"Fichier non trouvé: {api_client_path}"
content = api_client_path.read_text(encoding='utf-8')
# Vérifier que l'état initial est 'offline' et non 'checking'
assert "connectionState: ConnectionState = 'offline'" in content, \
"L'état initial du client API doit être 'offline' pour éviter les boucles"
# Vérifier qu'il n'y a pas d'état initial 'checking'
assert "connectionState: ConnectionState = 'checking'" not in content, \
"L'état initial ne doit PAS être 'checking' car cela cause des re-renders"
def test_api_client_no_immediate_callback_notification(self):
"""Vérifie que onConnectionStateChange ne notifie pas immédiatement."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
content = api_client_path.read_text(encoding='utf-8')
# Chercher la méthode onConnectionStateChange
method_match = re.search(
r'onConnectionStateChange\([^)]+\)[^{]*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}',
content,
re.DOTALL
)
assert method_match, "Méthode onConnectionStateChange non trouvée"
method_body = method_match.group(1)
# Vérifier qu'il n'y a PAS d'appel immédiat au callback
# Pattern: callback(this.connectionState) sans setTimeout
immediate_call_pattern = r'callback\s*\(\s*this\.connectionState\s*\)'
# Si le pattern est trouvé, vérifier qu'il est commenté ou dans un setTimeout
if re.search(immediate_call_pattern, method_body):
# Vérifier que c'est dans un commentaire
lines = method_body.split('\n')
for line in lines:
if re.search(immediate_call_pattern, line):
assert '//' in line or '/*' in line, \
"L'appel callback(this.connectionState) doit être commenté ou supprimé"
def test_api_client_lazy_initialization(self):
"""Vérifie que l'initialisation est paresseuse (lazy)."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
content = api_client_path.read_text(encoding='utf-8')
# Vérifier la présence du commentaire sur l'initialisation paresseuse
assert "paresseuse" in content.lower() or "lazy" in content.lower(), \
"Le code doit mentionner l'initialisation paresseuse (lazy)"
# Vérifier qu'il n'y a PAS d'appel automatique à initialize() à la fin du fichier
# Pattern: apiClient.initialize() sans être dans une fonction
lines = content.split('\n')
for i, line in enumerate(lines):
if 'apiClient.initialize()' in line and not line.strip().startswith('//'):
# Vérifier que c'est dans une fonction ou commenté
context = '\n'.join(lines[max(0, i-5):i+1])
assert 'async' in context or 'function' in context or '//' in line, \
f"Appel automatique à apiClient.initialize() trouvé ligne {i+1}"
def test_api_client_async_notifications(self):
"""Vérifie que les notifications sont asynchrones (setTimeout)."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
content = api_client_path.read_text(encoding='utf-8')
# Chercher la méthode setConnectionState
method_match = re.search(
r'setConnectionState\([^)]+\)[^{]*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}',
content,
re.DOTALL
)
assert method_match, "Méthode setConnectionState non trouvée"
method_body = method_match.group(1)
# Vérifier la présence de setTimeout pour les notifications asynchrones
assert 'setTimeout' in method_body, \
"Les notifications doivent être asynchrones (setTimeout) pour éviter les boucles"
class TestConnectionStatusHookStability:
"""Tests de stabilité du hook useConnectionStatus."""
def test_hook_initial_state_is_offline(self):
"""Vérifie que l'état initial du hook est 'offline'."""
hook_path = SRC_PATH / "hooks" / "useConnectionStatus.ts"
assert hook_path.exists(), f"Fichier non trouvé: {hook_path}"
content = hook_path.read_text(encoding='utf-8')
# Vérifier que l'état initial est défini comme 'offline'
assert "status: 'offline'" in content, \
"L'état initial du hook doit être 'offline'"
# Vérifier qu'il n'y a pas d'état initial dynamique basé sur apiClient
assert "apiClient.getConnectionState()" not in content or \
"// " in content.split("apiClient.getConnectionState()")[0].split('\n')[-1], \
"L'état initial ne doit PAS être basé sur apiClient.getConnectionState()"
def test_hook_uses_refs_for_callbacks(self):
"""Vérifie que le hook utilise des refs pour les callbacks."""
hook_path = SRC_PATH / "hooks" / "useConnectionStatus.ts"
content = hook_path.read_text(encoding='utf-8')
# Vérifier l'utilisation de useRef pour les callbacks
assert 'useRef' in content, \
"Le hook doit utiliser useRef pour éviter les re-renders"
# Vérifier que onStatusChange utilise une ref
assert 'onStatusChangeRef' in content or 'Ref' in content, \
"Les callbacks doivent être stockés dans des refs"
def test_hook_stable_initial_state_constant(self):
"""Vérifie que l'état initial est une constante stable."""
hook_path = SRC_PATH / "hooks" / "useConnectionStatus.ts"
content = hook_path.read_text(encoding='utf-8')
# Vérifier la présence d'une constante INITIAL_STATE
assert 'INITIAL_STATE' in content, \
"L'état initial doit être une constante INITIAL_STATE définie en dehors du hook"
class TestUseApiClientHookStability:
"""Tests de stabilité du hook useApiClient."""
def test_use_connection_state_initial_offline(self):
"""Vérifie que useConnectionState a un état initial 'offline'."""
hook_path = SRC_PATH / "hooks" / "useApiClient.ts"
assert hook_path.exists(), f"Fichier non trouvé: {hook_path}"
content = hook_path.read_text(encoding='utf-8')
# Chercher la fonction useConnectionState
func_match = re.search(
r'export function useConnectionState\(\)[^{]*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}',
content,
re.DOTALL
)
assert func_match, "Fonction useConnectionState non trouvée"
func_body = func_match.group(1)
# Vérifier que l'état initial est 'offline'
assert "'offline'" in func_body, \
"useConnectionState doit avoir un état initial 'offline'"
class TestWorkflowManagerStability:
"""Tests de stabilité du composant WorkflowManager."""
def test_workflow_manager_uses_connection_state(self):
"""Vérifie que WorkflowManager utilise useConnectionState."""
component_path = SRC_PATH / "components" / "WorkflowManager" / "index.tsx"
assert component_path.exists(), f"Fichier non trouvé: {component_path}"
content = component_path.read_text(encoding='utf-8')
# Vérifier l'import de useConnectionState
assert 'useConnectionState' in content, \
"WorkflowManager doit utiliser useConnectionState"
def test_workflow_manager_handles_offline_mode(self):
"""Vérifie que WorkflowManager gère le mode hors ligne."""
component_path = SRC_PATH / "components" / "WorkflowManager" / "index.tsx"
content = component_path.read_text(encoding='utf-8')
# Vérifier la gestion du mode hors ligne
assert 'isOffline' in content or 'offline' in content.lower(), \
"WorkflowManager doit gérer le mode hors ligne"
class TestExecutorStability:
"""Tests de stabilité du composant Executor."""
def test_executor_uses_connection_status(self):
"""Vérifie que Executor utilise useConnectionStatus."""
component_path = SRC_PATH / "components" / "Executor" / "index.tsx"
assert component_path.exists(), f"Fichier non trouvé: {component_path}"
content = component_path.read_text(encoding='utf-8')
# Vérifier l'import de useConnectionStatus
assert 'useConnectionStatus' in content, \
"Executor doit utiliser useConnectionStatus"
def test_executor_handles_offline_mode(self):
"""Vérifie que Executor gère le mode hors ligne."""
component_path = SRC_PATH / "components" / "Executor" / "index.tsx"
content = component_path.read_text(encoding='utf-8')
# Vérifier la gestion du mode hors ligne
assert 'isOffline' in content, \
"Executor doit gérer le mode hors ligne avec isOffline"
class TestTypescriptCompilation:
"""Tests de compilation TypeScript."""
def test_no_typescript_errors_in_api_client(self):
"""Vérifie qu'il n'y a pas d'erreurs TypeScript dans apiClient.ts."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
content = api_client_path.read_text(encoding='utf-8')
# Vérifications basiques de syntaxe TypeScript
assert content.count('{') == content.count('}'), \
"Accolades non équilibrées dans apiClient.ts"
assert content.count('(') == content.count(')'), \
"Parenthèses non équilibrées dans apiClient.ts"
def test_no_typescript_errors_in_hooks(self):
"""Vérifie qu'il n'y a pas d'erreurs TypeScript dans les hooks."""
hooks_path = SRC_PATH / "hooks"
for hook_file in hooks_path.glob("*.ts"):
content = hook_file.read_text(encoding='utf-8')
# Vérifications basiques de syntaxe TypeScript
assert content.count('{') == content.count('}'), \
f"Accolades non équilibrées dans {hook_file.name}"
assert content.count('(') == content.count(')'), \
f"Parenthèses non équilibrées dans {hook_file.name}"
class TestFrenchDocumentation:
"""Tests de documentation en français."""
def test_api_client_has_french_comments(self):
"""Vérifie que apiClient.ts a des commentaires en français."""
api_client_path = SRC_PATH / "services" / "apiClient.ts"
content = api_client_path.read_text(encoding='utf-8')
# Vérifier la présence de commentaires en français
french_words = ['Auteur', 'janvier', 'gestion', 'connexion', 'hors ligne']
found_french = any(word in content for word in french_words)
assert found_french, \
"apiClient.ts doit avoir des commentaires en français"
def test_hooks_have_french_comments(self):
"""Vérifie que les hooks ont des commentaires en français."""
hooks_path = SRC_PATH / "hooks"
for hook_file in hooks_path.glob("*.ts"):
content = hook_file.read_text(encoding='utf-8')
# Vérifier la présence de commentaires en français
french_words = ['Auteur', 'janvier', 'état', 'connexion']
found_french = any(word in content for word in french_words)
assert found_french, \
f"{hook_file.name} doit avoir des commentaires en français"
if __name__ == "__main__":
pytest.main([__file__, "-v", "--tb=short"])