many tests patched sys.platform or a module's _IS_WINDOWS flag, then ran on linux ci. the patch selects the branch under test, but the host does not have the behavior the branch exists for. the test proves the patch, not the platform. some gated assertions never ran on any host. this commit adds three markers: linux_only, macos_only, windows_only. a conftest hook skips a marked test on the other hosts, with a clear reason. no test fakes a host now. two documented fakes remain (android/termux, freebsd) because no ci runner exists for them. each fake site got one of four treatments: - gate it: the real host supplies the platform; mocks cover real dependencies only, never host identity - patch the module's own probe when the subject is the probe's consumer - assert against the real host when the fake stood in for any non-x host - delete the patch when it set the value the host already has bare skipif(sys.platform != ...) guards became markers too. the lane model skips these on linux and never imports them on windows, so they ran on no host. platform parametrize tables are now one marked test per os. running on real hosts found real errors: a chrome-sandbox failure in test_gui_command that main hides, and two windows failures fixed here. the agents.md testing section now documents the policy.
163 lines
6.6 KiB
Python
163 lines
6.6 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.
|
|
# Stub the probe itself rather than claiming the host is Linux — that
|
|
# keeps this assertion true on the macOS runner too, where a real
|
|
# scutil proxy would otherwise be picked up.
|
|
monkeypatch.setattr(
|
|
"gateway.platforms.base._detect_macos_system_proxy", 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
|
|
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()
|