"""Tests pour les contrôles HTTP de replay paused (resume/abort). Ces appels sont le fallback du chemin SocketIO `lea:replay_resume` / `lea:replay_abort` quand le bus feedback est déconnecté au moment où l'utilisateur clique dans la bulle paused (cf. `docs/CR_AUDIT_PAUSED_RESUME_BUS_2026-05-22.md`). """ from __future__ import annotations import sys from pathlib import Path from unittest.mock import patch, MagicMock import pytest ROOT = Path(__file__).parent.parent.parent sys.path.insert(0, str(ROOT)) from agent_v0.lea_ui.server_client import LeaServerClient # noqa: E402 # Préfixe partagé pour comparer les URLs sans coller à la valeur de # RPA_STREAMING_URL côté env d'exécution des tests. RESUME_PATH = "/traces/stream/replay/replay_xyz/resume" CANCEL_PATH = "/traces/stream/replay/replay_xyz/cancel" @pytest.fixture def client(monkeypatch): monkeypatch.setenv("RPA_API_TOKEN", "tok-test-1234") c = LeaServerClient() return c # ========================================================================= # resume_replay # ========================================================================= class TestResumeReplay: def test_returns_true_when_server_accepts(self, client): resp = MagicMock(ok=True) with patch("requests.post", return_value=resp) as post: assert client.resume_replay("replay_xyz") is True assert post.call_count == 1 def test_returns_false_when_server_rejects(self, client): resp = MagicMock(ok=False) with patch("requests.post", return_value=resp): assert client.resume_replay("replay_xyz") is False def test_returns_false_on_empty_replay_id(self, client): with patch("requests.post") as post: assert client.resume_replay("") is False post.assert_not_called() def test_returns_false_on_exception(self, client): with patch("requests.post", side_effect=ConnectionError("network down")): assert client.resume_replay("replay_xyz") is False def test_posts_to_resume_endpoint_with_auth_header(self, client): resp = MagicMock(ok=True) with patch("requests.post", return_value=resp) as post: client.resume_replay("replay_xyz") call = post.call_args url = call.args[0] if call.args else call.kwargs.get("url", "") assert url.endswith(RESUME_PATH) headers = call.kwargs.get("headers", {}) assert headers.get("Authorization") == "Bearer tok-test-1234" # ========================================================================= # abort_replay # ========================================================================= class TestAbortReplay: def test_returns_true_when_server_accepts(self, client): resp = MagicMock(ok=True) with patch("requests.post", return_value=resp): assert client.abort_replay("replay_xyz") is True def test_returns_false_when_server_rejects(self, client): resp = MagicMock(ok=False) with patch("requests.post", return_value=resp): assert client.abort_replay("replay_xyz") is False def test_returns_false_on_empty_replay_id(self, client): with patch("requests.post") as post: assert client.abort_replay("") is False post.assert_not_called() def test_returns_false_on_exception(self, client): with patch("requests.post", side_effect=TimeoutError("timeout")): assert client.abort_replay("replay_xyz") is False def test_posts_to_cancel_endpoint(self, client): resp = MagicMock(ok=True) with patch("requests.post", return_value=resp) as post: client.abort_replay("replay_xyz") url = post.call_args.args[0] assert url.endswith(CANCEL_PATH)