mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
158 lines
6.3 KiB
Python
158 lines
6.3 KiB
Python
"""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()
|