emma-hermes/tests/gateway/test_line_telegram_verifica...

274 lines
8.9 KiB
Python

from __future__ import annotations
import asyncio
import sys
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
from gateway.config import GatewayConfig, Platform, PlatformConfig
from gateway.pairing import PairingStore
from gateway.session import SessionSource
from gateway.user_verification import GatewayUserStore
from tests.gateway._plugin_adapter_loader import load_plugin_adapter
def _ensure_telegram_mock() -> None:
if "telegram" in sys.modules and hasattr(sys.modules["telegram"], "__file__"):
return
telegram_mod = MagicMock()
telegram_mod.ext.ContextTypes.DEFAULT_TYPE = type(None)
telegram_mod.constants.ParseMode.MARKDOWN_V2 = "MarkdownV2"
telegram_mod.constants.ChatType.GROUP = "group"
telegram_mod.constants.ChatType.SUPERGROUP = "supergroup"
telegram_mod.constants.ChatType.CHANNEL = "channel"
telegram_mod.constants.ChatType.PRIVATE = "private"
for name in ("telegram", "telegram.ext", "telegram.constants", "telegram.request"):
sys.modules.setdefault(name, telegram_mod)
_ensure_telegram_mock()
from gateway.run import GatewayRunner # noqa: E402
from plugins.platforms.telegram.adapter import TelegramAdapter # noqa: E402
_line = load_plugin_adapter("line")
LineAdapter = _line.LineAdapter
def _make_telegram_adapter() -> TelegramAdapter:
adapter = object.__new__(TelegramAdapter)
adapter.platform = Platform.TELEGRAM
adapter.config = PlatformConfig(enabled=True, token="fake-token", extra={})
adapter._bot = SimpleNamespace(id=999, username="test_bot")
adapter._message_handler = AsyncMock()
adapter._pending_text_batches = {}
adapter._pending_text_batch_tasks = {}
adapter._text_batch_delay_seconds = 0.01
adapter._text_batch_split_delay_seconds = 0.01
adapter._mention_patterns = adapter._compile_mention_patterns()
adapter._forum_lock = asyncio.Lock()
adapter._forum_command_registered = set()
adapter._active_sessions = {}
adapter._pending_messages = {}
return adapter
def _make_telegram_message(text: str, *, from_user_id: int, chat_id: int, chat_type: str = "private"):
return SimpleNamespace(
message_id=42,
text=text,
caption=None,
entities=[],
caption_entities=[],
message_thread_id=None,
is_topic_message=False,
chat=SimpleNamespace(id=chat_id, type=chat_type, title="Smoke", is_forum=False),
from_user=SimpleNamespace(id=from_user_id, full_name="Smoke User", first_name="Smoke"),
reply_to_message=None,
date=None,
location=None,
photo=None,
video=None,
audio=None,
voice=None,
document=None,
sticker=None,
media_group_id=None,
)
def _make_runner(platform: Platform) -> GatewayRunner:
runner = object.__new__(GatewayRunner)
runner.config = GatewayConfig(platforms={platform: PlatformConfig(enabled=True)})
runner.adapters = {platform: SimpleNamespace()}
runner.pairing_store = PairingStore()
return runner
async def _run_telegram_text(adapter: TelegramAdapter, update) -> None:
await adapter._handle_text_message(update, SimpleNamespace())
pending = list(adapter._pending_text_batch_tasks.values())
if pending:
await asyncio.gather(*pending)
def test_line_dm_verification_smoke(monkeypatch):
for key in (
"LINE_ALLOWED_USERS",
"LINE_ALLOWED_GROUPS",
"LINE_ALLOWED_ROOMS",
"LINE_ALLOW_ALL_USERS",
"GATEWAY_ALLOWED_USERS",
"GATEWAY_ALLOW_ALL_USERS",
):
monkeypatch.delenv(key, raising=False)
store = GatewayUserStore()
adapter = LineAdapter(
PlatformConfig(
enabled=True,
extra={"channel_access_token": "tok", "channel_secret": "sec"},
)
)
adapter._client = MagicMock()
adapter._client.reply = AsyncMock()
adapter._client.push = AsyncMock(
side_effect=RuntimeError('LINE push 429: {"message":"You have reached your monthly limit."}')
)
adapter._client.loading = AsyncMock()
sent = {}
async def _fake_email_send(_cfg, chat_id, message, **kwargs):
sent["chat_id"] = chat_id
sent["message"] = message
sent["kwargs"] = kwargs
return {"success": True}
monkeypatch.setattr(_line, "_email_standalone_send", _fake_email_send)
decisions = []
async def _handle_message(event):
decisions.append(
store.process_inbound_message(
event.source,
"line-smoke@bremen.com.tw",
public_base_url="https://agent.example.com",
)
)
adapter.handle_message = _handle_message
event = {
"type": "message",
"replyToken": "rt-1",
"source": {"type": "user", "userId": "UlineSmoke"},
"message": {"id": "m1", "type": "text", "text": "line-smoke@bremen.com.tw"},
}
asyncio.run(adapter._dispatch_event(event))
assert len(decisions) == 1
assert decisions[0].action == "send_verification_email"
assert decisions[0].token
source = SessionSource(
platform=Platform("line"),
user_id="UlineSmoke",
chat_id="UlineSmoke",
user_name="Smoke User",
chat_type="dm",
)
assert store.is_verified_source(source) is False
verify = store.verify_token(decisions[0].token)
assert verify.success is True
assert store.is_verified_source(source) is True
assert store.get_verified_email_for_source(source) == "line-smoke@bremen.com.tw"
payload = asyncio.run(adapter._send_email_quota_fallback("UlineSmoke", "hello", mode="final"))
assert payload is not None
assert payload["email"] == "line-smoke@bremen.com.tw"
assert sent["chat_id"] == "line-smoke@bremen.com.tw"
store.unbind_identity("line", "UlineSmoke")
assert store.is_verified_source(source) is False
assert store.get_verified_email_for_source(source) is None
payload_after = asyncio.run(adapter._send_email_quota_fallback("UlineSmoke", "hello", mode="final"))
assert payload_after is None
with store._connect() as conn:
conn.execute(
"UPDATE verification_requests SET last_sent_at = datetime('now', '-11 minutes') WHERE platform = ? AND external_user_id = ?",
("line", "UlineSmoke"),
)
conn.commit()
decisions.clear()
asyncio.run(adapter._dispatch_event(event))
assert len(decisions) == 1
assert decisions[0].action == "send_verification_email"
def test_telegram_dm_verification_smoke(monkeypatch):
for key in (
"TELEGRAM_ALLOWED_USERS",
"TELEGRAM_GROUP_ALLOWED_USERS",
"TELEGRAM_GROUP_ALLOWED_CHATS",
"TELEGRAM_ALLOW_ALL_USERS",
"GATEWAY_ALLOWED_USERS",
"GATEWAY_ALLOW_ALL_USERS",
):
monkeypatch.delenv(key, raising=False)
store = GatewayUserStore()
adapter = _make_telegram_adapter()
decisions = []
async def _handle_message(event):
decisions.append(
store.process_inbound_message(
event.source,
"tg-smoke@journeys.com.tw",
public_base_url="https://agent.example.com",
)
)
adapter.handle_message = _handle_message
update = SimpleNamespace(
update_id=1,
message=_make_telegram_message(
"tg-smoke@journeys.com.tw",
from_user_id=601504103,
chat_id=601504103,
chat_type="private",
),
effective_message=None,
)
asyncio.run(_run_telegram_text(adapter, update))
assert len(decisions) == 1
assert decisions[0].action == "send_verification_email"
assert decisions[0].token
source = SessionSource(
platform=Platform.TELEGRAM,
user_id="601504103",
chat_id="601504103",
user_name="Smoke User",
chat_type="dm",
)
assert store.is_verified_source(source) is False
verify = store.verify_token(decisions[0].token)
assert verify.success is True
assert store.is_verified_source(source) is True
assert store.get_verified_email_for_source(source) == "tg-smoke@journeys.com.tw"
runner = _make_runner(Platform.TELEGRAM)
assert runner._is_user_authorized(source) is False
PairingStore()._approve_user("telegram", "601504103", "Smoke User")
assert runner._is_user_authorized(source) is True
store.unbind_identity("telegram", "601504103")
assert store.is_verified_source(source) is False
assert store.get_verified_email_for_source(source) is None
assert runner._is_user_authorized(source) is False
with store._connect() as conn:
conn.execute(
"UPDATE verification_requests SET last_sent_at = datetime('now', '-11 minutes') WHERE platform = ? AND external_user_id = ?",
("telegram", "601504103"),
)
conn.commit()
decisions.clear()
asyncio.run(_run_telegram_text(adapter, update))
assert len(decisions) == 1
assert decisions[0].action == "send_verification_email"