Files
hermes-agent/tests/gateway/test_telegram_model_picker.py
kshitijk4poor da4ab40ea9 refactor(telegram): gate chat-id pickers once in the callback dispatcher
The model-picker fix added a second copy of the choice picker's auth
check, and each copy rebuilt the callback ctx that _handle_callback_query
had already computed as `cb`. The chat-id picker loop is the only route
into both handlers. Gate there once with the existing cb, so a picker
added to that loop later is covered without anyone having to copy the
check again. Fail-closed behaviour is unchanged: _callback_authorized
returns True only for an authorized tapper and answers the denial text
otherwise. The back-button test calls the handler directly and no longer
needs its auth stub.

Co-authored-by: Dusk1e <yusufalweshdemir@gmail.com>
2026-09-27 01:34:15 +05:30

132 lines
5.0 KiB
Python

"""Tests for Telegram model picker thread fallback."""
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
import pytest
from gateway.config import PlatformConfig
from plugins.platforms.telegram.adapter import TelegramAdapter
def _make_adapter():
adapter = TelegramAdapter(PlatformConfig(enabled=True, token="test-token"))
adapter._bot = AsyncMock()
adapter._app = MagicMock()
return adapter
class TestTelegramModelPicker:
@pytest.mark.asyncio
async def test_send_model_picker_escapes_dynamic_provider_label(self):
adapter = _make_adapter()
sent = {}
async def mock_send_message(**kwargs):
sent.update(kwargs)
return SimpleNamespace(message_id=101)
adapter._bot.send_message = AsyncMock(side_effect=mock_send_message)
result = await adapter.send_model_picker(
chat_id="12345",
providers=[
{"slug": "provider_one", "name": "Provider One", "total_models": 1, "is_current": True}
],
current_model="model_1",
current_provider="provider_one",
session_key="s",
on_model_selected=AsyncMock(),
metadata={"thread_id": "99999"},
)
assert result.success is True
assert "MARKDOWN_V2" in repr(sent["parse_mode"])
assert "provider\\_one" in sent["text"]
assert "`model_1`" in sent["text"]
@pytest.mark.asyncio
async def test_back_button_escapes_dynamic_provider_label(self):
adapter = _make_adapter()
adapter._model_picker_state["12345"] = {
"providers": [{"slug": "provider_one", "name": "Provider One", "total_models": 1, "is_current": True}],
"current_model": "model_1",
"current_provider": "provider_one",
"session_key": "s",
"on_model_selected": AsyncMock(),
"msg_id": 42,
}
query = AsyncMock()
query.data = "mb"
query.message = MagicMock()
query.message.chat_id = 12345
query.from_user = MagicMock()
query.answer = AsyncMock()
query.edit_message_text = AsyncMock()
await adapter._handle_model_picker_callback(query, "mb", "12345")
edit_kwargs = query.edit_message_text.call_args[1]
assert "MARKDOWN_V2" in repr(edit_kwargs["parse_mode"])
assert "provider\\_one" in edit_kwargs["text"]
assert "`model_1`" in edit_kwargs["text"]
def _gateway_wired_adapter():
"""Real adapter with the real gateway auth callback, as ``GatewayRunner`` wires it."""
from gateway.config import GatewayConfig, Platform
from gateway.pairing import PairingStore
from gateway.run import GatewayRunner
runner = object.__new__(GatewayRunner)
runner.config = GatewayConfig()
runner.config.platforms = {Platform.TELEGRAM: PlatformConfig(enabled=True, extra={})}
runner.pairing_store = PairingStore(profile="default")
runner.pairing_stores = {"default": runner.pairing_store}
runner._primary_profile_name = "default"
adapter = _make_adapter()
runner.adapters = {Platform.TELEGRAM: adapter}
adapter.set_authorization_check(runner._make_adapter_auth_check(Platform.TELEGRAM))
return adapter
@pytest.mark.asyncio
@pytest.mark.parametrize("tapper_id, switches", [("222", False), ("111", True)])
async def test_group_model_picker_switch_follows_callback_allowlist(monkeypatch, tapper_id, switches):
"""Only an allowlisted tapper can switch the model from a group /model picker."""
for key in ("GATEWAY_ALLOW_ALL_USERS", "GATEWAY_ALLOWED_USERS", "TELEGRAM_ALLOW_ALL_USERS",
"TELEGRAM_GROUP_ALLOWED_USERS", "TELEGRAM_GROUP_ALLOWED_CHATS"):
monkeypatch.delenv(key, raising=False)
monkeypatch.setenv("TELEGRAM_ALLOWED_USERS", "111")
import hermes_cli.model_selection_guards as guards
monkeypatch.setattr(guards, "combined_selection_warning", lambda *a, **k: None)
adapter = _gateway_wired_adapter()
on_model_selected = AsyncMock(return_value="Switched to `other-model`")
adapter._model_picker_state["-100777"] = {
"providers": [{"slug": "openai", "name": "OpenAI", "total_models": 1}],
"current_model": "owner-model",
"current_provider": "openai",
"session_key": "s",
"on_model_selected": on_model_selected,
"selected_provider": "openai",
"model_list": ["other-model"],
"msg_id": 42,
}
query = SimpleNamespace(
data="mm:0",
message=SimpleNamespace(
chat_id=-100777, chat=SimpleNamespace(type="supergroup"), message_thread_id=None
),
from_user=SimpleNamespace(id=int(tapper_id), first_name="Tapper"),
answer=AsyncMock(),
edit_message_text=AsyncMock(),
)
await adapter._handle_callback_query(SimpleNamespace(callback_query=query), MagicMock())
assert on_model_selected.await_count == (1 if switches else 0)
assert ("-100777" in adapter._model_picker_state) is not switches