Files
rpa_vision_v3/tests/integration/test_dialog_resolver_endpoint.py
Dom 84d2d4a667 feat(dialog): R2 MVP P0 — DialogResolver + catalogue 10 entrées (flag OFF default)
- agent_v0/server_v1/core/dialog/ : catalogue compact + DialogResolver
  stateless (match titre + evidence, trichotomie stricte auto/pause/skip).
- 10 entrées P0 : confirm-save-overwrite, notepad-unsaved-changes,
  windows-file-explorer (fallback replay 4c38dbb8), easily-save/overwrite/
  confirm-action/clinical-warning, windows-uac, windows-hello-credui,
  edge-update.
- Validateur déclaratif `system_modals_cannot_be_overridden` : rejette
  toute surcharge auto/skip sur modaux SYSTÈME (windows-/defender-).
- Endpoint POST /api/v1/dialog/resolve derrière flag
  RPA_DIALOG_RESOLVER_ENABLED (OFF par défaut → 503). Aucun
  rebranchement côté agent_v1 (executor.py inchangé, P1 plus tard).
- 25 tests pytest passants (19 unit + 6 intégration HTTP).

Spec : docs/recherche/SPEC_POPUPS_CATALOGUE.md §2bis / §3.

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

142 lines
4.5 KiB
Python

"""Tests intégration HTTP de POST /api/v1/dialog/resolve — R2 MVP P0.
Vérifie :
- flag OFF par défaut → 503.
- flag ON + match catalogue → 200 + payload conforme.
- flag ON + pas de match → 200 + matched=False + policy=pause.
"""
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)
pytestmark = pytest.mark.integration
_TEST_API_TOKEN = "test_dialog_resolver_endpoint_token"
@pytest.fixture
def client(monkeypatch):
"""TestClient FastAPI avec token. Le flag RPA_DIALOG_RESOLVER_ENABLED
est géré par chaque test (par défaut absent → 503)."""
monkeypatch.setenv("RPA_API_TOKEN", _TEST_API_TOKEN)
from fastapi.testclient import TestClient
from agent_v0.server_v1 import api_stream
monkeypatch.setattr(api_stream, "API_TOKEN", _TEST_API_TOKEN)
return TestClient(api_stream.app, raise_server_exceptions=False)
def _auth_headers():
return {"Authorization": f"Bearer {_TEST_API_TOKEN}"}
class TestDialogResolveEndpointFlag:
"""Flag OFF par défaut — protection anti-régression."""
def test_disabled_by_default_returns_503(self, client, monkeypatch):
# flag explicitement absent → 503 attendu.
monkeypatch.delenv("RPA_DIALOG_RESOLVER_ENABLED", raising=False)
resp = client.post(
"/api/v1/dialog/resolve",
json={"current_title": "Confirmer l'enregistrement", "evidence_texts": []},
headers=_auth_headers(),
)
assert resp.status_code == 503
assert "RPA_DIALOG_RESOLVER_ENABLED" in resp.text
class TestDialogResolveEndpointEnabled:
"""Flag ON : l'endpoint retourne une résolution conforme."""
@pytest.fixture(autouse=True)
def _enable_flag(self, monkeypatch):
monkeypatch.setenv("RPA_DIALOG_RESOLVER_ENABLED", "true")
def test_match_confirm_save_overwrite(self, client):
resp = client.post(
"/api/v1/dialog/resolve",
json={
"current_title": "Confirmer l'enregistrement",
"evidence_texts": [],
"machine_id": "pc-alpha",
},
headers=_auth_headers(),
)
assert resp.status_code == 200
body = resp.json()
assert body["matched"] is True
assert body["dialog_id"] == "confirm-save-overwrite"
assert body["policy"] == "auto"
assert body["action"] is not None
assert body["action"]["button_label"] == "Oui"
def test_match_notepad_with_evidence(self, client):
resp = client.post(
"/api/v1/dialog/resolve",
json={
"current_title": "Bloc-notes",
"evidence_texts": ["Ne pas enregistrer"],
"machine_id": "pc-alpha",
},
headers=_auth_headers(),
)
assert resp.status_code == 200
body = resp.json()
assert body["matched"] is True
assert body["dialog_id"] == "notepad-unsaved-changes"
assert body["policy"] == "auto"
def test_no_match_returns_pause(self, client):
resp = client.post(
"/api/v1/dialog/resolve",
json={
"current_title": "Fenêtre inconnue XYZ",
"evidence_texts": [],
"machine_id": "pc-alpha",
},
headers=_auth_headers(),
)
assert resp.status_code == 200
body = resp.json()
assert body["matched"] is False
assert body["dialog_id"] == ""
assert body["policy"] == "pause"
assert body["action"] is None
def test_match_windows_uac_returns_pause(self, client):
resp = client.post(
"/api/v1/dialog/resolve",
json={
"current_title": "Contrôle de compte d'utilisateur",
"evidence_texts": ["Voulez-vous autoriser cette application"],
"machine_id": "pc-alpha",
},
headers=_auth_headers(),
)
assert resp.status_code == 200
body = resp.json()
assert body["matched"] is True
assert body["dialog_id"] == "windows-uac"
assert body["policy"] == "pause"
assert body["action"] is None
def test_requires_auth(self, client):
resp = client.post(
"/api/v1/dialog/resolve",
json={"current_title": "Confirmer l'enregistrement", "evidence_texts": []},
# pas de header → 401.
)
assert resp.status_code == 401