Files
rpa_vision_v3/tests/integration/test_finalize_replay_chain.py
Dom 7df51d2c79 snapshot: WIP 5j replay reliability (B1 watchdog + dialog handlers + grounding drift)
Snapshot avant correction du blocage relance Léa (3 incidents 24h: SSH refusé,
polls morts ×2). Point de rollback stable.

Contenu:
- agent_v1/core/executor.py: 5 patchs dialog handling (saveas drift, close_tab
  hotkey fallback, confirm_save Unicode apostrophe, foreground dialog
  recontextualization, runtime_dialog in-loop) + helpers normalize_window_hint,
  requires_post_verify_window_transition
- agent_v1/core/grounding.py: garde drift template fix (fallback_x/y plumbed)
- server_v1/replay_watchdog.py (NEW): orphan watchdog B1, scan 10s timeout 30s
- server_v1/api_stream.py: dispatched_action plumbing, watchdog lifespan,
  metrics endpoint
- server_v1/replay_engine.py: _schedule_retry préserve original_action +
  dispatched_action
- stream_processor.py: gardes _infer_tab_switch_target (no false switch_tab
  on save_as dialog open) + _attach_expected_window_before
- tests/integration: test_replay_watchdog.py (8 cas), test_stream_processor.py
- tests/unit: test_executor_verify_window_guard.py (start_button, close_tab,
  runtime_dialog, post_verify, transition fallbacks)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:48:37 +02:00

135 lines
5.0 KiB
Python

"""Tests du chainage produit finalize -> replay-session."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
_ROOT = str(Path(__file__).resolve().parents[2])
if _ROOT not in sys.path:
sys.path.insert(0, _ROOT)
class TestFinalizeReplayChain:
_TEST_API_TOKEN = "test_finalize_replay_chain_token_0123456789"
@pytest.fixture(autouse=True)
def _ensure_api_token(self, monkeypatch):
monkeypatch.setenv("RPA_API_TOKEN", self._TEST_API_TOKEN)
api_stream_mod = sys.modules.get("agent_v0.server_v1.api_stream")
if api_stream_mod is not None:
monkeypatch.setattr(api_stream_mod, "API_TOKEN", self._TEST_API_TOKEN)
@pytest.fixture
def client(self, tmp_path, monkeypatch):
from fastapi.testclient import TestClient
from agent_v0.server_v1 import api_stream
from agent_v0.server_v1.stream_processor import StreamProcessor
from agent_v0.server_v1.worker_stream import StreamWorker
original_processor = api_stream.processor
original_worker = api_stream.worker
test_processor = StreamProcessor(data_dir=str(tmp_path))
api_stream.processor = test_processor
api_stream.worker = StreamWorker(
live_dir=str(tmp_path),
processor=test_processor,
)
monkeypatch.setattr(api_stream, "_enqueue_to_worker", lambda session_id: None)
client = TestClient(api_stream.app, raise_server_exceptions=False)
yield client, api_stream, test_processor, api_stream.API_TOKEN
api_stream.processor = original_processor
api_stream.worker = original_worker
def test_finalize_exposes_replay_request_without_launch(self, client):
c, _, proc, token = client
proc.session_manager.register_session("sess_final_001", machine_id="pc-alpha")
resp = c.post(
"/api/v1/traces/stream/finalize",
params={"session_id": "sess_final_001"},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "queued_for_processing"
assert data["replay_ready"] is True
assert data["replay_request"] == {
"endpoint": "/api/v1/traces/stream/replay-session",
"session_id": "sess_final_001",
"machine_id": "pc-alpha",
}
assert "replay_launch" not in data
def test_finalize_can_launch_replay_session(self, client, monkeypatch):
c, api_stream, proc, token = client
proc.session_manager.register_session("sess_final_002", machine_id="pc-beta")
calls = []
async def fake_replay_from_session(session_id: str, machine_id: str = "default"):
calls.append((session_id, machine_id))
return {
"replay_id": "replay_sess_1234abcd",
"status": "running",
"source_session_id": session_id,
"target_session_id": "agent_demo",
"machine_id": machine_id,
"total_actions": 7,
}
monkeypatch.setattr(api_stream, "replay_from_session", fake_replay_from_session)
resp = c.post(
"/api/v1/traces/stream/finalize",
params={
"session_id": "sess_final_002",
"launch_replay": "true",
},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
data = resp.json()
assert calls == [("sess_final_002", "pc-beta")]
assert data["replay_launch"]["status"] == "started"
assert data["replay_launch"]["replay"]["replay_id"] == "replay_sess_1234abcd"
assert data["replay_launch"]["replay"]["source_session_id"] == "sess_final_002"
assert data["replay_launch"]["replay"]["machine_id"] == "pc-beta"
def test_finalize_remains_successful_if_auto_replay_fails(self, client, monkeypatch):
c, api_stream, proc, token = client
proc.session_manager.register_session("sess_final_003", machine_id="pc-gamma")
async def fake_replay_from_session(session_id: str, machine_id: str = "default"):
raise api_stream.HTTPException(
status_code=404,
detail=f"Aucune session Agent V1 active sur {machine_id}",
)
monkeypatch.setattr(api_stream, "replay_from_session", fake_replay_from_session)
resp = c.post(
"/api/v1/traces/stream/finalize",
params={
"session_id": "sess_final_003",
"launch_replay": "true",
},
headers={"Authorization": f"Bearer {token}"},
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "queued_for_processing"
assert data["replay_launch"] == {
"status": "failed",
"status_code": 404,
"detail": "Aucune session Agent V1 active sur pc-gamma",
}
assert data["replay_request"]["machine_id"] == "pc-gamma"