From 620d7896c710748053257695d25c3391aa637dc5 Mon Sep 17 00:00:00 2001 From: ailuntz Date: Tue, 10 Mar 2026 00:14:34 +0800 Subject: [PATCH] fix(slack): define thread usage when sending messages --- nanobot/channels/slack.py | 2 +- tests/test_slack_channel.py | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/test_slack_channel.py diff --git a/nanobot/channels/slack.py b/nanobot/channels/slack.py index a4e7324..e36c4c9 100644 --- a/nanobot/channels/slack.py +++ b/nanobot/channels/slack.py @@ -82,6 +82,7 @@ class SlackChannel(BaseChannel): thread_ts = slack_meta.get("thread_ts") channel_type = slack_meta.get("channel_type") # Only reply in thread for channel/group messages; DMs don't use threads + use_thread = bool(thread_ts and channel_type != "im") thread_ts_param = thread_ts if use_thread else None # Slack rejects empty text payloads. Keep media-only messages media-only, @@ -278,4 +279,3 @@ class SlackChannel(BaseChannel): if parts: rows.append(" ยท ".join(parts)) return "\n".join(rows) - diff --git a/tests/test_slack_channel.py b/tests/test_slack_channel.py new file mode 100644 index 0000000..18b96ef --- /dev/null +++ b/tests/test_slack_channel.py @@ -0,0 +1,88 @@ +from __future__ import annotations + +import pytest + +from nanobot.bus.events import OutboundMessage +from nanobot.bus.queue import MessageBus +from nanobot.channels.slack import SlackChannel +from nanobot.config.schema import SlackConfig + + +class _FakeAsyncWebClient: + def __init__(self) -> None: + self.chat_post_calls: list[dict[str, object | None]] = [] + self.file_upload_calls: list[dict[str, object | None]] = [] + + async def chat_postMessage( + self, + *, + channel: str, + text: str, + thread_ts: str | None = None, + ) -> None: + self.chat_post_calls.append( + { + "channel": channel, + "text": text, + "thread_ts": thread_ts, + } + ) + + async def files_upload_v2( + self, + *, + channel: str, + file: str, + thread_ts: str | None = None, + ) -> None: + self.file_upload_calls.append( + { + "channel": channel, + "file": file, + "thread_ts": thread_ts, + } + ) + + +@pytest.mark.asyncio +async def test_send_uses_thread_for_channel_messages() -> None: + channel = SlackChannel(SlackConfig(enabled=True), MessageBus()) + fake_web = _FakeAsyncWebClient() + channel._web_client = fake_web + + await channel.send( + OutboundMessage( + channel="slack", + chat_id="C123", + content="hello", + media=["/tmp/demo.txt"], + metadata={"slack": {"thread_ts": "1700000000.000100", "channel_type": "channel"}}, + ) + ) + + assert len(fake_web.chat_post_calls) == 1 + assert fake_web.chat_post_calls[0]["thread_ts"] == "1700000000.000100" + assert len(fake_web.file_upload_calls) == 1 + assert fake_web.file_upload_calls[0]["thread_ts"] == "1700000000.000100" + + +@pytest.mark.asyncio +async def test_send_omits_thread_for_dm_messages() -> None: + channel = SlackChannel(SlackConfig(enabled=True), MessageBus()) + fake_web = _FakeAsyncWebClient() + channel._web_client = fake_web + + await channel.send( + OutboundMessage( + channel="slack", + chat_id="D123", + content="hello", + media=["/tmp/demo.txt"], + metadata={"slack": {"thread_ts": "1700000000.000100", "channel_type": "im"}}, + ) + ) + + assert len(fake_web.chat_post_calls) == 1 + assert fake_web.chat_post_calls[0]["thread_ts"] is None + assert len(fake_web.file_upload_calls) == 1 + assert fake_web.file_upload_calls[0]["thread_ts"] is None