"""Regression tests for the standalone Telegram send path's proxy support. The ``send_message`` tool, when invoked from a process *other than* the gateway (agent / TUI / cron), runs ``_send_telegram`` directly instead of delegating to the in-process gateway adapter. Before the fix that accompanies these tests, that standalone path constructed ``telegram.Bot(token=...)`` with no proxy, so in regions where api.telegram.org is blocked (e.g. RU) the send would just time out with ``Telegram send failed: Timed out`` and never show up in ``gateway.log``. These tests verify that the standalone path now honours ``TELEGRAM_PROXY`` the same way the gateway adapter (and the Discord standalone path) do. """ from __future__ import annotations import asyncio import sys from types import SimpleNamespace from typing import Any from unittest.mock import AsyncMock, MagicMock import pytest def _install_telegram_mock_with_request( monkeypatch: pytest.MonkeyPatch, bot_factory: MagicMock, httpx_request_factory: MagicMock, ) -> None: """Install a stub ``telegram`` package whose ``Bot`` and ``telegram.request.HTTPXRequest`` are the supplied mocks. Mirrors ``_install_telegram_mock`` in test_send_message_tool.py but also provides the ``telegram.request`` submodule that the proxy branch needs. """ parse_mode = SimpleNamespace(MARKDOWN_V2="MarkdownV2", HTML="HTML") constants_mod = SimpleNamespace(ParseMode=parse_mode) request_mod = SimpleNamespace(HTTPXRequest=httpx_request_factory) # MessageEntity needed by #27865 mention-detection path. _MessageEntity = lambda **_kw: SimpleNamespace(**_kw) telegram_mod = SimpleNamespace( Bot=bot_factory, MessageEntity=_MessageEntity, constants=constants_mod, request=request_mod, ) monkeypatch.setitem(sys.modules, "telegram", telegram_mod) monkeypatch.setitem(sys.modules, "telegram.constants", constants_mod) monkeypatch.setitem(sys.modules, "telegram.request", request_mod) def _make_bot() -> MagicMock: bot = MagicMock() bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=42)) return bot class TestSendTelegramStandaloneProxy: """The standalone ``_send_telegram`` path must route through ``TELEGRAM_PROXY`` when one is configured, even when no in-process gateway runner is available. """ def test_proxy_env_passed_to_httpx_request( self, monkeypatch: pytest.MonkeyPatch ) -> None: """With TELEGRAM_PROXY set, Bot() is constructed with HTTPXRequest instances whose ``proxy=`` kwarg is the configured URL — applied to both ``request`` and ``get_updates_request``. """ from tools.send_message_tool import _send_telegram proxy_url = "socks5://127.0.0.1:1080" monkeypatch.setenv("TELEGRAM_PROXY", proxy_url) # Clear NO_PROXY so resolve_proxy_url() doesn't short-circuit on # leftover env from the host running the tests. monkeypatch.delenv("NO_PROXY", raising=False) monkeypatch.delenv("no_proxy", raising=False) # Ensure the test does not depend on the in-process gateway runner. monkeypatch.setattr("gateway.run._gateway_runner_ref", lambda: None) bot = _make_bot() bot_factory = MagicMock(return_value=bot) httpx_request_factory = MagicMock(side_effect=lambda **kw: MagicMock(_kw=kw)) _install_telegram_mock_with_request(monkeypatch, bot_factory, httpx_request_factory) result: dict[str, Any] = asyncio.run( _send_telegram("tok", "123", "hello world") ) assert result["success"] is True bot_factory.assert_called_once() call_kwargs = bot_factory.call_args.kwargs assert call_kwargs.get("token") == "tok" assert "request" in call_kwargs, "request= kwarg missing — proxy not wired" assert "get_updates_request" in call_kwargs, ( "get_updates_request= kwarg missing — proxy not wired" ) # HTTPXRequest must have been invoked twice, both times with the # resolved proxy URL. assert httpx_request_factory.call_count == 2 for call in httpx_request_factory.call_args_list: assert call.kwargs.get("proxy") == proxy_url, ( f"HTTPXRequest called without proxy={proxy_url!r}: {call.kwargs!r}" ) # And the bot was actually used to send. bot.send_message.assert_awaited_once() def test_no_proxy_env_uses_plain_bot( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Without TELEGRAM_PROXY (and no inherited HTTPS_PROXY/etc), Bot() is constructed plainly — no ``request``/``get_updates_request`` kwargs, and HTTPXRequest is not invoked at all. """ from tools.send_message_tool import _send_telegram # Wipe every env var resolve_proxy_url() inspects so the host's # ambient proxy settings can't flip this test green-or-red. for var in ( "TELEGRAM_PROXY", "HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy", ): monkeypatch.delenv(var, raising=False) monkeypatch.setattr("gateway.run._gateway_runner_ref", lambda: None) # Make sure macOS system-proxy auto-detection (scutil) can't kick in. monkeypatch.setattr(sys, "platform", "linux") bot = _make_bot() bot_factory = MagicMock(return_value=bot) httpx_request_factory = MagicMock(side_effect=lambda **kw: MagicMock(_kw=kw)) _install_telegram_mock_with_request(monkeypatch, bot_factory, httpx_request_factory) result: dict[str, Any] = asyncio.run( _send_telegram("tok", "123", "hello world") ) assert result["success"] is True bot_factory.assert_called_once() call_kwargs = bot_factory.call_args.kwargs call_args = bot_factory.call_args.args # token may be passed positionally or as a kwarg; either is fine. assert call_kwargs.get("token", call_args[0] if call_args else None) == "tok" assert "request" not in call_kwargs assert "get_updates_request" not in call_kwargs httpx_request_factory.assert_not_called() bot.send_message.assert_awaited_once()