feat: chat unifié, GestureCatalog, Copilot, Léa UI, extraction données, vérification replay
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>
This commit is contained in:
@@ -169,3 +169,153 @@ class TestDashboardRoutes:
|
||||
"""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
|
||||
|
||||
Reference in New Issue
Block a user