import copy from datetime import datetime, timezone from unittest.mock import Mock, call, patch import pytest from arcade.sdk.errors import RetryableToolError, ToolExecutionError from slack_sdk.errors import SlackApiError from slack_sdk.web.async_slack_response import AsyncSlackResponse from arcade_slack.constants import MAX_PAGINATION_SIZE_LIMIT from arcade_slack.models import ConversationType, ConversationTypeSlackName from arcade_slack.tools.chat import ( get_channel_metadata_by_name, get_conversation_metadata_by_id, get_direct_message_conversation_metadata_by_username, get_members_in_channel_by_name, get_members_in_conversation_by_id, get_messages_in_channel_by_name, get_messages_in_conversation_by_id, get_messages_in_direct_message_conversation_by_username, get_messages_in_multi_person_dm_conversation_by_usernames, get_multi_person_dm_conversation_metadata_by_usernames, list_conversations_metadata, list_direct_message_conversations_metadata, list_group_direct_message_conversations_metadata, list_private_channels_metadata, list_public_channels_metadata, send_dm_to_user, send_message_to_channel, ) from arcade_slack.utils import extract_basic_user_info, extract_conversation_metadata @pytest.fixture def mock_list_conversations_metadata(mocker): return mocker.patch("arcade_slack.tools.chat.list_conversations_metadata", autospec=True) @pytest.fixture def mock_channel_info() -> dict: return {"name": "general", "id": "C12345", "is_member": True, "is_channel": True} @pytest.mark.asyncio async def test_send_dm_to_user(mock_context, mock_chat_slack_client): mock_chat_slack_client.users_list.return_value = { "ok": True, "members": [{"name": "testuser", "id": "U12345"}], } mock_chat_slack_client.conversations_open.return_value = { "ok": True, "channel": {"id": "D12345"}, } mock_slack_response = Mock(spec=AsyncSlackResponse) mock_slack_response.data = {"ok": True} mock_chat_slack_client.chat_postMessage.return_value = mock_slack_response response = await send_dm_to_user(mock_context, "testuser", "Hello!") assert response["response"]["ok"] is True mock_chat_slack_client.users_list.assert_called_once() mock_chat_slack_client.conversations_open.assert_called_once_with(users=["U12345"]) mock_chat_slack_client.chat_postMessage.assert_called_once_with(channel="D12345", text="Hello!") @pytest.mark.asyncio async def test_send_dm_to_inexistent_user(mock_context, mock_chat_slack_client): mock_chat_slack_client.users_list.return_value = { "ok": True, "members": [{"name": "testuser", "id": "U12345"}], } with pytest.raises(RetryableToolError): await send_dm_to_user(mock_context, "inexistent_user", "Hello!") mock_chat_slack_client.users_list.assert_called_once() mock_chat_slack_client.conversations_open.assert_not_called() mock_chat_slack_client.chat_postMessage.assert_not_called() @pytest.mark.asyncio async def test_send_message_to_channel(mock_context, mock_chat_slack_client): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [{"id": "C12345", "name": "general"}], } mock_slack_response = Mock(spec=AsyncSlackResponse) mock_slack_response.data = {"ok": True} mock_chat_slack_client.chat_postMessage.return_value = mock_slack_response response = await send_message_to_channel(mock_context, "general", "Hello, channel!") assert response["response"]["ok"] is True mock_chat_slack_client.conversations_list.assert_called_once() mock_chat_slack_client.chat_postMessage.assert_called_once_with( channel="C12345", text="Hello, channel!" ) @pytest.mark.asyncio async def test_send_message_to_inexistent_channel(mock_context, mock_chat_slack_client): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [], } with pytest.raises(RetryableToolError): await send_message_to_channel(mock_context, "inexistent_channel", "Hello!") mock_chat_slack_client.conversations_list.assert_called_once() mock_chat_slack_client.chat_postMessage.assert_not_called() @pytest.mark.asyncio async def test_list_conversations_metadata_with_default_args( mock_context, mock_chat_slack_client, mock_channel_info ): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [mock_channel_info], } response = await list_conversations_metadata(mock_context) assert response["conversations"] == [extract_conversation_metadata(mock_channel_info)] assert response["next_cursor"] is None mock_chat_slack_client.conversations_list.assert_called_once_with( types=",".join([conv_type.value for conv_type in ConversationTypeSlackName]), exclude_archived=True, limit=MAX_PAGINATION_SIZE_LIMIT, cursor=None, ) @pytest.mark.asyncio async def test_list_conversations_metadata_filtering_single_conversation_type( mock_context, mock_chat_slack_client, mock_channel_info ): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [mock_channel_info], } response = await list_conversations_metadata( mock_context, conversation_types=ConversationType.PUBLIC_CHANNEL ) assert response["conversations"] == [extract_conversation_metadata(mock_channel_info)] assert response["next_cursor"] is None mock_chat_slack_client.conversations_list.assert_called_once_with( types=ConversationTypeSlackName.PUBLIC_CHANNEL.value, exclude_archived=True, limit=MAX_PAGINATION_SIZE_LIMIT, cursor=None, ) @pytest.mark.asyncio async def test_list_conversations_metadata_filtering_multiple_conversation_types( mock_context, mock_chat_slack_client, mock_channel_info ): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [mock_channel_info], } response = await list_conversations_metadata( mock_context, conversation_types=[ ConversationTypeSlackName.PUBLIC_CHANNEL, ConversationTypeSlackName.PRIVATE_CHANNEL, ], ) assert response["conversations"] == [extract_conversation_metadata(mock_channel_info)] assert response["next_cursor"] is None mock_chat_slack_client.conversations_list.assert_called_once_with( types=f"{ConversationTypeSlackName.PUBLIC_CHANNEL.value},{ConversationTypeSlackName.PRIVATE_CHANNEL.value}", exclude_archived=True, limit=MAX_PAGINATION_SIZE_LIMIT, cursor=None, ) @pytest.mark.asyncio async def test_list_conversations_metadata_with_custom_pagination_args( mock_context, mock_chat_slack_client, mock_channel_info ): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [mock_channel_info] * 3, "response_metadata": {"next_cursor": "456"}, } response = await list_conversations_metadata(mock_context, limit=3, next_cursor="123") assert response["conversations"] == [ extract_conversation_metadata(mock_channel_info) for _ in range(3) ] assert response["next_cursor"] == "456" mock_chat_slack_client.conversations_list.assert_called_once_with( types=",".join([conv_type.value for conv_type in ConversationTypeSlackName]), exclude_archived=True, limit=3, cursor="123", ) @pytest.mark.asyncio @pytest.mark.parametrize( "faulty_slack_function_name, tool_function, tool_args", [ ("users_list", send_dm_to_user, ("testuser", "Hello!")), ("conversations_list", send_message_to_channel, ("general", "Hello!")), ], ) async def test_tools_with_slack_error( mock_context, mock_chat_slack_client, faulty_slack_function_name, tool_function, tool_args ): getattr(mock_chat_slack_client, faulty_slack_function_name).side_effect = SlackApiError( message="test_slack_error", response={"ok": False, "error": "test_slack_error"}, ) with pytest.raises(ToolExecutionError) as e: await tool_function(mock_context, *tool_args) assert "test_slack_error" in str(e.value) @pytest.mark.asyncio @pytest.mark.parametrize( "tool_function, conversation_type", [ (list_public_channels_metadata, ConversationType.PUBLIC_CHANNEL), (list_private_channels_metadata, ConversationType.PRIVATE_CHANNEL), ( list_group_direct_message_conversations_metadata, ConversationType.MULTI_PERSON_DIRECT_MESSAGE, ), (list_direct_message_conversations_metadata, ConversationType.DIRECT_MESSAGE), ], ) async def test_list_channels_metadata( mock_context, mock_list_conversations_metadata, tool_function, conversation_type, ): response = await tool_function(mock_context, limit=3) mock_list_conversations_metadata.assert_called_once_with( mock_context, conversation_types=[conversation_type], limit=3 ) assert response == mock_list_conversations_metadata.return_value @pytest.mark.asyncio async def test_get_conversation_metadata_by_id( mock_context, mock_chat_slack_client, mock_channel_info ): mock_chat_slack_client.conversations_info.return_value = { "ok": True, "channel": mock_channel_info, } response = await get_conversation_metadata_by_id(mock_context, "C12345") assert response == extract_conversation_metadata(mock_channel_info) mock_chat_slack_client.conversations_info.assert_called_once_with( channel="C12345", include_locale=True, include_num_members=True, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.list_conversations_metadata") async def test_get_conversation_metadata_by_id_slack_api_error( mock_list_conversations_metadata, mock_context, mock_chat_slack_client, mock_channel_info ): mock_channel_info["name"] = "whatever_conversation_should_be_present_in_additional_prompt" mock_list_conversations_metadata.return_value = { "conversations": [extract_conversation_metadata(mock_channel_info)], "response_metadata": {"next_cursor": None}, } mock_chat_slack_client.conversations_info.side_effect = SlackApiError( message="channel_not_found", response={"ok": False, "error": "channel_not_found"}, ) with pytest.raises(RetryableToolError) as e: await get_conversation_metadata_by_id(mock_context, "C12345") assert ( "whatever_conversation_should_be_present_in_additional_prompt" in e.additional_prompt_content ) mock_chat_slack_client.conversations_info.assert_called_once_with( channel="C12345", include_locale=True, include_num_members=True, ) mock_list_conversations_metadata.assert_called_once_with(mock_context) @pytest.mark.asyncio async def test_get_conversation_metadata_by_name( mock_context, mock_list_conversations_metadata, mock_channel_info ): sample_conversation = extract_conversation_metadata(mock_channel_info) mock_list_conversations_metadata.return_value = { "conversations": [sample_conversation], "next_cursor": None, } response = await get_channel_metadata_by_name(mock_context, sample_conversation["name"]) assert response == sample_conversation mock_list_conversations_metadata.assert_called_once_with(mock_context, next_cursor=None) @pytest.mark.asyncio async def test_get_channel_metadata_by_name_triggering_pagination( mock_context, mock_list_conversations_metadata, mock_channel_info ): target_channel = extract_conversation_metadata(mock_channel_info) another_channel = extract_conversation_metadata(mock_channel_info) another_channel["name"] = "another_channel" mock_list_conversations_metadata.side_effect = [ { "conversations": [another_channel], "next_cursor": "123", }, { "conversations": [target_channel], "next_cursor": None, }, ] response = await get_channel_metadata_by_name(mock_context, target_channel["name"]) assert response == target_channel assert mock_list_conversations_metadata.call_count == 2 mock_list_conversations_metadata.assert_has_calls([ call(mock_context, next_cursor=None), call(mock_context, next_cursor="123"), ]) @pytest.mark.asyncio async def test_get_channel_metadata_by_name_not_found( mock_context, mock_list_conversations_metadata, mock_channel_info ): first_channel = extract_conversation_metadata(mock_channel_info) second_channel = extract_conversation_metadata(mock_channel_info) second_channel["name"] = "second_channel" mock_list_conversations_metadata.side_effect = [ { "conversations": [second_channel], "next_cursor": "123", }, { "conversations": [first_channel], "next_cursor": None, }, ] with pytest.raises(RetryableToolError): await get_channel_metadata_by_name(mock_context, "inexistent_channel") assert mock_list_conversations_metadata.call_count == 2 mock_list_conversations_metadata.assert_has_calls([ call(mock_context, next_cursor=None), call(mock_context, next_cursor="123"), ]) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.async_paginate") @patch("arcade_slack.tools.chat.get_user_info_by_id") async def test_get_members_from_conversation_id( mock_get_user_info_by_id, mock_async_paginate, mock_context, mock_chat_slack_client ): member1 = {"id": "U123", "name": "testuser123"} member1_info = extract_basic_user_info(member1) member2 = {"id": "U456", "name": "testuser456"} member2_info = extract_basic_user_info(member2) mock_async_paginate.return_value = [member1["id"], member2["id"]], "token123" mock_get_user_info_by_id.side_effect = [member1_info, member2_info] response = await get_members_in_conversation_by_id( mock_context, conversation_id="C12345", limit=2 ) assert response == { "members": [member1_info, member2_info], "next_cursor": "token123", } mock_async_paginate.assert_called_once_with( mock_chat_slack_client.conversations_members, "members", limit=2, next_cursor=None, channel="C12345", ) mock_get_user_info_by_id.assert_has_calls([ call(mock_context, member1["id"]), call(mock_context, member2["id"]), ]) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.async_paginate") @patch("arcade_slack.tools.chat.get_user_info_by_id") @patch("arcade_slack.tools.chat.list_conversations_metadata") async def test_get_members_from_conversation_id_channel_not_found( mock_list_conversations_metadata, mock_get_user_info_by_id, mock_async_paginate, mock_context, mock_chat_slack_client, mock_channel_info, ): conversations = [extract_conversation_metadata(mock_channel_info)] * 2 mock_list_conversations_metadata.return_value = { "conversations": conversations, "next_cursor": None, } member1 = {"id": "U123", "name": "testuser123"} member1_info = extract_basic_user_info(member1) member2 = {"id": "U456", "name": "testuser456"} member2_info = extract_basic_user_info(member2) mock_async_paginate.side_effect = SlackApiError( message="channel_not_found", response={"ok": False, "error": "channel_not_found"}, ) mock_get_user_info_by_id.side_effect = [member1_info, member2_info] with pytest.raises(RetryableToolError): await get_members_in_conversation_by_id(mock_context, conversation_id="C12345", limit=2) mock_async_paginate.assert_called_once_with( mock_chat_slack_client.conversations_members, "members", limit=2, next_cursor=None, channel="C12345", ) mock_get_user_info_by_id.assert_not_called() @pytest.mark.asyncio @patch("arcade_slack.tools.chat.list_conversations_metadata") @patch("arcade_slack.tools.chat.get_members_in_conversation_by_id") async def test_get_members_in_channel_by_name( mock_get_members_in_conversation_by_id, mock_list_conversations_metadata, mock_context, mock_channel_info, ): mock_list_conversations_metadata.return_value = { "conversations": [extract_conversation_metadata(mock_channel_info)], "next_cursor": None, } response = await get_members_in_channel_by_name( mock_context, mock_channel_info["name"], limit=2 ) assert response == mock_get_members_in_conversation_by_id.return_value mock_list_conversations_metadata.assert_called_once_with(mock_context, next_cursor=None) mock_get_members_in_conversation_by_id.assert_called_once_with( context=mock_context, conversation_id="C12345", limit=2, next_cursor=None, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.list_conversations_metadata") @patch("arcade_slack.tools.chat.get_members_in_conversation_by_id") async def test_get_members_in_channel_by_name_triggering_pagination( mock_get_members_in_conversation_by_id, mock_list_conversations_metadata, mock_context, mock_channel_info, ): conversation1 = copy.deepcopy(mock_channel_info) conversation1["name"] = "conversation1" conversation2 = copy.deepcopy(mock_channel_info) conversation2["name"] = "conversation2" mock_list_conversations_metadata.side_effect = [ { "conversations": [extract_conversation_metadata(conversation1)], "next_cursor": "123", }, { "conversations": [extract_conversation_metadata(conversation2)], "next_cursor": None, }, ] response = await get_members_in_channel_by_name(mock_context, conversation2["name"], limit=2) assert response == mock_get_members_in_conversation_by_id.return_value mock_list_conversations_metadata.assert_has_calls([ call(mock_context, next_cursor=None), call(mock_context, next_cursor="123"), ]) mock_get_members_in_conversation_by_id.assert_called_once_with( context=mock_context, conversation_id="C12345", limit=2, next_cursor=None, ) @pytest.mark.asyncio async def test_get_conversation_history_by_id(mock_context, mock_chat_slack_client): mock_chat_slack_client.conversations_history.return_value = { "ok": True, "messages": [{"text": "Hello, world!"}], } response = await get_messages_in_conversation_by_id(mock_context, "C12345", limit=1) assert response == {"messages": [{"text": "Hello, world!"}], "next_cursor": None} mock_chat_slack_client.conversations_history.assert_called_once_with( channel="C12345", include_all_metadata=True, inclusive=True, limit=1, cursor=None, ) # TODO: pass a current unix timestamp to the tool, instead of mocking the datetime # conversion. Have to wait until arcade.core.annotations.Inferrable is implemented. @pytest.mark.asyncio @patch("arcade_slack.tools.chat.convert_relative_datetime_to_unix_timestamp") @patch("arcade_slack.tools.chat.datetime") async def test_get_conversation_history_by_id_with_relative_datetime_args( mock_datetime, mock_convert_relative_datetime_to_unix_timestamp, mock_context, mock_chat_slack_client, ): mock_chat_slack_client.conversations_history.return_value = { "ok": True, "messages": [{"text": "Hello, world!"}], } expected_oldest_timestamp = 1716489600 expected_latest_timestamp = 1716403200 # Ideally we'd pass the current unix timestamp to the function, instead of mocking, but # currently there's no way to have a tool argument that is not exposed to the LLM. We # should have that soon, though. mock_datetime.now.return_value = datetime(2025, 1, 1, 0, 0, 0, tzinfo=timezone.utc) expected_current_unix_timestamp = int(mock_datetime.now.return_value.timestamp()) mock_convert_relative_datetime_to_unix_timestamp.side_effect = [ expected_latest_timestamp, expected_oldest_timestamp, ] response = await get_messages_in_conversation_by_id( mock_context, "C12345", oldest_relative="02:00:00", latest_relative="01:00:00", limit=1 ) assert response == {"messages": [{"text": "Hello, world!"}], "next_cursor": None} mock_convert_relative_datetime_to_unix_timestamp.assert_has_calls([ call("01:00:00", expected_current_unix_timestamp), call("02:00:00", expected_current_unix_timestamp), ]) mock_chat_slack_client.conversations_history.assert_called_once_with( channel="C12345", include_all_metadata=True, inclusive=True, limit=1, cursor=None, oldest=expected_oldest_timestamp, latest=expected_latest_timestamp, ) # TODO: pass a current unix timestamp to the tool, instead of mocking the datetime # conversion. Have to wait until arcade.core.annotations.Inferrable is implemented. @pytest.mark.asyncio @patch("arcade_slack.tools.chat.convert_datetime_to_unix_timestamp") async def test_get_conversation_history_by_id_with_absolute_datetime_args( mock_convert_datetime_to_unix_timestamp, mock_context, mock_chat_slack_client ): mock_chat_slack_client.conversations_history.return_value = { "ok": True, "messages": [{"text": "Hello, world!"}], } expected_latest_timestamp = 1716403200 expected_oldest_timestamp = 1716489600 # Ideally we'd pass the current unix timestamp to the function, instead of mocking, but # currently there's no way to have a tool argument that is not exposed to the LLM. We # should have that soon, though. mock_convert_datetime_to_unix_timestamp.side_effect = [ expected_latest_timestamp, expected_oldest_timestamp, ] response = await get_messages_in_conversation_by_id( mock_context, "C12345", oldest_datetime="2025-01-01 00:00:00", latest_datetime="2025-01-02 00:00:00", limit=1, ) assert response == {"messages": [{"text": "Hello, world!"}], "next_cursor": None} mock_convert_datetime_to_unix_timestamp.assert_has_calls([ call("2025-01-02 00:00:00"), call("2025-01-01 00:00:00"), ]) mock_chat_slack_client.conversations_history.assert_called_once_with( channel="C12345", include_all_metadata=True, inclusive=True, limit=1, cursor=None, oldest=expected_oldest_timestamp, latest=expected_latest_timestamp, ) @pytest.mark.asyncio async def test_get_conversation_history_by_id_with_messed_oldest_args( mock_context, mock_chat_slack_client ): with pytest.raises(ToolExecutionError): await get_messages_in_conversation_by_id( mock_context, "C12345", oldest_datetime="2025-01-01 00:00:00", oldest_relative="01:00:00", ) @pytest.mark.asyncio async def test_get_conversation_history_by_id_with_messed_latest_args( mock_context, mock_chat_slack_client ): with pytest.raises(ToolExecutionError): await get_messages_in_conversation_by_id( mock_context, "C12345", latest_datetime="2025-01-01 00:00:00", latest_relative="01:00:00", ) @pytest.mark.asyncio async def test_get_conversation_history_by_name(mock_context, mock_chat_slack_client): mock_chat_slack_client.conversations_list.return_value = { "ok": True, "channels": [ { "id": "C12345", "name": "general", "is_member": True, "is_channel": True, } ], } mock_chat_slack_client.conversations_history.return_value = { "ok": True, "messages": [{"text": "Hello, world!"}], } response = await get_messages_in_channel_by_name(mock_context, "general", limit=1) assert response == {"messages": [{"text": "Hello, world!"}], "next_cursor": None} mock_chat_slack_client.conversations_history.assert_called_once_with( channel="C12345", include_all_metadata=True, inclusive=True, limit=1, cursor=None ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.retrieve_conversations_by_user_ids") async def test_get_direct_message_conversation_metadata_by_username( mock_retrieve_conversations_by_user_ids, mock_context, mock_chat_slack_client, mock_users_slack_client, ): mock_chat_slack_client.auth_test.return_value = { "ok": True, "user_id": "U1", "team_id": "T1", "user": "user1", } mock_users_slack_client.users_list.return_value = { "ok": True, "members": [ {"id": "U1", "name": "user1"}, {"id": "U2", "name": "user2"}, ], "response_metadata": {"next_cursor": None}, } conversation = { "id": "C12345", "type": ConversationTypeSlackName.IM.value, "is_im": True, "members": ["U1", "U2"], } mock_retrieve_conversations_by_user_ids.return_value = [conversation] response = await get_direct_message_conversation_metadata_by_username( context=mock_context, username="user2" ) assert response == conversation mock_retrieve_conversations_by_user_ids.assert_called_once_with( list_conversations_func=list_conversations_metadata, get_members_in_conversation_func=get_members_in_conversation_by_id, context=mock_context, conversation_types=[ConversationType.DIRECT_MESSAGE], user_ids=["U1", "U2"], exact_match=True, limit=1, next_cursor=None, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.retrieve_conversations_by_user_ids") async def test_get_direct_message_conversation_metadata_by_username_username_not_found( mock_retrieve_conversations_by_user_ids, mock_context, mock_chat_slack_client, mock_users_slack_client, ): mock_chat_slack_client.users_identity.return_value = { "ok": True, "user": {"id": "U1", "name": "user1"}, "team": {"id": "T1", "name": "team1"}, } mock_users_slack_client.users_list.return_value = { "ok": True, "members": [ {"id": "U1", "name": "user1"}, {"id": "U2", "name": "user2"}, ], "response_metadata": {"next_cursor": None}, } mock_retrieve_conversations_by_user_ids.side_effect = TimeoutError() with pytest.raises(RetryableToolError): await get_direct_message_conversation_metadata_by_username( context=mock_context, username="user999" ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.get_messages_in_conversation_by_id") @patch("arcade_slack.tools.chat.get_direct_message_conversation_metadata_by_username") async def test_get_messages_in_direct_conversation_by_username( mock_get_direct_message_conversation_metadata_by_username, mock_get_messages_in_conversation_by_id, mock_context, ): mock_get_direct_message_conversation_metadata_by_username.return_value = { "id": "C12345", } response = await get_messages_in_direct_message_conversation_by_username( context=mock_context, username="user2" ) assert response == mock_get_messages_in_conversation_by_id.return_value mock_get_direct_message_conversation_metadata_by_username.assert_called_once_with( context=mock_context, username="user2" ) mock_get_messages_in_conversation_by_id.assert_called_once_with( context=mock_context, conversation_id="C12345", oldest_relative=None, latest_relative=None, oldest_datetime=None, latest_datetime=None, limit=None, next_cursor=None, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.get_direct_message_conversation_metadata_by_username") async def test_get_messages_in_direct_conversation_by_username_not_found( mock_get_direct_message_conversation_metadata_by_username, mock_context, ): mock_get_direct_message_conversation_metadata_by_username.return_value = None with pytest.raises(ToolExecutionError): await get_messages_in_direct_message_conversation_by_username( context=mock_context, username="user2" ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.retrieve_conversations_by_user_ids") async def test_get_multi_person_direct_message_conversation_metadata_by_username( mock_retrieve_conversations_by_user_ids, mock_context, mock_chat_slack_client, mock_users_slack_client, ): mock_chat_slack_client.auth_test.return_value = { "ok": True, "user_id": "U1", "team_id": "T1", "user": "user1", } mock_users_slack_client.users_list.return_value = { "ok": True, "members": [ {"id": "U1", "name": "user1"}, {"id": "U2", "name": "user2"}, {"id": "U3", "name": "user3"}, {"id": "U4", "name": "user4"}, {"id": "U5", "name": "user5"}, ], "response_metadata": {"next_cursor": None}, } conversation = { "id": "C12345", "type": ConversationTypeSlackName.MPIM.value, "is_mpim": True, "members": ["U1", "U4", "U5"], } mock_retrieve_conversations_by_user_ids.return_value = [conversation] response = await get_multi_person_dm_conversation_metadata_by_usernames( context=mock_context, usernames=["user1", "user4", "user5"] ) assert response == conversation mock_retrieve_conversations_by_user_ids.assert_called_once_with( list_conversations_func=list_conversations_metadata, get_members_in_conversation_func=get_members_in_conversation_by_id, context=mock_context, conversation_types=[ConversationType.MULTI_PERSON_DIRECT_MESSAGE], user_ids=["U1", "U4", "U5"], exact_match=True, limit=1, next_cursor=None, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.retrieve_conversations_by_user_ids") async def test_get_multi_person_direct_message_conversation_metadata_by_username_username_not_found( mock_retrieve_conversations_by_user_ids, mock_context, mock_chat_slack_client, mock_users_slack_client, ): mock_chat_slack_client.users_identity.return_value = { "ok": True, "user": {"id": "U1", "name": "user1"}, "team": {"id": "T1", "name": "team1"}, } mock_users_slack_client.users_list.return_value = { "ok": True, "members": [ {"id": "U1", "name": "user1"}, {"id": "U2", "name": "user2"}, ], "response_metadata": {"next_cursor": None}, } mock_retrieve_conversations_by_user_ids.side_effect = TimeoutError() with pytest.raises(RetryableToolError): await get_multi_person_dm_conversation_metadata_by_usernames( context=mock_context, usernames=["user999", "user1", "user2"] ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.get_messages_in_conversation_by_id") @patch("arcade_slack.tools.chat.get_multi_person_dm_conversation_metadata_by_usernames") async def test_get_messages_in_multi_person_dm_conversation_by_usernames( mock_get_multi_person_dm_conversation_metadata_by_usernames, mock_get_messages_in_conversation_by_id, mock_context, ): mock_get_multi_person_dm_conversation_metadata_by_usernames.return_value = { "id": "C12345", } response = await get_messages_in_multi_person_dm_conversation_by_usernames( context=mock_context, usernames=["user1", "user4", "user5"] ) assert response == mock_get_messages_in_conversation_by_id.return_value mock_get_multi_person_dm_conversation_metadata_by_usernames.assert_called_once_with( context=mock_context, usernames=["user1", "user4", "user5"] ) mock_get_messages_in_conversation_by_id.assert_called_once_with( context=mock_context, conversation_id="C12345", oldest_relative=None, latest_relative=None, oldest_datetime=None, latest_datetime=None, limit=None, next_cursor=None, ) @pytest.mark.asyncio @patch("arcade_slack.tools.chat.get_multi_person_dm_conversation_metadata_by_usernames") async def test_get_messages_in_multi_person_dm_conversation_by_usernames_not_found( mock_get_multi_person_dm_conversation_metadata_by_usernames, mock_context, ): mock_get_multi_person_dm_conversation_metadata_by_usernames.return_value = None with pytest.raises(ToolExecutionError): await get_messages_in_direct_message_conversation_by_username( context=mock_context, username="user2" )