Files
hermes-agent/tests/gateway/test_telegram_forum_commands.py
T
红尘 d73ff9b0fb
Deploy Site / deploy-vercel (push) Has been cancelled
Deploy Site / deploy-docs (push) Has been cancelled
Docker / shell lint / Lint Dockerfile (hadolint) (push) Has been cancelled
Docker / shell lint / Lint docker/ shell scripts (shellcheck) (push) Has been cancelled
Docker Build and Publish / build-amd64 (push) Has been cancelled
Docker Build and Publish / build-arm64 (push) Has been cancelled
Lint (ruff + ty) / ruff + ty diff (push) Has been cancelled
Lint (ruff + ty) / ruff enforcement (blocking) (push) Has been cancelled
Lint (ruff + ty) / Windows footguns (blocking) (push) Has been cancelled
Nix Lockfile Fix / auto-fix-main (push) Has been cancelled
Nix Lockfile Fix / fix (push) Has been cancelled
Nix / nix (macos-latest) (push) Has been cancelled
Nix / nix (ubuntu-latest) (push) Has been cancelled
OSV-Scanner / Scan lockfiles (push) Has been cancelled
Build Skills Index / build-index (push) Has been cancelled
Tests / test (1) (push) Has been cancelled
Tests / test (2) (push) Has been cancelled
Tests / test (3) (push) Has been cancelled
Tests / test (4) (push) Has been cancelled
Tests / test (5) (push) Has been cancelled
Tests / test (6) (push) Has been cancelled
Tests / e2e (push) Has been cancelled
uv.lock check / uv lock --check (push) Has been cancelled
Docker Build and Publish / merge (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
Tests / save-durations (push) Has been cancelled
Initial import of NousResearch/hermes-agent
2026-05-31 09:36:58 +08:00

119 lines
4.4 KiB
Python

"""Tests for lazy forum command registration in TelegramAdapter."""
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from gateway.config import Platform, PlatformConfig
def _make_test_adapter():
"""Build a TelegramAdapter without running __init__."""
from gateway.platforms.telegram import TelegramAdapter
adapter = object.__new__(TelegramAdapter)
adapter.platform = Platform.TELEGRAM
adapter.config = PlatformConfig(enabled=True, token="***", extra={})
# ``name`` is a property derived from platform.value.title()
adapter._bot = MagicMock()
adapter._bot.set_my_commands = AsyncMock()
adapter._forum_command_registered = set()
adapter._forum_lock = asyncio.Lock()
return adapter
def _forum_message(chat_id=-100, is_forum=True):
return SimpleNamespace(
chat=SimpleNamespace(id=chat_id, is_forum=is_forum),
)
@pytest.mark.asyncio
async def test_ensure_forum_commands_skips_non_forum():
adapter = _make_test_adapter()
msg = _forum_message(is_forum=False)
await adapter._ensure_forum_commands(msg)
adapter._bot.set_my_commands.assert_not_called()
@pytest.mark.asyncio
async def test_ensure_forum_commands_skips_already_registered():
adapter = _make_test_adapter()
adapter._forum_command_registered.add(-100)
msg = _forum_message(is_forum=True)
await adapter._ensure_forum_commands(msg)
adapter._bot.set_my_commands.assert_not_called()
@pytest.mark.asyncio
async def test_ensure_forum_commands_registers_once():
adapter = _make_test_adapter()
msg = _forum_message(chat_id=-123, is_forum=True)
with patch("hermes_cli.commands.telegram_menu_commands") as mock_menu:
mock_menu.return_value = ([("new", "Start new session"), ("help", "Show help")], 0)
with patch("telegram.BotCommand") as MockBotCommand:
instances = []
def _make_cmd(name, desc):
cmd = MagicMock()
cmd.name = name
cmd.description = desc
instances.append(cmd)
return cmd
MockBotCommand.side_effect = _make_cmd
with patch("telegram.BotCommandScopeChat") as MockScope:
# Track the chat_id passed to the BotCommandScopeChat constructor
# so the assertions below see an int instead of a bare MagicMock.
def _make_scope(chat_id):
s = MagicMock()
s.chat_id = chat_id
return s
MockScope.side_effect = _make_scope
await adapter._ensure_forum_commands(msg)
assert -123 in adapter._forum_command_registered
adapter._bot.set_my_commands.assert_awaited_once()
args, kwargs = adapter._bot.set_my_commands.call_args
assert len(args[0]) == 2 # two BotCommand instances
assert kwargs["scope"] is not None
assert isinstance(kwargs["scope"].chat_id, int)
assert kwargs["scope"].chat_id == -123
@pytest.mark.asyncio
async def test_ensure_forum_commands_handles_set_failure():
adapter = _make_test_adapter()
msg = _forum_message(chat_id=-456, is_forum=True)
adapter._bot.set_my_commands.side_effect = Exception("Telegram API error")
with patch("hermes_cli.commands.telegram_menu_commands") as mock_menu:
mock_menu.return_value = ([("new", "Start new session")], 0)
# Should NOT raise despite the API error
await adapter._ensure_forum_commands(msg)
# On failure we don't retry for this chat, so it's added to the set
# to avoid hammering a broken chat.
assert -456 not in adapter._forum_command_registered
@pytest.mark.asyncio
async def test_ensure_forum_commands_race_safety():
"""Two concurrent coroutines must not double-register the same chat."""
adapter = _make_test_adapter()
msg = _forum_message(chat_id=-789, is_forum=True)
with patch("hermes_cli.commands.telegram_menu_commands") as mock_menu:
mock_menu.return_value = ([("new", "Start new session")], 0)
with patch("telegram.BotCommand"):
with patch("telegram.BotCommandScopeChat"):
coro1 = adapter._ensure_forum_commands(msg)
coro2 = adapter._ensure_forum_commands(msg)
await asyncio.gather(coro1, coro2)
# The lock should make this exactly 1 call, not 2.
assert adapter._bot.set_my_commands.await_count == 1