- 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>
142 lines
4.5 KiB
Python
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
|