### Overview Major restructuring from monolithic `arcade-ai` package to modular library architecture with standardized uv-based dependency management.  ### New Package Structure - **`arcade-tdk`** - Lightweight toolkit development kit (core decorators, auth) - **`arcade-core`** - Core execution engine and catalog functionality - **`arcade-serve`** - FastAPI/MCP server components - **`arcade-ai`** - Meta package that includes CLI functionality. Optionally include evals via the `evals` extra. Optionally include all packages via the `all` extra. ### Key Benefits - **Lighter Dependencies**: Toolkits now depend only on `arcade-tdk` (~2 deps) vs full `arcade-ai` (~30+ deps) - **Faster Builds**: uv provides 10-100x faster dependency resolution and installation - **Better Modularity**: Clear separation of concerns, consumers import only what they need - **Standard Tooling**: Eliminates custom poetry scripts, uses standard Python packaging ### Migration Impact - All 20 toolkits converted from poetry → uv with `arcade-tdk` dependencies plus `arcade-ai[evals]` and `arcade-serve` dev dependencies. When developing locally, devs should install toolkits via `make install-local`. - Modern Python 3.10+ type hints throughout - Standardized build system with hatchling backend - Enhanced Makefile with robust toolkit management commands - Removed `arcade dev` CLI command - Reduce the number of files created by `arcade new` and add an option to not generate a tests and evals folder. This foundation enables faster development cycles and cleaner dependency chains for the growing toolkit ecosystem. ### Todo After this PR is merged - [ ] Post-merge workflow(s) (release & publish containers, etc) - [ ] Release order plan. @EricGustin suggests releasing in the following order: 1. `arcade-core` version 0.1.0 2. `arcade-serve` version 0.1.0 and `arcade-tdk` version 0.1.0 3. `arcade-ai` version 2.0.0 4. Patch release for all toolkits (all changes in toolkits are internal refactors) - [ ] [Update docs](https://github.com/ArcadeAI/docs/pull/318) --------- Co-authored-by: Eric Gustin <eric@arcade.dev> Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
684 lines
22 KiB
Python
684 lines
22 KiB
Python
import asyncio
|
|
from unittest.mock import AsyncMock, MagicMock, call, patch
|
|
|
|
import pytest
|
|
from arcade_tdk import ToolContext
|
|
from slack_sdk.errors import SlackApiError
|
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
|
|
from arcade_slack.exceptions import PaginationTimeoutError
|
|
from arcade_slack.models import (
|
|
ConversationType,
|
|
ConversationTypeSlackName,
|
|
)
|
|
from arcade_slack.tools.chat import (
|
|
get_members_in_conversation_by_id,
|
|
list_conversations_metadata,
|
|
)
|
|
from arcade_slack.utils import (
|
|
async_paginate,
|
|
convert_conversation_type_to_slack_name,
|
|
filter_conversations_by_user_ids,
|
|
retrieve_conversations_by_user_ids,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_paginate():
|
|
mock_slack_client = AsyncMock()
|
|
mock_slack_client.conversations_list.return_value = {
|
|
"ok": True,
|
|
"channels": [{"id": "123"}],
|
|
"response_metadata": {"next_cursor": None},
|
|
}
|
|
|
|
results, next_cursor = await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
)
|
|
|
|
assert results == [{"id": "123"}]
|
|
assert next_cursor is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_paginate_with_response_error():
|
|
mock_slack_client = AsyncMock()
|
|
mock_slack_client.conversations_list.side_effect = SlackApiError(
|
|
message="slack_error",
|
|
response={"ok": False, "error": "slack_error"},
|
|
)
|
|
|
|
with pytest.raises(SlackApiError) as e:
|
|
await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
)
|
|
assert str(e.value) == "slack_error"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_paginate_with_custom_pagination_args():
|
|
mock_slack_client = AsyncMock()
|
|
mock_slack_client.conversations_list.return_value = {
|
|
"ok": True,
|
|
"channels": [{"id": "123"}],
|
|
"response_metadata": {"next_cursor": "456"},
|
|
}
|
|
|
|
results, next_cursor = await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
limit=1,
|
|
next_cursor="123",
|
|
hello="world",
|
|
)
|
|
|
|
assert results == [{"id": "123"}]
|
|
assert next_cursor == "456"
|
|
|
|
mock_slack_client.conversations_list.assert_called_once_with(
|
|
hello="world",
|
|
limit=1,
|
|
cursor="123",
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"test_limit, last_next_cursor, last_expected_limit",
|
|
[(5, "cursor3", 1), (None, None, 2)],
|
|
)
|
|
async def test_async_paginate_large_limit(test_limit, last_next_cursor, last_expected_limit):
|
|
mock_slack_client = AsyncMock(spec=AsyncWebClient)
|
|
mock_slack_client.conversations_list.side_effect = [
|
|
{
|
|
"ok": True,
|
|
"channels": [{"id": "channel1"}, {"id": "channel2"}],
|
|
"response_metadata": {"next_cursor": "cursor1"},
|
|
},
|
|
{
|
|
"ok": True,
|
|
"channels": [{"id": "channel3"}, {"id": "channel4"}],
|
|
"response_metadata": {"next_cursor": "cursor2"},
|
|
},
|
|
{
|
|
"ok": True,
|
|
"channels": [{"id": "channel5"}],
|
|
"response_metadata": {"next_cursor": last_next_cursor},
|
|
},
|
|
]
|
|
|
|
with patch("arcade_slack.utils.MAX_PAGINATION_SIZE_LIMIT", 2):
|
|
results, next_cursor = await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
limit=test_limit,
|
|
hello="world",
|
|
)
|
|
|
|
assert results == [
|
|
{"id": "channel1"},
|
|
{"id": "channel2"},
|
|
{"id": "channel3"},
|
|
{"id": "channel4"},
|
|
{"id": "channel5"},
|
|
]
|
|
assert next_cursor == last_next_cursor
|
|
assert mock_slack_client.conversations_list.call_count == 3
|
|
mock_slack_client.conversations_list.assert_has_calls([
|
|
call(hello="world", limit=2, cursor=None),
|
|
call(hello="world", limit=2, cursor="cursor1"),
|
|
call(hello="world", limit=last_expected_limit, cursor="cursor2"),
|
|
])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_paginate_large_limit_with_response_error():
|
|
mock_slack_client = AsyncMock()
|
|
mock_slack_client.conversations_list.side_effect = [
|
|
{
|
|
"ok": True,
|
|
"channels": [{"id": "channel1"}, {"id": "channel2"}],
|
|
"response_metadata": {"next_cursor": "cursor1"},
|
|
},
|
|
SlackApiError(message="slack_error", response={"ok": False, "error": "slack_error"}),
|
|
{
|
|
"ok": True,
|
|
"channels": [{"id": "channel5"}],
|
|
"response_metadata": {"next_cursor": "cursor3"},
|
|
},
|
|
]
|
|
|
|
with (
|
|
patch("arcade_slack.utils.MAX_PAGINATION_SIZE_LIMIT", 2),
|
|
pytest.raises(SlackApiError) as e,
|
|
):
|
|
await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
limit=5,
|
|
hello="world",
|
|
)
|
|
assert str(e.value) == "slack_error"
|
|
|
|
assert mock_slack_client.conversations_list.call_count == 2
|
|
mock_slack_client.conversations_list.assert_has_calls([
|
|
call(hello="world", limit=2, cursor=None),
|
|
call(hello="world", limit=2, cursor="cursor1"),
|
|
])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_paginate_with_timeout():
|
|
# Mock Slack client
|
|
mock_slack_client = AsyncMock()
|
|
|
|
# Simulate a network delay by making the mock function sleep
|
|
async def mock_conversations_list(*args, **kwargs):
|
|
await asyncio.sleep(1) # Sleep for 1 second to simulate delay
|
|
return {
|
|
"ok": True,
|
|
"channels": [{"id": "123"}],
|
|
"response_metadata": {"next_cursor": None},
|
|
}
|
|
|
|
mock_slack_client.conversations_list.side_effect = mock_conversations_list
|
|
|
|
# Set a low timeout to trigger the timeout error quickly during the test
|
|
max_pagination_timeout_seconds = 0.1 # 100 milliseconds
|
|
|
|
with pytest.raises(PaginationTimeoutError) as exc_info:
|
|
await async_paginate(
|
|
func=mock_slack_client.conversations_list,
|
|
response_key="channels",
|
|
max_pagination_timeout_seconds=max_pagination_timeout_seconds,
|
|
)
|
|
|
|
assert (
|
|
str(exc_info.value)
|
|
== f"The pagination process timed out after {max_pagination_timeout_seconds} seconds."
|
|
)
|
|
|
|
|
|
def test_filter_conversations_by_user_ids():
|
|
conversations = [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}]},
|
|
{"id": "456", "members": [{"id": "user2"}, {"id": "user3"}]},
|
|
]
|
|
response = filter_conversations_by_user_ids(
|
|
conversations=conversations,
|
|
user_ids=["user1", "user2"],
|
|
exact_match=False,
|
|
)
|
|
assert response == [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}]},
|
|
]
|
|
|
|
|
|
def test_filter_conversations_by_user_ids_empty_response():
|
|
conversations = [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user3"}, {"id": "user4"}]},
|
|
{"id": "456", "members": [{"id": "user2"}, {"id": "user3"}, {"id": "user4"}]},
|
|
]
|
|
response = filter_conversations_by_user_ids(
|
|
conversations=conversations,
|
|
user_ids=["user1", "user2"],
|
|
exact_match=False,
|
|
)
|
|
assert response == []
|
|
|
|
|
|
def test_filter_conversations_by_user_ids_multiple_matches():
|
|
conversations = [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}]},
|
|
{"id": "456", "members": [{"id": "user2"}, {"id": "user3"}]},
|
|
{
|
|
"id": "789",
|
|
"members": [{"id": "user4"}, {"id": "user1"}, {"id": "user2"}, {"id": "user3"}],
|
|
},
|
|
]
|
|
response = filter_conversations_by_user_ids(
|
|
conversations=conversations,
|
|
user_ids=["user1", "user2"],
|
|
exact_match=False,
|
|
)
|
|
assert response == [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}]},
|
|
{
|
|
"id": "789",
|
|
"members": [{"id": "user4"}, {"id": "user1"}, {"id": "user2"}, {"id": "user3"}],
|
|
},
|
|
]
|
|
|
|
|
|
def test_filter_conversations_by_user_ids_exact_match():
|
|
conversations = [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}]},
|
|
{"id": "456", "members": [{"id": "user2"}, {"id": "user3"}]},
|
|
]
|
|
response = filter_conversations_by_user_ids(
|
|
conversations=conversations,
|
|
user_ids=["user1", "user2"],
|
|
exact_match=True,
|
|
)
|
|
assert response == [{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}]}]
|
|
|
|
|
|
def test_filter_conversations_by_user_ids_exact_match_empty_response():
|
|
conversations = [
|
|
{"id": "123", "members": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}]},
|
|
{"id": "456", "members": [{"id": "user2"}, {"id": "user3"}]},
|
|
]
|
|
response = filter_conversations_by_user_ids(
|
|
conversations=conversations,
|
|
user_ids=["user1", "user2"],
|
|
exact_match=True,
|
|
)
|
|
assert response == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"search_user_ids, conversation_types, exact_match, limit, expected_conversation_ids",
|
|
[
|
|
(["U1", "U2"], [ConversationType.DIRECT_MESSAGE], False, 1, ["C1"]),
|
|
(["U1", "U2"], [ConversationType.DIRECT_MESSAGE], True, 1, ["C1"]),
|
|
(["U1", "U2", "U3"], [ConversationType.DIRECT_MESSAGE], False, 1, []),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
False,
|
|
10,
|
|
["C1", "C3", "C4"],
|
|
),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
True,
|
|
10,
|
|
["C1", "C3"],
|
|
),
|
|
],
|
|
)
|
|
async def test_retrieve_conversations_by_user_ids(
|
|
mock_chat_slack_client,
|
|
mock_users_slack_client,
|
|
search_user_ids,
|
|
conversation_types,
|
|
exact_match,
|
|
limit,
|
|
expected_conversation_ids,
|
|
):
|
|
context = MagicMock(spec=ToolContext)
|
|
context.authorization = MagicMock()
|
|
context.authorization.token = MagicMock()
|
|
|
|
conversation_types_slack_name_str = [
|
|
convert_conversation_type_to_slack_name(conv_type).value
|
|
for conv_type in conversation_types or ConversationType
|
|
]
|
|
|
|
conversations = [
|
|
{
|
|
"conversation": {
|
|
"id": "C1",
|
|
"type": ConversationTypeSlackName.IM.value,
|
|
"name": "im-1",
|
|
"is_channel": False,
|
|
"is_im": True,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C2",
|
|
"type": ConversationTypeSlackName.IM.value,
|
|
"name": "im-2",
|
|
"is_channel": False,
|
|
"is_im": True,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U2", "U3"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C3",
|
|
"type": ConversationTypeSlackName.PUBLIC_CHANNEL.value,
|
|
"name": "general",
|
|
"is_channel": True,
|
|
"is_im": False,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C4",
|
|
"type": ConversationTypeSlackName.PUBLIC_CHANNEL.value,
|
|
"name": "random",
|
|
"is_channel": True,
|
|
"is_im": False,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2", "U3", "U4"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
{"ok": True, "user": {"id": "U4", "team_id": "T123", "name": "user4"}},
|
|
],
|
|
},
|
|
]
|
|
|
|
conversations_listed = [
|
|
conversation
|
|
for conversation in conversations
|
|
if conversation["conversation"]["type"] in conversation_types_slack_name_str
|
|
]
|
|
|
|
mock_chat_slack_client.conversations_list.return_value = {
|
|
"ok": True,
|
|
"channels": [conversation["conversation"] for conversation in conversations_listed],
|
|
"response_metadata": {"next_cursor": None},
|
|
}
|
|
|
|
mock_chat_slack_client.conversations_members.side_effect = [
|
|
conversation["members"] for conversation in conversations_listed
|
|
]
|
|
|
|
mock_users_slack_client.users_info.side_effect = [
|
|
user for conversation in conversations_listed for user in conversation["users"]
|
|
]
|
|
|
|
conversations_found = await retrieve_conversations_by_user_ids(
|
|
list_conversations_func=list_conversations_metadata,
|
|
get_members_in_conversation_func=get_members_in_conversation_by_id,
|
|
context=context,
|
|
conversation_types=conversation_types,
|
|
user_ids=search_user_ids,
|
|
exact_match=exact_match,
|
|
limit=limit,
|
|
next_cursor=None,
|
|
)
|
|
|
|
assert [conversation["id"] for conversation in conversations_found] == expected_conversation_ids
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"search_user_ids, conversation_types, exact_match, limit, "
|
|
"expected_conversation_ids, expected_conversations_list_calls"
|
|
),
|
|
[
|
|
(
|
|
["U1", "U2", "U3"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE],
|
|
False,
|
|
None,
|
|
["C1", "C3"],
|
|
2,
|
|
),
|
|
(
|
|
["U1", "U2", "U3"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE],
|
|
True,
|
|
None,
|
|
["C1"],
|
|
2,
|
|
),
|
|
(["U1", "U2", "U99"], [ConversationType.MULTI_PERSON_DIRECT_MESSAGE], False, None, [], 2),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
False,
|
|
None,
|
|
["C1", "C3", "C4", "C6"],
|
|
2,
|
|
),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
False,
|
|
1,
|
|
["C1"],
|
|
2,
|
|
),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
False,
|
|
3,
|
|
["C1", "C3", "C4"],
|
|
2,
|
|
),
|
|
(
|
|
["U1", "U2"],
|
|
[ConversationType.MULTI_PERSON_DIRECT_MESSAGE, ConversationType.PUBLIC_CHANNEL],
|
|
True,
|
|
None,
|
|
["C4"],
|
|
2,
|
|
),
|
|
],
|
|
)
|
|
async def test_retrieve_conversations_by_user_ids_with_pagination(
|
|
mock_chat_slack_client,
|
|
mock_users_slack_client,
|
|
search_user_ids,
|
|
conversation_types,
|
|
exact_match,
|
|
limit,
|
|
expected_conversation_ids,
|
|
expected_conversations_list_calls,
|
|
):
|
|
context = MagicMock(spec=ToolContext)
|
|
context.authorization = MagicMock()
|
|
context.authorization.token = MagicMock()
|
|
|
|
conversation_types_slack_name_str = [
|
|
convert_conversation_type_to_slack_name(conv_type).value
|
|
for conv_type in conversation_types or ConversationType
|
|
]
|
|
|
|
conversations = [
|
|
{
|
|
"conversation": {
|
|
"id": "C1",
|
|
"type": ConversationTypeSlackName.MPIM.value,
|
|
"name": "mpim-1",
|
|
"is_channel": False,
|
|
"is_im": False,
|
|
"is_mpim": True,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2", "U3"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C2",
|
|
"type": ConversationTypeSlackName.MPIM.value,
|
|
"name": "mpim-2",
|
|
"is_channel": False,
|
|
"is_im": False,
|
|
"is_mpim": True,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U2", "U3"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C3",
|
|
"type": ConversationTypeSlackName.MPIM.value,
|
|
"name": "mpim-3",
|
|
"is_channel": False,
|
|
"is_im": False,
|
|
"is_mpim": True,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2", "U3", "U4"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
{"ok": True, "user": {"id": "U4", "team_id": "T123", "name": "user4"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C4",
|
|
"type": ConversationTypeSlackName.PUBLIC_CHANNEL.value,
|
|
"name": "channel-4",
|
|
"is_channel": True,
|
|
"is_im": False,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C5",
|
|
"type": ConversationTypeSlackName.PUBLIC_CHANNEL.value,
|
|
"name": "channel-5",
|
|
"is_channel": True,
|
|
"is_im": False,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U2", "U3", "U4"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
{"ok": True, "user": {"id": "U4", "team_id": "T123", "name": "user4"}},
|
|
],
|
|
},
|
|
{
|
|
"conversation": {
|
|
"id": "C6",
|
|
"type": ConversationTypeSlackName.PUBLIC_CHANNEL.value,
|
|
"name": "channel-6",
|
|
"is_channel": True,
|
|
"is_im": False,
|
|
"is_member": True,
|
|
},
|
|
"members": {
|
|
"ok": True,
|
|
"members": ["U1", "U2", "U3", "U4"],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
"users": [
|
|
{"ok": True, "user": {"id": "U1", "team_id": "T123", "name": "user1"}},
|
|
{"ok": True, "user": {"id": "U2", "team_id": "T123", "name": "user2"}},
|
|
{"ok": True, "user": {"id": "U3", "team_id": "T123", "name": "user3"}},
|
|
{"ok": True, "user": {"id": "U4", "team_id": "T123", "name": "user4"}},
|
|
],
|
|
},
|
|
]
|
|
|
|
conversations_listed = [
|
|
conversation
|
|
for conversation in conversations
|
|
if conversation["conversation"]["type"] in conversation_types_slack_name_str
|
|
]
|
|
|
|
split_size = len(conversations_listed) // 2
|
|
|
|
conversations_listed_1 = conversations_listed[:split_size]
|
|
conversations_listed_2 = conversations_listed[split_size:]
|
|
|
|
mock_chat_slack_client.conversations_list.side_effect = [
|
|
{
|
|
"ok": True,
|
|
"channels": [conversation["conversation"] for conversation in conversations_listed_1],
|
|
"response_metadata": {"next_cursor": "cursor_1"},
|
|
},
|
|
{
|
|
"ok": True,
|
|
"channels": [conversation["conversation"] for conversation in conversations_listed_2],
|
|
"response_metadata": {"next_cursor": None},
|
|
},
|
|
]
|
|
|
|
mock_chat_slack_client.conversations_members.side_effect = [
|
|
conversation["members"] for conversation in conversations_listed
|
|
]
|
|
|
|
mock_users_slack_client.users_info.side_effect = [
|
|
user for conversation in conversations_listed for user in conversation["users"]
|
|
]
|
|
|
|
conversations_found = await retrieve_conversations_by_user_ids(
|
|
list_conversations_func=list_conversations_metadata,
|
|
get_members_in_conversation_func=get_members_in_conversation_by_id,
|
|
context=context,
|
|
conversation_types=conversation_types,
|
|
user_ids=search_user_ids,
|
|
exact_match=exact_match,
|
|
limit=limit,
|
|
next_cursor=None,
|
|
)
|
|
|
|
assert [conversation["id"] for conversation in conversations_found] == expected_conversation_ids
|
|
assert mock_chat_slack_client.conversations_list.call_count == expected_conversations_list_calls
|