implements additional tools for Slack related to retrieving conversations metadata, list of members, history of messages, as well as sending messages to private/public channels and DMs / multi-person DMs. --------- Co-authored-by: Eric Gustin <eric@arcade-ai.com> Co-authored-by: Renato Byrro <rmbyrro@gmail.com>
187 lines
5.8 KiB
Python
187 lines
5.8 KiB
Python
import asyncio
|
|
from unittest.mock import AsyncMock, call, patch
|
|
|
|
import pytest
|
|
from slack_sdk.errors import SlackApiError
|
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
|
|
from arcade_slack.exceptions import PaginationTimeoutError
|
|
from arcade_slack.utils import async_paginate
|
|
|
|
|
|
@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."
|
|
)
|