From 70737cb9c984b792c643507aa5f0130163ee70f9 Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:48:55 +0400 Subject: [PATCH] test: speed up telegram tests (#148) --- src/takopi/settings.py | 1 + src/takopi/telegram/backend.py | 1 + src/takopi/telegram/bridge.py | 1 + src/takopi/telegram/loop.py | 5 ++- tests/test_telegram_bridge.py | 62 ++++++++++++++++++++++++++++++++++ tests/test_telegram_queue.py | 12 ++++--- 6 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/takopi/settings.py b/src/takopi/settings.py index 678b71e..247db49 100644 --- a/src/takopi/settings.py +++ b/src/takopi/settings.py @@ -101,6 +101,7 @@ class TelegramTransportSettings(BaseModel): session_mode: Literal["stateless", "chat"] = "stateless" show_resume_line: bool = True forward_coalesce_s: float = Field(default=1.0, ge=0) + media_group_debounce_s: float = Field(default=1.0, ge=0) topics: TelegramTopicsSettings = Field(default_factory=TelegramTopicsSettings) files: TelegramFilesSettings = Field(default_factory=TelegramFilesSettings) diff --git a/src/takopi/telegram/backend.py b/src/takopi/telegram/backend.py index 574ae0e..9783f36 100644 --- a/src/takopi/telegram/backend.py +++ b/src/takopi/telegram/backend.py @@ -139,6 +139,7 @@ class TelegramBackend(TransportBackend): voice_max_bytes=int(settings.voice_max_bytes), voice_transcription_model=settings.voice_transcription_model, forward_coalesce_s=settings.forward_coalesce_s, + media_group_debounce_s=settings.media_group_debounce_s, topics=settings.topics, files=settings.files, ) diff --git a/src/takopi/telegram/bridge.py b/src/takopi/telegram/bridge.py index 8af9576..3775b3f 100644 --- a/src/takopi/telegram/bridge.py +++ b/src/takopi/telegram/bridge.py @@ -125,6 +125,7 @@ class TelegramBridgeConfig: voice_max_bytes: int = 10 * 1024 * 1024 voice_transcription_model: str = "gpt-4o-mini-transcribe" forward_coalesce_s: float = 1.0 + media_group_debounce_s: float = 1.0 files: TelegramFilesSettings = field(default_factory=TelegramFilesSettings) chat_ids: tuple[int, ...] | None = None topics: TelegramTopicsSettings = field(default_factory=TelegramTopicsSettings) diff --git a/src/takopi/telegram/loop.py b/src/takopi/telegram/loop.py index b61b861..55fbe0c 100644 --- a/src/takopi/telegram/loop.py +++ b/src/takopi/telegram/loop.py @@ -73,8 +73,6 @@ logger = get_logger(__name__) __all__ = ["poll_updates", "run_main_loop", "send_with_resume"] -_MEDIA_GROUP_DEBOUNCE_S = 1.0 - ForwardKey = tuple[int, int, int] @@ -498,6 +496,7 @@ async def run_main_loop( topics_chat_ids: frozenset[int] = frozenset() bot_username: str | None = None forward_coalesce_s = max(0.0, float(cfg.forward_coalesce_s)) + media_group_debounce_s = max(0.0, float(cfg.media_group_debounce_s)) def refresh_topics_scope() -> None: nonlocal resolved_topics_scope, topics_chat_ids @@ -1226,7 +1225,7 @@ async def run_main_loop( if state is None: return token = state.token - await anyio.sleep(_MEDIA_GROUP_DEBOUNCE_S) + await anyio.sleep(media_group_debounce_s) state = media_groups.get(key) if state is None: return diff --git a/tests/test_telegram_bridge.py b/tests/test_telegram_bridge.py index 35de138..ea36068 100644 --- a/tests/test_telegram_bridge.py +++ b/tests/test_telegram_bridge.py @@ -61,6 +61,10 @@ from takopi.transport import MessageRef, RenderedMessage, SendOptions from tests.plugin_fixtures import FakeEntryPoint, install_entrypoints CODEX_ENGINE = "codex" +FAST_FORWARD_COALESCE_S = 0.0 +FAST_MEDIA_GROUP_DEBOUNCE_S = 0.0 +BATCH_MEDIA_GROUP_DEBOUNCE_S = 0.05 +DEBOUNCE_FORWARD_COALESCE_S = 0.05 def _empty_projects() -> ProjectsConfig: @@ -323,6 +327,8 @@ def _make_cfg( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) @@ -952,6 +958,8 @@ async def test_handle_file_put_writes_file(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, files=TelegramFilesSettings(enabled=True), ) msg = TelegramIncomingMessage( @@ -1015,6 +1023,8 @@ async def test_handle_file_get_sends_document_for_allowed_user( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, files=TelegramFilesSettings( enabled=True, allowed_user_ids=[42], @@ -1315,6 +1325,8 @@ async def test_topic_command_recreates_stale_topic(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, topics=TelegramTopicsSettings(enabled=True, scope="main"), ) store = TopicStateStore(tmp_path / "telegram_topics_state.json") @@ -1777,6 +1789,8 @@ async def test_run_main_loop_routes_reply_to_running_resume() -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -1866,6 +1880,8 @@ async def test_run_main_loop_persists_topic_sessions_in_project_scope( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, topics=TelegramTopicsSettings( enabled=True, scope="projects", @@ -1946,6 +1962,8 @@ async def test_run_main_loop_auto_resumes_topic_default_engine( presenter=MarkdownPresenter(), final_notify=True, ), + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, topics=TelegramTopicsSettings( enabled=True, scope="main", @@ -2011,6 +2029,8 @@ async def test_run_main_loop_auto_resumes_chat_sessions(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", ) @@ -2044,6 +2064,8 @@ async def test_run_main_loop_auto_resumes_chat_sessions(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", ) @@ -2113,6 +2135,8 @@ async def test_run_main_loop_prompt_upload_uses_caption_directives( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, files=TelegramFilesSettings( enabled=True, auto_put=True, @@ -2176,6 +2200,8 @@ async def test_run_main_loop_voice_transcript_preserves_directive( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, voice_transcription=True, ) @@ -2245,6 +2271,8 @@ async def test_run_main_loop_debounces_forwarded_messages_preserves_directives() chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=DEBOUNCE_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -2313,6 +2341,8 @@ async def test_run_main_loop_ignores_forwarded_without_prompt() -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -2395,6 +2425,8 @@ async def test_run_main_loop_prompt_upload_auto_resumes_chat_sessions( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", files=TelegramFilesSettings( enabled=True, @@ -2446,6 +2478,8 @@ async def test_run_main_loop_prompt_upload_auto_resumes_chat_sessions( chat_id=123, startup_msg="", exec_cfg=exec_cfg2, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", files=TelegramFilesSettings( enabled=True, @@ -2530,6 +2564,8 @@ async def test_run_main_loop_command_updates_chat_session_resume( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", show_resume_line=False, ) @@ -2570,6 +2606,8 @@ async def test_run_main_loop_command_updates_chat_session_resume( chat_id=123, startup_msg="", exec_cfg=exec_cfg2, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", show_resume_line=False, ) @@ -2634,6 +2672,8 @@ async def test_run_main_loop_hides_resume_line_when_disabled( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", show_resume_line=False, ) @@ -2687,6 +2727,8 @@ async def test_run_main_loop_chat_sessions_isolate_group_senders( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", ) @@ -2716,6 +2758,8 @@ async def test_run_main_loop_chat_sessions_isolate_group_senders( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", ) @@ -2763,6 +2807,8 @@ async def test_run_main_loop_new_clears_chat_sessions(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, session_mode="chat", ) @@ -2811,6 +2857,8 @@ async def test_run_main_loop_new_clears_topic_sessions(tmp_path: Path) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, topics=TelegramTopicsSettings(enabled=True, scope="main"), ) @@ -2854,6 +2902,8 @@ async def test_run_main_loop_replies_in_same_thread() -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -2927,6 +2977,8 @@ async def test_run_main_loop_batches_media_group_upload( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=BATCH_MEDIA_GROUP_DEBOUNCE_S, files=TelegramFilesSettings(enabled=True, auto_put=True), ) msg1 = TelegramIncomingMessage( @@ -3032,6 +3084,8 @@ async def test_run_main_loop_handles_command_plugins(monkeypatch) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -3117,6 +3171,8 @@ async def test_run_main_loop_command_uses_project_default_engine( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -3201,6 +3257,8 @@ async def test_run_main_loop_command_defaults_to_chat_project( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -3269,6 +3327,8 @@ async def test_run_main_loop_refreshes_command_ids(monkeypatch) -> None: chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, ) async def poller(_cfg: TelegramBridgeConfig): @@ -3329,6 +3389,8 @@ async def test_run_main_loop_mentions_only_skips_voice_and_files( chat_id=123, startup_msg="", exec_cfg=exec_cfg, + forward_coalesce_s=FAST_FORWARD_COALESCE_S, + media_group_debounce_s=FAST_MEDIA_GROUP_DEBOUNCE_S, voice_transcription=True, files=TelegramFilesSettings(enabled=True, auto_put=True), ) diff --git a/tests/test_telegram_queue.py b/tests/test_telegram_queue.py index 30fe15e..169467a 100644 --- a/tests/test_telegram_queue.py +++ b/tests/test_telegram_queue.py @@ -241,7 +241,7 @@ async def test_edits_coalesce_latest() -> None: @pytest.mark.anyio async def test_send_preempts_pending_edit() -> None: bot = _FakeBot() - client = TelegramClient(client=bot, private_chat_rps=10.0, group_chat_rps=10.0) + client = TelegramClient(client=bot, private_chat_rps=0.0, group_chat_rps=0.0) await client.edit_message_text( chat_id=1, @@ -259,7 +259,9 @@ async def test_send_preempts_pending_edit() -> None: with anyio.fail_after(1): await client.send_message(chat_id=1, text="final") - await anyio.sleep(0.2) + with anyio.fail_after(1): + while len(bot.calls) < 3: + await anyio.sleep(0) assert bot.calls[0] == "edit_message_text" assert bot.calls[1] == "send_message" assert bot.calls[-1] == "edit_message_text" @@ -268,7 +270,7 @@ async def test_send_preempts_pending_edit() -> None: @pytest.mark.anyio async def test_delete_drops_pending_edits() -> None: bot = _FakeBot() - client = TelegramClient(client=bot, private_chat_rps=10.0, group_chat_rps=10.0) + client = TelegramClient(client=bot, private_chat_rps=0.0, group_chat_rps=0.0) await client.edit_message_text( chat_id=1, @@ -289,7 +291,9 @@ async def test_delete_drops_pending_edits() -> None: message_id=1, ) - await anyio.sleep(0.2) + with anyio.fail_after(1): + while not bot.delete_calls: + await anyio.sleep(0) assert bot.delete_calls == [(1, 1)] assert bot.edit_calls == ["first"]