ForcePilot/backend/test/unit/channels/test_channels_debounce.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

132 lines
4.2 KiB
Python

from __future__ import annotations
import asyncio
import pytest
from yuxi.channels.policy.debounce import QueueDebounce
class TestQueueDebounce:
@pytest.fixture
def debouncer(self) -> QueueDebounce:
return QueueDebounce(debounce_ms=100)
@pytest.mark.asyncio
async def test_single_message_flushed(self, debouncer):
results: list[list[str]] = []
async def handler(msgs: list[str]) -> None:
results.append(msgs)
await debouncer.enqueue("session-1", "hello", handler)
await asyncio.sleep(0.3)
assert len(results) == 1
assert results[0] == ["hello"]
@pytest.mark.asyncio
async def test_multiple_messages_merged(self, debouncer):
results: list[list[str]] = []
async def handler(msgs: list[str]) -> None:
results.append(msgs)
for msg in ["m1", "m2", "m3"]:
await debouncer.enqueue("session-1", msg, handler)
await asyncio.sleep(0.02)
await asyncio.sleep(0.3)
assert len(results) == 1
assert results[0] == ["m1", "m2", "m3"]
@pytest.mark.asyncio
async def test_different_sessions_not_merged(self, debouncer):
results: list[tuple[str, list[str]]] = []
async def make_handler(sid: str):
async def handler(msgs: list[str]) -> None:
results.append((sid, msgs))
return handler
await debouncer.enqueue("session-a", "a1", await make_handler("session-a"))
await debouncer.enqueue("session-b", "b1", await make_handler("session-b"))
await debouncer.enqueue("session-a", "a2", await make_handler("session-a"))
await asyncio.sleep(0.3)
session_a_msgs = [m for s, m in results if s == "session-a"]
session_b_msgs = [m for s, m in results if s == "session-b"]
assert len(session_a_msgs) == 1
assert session_a_msgs[0] == ["a1", "a2"]
assert len(session_b_msgs) == 1
assert session_b_msgs[0] == ["b1"]
@pytest.mark.asyncio
async def test_cancel_prevents_flush(self, debouncer):
results: list[list[str]] = []
async def handler(msgs: list[str]) -> None:
results.append(msgs)
await debouncer.enqueue("session-1", "hello", handler)
await debouncer.cancel("session-1")
await asyncio.sleep(0.3)
assert len(results) == 0
@pytest.mark.asyncio
async def test_pending_count_reflects_active_sessions(self, debouncer):
results: list[list[str]] = []
async def handler(msgs: list[str]) -> None:
results.append(msgs)
assert debouncer.pending_count == 0
await debouncer.enqueue("session-1", "m1", handler)
await debouncer.enqueue("session-2", "m2", handler)
assert debouncer.pending_count == 2
await asyncio.sleep(0.3)
assert debouncer.pending_count == 0
@pytest.mark.asyncio
async def test_handler_exception_does_not_crash(self, debouncer):
async def failing_handler(msgs: list[str]) -> None:
raise RuntimeError("handler error")
await debouncer.enqueue("session-1", "m1", failing_handler)
await asyncio.sleep(0.3)
assert debouncer.pending_count == 0
@pytest.mark.asyncio
async def test_consecutive_enqueues_reset_timer(self, debouncer):
debouncer_long = QueueDebounce(debounce_ms=300)
results: list[list[str]] = []
async def handler(msgs: list[str]) -> None:
results.append(msgs)
await debouncer_long.enqueue("s1", "first", handler)
await asyncio.sleep(0.1)
await debouncer_long.enqueue("s1", "second", handler)
await asyncio.sleep(0.1)
await debouncer_long.enqueue("s1", "third", handler)
# After the first 0.3s (from first enqueue), it should NOT have flushed yet
await asyncio.sleep(0.15)
assert len(results) == 0
# Wait for the full timer from the last enqueue
await asyncio.sleep(0.3)
assert len(results) == 1
assert results[0] == ["first", "second", "third"]
@pytest.mark.asyncio
async def test_cancel_nonexistent_session_no_error(self, debouncer):
await debouncer.cancel("nonexistent")
assert debouncer.pending_count == 0