Files
hermes-agent/tests/tools/test_telegram_send_message_caption.py
ethernet 30da5d0a89 test: run os-specific tests on their real host, not a faked one
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.
2026-08-09 22:09:49 -04:00

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)