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>
This commit is contained in:
134
tests/integration/test_finalize_replay_chain.py
Normal file
134
tests/integration/test_finalize_replay_chain.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user