新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
132 lines
4.2 KiB
Python
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 |