"""Unit tests for the dynamic rendering path (#92): fetch_url(render=True).""" import pytest import backend.tools.web as web from backend.tools import webrender from backend.tools.context import ToolContext, ToolError, ToolMode from backend.tools.registry import get_tool def _ctx() -> ToolContext: return ToolContext(user={"username": "tester", "vaults": []}, mode=ToolMode.IN_APP) class TestRegistration: def test_render_param_exposed_in_schema(self): spec = get_tool("fetch_url") assert spec is not None assert "render" in spec.input_model.model_fields class TestRenderUnavailable: 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( url="https://example.com/spa", render=True)) assert ei.value.code == "playwright_unavailable" def test_ssrf_guard_applied_before_render(self, monkeypatch): monkeypatch.setattr(webrender, "_playwright_available", lambda: True) with pytest.raises(ToolError) as ei: web.fetch_url(_ctx(), web.FetchUrlInput( url="http://127.0.0.1:9222/devtools", render=True)) 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, no_dns): captured = {} def fake_render(url): captured["url"] = url return {"url": url, "status": 200, "title": "SPA", "text": "dynamic content", "rendered": True, "truncated": False} monkeypatch.setattr(webrender, "render_page", fake_render) out = web.fetch_url(_ctx(), web.FetchUrlInput( url="https://example.com/spa", render=True)) assert captured["url"] == "https://example.com/spa" assert out["rendered"] is True assert "dynamic content" in out["text"] def test_worker_failure_maps_to_tool_error(self, monkeypatch, no_dns): monkeypatch.setattr(webrender, "_playwright_available", lambda: True) def boom(url): raise RuntimeError("chromium crashed") # The executor re-raises the worker exception on .result(); render_page # must wrap it into a ToolError instead of leaking a bare exception. monkeypatch.setattr(webrender, "_render_in_worker", boom) with pytest.raises(ToolError) as ei: web.fetch_url(_ctx(), web.FetchUrlInput( url="https://example.com/spa", render=True)) assert ei.value.code == "render_unavailable" class TestMarkdownExtraction: def test_html_to_text_reused(self): text = webrender._html_to_text("
hello
") assert "hello" in text assert "x()" not in text