274 lines
8.9 KiB
Python
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"
|