arcade-mcp/toolkits/slack/tests/test_chat.py

954 lines
32 KiB
Python

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", "is_member": True, "is_group": True}],
}
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"
)