Refonte majeure du système Agent Chat et ajout de nombreux modules : - Chat unifié : suppression du dual Workflows/Agent Libre, tout passe par /api/chat avec résolution en 3 niveaux (workflow → geste → "montre-moi") - GestureCatalog : 38 raccourcis clavier universels Windows avec matching sémantique, substitution automatique dans les replays, et endpoint /api/gestures - Mode Copilot : exécution pas-à-pas des workflows avec validation humaine via WebSocket (approve/skip/abort) avant chaque action - Léa UI (agent_v0/lea_ui/) : interface PyQt5 pour Windows avec overlay transparent pour feedback visuel pendant le replay - Data Extraction (core/extraction/) : moteur d'extraction visuelle de données (OCR + VLM → SQLite), avec schémas YAML et export CSV/Excel - ReplayVerifier (agent_v0/server_v1/) : vérification post-action par comparaison de screenshots, avec logique de retry (max 3) - IntentParser durci : meilleur fallback regex, type GREETING, patterns améliorés - Dashboard : nouvelles pages gestures, streaming, extractions - Tests : 63 tests GestureCatalog, 47 tests extraction, corrections tests existants - Dépréciation : /api/agent/plan et /api/agent/execute retournent HTTP 410, suppression du code hardcodé _plan_to_replay_actions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
322 lines
11 KiB
Python
322 lines
11 KiB
Python
"""
|
|
Tests pour le dashboard web RPA Vision V3.
|
|
|
|
Vérifie que les routes principales répondent correctement
|
|
et que le template se rend sans erreur.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
# Ajouter le répertoire racine au path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
from web_dashboard.app import app
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Client de test Flask."""
|
|
app.config['TESTING'] = True
|
|
with app.test_client() as c:
|
|
yield c
|
|
|
|
|
|
class TestDashboardRoutes:
|
|
"""Tests des routes du dashboard."""
|
|
|
|
def test_index_renders(self, client):
|
|
"""La page d'accueil se rend correctement."""
|
|
resp = client.get('/')
|
|
assert resp.status_code == 200
|
|
assert b'RPA Vision V3' in resp.data
|
|
|
|
def test_healthz(self, client):
|
|
"""Le healthcheck retourne OK."""
|
|
resp = client.get('/healthz')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert data['status'] == 'ok'
|
|
|
|
def test_system_status(self, client):
|
|
"""L'API system/status retourne les compteurs."""
|
|
resp = client.get('/api/system/status')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'sessions_count' in data
|
|
assert 'workflows_count' in data
|
|
assert 'tests' in data
|
|
|
|
def test_system_performance(self, client):
|
|
"""L'API system/performance retourne les metriques."""
|
|
resp = client.get('/api/system/performance')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'faiss' in data
|
|
assert 'metrics' in data
|
|
|
|
def test_version(self, client):
|
|
"""L'API version retourne la version actuelle."""
|
|
resp = client.get('/api/version')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'version' in data
|
|
# version est un dict avec la clé 'version' (string)
|
|
assert 'version' in data['version']
|
|
|
|
def test_version_system_info(self, client):
|
|
"""L'API version/system-info retourne les infos systeme."""
|
|
resp = client.get('/api/version/system-info')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'system_info' in data
|
|
si = data['system_info']
|
|
assert 'system' in si
|
|
assert 'python_version' in si['system']
|
|
|
|
def test_version_backups(self, client):
|
|
"""L'API version/backups retourne la liste."""
|
|
resp = client.get('/api/version/backups')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'backups' in data
|
|
assert isinstance(data['backups'], list)
|
|
|
|
def test_services_list(self, client):
|
|
"""L'API services retourne la liste des services."""
|
|
resp = client.get('/api/services')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'services' in data
|
|
services = data['services']
|
|
assert len(services) >= 5 # Au moins 5 services configurés
|
|
# Vérifier que le dashboard est dans la liste
|
|
ids = [s['service_id'] for s in services]
|
|
assert 'web_dashboard' in ids
|
|
|
|
def test_config_get(self, client):
|
|
"""L'API config retourne la configuration."""
|
|
resp = client.get('/api/config')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert data['success'] is True
|
|
assert 'config' in data
|
|
|
|
def test_backup_stats(self, client):
|
|
"""L'API backup/stats retourne les statistiques."""
|
|
resp = client.get('/api/backup/stats')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'stats' in data
|
|
stats = data['stats']
|
|
assert 'workflows' in stats
|
|
|
|
def test_workflows_list(self, client):
|
|
"""L'API workflows retourne la liste."""
|
|
resp = client.get('/api/workflows')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'workflows' in data
|
|
|
|
def test_sessions_list(self, client):
|
|
"""L'API sessions retourne la liste."""
|
|
resp = client.get('/api/agent/sessions')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'sessions' in data
|
|
|
|
def test_tests_list(self, client):
|
|
"""L'API tests retourne la liste des tests."""
|
|
resp = client.get('/api/tests')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'tests' in data
|
|
assert 'total' in data
|
|
|
|
def test_logs(self, client):
|
|
"""L'API logs retourne les logs."""
|
|
resp = client.get('/api/logs')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'logs' in data
|
|
|
|
def test_chains(self, client):
|
|
"""L'API chains retourne la liste."""
|
|
resp = client.get('/api/chains')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'chains' in data
|
|
|
|
def test_triggers(self, client):
|
|
"""L'API triggers retourne la liste."""
|
|
resp = client.get('/api/triggers')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'triggers' in data
|
|
|
|
def test_automation_status(self, client):
|
|
"""L'API automation/status retourne le statut."""
|
|
resp = client.get('/api/automation/status')
|
|
assert resp.status_code == 200
|
|
|
|
def test_metrics_endpoint(self, client):
|
|
"""L'endpoint Prometheus /metrics fonctionne."""
|
|
resp = client.get('/metrics')
|
|
assert resp.status_code == 200
|
|
|
|
def test_no_rollback_route(self, client):
|
|
"""La route /api/version/rollback n'existe pas (non implementee)."""
|
|
resp = client.post('/api/version/rollback/test-id')
|
|
assert resp.status_code == 404 or resp.status_code == 405
|
|
|
|
|
|
class TestGesturesRoutes:
|
|
"""Tests des routes du catalogue de gestes."""
|
|
|
|
def test_gestures_page_renders(self, client):
|
|
"""La page /gestures se rend correctement."""
|
|
resp = client.get('/gestures')
|
|
assert resp.status_code == 200
|
|
assert b'Gestes Primitifs' in resp.data
|
|
|
|
def test_gestures_page_has_categories(self, client):
|
|
"""La page /gestures affiche les catégories de gestes."""
|
|
resp = client.get('/gestures')
|
|
assert resp.status_code == 200
|
|
# Vérifier qu'au moins une catégorie est présente
|
|
assert b'windows' in resp.data or b'chrome' in resp.data
|
|
|
|
def test_gestures_page_has_shortcuts(self, client):
|
|
"""La page /gestures affiche les raccourcis clavier."""
|
|
resp = client.get('/gestures')
|
|
assert resp.status_code == 200
|
|
assert b'Ctrl' in resp.data or b'Alt' in resp.data
|
|
|
|
def test_api_gestures(self, client):
|
|
"""L'API /api/gestures retourne les gestes en JSON."""
|
|
resp = client.get('/api/gestures')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'gestures' in data
|
|
assert 'total' in data
|
|
assert 'categories' in data
|
|
assert data['total'] > 0
|
|
assert isinstance(data['gestures'], list)
|
|
assert len(data['gestures']) == data['total']
|
|
|
|
def test_api_gestures_structure(self, client):
|
|
"""Chaque geste a les champs requis."""
|
|
resp = client.get('/api/gestures')
|
|
data = resp.get_json()
|
|
for gesture in data['gestures']:
|
|
assert 'name' in gesture
|
|
assert 'category' in gesture
|
|
assert 'description' in gesture
|
|
|
|
def test_api_gestures_categories(self, client):
|
|
"""Les catégories sont bien structurées."""
|
|
resp = client.get('/api/gestures')
|
|
data = resp.get_json()
|
|
categories = data['categories']
|
|
assert len(categories) >= 4 # windows, chrome, edition, system au minimum
|
|
for cat in categories:
|
|
assert 'id' in cat
|
|
assert 'name' in cat
|
|
assert 'count' in cat
|
|
assert cat['count'] > 0
|
|
|
|
|
|
class TestStreamingRoutes:
|
|
"""Tests des routes streaming."""
|
|
|
|
def test_streaming_page_renders(self, client):
|
|
"""La page /streaming se rend correctement."""
|
|
resp = client.get('/streaming')
|
|
assert resp.status_code == 200
|
|
assert b'Streaming' in resp.data
|
|
|
|
def test_streaming_page_has_stats_section(self, client):
|
|
"""La page /streaming contient les sections de stats."""
|
|
resp = client.get('/streaming')
|
|
assert resp.status_code == 200
|
|
assert b'Sessions actives' in resp.data
|
|
assert b'Serveur streaming' in resp.data
|
|
|
|
def test_api_streaming_status(self, client):
|
|
"""L'API /api/streaming/status retourne un résultat (même si serveur offline)."""
|
|
resp = client.get('/api/streaming/status')
|
|
# Le serveur streaming peut ne pas être lancé (502) ou répondre (200)
|
|
assert resp.status_code in (200, 502)
|
|
data = resp.get_json()
|
|
assert isinstance(data, dict)
|
|
|
|
|
|
class TestExtractionsRoutes:
|
|
"""Tests des routes extractions."""
|
|
|
|
def test_extractions_page_renders(self, client):
|
|
"""La page /extractions se rend correctement."""
|
|
resp = client.get('/extractions')
|
|
assert resp.status_code == 200
|
|
assert b'Extractions' in resp.data
|
|
|
|
def test_extractions_page_module_unavailable(self, client):
|
|
"""La page /extractions affiche un message si le module n'est pas disponible."""
|
|
resp = client.get('/extractions')
|
|
assert resp.status_code == 200
|
|
# Le module core.extraction n'existe pas, on doit voir le message
|
|
assert b'non disponible' in resp.data or b'Module' in resp.data
|
|
|
|
def test_api_extractions(self, client):
|
|
"""L'API /api/extractions retourne un résultat valide."""
|
|
resp = client.get('/api/extractions')
|
|
assert resp.status_code == 200
|
|
data = resp.get_json()
|
|
assert 'available' in data
|
|
assert 'extractions' in data
|
|
assert isinstance(data['extractions'], list)
|
|
|
|
def test_api_extractions_module_status(self, client):
|
|
"""L'API /api/extractions indique si le module est disponible."""
|
|
resp = client.get('/api/extractions')
|
|
data = resp.get_json()
|
|
# Le module n'existe pas dans ce contexte
|
|
assert data['available'] is False
|
|
assert 'message' in data
|
|
|
|
def test_api_extraction_export_no_module(self, client):
|
|
"""L'export CSV retourne 501 si le module n'est pas disponible."""
|
|
resp = client.get('/api/extractions/test-id/export?format=csv')
|
|
assert resp.status_code == 501
|
|
data = resp.get_json()
|
|
assert 'error' in data
|
|
|
|
|
|
class TestNavigationLinks:
|
|
"""Tests de la navigation entre pages."""
|
|
|
|
def test_index_has_gestures_link(self, client):
|
|
"""La page d'accueil contient un lien vers /gestures."""
|
|
resp = client.get('/')
|
|
assert resp.status_code == 200
|
|
assert b'/gestures' in resp.data
|
|
|
|
def test_index_has_streaming_link(self, client):
|
|
"""La page d'accueil contient un lien vers /streaming."""
|
|
resp = client.get('/')
|
|
assert resp.status_code == 200
|
|
assert b'/streaming' in resp.data
|
|
|
|
def test_index_has_extractions_link(self, client):
|
|
"""La page d'accueil contient un lien vers /extractions."""
|
|
resp = client.get('/')
|
|
assert resp.status_code == 200
|
|
assert b'/extractions' in resp.data
|
|
|
|
def test_gestures_has_back_link(self, client):
|
|
"""La page gestures contient un lien retour vers le dashboard."""
|
|
resp = client.get('/gestures')
|
|
assert resp.status_code == 200
|
|
assert b'href="/"' in resp.data or b"href='/'" in resp.data
|