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.channels.slack 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]] = [] self.reactions_add_calls: list[dict[str, object | None]] = [] self.reactions_remove_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, } ) async def reactions_add( self, *, channel: str, name: str, timestamp: str, ) -> None: self.reactions_add_calls.append( { "channel": channel, "name": name, "timestamp": timestamp, } ) async def reactions_remove( self, *, channel: str, name: str, timestamp: str, ) -> None: self.reactions_remove_calls.append( { "channel": channel, "name": name, "timestamp": timestamp, } ) @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]["text"] == "hello\n" 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]["text"] == "hello\n" 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 @pytest.mark.asyncio async def test_send_updates_reaction_when_final_response_sent() -> None: channel = SlackChannel(SlackConfig(enabled=True, react_emoji="eyes"), MessageBus()) fake_web = _FakeAsyncWebClient() channel._web_client = fake_web await channel.send( OutboundMessage( channel="slack", chat_id="C123", content="done", metadata={ "slack": {"event": {"ts": "1700000000.000100"}, "channel_type": "channel"}, }, ) ) assert fake_web.reactions_remove_calls == [ {"channel": "C123", "name": "eyes", "timestamp": "1700000000.000100"} ] assert fake_web.reactions_add_calls == [ {"channel": "C123", "name": "white_check_mark", "timestamp": "1700000000.000100"} ]