diff --git a/contributors/emails/teonexcore@gmail.com b/contributors/emails/teonexcore@gmail.com new file mode 100644 index 0000000000..54dbd6417c --- /dev/null +++ b/contributors/emails/teonexcore@gmail.com @@ -0,0 +1 @@ +teo-nex diff --git a/tests/gateway/test_slack.py b/tests/gateway/test_slack.py index 473d480d7b..31f0903898 100644 --- a/tests/gateway/test_slack.py +++ b/tests/gateway/test_slack.py @@ -6057,67 +6057,31 @@ class TestAgentSessionsApiRouting: class TestNonConversationalSubtypeAllowlist: - """#110778 — housekeeping subtypes must not start a turn in free-response - channels. The gate is an allowlist so subtypes Slack adds later are dropped - instead of silently readmitted.""" + """#110778 — Slack system messages must not start a turn in free-response channels; the + gate is an allowlist so subtypes Slack adds later are dropped instead of readmitted.""" @staticmethod - def _event(subtype): - event = { - "type": "message", - "user": "U_HUMAN", - "text": "hello", - "ts": "12345.6789", - "channel": "C_FREE", - } + def _event(subtype, **extra): + # Distinct ts per subtype: the prefilter dedups by (team, ts) before the subtype gate. + event = {"type": "message", "user": "U_HUMAN", "text": "hello", + "ts": f"12345.{abs(hash(subtype)) % 10**6}", "channel": "C_FREE", + "client_msg_id": "m1", **extra} if subtype is not None: event["subtype"] = subtype return event @pytest.mark.asyncio - @pytest.mark.parametrize( - "subtype", - [ - "channel_join", - "channel_leave", - "channel_topic", - "channel_purpose", - "channel_name", - "channel_convert_to_private", - "channel_convert_to_public", - "pinned_item", - "unpinned_item", - "message_deleted", - "file_comment", - ], - ) - async def test_housekeeping_subtypes_are_dropped(self, adapter, subtype): - assert await adapter._prefilter_inbound(self._event(subtype), None) is None + async def test_housekeeping_subtypes_are_dropped(self, adapter): + for subtype in ("channel_join", "channel_topic", "channel_convert_to_private", + "pinned_item", "file_comment", "message_deleted"): + assert await adapter._prefilter_inbound(self._event(subtype), None) is None, subtype @pytest.mark.asyncio - @pytest.mark.parametrize( - "subtype", [None, "file_share", "thread_broadcast", "me_message"] - ) - async def test_conversational_subtypes_pass(self, adapter, subtype): - accepted = await adapter._prefilter_inbound(self._event(subtype), None) - assert accepted is not None - assert accepted[0].get("channel") == "C_FREE" - - @pytest.mark.asyncio - async def test_edited_message_still_wakes_the_bot(self, adapter): - event = { - "type": "message", - "subtype": "message_changed", - "channel": "C_FREE", - "ts": "12340.0000", - "message": { - "type": "message", - "user": "U_HUMAN", - "text": "edited hello", - "ts": "12345.6789", - "edited": {"ts": "12346.0000"}, - }, - } - accepted = await adapter._prefilter_inbound(event, None) - assert accepted is not None - assert accepted[0].get("text") == "edited hello" + async def test_conversational_subtypes_still_pass(self, adapter): + adapter.config.extra["allow_bots"] = "all" + events = [self._event(s) for s in (None, "file_share", "thread_broadcast", "me_message", + "document_mention")] + events.append(self._event("bot_message", bot_id="B_OTHER")) + for event in events: + accepted = await adapter._prefilter_inbound(event, None) + assert accepted is not None and accepted[0]["channel"] == "C_FREE", event.get("subtype") diff --git a/tests/gateway/test_slack_conversational_senders.py b/tests/gateway/test_slack_conversational_senders.py deleted file mode 100644 index 71431e4efb..0000000000 --- a/tests/gateway/test_slack_conversational_senders.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Preserve conversational senders through the subtype filter in PR #110780.""" - -from types import SimpleNamespace -from unittest.mock import AsyncMock - -import pytest - -from gateway.config import PlatformConfig -from plugins.platforms.slack.adapter import SlackAdapter - - -@pytest.fixture -def adapter(): - instance = SlackAdapter(PlatformConfig( - enabled=True, - token="xoxb-test", - extra={"free_response_channels": ["C_TEST"], "allow_bots": "all"}, - )) - instance._bot_user_id = "U_BOT" - instance._running = True - instance._app = SimpleNamespace(client=SimpleNamespace( - users_info=AsyncMock(return_value={ - "ok": True, "user": {"is_bot": False, "real_name": "Test User"}, - }), - conversations_info=AsyncMock(return_value={ - "ok": True, "channel": {"name": "test"}, - }), - conversations_replies=AsyncMock(return_value={"ok": True, "messages": []}), - )) - instance.handle_message = AsyncMock() - return instance - - -def _event(subtype, text, *, edited): - message = { - "type": "app_mention" if subtype == "document_mention" else "message", - "subtype": subtype, "user": "U_TEST", "text": text, - "channel": "C_TEST", "channel_type": "channel", "team": "T_TEST", - "ts": "100.000001", - } - if subtype == "bot_message": - message["bot_id"] = "B_OTHER" - else: - message["document_mention"] = { - "file_id": "F_CANVAS", "section_id": "section-1", - "mentioning_user_ids": ["U_TEST"], - } - if not edited: - return message - return { - "type": "message", "subtype": "message_changed", - "channel": "C_TEST", "channel_type": "channel", "team": "T_TEST", - "ts": "101.000001", "event_ts": "101.000001", "message": message, - } - - -@pytest.mark.asyncio -@pytest.mark.parametrize("edited", [False, True], ids=["original", "edited"]) -@pytest.mark.parametrize(("policy", "mentioned", "accepted"), [ - ("none", False, False), - ("none", True, False), - ("mentions", False, False), - ("mentions", True, True), - ("all", False, True), -]) -async def test_bot_messages_retain_the_configured_policy(adapter, policy, mentioned, accepted, edited): - adapter.config.extra["allow_bots"] = policy - text = "<@U_BOT> A message" if mentioned else "A message" - - await adapter._handle_slack_message(_event("bot_message", text, edited=edited)) - - assert adapter.handle_message.await_count == int(accepted) - if accepted: - delivered = adapter.handle_message.await_args.args[0] - assert delivered.text == "A message" - assert delivered.source.is_bot is True - - -@pytest.mark.asyncio -@pytest.mark.parametrize("edited", [False, True], ids=["original", "edited"]) -async def test_canvas_mentions_reach_the_agent(adapter, edited): - adapter.config.extra = {"allow_bots": "none"} - - await adapter._handle_slack_message( - _event("document_mention", "<@U_BOT> Summarize this canvas", edited=edited) - ) - - adapter.handle_message.assert_awaited_once() - delivered = adapter.handle_message.await_args.args[0] - assert delivered.text == "Summarize this canvas" - assert delivered.source.user_id == "U_TEST" - assert delivered.raw_message["document_mention"]["file_id"] == "F_CANVAS"