from __future__ import annotations import asyncio import pytest from yuxi.channels.adapters.wechat.debounce import WeChatQueueDebounce from yuxi.channels.models import ( ChannelIdentity, ChannelMessage, ChannelType, ChatType, ) def _make_msg(content: str) -> ChannelMessage: return ChannelMessage( identity=ChannelIdentity( channel_id="wechat", channel_type=ChannelType.WECHAT, channel_user_id="u1", channel_chat_id="c1", ), content=content, ) class TestWeChatQueueDebounce: @pytest.fixture def debouncer(self) -> WeChatQueueDebounce: return WeChatQueueDebounce(debounce_ms=100) @pytest.mark.asyncio async def test_single_message_flushed(self, debouncer): results: list[list[ChannelMessage]] = [] async def handler(msgs): results.append(msgs) msg = _make_msg("hello") await debouncer.enqueue("session-1", msg, handler) await asyncio.sleep(0.3) assert len(results) == 1 assert len(results[0]) == 1 assert results[0][0].content == "hello" @pytest.mark.asyncio async def test_multiple_messages_merged(self, debouncer): results: list[list[ChannelMessage]] = [] async def handler(msgs): results.append(msgs) for content in ["m1", "m2", "m3"]: await debouncer.enqueue("session-1", _make_msg(content), handler) await asyncio.sleep(0.02) await asyncio.sleep(0.3) assert len(results) == 1 assert [m.content for m in results[0]] == ["m1", "m2", "m3"] @pytest.mark.asyncio async def test_different_sessions_not_merged(self, debouncer): results: list[tuple[str, str]] = [] async def make_handler(sid: str): async def handler(msgs): for m in msgs: results.append((sid, m.content)) return handler await debouncer.enqueue("session-a", _make_msg("a1"), await make_handler("session-a")) await debouncer.enqueue("session-b", _make_msg("b1"), await make_handler("session-b")) await debouncer.enqueue("session-a", _make_msg("a2"), await make_handler("session-a")) await asyncio.sleep(0.3) a_msgs = [c for s, c in results if s == "session-a"] b_msgs = [c for s, c in results if s == "session-b"] assert a_msgs == ["a1", "a2"] assert b_msgs == ["b1"] @pytest.mark.asyncio async def test_consecutive_enqueues_reset_timer(self, debouncer): debouncer_long = WeChatQueueDebounce(debounce_ms=300) results: list[list[ChannelMessage]] = [] async def handler(msgs): results.append(msgs) await debouncer_long.enqueue("s1", _make_msg("first"), handler) await asyncio.sleep(0.1) await debouncer_long.enqueue("s1", _make_msg("second"), handler) await asyncio.sleep(0.1) await debouncer_long.enqueue("s1", _make_msg("third"), handler) await asyncio.sleep(0.15) assert len(results) == 0 await asyncio.sleep(0.3) assert len(results) == 1 assert [m.content for m in results[0]] == ["first", "second", "third"] @pytest.mark.asyncio async def test_flush_all_clears_pending(self, debouncer): results: list[list[ChannelMessage]] = [] async def handler(msgs): results.append(msgs) await debouncer.enqueue("session-1", _make_msg("m1"), handler) await debouncer.enqueue("session-2", _make_msg("m2"), handler) await debouncer.flush_all() assert len(results) == 0 @pytest.mark.asyncio async def test_debounce_ms_property(self): debouncer = WeChatQueueDebounce(debounce_ms=500) assert debouncer.debounce_ms == 500 @pytest.mark.asyncio async def test_default_debounce_value(self): debouncer = WeChatQueueDebounce() assert debouncer.debounce_ms == 500