diff --git a/botpy.log b/botpy.log deleted file mode 100644 index 6ebafc0..0000000 --- a/botpy.log +++ /dev/null @@ -1,36 +0,0 @@ -2026-02-15 13:46:42,249 [INFO] (client.py:162)_bot_login [botpy] 登录机器人账号中... -2026-02-15 13:46:42,535 [INFO] (robot.py:65)update_access_token [botpy] access_token expires_in 7200 -2026-02-15 13:46:43,622 [INFO] (client.py:181)_bot_init [botpy] 程序启动... -2026-02-15 13:46:43,623 [INFO] (connection.py:60)multi_run [botpy] 最大并发连接数: 1, 启动会话数: 1 -2026-02-15 13:46:43,623 [INFO] (client.py:242)bot_connect [botpy] 会话启动中... -2026-02-15 13:46:43,623 [INFO] (gateway.py:115)ws_connect [botpy] 启动中... -2026-02-15 13:46:43,792 [INFO] (gateway.py:142)ws_identify [botpy] 鉴权中... -2026-02-15 13:46:43,915 [INFO] (gateway.py:85)on_message [botpy] 机器人「EvoSci-测试中」启动成功! -2026-02-15 13:46:43,916 [INFO] (gateway.py:223)_send_heart [botpy] 心跳维持启动... -2026-02-15 13:57:47,578 [INFO] (client.py:162)_bot_login [botpy] 登录机器人账号中... -2026-02-15 13:57:47,775 [INFO] (robot.py:65)update_access_token [botpy] access_token expires_in 6535 -2026-02-15 13:57:48,887 [INFO] (client.py:181)_bot_init [botpy] 程序启动... -2026-02-15 13:57:48,888 [INFO] (connection.py:60)multi_run [botpy] 最大并发连接数: 1, 启动会话数: 1 -2026-02-15 13:57:48,888 [INFO] (client.py:242)bot_connect [botpy] 会话启动中... -2026-02-15 13:57:48,888 [INFO] (gateway.py:115)ws_connect [botpy] 启动中... -2026-02-15 13:57:49,073 [INFO] (gateway.py:142)ws_identify [botpy] 鉴权中... -2026-02-15 13:57:49,294 [INFO] (gateway.py:85)on_message [botpy] 机器人「EvoSci-测试中」启动成功! -2026-02-15 13:57:49,295 [INFO] (gateway.py:223)_send_heart [botpy] 心跳维持启动... -2026-02-15 18:01:03,252 [INFO] (client.py:162)_bot_login [botpy] 登录机器人账号中... -2026-02-15 18:01:03,471 [INFO] (robot.py:65)update_access_token [botpy] access_token expires_in 2348 -2026-02-15 18:01:03,935 [INFO] (client.py:181)_bot_init [botpy] 程序启动... -2026-02-15 18:01:03,935 [INFO] (connection.py:60)multi_run [botpy] 最大并发连接数: 1, 启动会话数: 1 -2026-02-15 18:01:03,935 [INFO] (client.py:242)bot_connect [botpy] 会话启动中... -2026-02-15 18:01:03,935 [INFO] (gateway.py:115)ws_connect [botpy] 启动中... -2026-02-15 18:01:04,118 [INFO] (gateway.py:142)ws_identify [botpy] 鉴权中... -2026-02-15 18:01:04,298 [INFO] (gateway.py:85)on_message [botpy] 机器人「EvoSci-测试中」启动成功! -2026-02-15 18:01:04,299 [INFO] (gateway.py:223)_send_heart [botpy] 心跳维持启动... -2026-02-15 18:03:43,137 [INFO] (client.py:162)_bot_login [botpy] 登录机器人账号中... -2026-02-15 18:03:43,319 [INFO] (robot.py:65)update_access_token [botpy] access_token expires_in 2188 -2026-02-15 18:03:43,860 [INFO] (client.py:181)_bot_init [botpy] 程序启动... -2026-02-15 18:03:43,860 [INFO] (connection.py:60)multi_run [botpy] 最大并发连接数: 1, 启动会话数: 1 -2026-02-15 18:03:43,861 [INFO] (client.py:242)bot_connect [botpy] 会话启动中... -2026-02-15 18:03:43,861 [INFO] (gateway.py:115)ws_connect [botpy] 启动中... -2026-02-15 18:03:44,033 [INFO] (gateway.py:142)ws_identify [botpy] 鉴权中... -2026-02-15 18:03:44,174 [INFO] (gateway.py:85)on_message [botpy] 机器人「EvoSci-测试中」启动成功! -2026-02-15 18:03:44,174 [INFO] (gateway.py:223)_send_heart [botpy] 心跳维持启动... diff --git a/channel_feature_matrix.xlsx b/channel_feature_matrix.xlsx deleted file mode 100644 index 8fa95ad..0000000 Binary files a/channel_feature_matrix.xlsx and /dev/null differ diff --git a/tests/test_channel_comprehensive.py b/tests/test_channel_comprehensive.py index 6b0e9d9..9bab9f9 100644 --- a/tests/test_channel_comprehensive.py +++ b/tests/test_channel_comprehensive.py @@ -567,7 +567,7 @@ class TestChannelBuildInbound: sender_id="u1", chat_id="c1", text="hello", message_id="m1", media_files=["/path/img.jpg"], ) - msg = ch._build_inbound(raw) + msg = ch._raw_to_inbound(raw) assert msg is not None assert msg.channel == "stub" assert msg.sender_id == "u1" @@ -575,21 +575,27 @@ class TestChannelBuildInbound: assert msg.media == ["/path/img.jpg"] def test_drops_disallowed_sender(self): - cfg = _FakeConfig(allowed_senders=["alice"]) - ch = StubChannel(cfg) - raw = RawIncoming(sender_id="eve", chat_id="c1", text="hack") - assert ch._build_inbound(raw) is None + async def _test(): + cfg = _FakeConfig(allowed_senders=["alice"]) + ch = StubChannel(cfg) + raw = RawIncoming(sender_id="eve", chat_id="c1", text="hack") + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 0 + _run(_test()) def test_drops_disallowed_channel(self): - cfg = _FakeConfig(allowed_channels=["c1"]) - ch = StubChannel(cfg) - raw = RawIncoming(sender_id="u1", chat_id="c2", text="hello") - assert ch._build_inbound(raw) is None + async def _test(): + cfg = _FakeConfig(allowed_channels=["c1"]) + ch = StubChannel(cfg) + raw = RawIncoming(sender_id="u1", chat_id="c2", text="hello") + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 0 + _run(_test()) def test_drops_empty_content_no_media(self): ch = StubChannel() raw = RawIncoming(sender_id="u1", chat_id="c1", text="") - assert ch._build_inbound(raw) is None + assert ch._raw_to_inbound(raw) is None def test_media_only_message_passes(self): ch = StubChannel() @@ -597,7 +603,7 @@ class TestChannelBuildInbound: sender_id="u1", chat_id="c1", text="", media_files=["/path/file.pdf"], ) - msg = ch._build_inbound(raw) + msg = ch._raw_to_inbound(raw) assert msg is not None assert msg.content == "[media only]" @@ -607,14 +613,14 @@ class TestChannelBuildInbound: sender_id="u1", chat_id="c1", text="main text", content_annotations=["[attachment: photo.jpg]"], ) - msg = ch._build_inbound(raw) + msg = ch._raw_to_inbound(raw) assert "[attachment: photo.jpg]" in msg.content def test_metadata_preserves_chat_id(self): ch = StubChannel() raw = RawIncoming(sender_id="u1", chat_id="c1", text="hi", metadata={"extra": "data"}) - msg = ch._build_inbound(raw) + msg = ch._raw_to_inbound(raw) assert msg.metadata["chat_id"] == "c1" assert msg.metadata["extra"] == "data" diff --git a/tests/test_mention_gating.py b/tests/test_mention_gating.py index 0c74bd8..57edf84 100644 --- a/tests/test_mention_gating.py +++ b/tests/test_mention_gating.py @@ -6,6 +6,7 @@ from dataclasses import dataclass import pytest from EvoScientist.channels.base import Channel, RawIncoming +from EvoScientist.channels.capabilities import ChannelCapabilities # ── Minimal concrete channel for testing base-class logic ───────────── @@ -15,6 +16,7 @@ from EvoScientist.channels.base import Channel, RawIncoming class _StubConfig: allowed_senders: set[str] | None = None require_mention: str = "group" + dm_policy: str = "allowlist" class _StubChannel(Channel): @@ -27,6 +29,11 @@ class _StubChannel(Channel): pass +class _MentionStubChannel(_StubChannel): + """Stub with mention gating enabled.""" + capabilities = ChannelCapabilities(mentions=True) + + def _run(coro): loop = asyncio.new_event_loop() try: @@ -86,69 +93,83 @@ class TestShouldProcess: assert ch._should_process(raw) is True -class TestBuildInboundGating: - """Tests that _build_inbound integrates _should_process and _strip_mention.""" +class TestPipelineGating: + """Tests that _enqueue_raw pipeline integrates mention gating and _strip_mention.""" - def test_group_not_mentioned_returns_none(self): - config = _StubConfig(require_mention="group") - ch = _StubChannel(config) - raw = RawIncoming( - sender_id="u1", chat_id="c1", text="hello", - is_group=True, was_mentioned=False, - ) - assert ch._build_inbound(raw) is None + def test_group_not_mentioned_dropped(self): + async def _test(): + config = _StubConfig(require_mention="group") + ch = _MentionStubChannel(config) + raw = RawIncoming( + sender_id="u1", chat_id="c1", text="hello", + is_group=True, was_mentioned=False, + ) + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 0 + _run(_test()) - def test_group_mentioned_returns_message(self): - config = _StubConfig(require_mention="group") - ch = _StubChannel(config) - raw = RawIncoming( - sender_id="u1", chat_id="c1", text="hello", - is_group=True, was_mentioned=True, - ) - msg = ch._build_inbound(raw) - assert msg is not None - assert msg.content == "hello" + def test_group_mentioned_passes(self): + async def _test(): + config = _StubConfig(require_mention="group") + ch = _MentionStubChannel(config) + raw = RawIncoming( + sender_id="u1", chat_id="c1", text="hello", + is_group=True, was_mentioned=True, + ) + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 1 + msg = await ch._queue.get() + assert msg.content == "hello" + _run(_test()) - def test_dm_returns_message_even_when_not_mentioned(self): - config = _StubConfig(require_mention="group") - ch = _StubChannel(config) - raw = RawIncoming( - sender_id="u1", chat_id="c1", text="hello", - is_group=False, was_mentioned=False, - ) - msg = ch._build_inbound(raw) - assert msg is not None + def test_dm_passes_even_when_not_mentioned(self): + async def _test(): + config = _StubConfig(require_mention="group") + ch = _MentionStubChannel(config) + raw = RawIncoming( + sender_id="u1", chat_id="c1", text="hello", + is_group=False, was_mentioned=False, + ) + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 1 + _run(_test()) def test_strip_mention_called_for_group(self): """When is_group=True, _strip_mention should be applied to text.""" - config = _StubConfig(require_mention="group") - ch = _StubChannel(config) + async def _test(): + config = _StubConfig(require_mention="group") + ch = _MentionStubChannel(config) + ch._strip_mention = lambda text: text.replace("@bot ", "").strip() + # Rebuild middlewares so MentionGatingMiddleware picks up new strip_fn + ch._inbound_middlewares = ch._build_inbound_middlewares() - # Override _strip_mention to verify it's called - ch._strip_mention = lambda text: text.replace("@bot ", "").strip() - - raw = RawIncoming( - sender_id="u1", chat_id="c1", text="@bot hello", - is_group=True, was_mentioned=True, - ) - msg = ch._build_inbound(raw) - assert msg is not None - assert msg.content == "hello" + raw = RawIncoming( + sender_id="u1", chat_id="c1", text="@bot hello", + is_group=True, was_mentioned=True, + ) + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 1 + msg = await ch._queue.get() + assert msg.content == "hello" + _run(_test()) def test_strip_mention_not_called_for_dm(self): """When is_group=False, _strip_mention should NOT be applied.""" - config = _StubConfig(require_mention="group") - ch = _StubChannel(config) + async def _test(): + config = _StubConfig(require_mention="group") + ch = _MentionStubChannel(config) + ch._strip_mention = lambda text: text.replace("@bot ", "").strip() + ch._inbound_middlewares = ch._build_inbound_middlewares() - ch._strip_mention = lambda text: text.replace("@bot ", "").strip() - - raw = RawIncoming( - sender_id="u1", chat_id="c1", text="@bot hello", - is_group=False, was_mentioned=True, - ) - msg = ch._build_inbound(raw) - assert msg is not None - assert msg.content == "@bot hello" + raw = RawIncoming( + sender_id="u1", chat_id="c1", text="@bot hello", + is_group=False, was_mentioned=True, + ) + await ch._enqueue_raw(raw) + assert ch._queue.qsize() == 1 + msg = await ch._queue.get() + assert msg.content == "@bot hello" + _run(_test()) # ── Per-channel _strip_mention tests ─────────────────────────────────