test: isole le garde SSRF dans les tests fetch_url - plus de dependance au DNS reel BUG-092
CI / lint (push) Successful in 2m28s
CI / security (push) Failing after 2m16s
CI / test (push) Successful in 4m16s
CI / build (push) Successful in 1m40s
CI / e2e (push) Successful in 14m53s

This commit is contained in:
2026-09-29 10:05:24 -04:00
parent dbf935bec0
commit 435a0687d7
12 changed files with 60 additions and 18 deletions
+15 -2
View File
@@ -10,6 +10,19 @@ from backend.tools.context import ToolContext, ToolError, ToolMode, ToolRisk
from backend.tools.registry import get_tool
@pytest.fixture
def no_dns(monkeypatch):
"""Neutralise la résolution DNS réelle du garde SSRF (runner au réseau fragile).
Seuls les tests qui vérifient l'extraction HTML mockent ``httpx.get`` ; sans
ce mock, ``_assert_public_http_url`` résolvait ``example.com`` pour de vrai et
le test échouait en ``dns_error`` sur un runner dont le DNS est instable.
Les tests de garde SSRF (``test_private_address_rejected``) n'utilisent PAS
la fixture : ils doivent au contraire traverser le vrai garde.
"""
monkeypatch.setattr(web, "_assert_public_http_url", lambda url: url)
class FakeResponse:
def __init__(self, payload: Any = None, json_data: Any = None, status_code: int = 200,
content: bytes = b"", headers: dict | None = None, url: str = "https://example.com/x"):
@@ -171,7 +184,7 @@ class TestWebSearch:
class TestFetchUrl:
def test_html_converted_to_text(self, monkeypatch):
def test_html_converted_to_text(self, monkeypatch, no_dns):
html = (b"<html><head><title>T&</title><style>b{}</style>"
b"<script>evil()</script></head><body><p>hello</p><ul>"
b"<li>one</li><li>two</li></ul></body></html>")
@@ -196,7 +209,7 @@ class TestFetchUrl:
web.fetch_url(_ctx(), web.FetchUrlInput(url="file:///etc/passwd"))
assert ei.value.code == "invalid_scheme"
def test_binary_content_rejected(self, monkeypatch):
def test_binary_content_rejected(self, monkeypatch, no_dns):
monkeypatch.setattr(web.httpx, "get",
lambda *a, **k: FakeResponse(content=b"%PDF-1.4...",
headers={"content-type": "application/pdf"}))
+16 -3
View File
@@ -20,7 +20,7 @@ class TestRegistration:
class TestRenderUnavailable:
def test_missing_playwright_clear_error(self, monkeypatch):
def test_missing_playwright_clear_error(self, monkeypatch, no_dns):
monkeypatch.setattr(webrender, "_playwright_available", lambda: False)
with pytest.raises(ToolError) as ei:
web.fetch_url(_ctx(), web.FetchUrlInput(
@@ -35,8 +35,21 @@ class TestRenderUnavailable:
assert ei.value.code in ("ssrf_blocked", "dns_error")
@pytest.fixture
def no_dns(monkeypatch):
"""Neutralise la résolution DNS réelle du garde SSRF.
``fetch_url`` appelle ``_assert_public_http_url`` (getaddrinfo) *avant* le
rendu : sur un runner au DNS instable le test échouait en ``dns_error``
au lieu d'atteindre le worker Playwright mocké. ``webrender`` importe la
fonction dans son propre namespace : les deux références sont mockées.
"""
monkeypatch.setattr(web, "_assert_public_http_url", lambda url: url)
monkeypatch.setattr(webrender, "_assert_public_http_url", lambda url: url)
class TestRenderSuccess:
def test_fetch_url_delegates_to_worker(self, monkeypatch):
def test_fetch_url_delegates_to_worker(self, monkeypatch, no_dns):
captured = {}
def fake_render(url):
@@ -51,7 +64,7 @@ class TestRenderSuccess:
assert out["rendered"] is True
assert "dynamic content" in out["text"]
def test_worker_failure_maps_to_tool_error(self, monkeypatch):
def test_worker_failure_maps_to_tool_error(self, monkeypatch, no_dns):
monkeypatch.setattr(webrender, "_playwright_available", lambda: True)
def boom(url):