Files
rpa_vision_v3/tests/integration/test_feedback_bus.py
Dom 5e31cdf666 feat(agent_chat): bus feedback Léa 'lea:*' derrière flag LEA_FEEDBACK_BUS
Surface d'observation pour bulles temps réel ChatWindow (J2 démo GHT Sud 95).

- Helper _emit_lea(event, payload): no-op silencieux si flag off
- Helper _emit_dual(legacy, lea, payload): émet event existant + alias 'lea:*'
- Détection paused_need_help dans _poll_replay_progress → lea:paused
- Détection sortie de pause → lea:resumed
- Timeout étendu (120s→600s) pendant pause supervisée
- 12 emits SocketIO existants aliasés (execution_started/progress/completed,
  copilot_step/step_result/complete) — payloads identiques, zéro régression

Flag LEA_FEEDBACK_BUS=0 par défaut. Comportement legacy strictement préservé.
8 tests pytest verts (tests/integration/test_feedback_bus.py).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:48:38 +02:00

103 lines
3.4 KiB
Python

"""Tests du bus feedback Léa (events lea:* via Flask-SocketIO).
Couvre J2.5 et J2.6 :
- Flag LEA_FEEDBACK_BUS=0 → _emit_lea no-op, _emit_dual ne propage que l'event legacy
- Flag LEA_FEEDBACK_BUS=1 → _emit_lea propage 'lea:{event}', _emit_dual propage les deux
Approche : on intercepte socketio.emit avec monkeypatch (plus fiable que test_client
de Flask-SocketIO qui ne capte pas toujours les broadcasts hors contexte requête).
"""
import importlib
import pytest
def _reload_app(monkeypatch, flag_value: str):
monkeypatch.setenv("LEA_FEEDBACK_BUS", flag_value)
import agent_chat.app as app_mod
importlib.reload(app_mod)
return app_mod
def _capture_emits(monkeypatch, app_mod):
calls = []
monkeypatch.setattr(
app_mod.socketio, "emit",
lambda event, payload=None, **kwargs: calls.append((event, payload, kwargs)),
)
return calls
@pytest.fixture
def app_off(monkeypatch):
return _reload_app(monkeypatch, "0")
@pytest.fixture
def app_on(monkeypatch):
return _reload_app(monkeypatch, "1")
def test_flag_off_by_default(monkeypatch):
monkeypatch.delenv("LEA_FEEDBACK_BUS", raising=False)
import agent_chat.app as app_mod
importlib.reload(app_mod)
assert app_mod.LEA_FEEDBACK_BUS is False
def test_flag_accepts_truthy_values(monkeypatch):
for truthy in ["1", "true", "True", "yes", "on", "TRUE"]:
monkeypatch.setenv("LEA_FEEDBACK_BUS", truthy)
import agent_chat.app as app_mod
importlib.reload(app_mod)
assert app_mod.LEA_FEEDBACK_BUS is True, f"{truthy!r} devrait activer le flag"
def test_emit_lea_noop_when_flag_off(app_off, monkeypatch):
calls = _capture_emits(monkeypatch, app_off)
app_off._emit_lea("paused", {"workflow": "demo", "reason": "test"})
assert calls == []
def test_emit_lea_emits_when_flag_on(app_on, monkeypatch):
calls = _capture_emits(monkeypatch, app_on)
app_on._emit_lea("paused", {"workflow": "demo", "reason": "test"})
assert len(calls) == 1
event, payload, _ = calls[0]
assert event == "lea:paused"
assert payload == {"workflow": "demo", "reason": "test"}
def test_emit_dual_emits_only_legacy_when_flag_off(app_off, monkeypatch):
calls = _capture_emits(monkeypatch, app_off)
app_off._emit_dual("execution_started", "action_started", {"workflow": "demo"})
assert len(calls) == 1
assert calls[0][0] == "execution_started"
def test_emit_dual_emits_both_when_flag_on(app_on, monkeypatch):
calls = _capture_emits(monkeypatch, app_on)
payload = {"workflow": "demo", "params": {"k": "v"}}
app_on._emit_dual("execution_started", "action_started", payload)
events = [c[0] for c in calls]
assert "execution_started" in events
assert "lea:action_started" in events
assert len(calls) == 2
def test_emit_dual_preserves_kwargs(app_on, monkeypatch):
"""broadcast=True et autres kwargs Flask-SocketIO doivent être propagés au legacy."""
calls = _capture_emits(monkeypatch, app_on)
app_on._emit_dual("execution_cancelled", "cancelled", {}, broadcast=True)
legacy_call = next(c for c in calls if c[0] == "execution_cancelled")
assert legacy_call[2].get("broadcast") is True
def test_emit_lea_silenced_on_socketio_error(app_on, monkeypatch):
"""Une exception dans socketio.emit ne doit jamais remonter."""
def boom(*args, **kwargs):
raise RuntimeError("socketio fail")
monkeypatch.setattr(app_on.socketio, "emit", boom)
app_on._emit_lea("paused", {"x": 1})