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.
108 lines
3.9 KiB
Python
108 lines
3.9 KiB
Python
"""Standalone Telegram MEDIA:<path> caption delivery.
|
|
|
|
When `hermes send --to telegram "MEDIA:/x.png This Caption"` carries a single
|
|
captionable file plus short text, the text must ride on the media bubble as the
|
|
sendPhoto/sendVideo/sendDocument ``caption`` rather than being posted as a
|
|
separate sendMessage beforehand. Longer text (> Telegram's 1024 caption cap)
|
|
falls back to a separate message. The ``telegram`` package is stubbed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
|
|
|
|
def _install_telegram_mock(monkeypatch: pytest.MonkeyPatch, bot_factory: MagicMock) -> None:
|
|
parse_mode = SimpleNamespace(MARKDOWN_V2="MarkdownV2", HTML="HTML")
|
|
constants_mod = SimpleNamespace(ParseMode=parse_mode)
|
|
_MessageEntity = lambda **_kw: SimpleNamespace(**_kw)
|
|
telegram_mod = SimpleNamespace(
|
|
Bot=bot_factory,
|
|
MessageEntity=_MessageEntity,
|
|
constants=constants_mod,
|
|
)
|
|
monkeypatch.setitem(sys.modules, "telegram", telegram_mod)
|
|
monkeypatch.setitem(sys.modules, "telegram.constants", constants_mod)
|
|
|
|
|
|
def _make_bot() -> MagicMock:
|
|
bot = MagicMock()
|
|
bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=1))
|
|
bot.send_photo = AsyncMock(return_value=SimpleNamespace(message_id=2))
|
|
bot.send_video = AsyncMock(return_value=SimpleNamespace(message_id=3))
|
|
bot.send_document = AsyncMock(return_value=SimpleNamespace(message_id=4))
|
|
return bot
|
|
|
|
|
|
def _no_proxy(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
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, raising=False)
|
|
# Neutralize macOS system-proxy auto-detection at its probe rather than by
|
|
# claiming the host is Linux: this keeps the test honest on the macOS
|
|
# runner (and on a developer's Mac), where a real scutil-configured proxy
|
|
# would otherwise leak into the assertion.
|
|
monkeypatch.setattr(
|
|
"gateway.platforms.base._detect_macos_system_proxy", lambda: None
|
|
)
|
|
|
|
|
|
def _tmpfile(suffix: str) -> str:
|
|
f = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
|
|
f.write(b"x")
|
|
f.close()
|
|
return f.name
|
|
|
|
|
|
def test_image_caption_rides_bubble_no_separate_text(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from tools.send_message_tool import _send_telegram
|
|
|
|
_no_proxy(monkeypatch)
|
|
bot = _make_bot()
|
|
_install_telegram_mock(monkeypatch, MagicMock(return_value=bot))
|
|
img = _tmpfile(".png")
|
|
try:
|
|
res = asyncio.run(
|
|
_send_telegram("tok", "123", "This Caption", media_files=[(img, False)])
|
|
)
|
|
assert res["success"] is True
|
|
# No separate text message; caption rides the photo.
|
|
bot.send_message.assert_not_awaited()
|
|
bot.send_photo.assert_awaited_once()
|
|
assert bot.send_photo.await_args.kwargs.get("caption") == "This Caption"
|
|
finally:
|
|
os.unlink(img)
|
|
|
|
|
|
def test_multi_file_keeps_separate_text(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
from tools.send_message_tool import _send_telegram
|
|
|
|
_no_proxy(monkeypatch)
|
|
bot = _make_bot()
|
|
_install_telegram_mock(monkeypatch, MagicMock(return_value=bot))
|
|
img = _tmpfile(".png")
|
|
img2 = _tmpfile(".jpg")
|
|
try:
|
|
res = asyncio.run(
|
|
_send_telegram("tok", "123", "two pics", media_files=[(img, False), (img2, False)])
|
|
)
|
|
assert res["success"] is True
|
|
# Ambiguous caption→file association: text stays a separate message.
|
|
bot.send_message.assert_awaited()
|
|
assert bot.send_photo.await_count == 2
|
|
for call in bot.send_photo.await_args_list:
|
|
assert not call.kwargs.get("caption")
|
|
finally:
|
|
os.unlink(img)
|
|
os.unlink(img2)
|