dc6079821b
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
Automatic PR Merger / mergepal (push) Has been cancelled
CI Github Actions / Run Tests (push) Has been cancelled
Semgrep / Semgrep Workflow Security Scan (push) Has been cancelled
988 lines
29 KiB
Python
988 lines
29 KiB
Python
from http import HTTPStatus
|
|
import json
|
|
import logging
|
|
import time
|
|
from typing import Any, Dict, Text
|
|
from unittest import mock
|
|
from unittest.mock import Mock, patch
|
|
|
|
from aioresponses import aioresponses
|
|
import pytest
|
|
from sanic.compat import Header
|
|
from sanic.request import Request
|
|
|
|
from rasa.core.channels import SlackInput
|
|
from rasa.core.channels.channel import UserMessage
|
|
from rasa.shared.exceptions import InvalidConfigException
|
|
from tests.utilities import json_of_latest_request, latest_request
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SLACK_TEST_ATTACHMENT = {
|
|
"fallback": "Financial Advisor Summary",
|
|
"color": "#36a64f",
|
|
"author_name": "ABE",
|
|
"title": "Financial Advisor Summary",
|
|
"title_link": "http://tenfactorialrocks.com",
|
|
"image_url": "https://r.com/cancel/r12",
|
|
"thumb_url": "https://r.com/cancel/r12",
|
|
"actions": [
|
|
{
|
|
"type": "button",
|
|
"text": "\ud83d\udcc8 Dashboard",
|
|
"url": "https://r.com/cancel/r12",
|
|
"style": "primary",
|
|
},
|
|
{
|
|
"type": "button",
|
|
"text": "\ud83d\udccb Download XL",
|
|
"url": "https://r.com/cancel/r12",
|
|
"style": "danger",
|
|
},
|
|
{
|
|
"type": "button",
|
|
"text": "\ud83d\udce7 E-Mail",
|
|
"url": "https://r.com/cancel/r12",
|
|
"style": "danger",
|
|
},
|
|
],
|
|
"footer": "Powered by 1010rocks",
|
|
"ts": 1531889719,
|
|
}
|
|
|
|
|
|
def test_slack_metadata():
|
|
user = "user1"
|
|
channel = "channel1"
|
|
authed_users = ["XXXXXXX", "YYYYYYY", "ZZZZZZZ"]
|
|
ts = "1579802617.000800"
|
|
header = {"content-type": "application/json"}
|
|
direct_message_event = {
|
|
"authed_users": authed_users,
|
|
"event": {
|
|
"client_msg_id": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
|
"type": "message",
|
|
"text": "hello world",
|
|
"user": user,
|
|
"ts": ts,
|
|
"team": "XXXXXXXXX",
|
|
"blocks": [
|
|
{
|
|
"type": "rich_text",
|
|
"block_id": "XXXXX",
|
|
"elements": [
|
|
{
|
|
"type": "rich_text_section",
|
|
"elements": [{"type": "text", "text": "hi"}],
|
|
}
|
|
],
|
|
}
|
|
],
|
|
"channel": channel,
|
|
"event_ts": "1579802617.000800",
|
|
"channel_type": "im",
|
|
},
|
|
}
|
|
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
r = Mock()
|
|
r.json = direct_message_event
|
|
r.headers = header
|
|
metadata = input_channel.get_metadata(request=r)
|
|
assert metadata["out_channel"] == channel
|
|
assert metadata["users"] == authed_users
|
|
assert metadata["thread_id"] == ts
|
|
|
|
|
|
def test_slack_form_metadata():
|
|
user = "user1"
|
|
channel = "channel1"
|
|
authed_user = "XXXXXXX"
|
|
ts = "1579802617.000800"
|
|
header = {"content-type": "application/x-www-form-urlencoded"}
|
|
payload = {
|
|
"type": "block_actions",
|
|
"user": {"id": authed_user, "username": user, "name": "name"},
|
|
"channel": {"id": channel},
|
|
"message": {
|
|
"type": "message",
|
|
"text": "text",
|
|
"user": authed_user,
|
|
"ts": ts,
|
|
"blocks": [
|
|
{
|
|
"type": "actions",
|
|
"block_id": "XXXXX",
|
|
"elements": [
|
|
{
|
|
"type": "button",
|
|
"action_id": "XXXXX",
|
|
"text": {"type": "plain_text", "text": "text"},
|
|
"value": "value",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
}
|
|
form_event = {"payload": [json.dumps(payload)]}
|
|
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
r = Mock()
|
|
r.form = form_event
|
|
r.headers = header
|
|
metadata = input_channel.get_metadata(request=r)
|
|
assert metadata["out_channel"] == channel
|
|
assert metadata["users"][0] == authed_user
|
|
assert metadata["thread_id"] == ts
|
|
|
|
|
|
def test_slack_metadata_missing_keys():
|
|
channel = "channel1"
|
|
ts = "1579802617.000800"
|
|
header = {"content-type": "application/json"}
|
|
direct_message_event = {
|
|
"event": {
|
|
"client_msg_id": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
|
"type": "message",
|
|
"text": "hello world",
|
|
"ts": ts,
|
|
"team": "XXXXXXXXX",
|
|
"blocks": [
|
|
{
|
|
"type": "rich_text",
|
|
"block_id": "XXXXX",
|
|
"elements": [
|
|
{
|
|
"type": "rich_text_section",
|
|
"elements": [{"type": "text", "text": "hi"}],
|
|
}
|
|
],
|
|
}
|
|
],
|
|
"channel": channel,
|
|
"event_ts": "1579802617.000800",
|
|
"channel_type": "im",
|
|
}
|
|
}
|
|
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
r = Mock()
|
|
r.json = direct_message_event
|
|
r.headers = header
|
|
metadata = input_channel.get_metadata(request=r)
|
|
assert metadata["users"] == []
|
|
assert metadata["out_channel"] == channel
|
|
assert metadata["thread_id"] == ts
|
|
|
|
|
|
def test_slack_form_metadata_missing_keys():
|
|
channel = "channel1"
|
|
ts = "1579802617.000800"
|
|
header = {"content-type": "application/x-www-form-urlencoded"}
|
|
payload = {
|
|
"type": "block_actions",
|
|
"channel": {"id": channel},
|
|
"message": {
|
|
"type": "message",
|
|
"text": "text",
|
|
"ts": ts,
|
|
"blocks": [
|
|
{
|
|
"type": "actions",
|
|
"block_id": "XXXXX",
|
|
"elements": [
|
|
{
|
|
"type": "button",
|
|
"action_id": "XXXXX",
|
|
"text": {"type": "plain_text", "text": "text"},
|
|
"value": "value",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
},
|
|
}
|
|
form_event = {"payload": [json.dumps(payload)]}
|
|
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
r = Mock()
|
|
r.form = form_event
|
|
r.headers = header
|
|
metadata = input_channel.get_metadata(request=r)
|
|
assert metadata["users"] == []
|
|
assert metadata["out_channel"] == channel
|
|
assert metadata["thread_id"] == ts
|
|
|
|
|
|
def test_slack_no_metadata():
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
r = Mock()
|
|
metadata = input_channel.get_metadata(request=r)
|
|
assert metadata == {}
|
|
|
|
|
|
def test_slack_message_sanitization():
|
|
test_uid = "17213535"
|
|
target_message_1 = "You can sit here if you want"
|
|
target_message_2 = "Hey, you can sit here if you want !"
|
|
target_message_3 = "Hey, you can sit here if you want!"
|
|
target_message_4 = "convert garbled url to vicdb-f.net"
|
|
target_message_5 = "convert multiple garbled url to vicdb-f.net. Also eemdb-p.net"
|
|
|
|
uid_token = f"<@{test_uid}>"
|
|
raw_messages = [
|
|
test.format(uid=uid_token)
|
|
for test in [
|
|
"You can sit here {uid} if you want{uid}",
|
|
"{uid} You can sit here if you want{uid} ",
|
|
"{uid}You can sit here if you want {uid}",
|
|
# those last cases may be disputable
|
|
# as we're virtually altering the entered text,
|
|
# but this seem to be the correct course of action
|
|
# (to be decided)
|
|
"You can sit here{uid}if you want",
|
|
"Hey {uid}, you can sit here if you want{uid}!",
|
|
"Hey{uid} , you can sit here if you want {uid}!",
|
|
"convert garbled url to <http://vicdb-f.net|vicdb-f.net>",
|
|
"convert multiple garbled url to <http://vicdb-f.net|vicdb-f.net>. "
|
|
"Also <http://eemdb-p.net|eemdb-p.net>",
|
|
]
|
|
]
|
|
|
|
target_messages = [
|
|
target_message_1,
|
|
target_message_1,
|
|
target_message_1,
|
|
target_message_1,
|
|
target_message_2,
|
|
target_message_3,
|
|
target_message_4,
|
|
target_message_5,
|
|
]
|
|
|
|
sanitized_messages = [
|
|
SlackInput._sanitize_user_message(message, [test_uid])
|
|
for message in raw_messages
|
|
]
|
|
|
|
# no message that is wrongly sanitized please
|
|
assert (
|
|
len(
|
|
[
|
|
sanitized
|
|
for sanitized, target in zip(sanitized_messages, target_messages)
|
|
if sanitized != target
|
|
]
|
|
)
|
|
== 0
|
|
)
|
|
|
|
|
|
def test_escape_called():
|
|
with patch("re.escape") as mock_escape:
|
|
input_text = "Some text"
|
|
uids_to_remove = ["uid1"]
|
|
SlackInput._sanitize_user_message(input_text, uids_to_remove)
|
|
|
|
# Check if re.escape was called with the expected argument
|
|
mock_escape.assert_called_with("uid1")
|
|
|
|
|
|
def test_slack_init_token_parameter():
|
|
ch = SlackInput("xoxb-test", slack_signing_secret="foobar")
|
|
assert ch.slack_token == "xoxb-test"
|
|
assert ch.slack_channel is None
|
|
|
|
|
|
def test_slack_init_token_channel_parameters():
|
|
ch = SlackInput("xoxb-test", "test", slack_signing_secret="foobar")
|
|
assert ch.slack_token == "xoxb-test"
|
|
assert ch.slack_channel == "test"
|
|
assert ch.conversation_granularity == "sender"
|
|
|
|
|
|
def test_slack_init_token_channel_conversation_granularity_parameters():
|
|
ch = SlackInput(
|
|
"xoxb-test",
|
|
"test",
|
|
slack_signing_secret="foobar",
|
|
conversation_granularity="channel",
|
|
)
|
|
assert ch.slack_token == "xoxb-test"
|
|
assert ch.slack_channel == "test"
|
|
assert ch.conversation_granularity == "channel"
|
|
|
|
|
|
def test_slack_init_token_channel_threads_parameters():
|
|
ch = SlackInput(
|
|
"xoxb-test",
|
|
"test",
|
|
slack_signing_secret="foobar",
|
|
use_threads=True,
|
|
conversation_granularity="thread",
|
|
)
|
|
assert ch.slack_token == "xoxb-test"
|
|
assert ch.slack_channel == "test"
|
|
assert ch.use_threads is True
|
|
assert ch.conversation_granularity == "thread"
|
|
|
|
|
|
def test_get_conversation_id_sender_id():
|
|
ch = SlackInput(
|
|
"xoxb-test",
|
|
"test",
|
|
slack_signing_secret="foobar",
|
|
use_threads=True,
|
|
conversation_granularity="sender",
|
|
)
|
|
conversation_id = ch._get_conversation_id(
|
|
"test_sender_id", "test_channel_id", "test_thread_id"
|
|
)
|
|
assert conversation_id == "test_sender_id"
|
|
|
|
|
|
def test_get_conversation_id_channel_id():
|
|
ch = SlackInput(
|
|
"xoxb-test",
|
|
"test",
|
|
slack_signing_secret="foobar",
|
|
use_threads=True,
|
|
conversation_granularity="channel",
|
|
)
|
|
conversation_id = ch._get_conversation_id("test_sender_id", "test_channel_id", None)
|
|
assert conversation_id == "test_sender_id_test_channel_id"
|
|
|
|
conversation_id = ch._get_conversation_id("test_sender_id", None, "test_thread_id")
|
|
assert conversation_id == "test_sender_id"
|
|
|
|
|
|
def test_get_conversation_id_thread_id():
|
|
ch = SlackInput(
|
|
"xoxb-test",
|
|
"test",
|
|
slack_signing_secret="foobar",
|
|
use_threads=True,
|
|
conversation_granularity="thread",
|
|
)
|
|
conversation_id = ch._get_conversation_id(
|
|
"test_sender_id", "test_channel_id", "test_thread_id"
|
|
)
|
|
assert conversation_id == "test_sender_id_test_channel_id_test_thread_id"
|
|
|
|
conversation_id = ch._get_conversation_id("test_sender_id", None, "test_thread_id")
|
|
assert conversation_id == "test_sender_id"
|
|
|
|
conversation_id = ch._get_conversation_id("test_sender_id", "test_channel_id", None)
|
|
assert conversation_id == "test_sender_id"
|
|
|
|
conversation_id = ch._get_conversation_id("test_sender_id", None, None)
|
|
assert conversation_id == "test_sender_id"
|
|
|
|
|
|
def test_is_slack_message_none():
|
|
payload = {}
|
|
slack_message = json.loads(json.dumps(payload))
|
|
assert SlackInput._is_user_message(slack_message) is False
|
|
|
|
|
|
def test_is_slack_message_true():
|
|
event = {
|
|
"type": "message",
|
|
"channel": "C2147483705",
|
|
"user": "U2147483697",
|
|
"text": "Hello world",
|
|
"ts": "1355517523",
|
|
}
|
|
payload = json.dumps({"event": event})
|
|
slack_message = json.loads(payload)
|
|
assert SlackInput._is_user_message(slack_message) is True
|
|
|
|
|
|
def test_is_slack_message_false():
|
|
event = {
|
|
"type": "message",
|
|
"channel": "C2147483705",
|
|
"user": "U2147483697",
|
|
"text": "Hello world",
|
|
"ts": "1355517523",
|
|
"bot_id": "1355517523",
|
|
}
|
|
payload = json.dumps({"event": event})
|
|
slack_message = json.loads(payload)
|
|
assert SlackInput._is_user_message(slack_message) is False
|
|
|
|
|
|
def test_slackbot_init_one_parameter():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
ch = SlackBot("DummyToken")
|
|
assert ch.client.token == "DummyToken"
|
|
assert ch.slack_channel is None
|
|
|
|
|
|
def test_slackbot_init_two_parameter():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
assert bot.client.token == "DummyToken"
|
|
assert bot.slack_channel == "General"
|
|
|
|
|
|
def test_slackbot_init_three_parameter():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
assert bot.client.token == "DummyToken"
|
|
assert bot.slack_channel == "General"
|
|
assert bot.thread_id == "DummyThread"
|
|
|
|
|
|
# Use monkeypatch for sending attachments, images and plain text.
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_attachment_only():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
attachment = SLACK_TEST_ATTACHMENT
|
|
|
|
await bot.send_attachment("ID", attachment)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"channel": "General",
|
|
"as_user": True,
|
|
"attachments": [attachment],
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_attachment_only_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
attachment = SLACK_TEST_ATTACHMENT
|
|
|
|
await bot.send_attachment("ID", attachment)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"channel": "General",
|
|
"as_user": True,
|
|
"attachments": [attachment],
|
|
"thread_ts": "DummyThread",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_attachment_with_text():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
attachment = SLACK_TEST_ATTACHMENT
|
|
attachment["text"] = "Here is the summary:"
|
|
|
|
await bot.send_attachment("ID", attachment)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"channel": "General",
|
|
"as_user": True,
|
|
"attachments": [attachment],
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_attachment_with_text_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
attachment = SLACK_TEST_ATTACHMENT
|
|
attachment["text"] = "Here is the summary:"
|
|
|
|
await bot.send_attachment("ID", attachment)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"channel": "General",
|
|
"as_user": True,
|
|
"attachments": [attachment],
|
|
"thread_ts": "DummyThread",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_image_url():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
url = "http://www.rasa.net"
|
|
await bot.send_image_url("ID", url)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params["as_user"] is True
|
|
assert request_params["channel"] == "General"
|
|
assert len(request_params["blocks"]) == 1
|
|
assert request_params["blocks"][0].get("type") == "image"
|
|
assert request_params["blocks"][0].get("alt_text") == "http://www.rasa.net"
|
|
assert request_params["blocks"][0].get("image_url") == "http://www.rasa.net"
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_image_url_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
url = "http://www.rasa.net"
|
|
await bot.send_image_url("ID", url)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params["as_user"] is True
|
|
assert request_params["channel"] == "General"
|
|
assert request_params["thread_ts"] == "DummyThread"
|
|
assert len(request_params["blocks"]) == 1
|
|
assert request_params["blocks"][0].get("type") == "image"
|
|
assert request_params["blocks"][0].get("alt_text") == "http://www.rasa.net"
|
|
assert request_params["blocks"][0].get("image_url") == "http://www.rasa.net"
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_text():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
await bot.send_text_message("ID", "my message")
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"text": "my message",
|
|
"type": "mrkdwn",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_text_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
await bot.send_text_message("ID", "my message")
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"text": "my message",
|
|
"type": "mrkdwn",
|
|
"thread_ts": "DummyThread",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_text_with_buttons():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
buttons = [{"title": "title", "payload": "payload"}]
|
|
|
|
await bot.send_text_with_buttons("ID", "my message", buttons)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
text_block = {
|
|
"type": "section",
|
|
"text": {"type": "plain_text", "text": "my message"},
|
|
}
|
|
button_block = {
|
|
"type": "actions",
|
|
"elements": [
|
|
{
|
|
"type": "button",
|
|
"text": {"type": "plain_text", "text": "title"},
|
|
"value": "payload",
|
|
}
|
|
],
|
|
}
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"text": "my message",
|
|
"blocks": [text_block, button_block],
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_text_with_buttons_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
buttons = [{"title": "title", "payload": "payload"}]
|
|
|
|
await bot.send_text_with_buttons("ID", "my message", buttons)
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
text_block = {
|
|
"type": "section",
|
|
"text": {"type": "plain_text", "text": "my message"},
|
|
}
|
|
button_block = {
|
|
"type": "actions",
|
|
"elements": [
|
|
{
|
|
"type": "button",
|
|
"text": {"type": "plain_text", "text": "title"},
|
|
"value": "payload",
|
|
}
|
|
],
|
|
}
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"text": "my message",
|
|
"blocks": [text_block, button_block],
|
|
"thread_ts": "DummyThread",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_custom_json():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General")
|
|
await bot.send_custom_json("ID", {"test_key": "test_value"})
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"test_key": "test_value",
|
|
}
|
|
|
|
|
|
@pytest.mark.filterwarnings("ignore:unclosed.*:ResourceWarning")
|
|
@pytest.mark.asyncio
|
|
async def test_slackbot_send_custom_json_threaded():
|
|
from rasa.core.channels.slack import SlackBot
|
|
|
|
with aioresponses() as mocked:
|
|
mocked.post(
|
|
"https://www.slack.com/api/chat.postMessage",
|
|
payload={"ok": True, "purpose": "Testing bots"},
|
|
)
|
|
|
|
bot = SlackBot("DummyToken", "General", thread_id="DummyThread")
|
|
await bot.send_custom_json("ID", {"test_key": "test_value"})
|
|
|
|
r = latest_request(mocked, "POST", "https://www.slack.com/api/chat.postMessage")
|
|
|
|
assert r
|
|
|
|
request_params = json_of_latest_request(r)
|
|
|
|
assert request_params == {
|
|
"as_user": True,
|
|
"channel": "General",
|
|
"thread_ts": "DummyThread",
|
|
"test_key": "test_value",
|
|
}
|
|
|
|
|
|
def prepare_slack_request(headers: Dict[Text, Any]) -> Request:
|
|
request = Request(
|
|
b"/webhooks/slack/webhook",
|
|
headers=Header(headers),
|
|
version="1.1",
|
|
method="POST",
|
|
transport=None,
|
|
app=None,
|
|
)
|
|
request.body = b"""{"foo": "bar"}"""
|
|
return request
|
|
|
|
|
|
def test_slack_fails_if_signature_is_missing():
|
|
with pytest.raises(InvalidConfigException):
|
|
SlackInput("mytoken")
|
|
|
|
|
|
@mock.patch("time.time", mock.MagicMock(return_value=1604586653))
|
|
def test_slack_verify_signature():
|
|
request = prepare_slack_request(
|
|
{
|
|
"x-slack-signature": "v0=80a3bd62ce5af04d8d80781134f165df"
|
|
"185b90342d467abf5c74a27d2d0dd1f5",
|
|
"x-slack-request-timestamp": str(int(time.time())),
|
|
}
|
|
)
|
|
input_with_right_secret = SlackInput("mytoken", slack_signing_secret="foobar")
|
|
|
|
assert input_with_right_secret.is_request_from_slack_authentic(request) is True
|
|
|
|
|
|
def test_slack_fail_on_old_timestamp():
|
|
request = prepare_slack_request(
|
|
{
|
|
"x-slack-signature": "v0=80a3bd62ce5af04d8d80781134f165df"
|
|
"185b90342d467abf5c74a27d2d0dd1f5",
|
|
"x-slack-request-timestamp": str(int(time.time()) - 10 * 60),
|
|
}
|
|
)
|
|
input_with_right_secret = SlackInput("mytoken", slack_signing_secret="foobar")
|
|
|
|
assert input_with_right_secret.is_request_from_slack_authentic(request) is False
|
|
|
|
|
|
def test_slack_handles_invalid_timestamp():
|
|
request = prepare_slack_request(
|
|
{
|
|
"x-slack-signature": "v0=80a3bd62ce5af04d8d80781134f165df"
|
|
"185b90342d467abf5c74a27d2d0dd1f5",
|
|
"x-slack-request-timestamp": "foobar",
|
|
}
|
|
)
|
|
input_with_right_secret = SlackInput("mytoken", slack_signing_secret="foobar")
|
|
|
|
assert input_with_right_secret.is_request_from_slack_authentic(request) is False
|
|
|
|
|
|
def test_slack_verify_wrong_signature():
|
|
request = prepare_slack_request(
|
|
{
|
|
"x-slack-signature": "v0=80a3bd62ce5af04d8d80781134f165df"
|
|
"185b90342d467abf5c74a27d2d0dd1f5",
|
|
"x-slack-request-timestamp": str(int(time.time())),
|
|
}
|
|
)
|
|
input_with_wrong_secret = SlackInput("mytoken", slack_signing_secret="foobaz")
|
|
|
|
assert input_with_wrong_secret.is_request_from_slack_authentic(request) is False
|
|
|
|
|
|
def test_slack_verify_signature_missing_headers():
|
|
request = prepare_slack_request(
|
|
{
|
|
# let's check what happens if verification headers are missing
|
|
}
|
|
)
|
|
slack = SlackInput("mytoken", slack_signing_secret="foobar")
|
|
|
|
assert slack.is_request_from_slack_authentic(request) is False
|
|
|
|
|
|
async def fake_on_new_message(message: UserMessage):
|
|
pass
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slack_process_message_retry():
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
request = Mock()
|
|
request.headers = {
|
|
input_channel.retry_num_header: 1,
|
|
input_channel.retry_reason_header: input_channel.errors_ignore_retry[0],
|
|
}
|
|
|
|
response = await input_channel.process_message(
|
|
request=request,
|
|
on_new_message=fake_on_new_message,
|
|
text="",
|
|
sender_id=None,
|
|
metadata=None,
|
|
)
|
|
|
|
assert response.status == HTTPStatus.CREATED
|
|
assert response.headers == {"X-Slack-No-Retry": "1"}
|
|
|
|
|
|
async def fake_on_new_message_sleep(message: UserMessage):
|
|
time.sleep(3)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_slack_process_message_timeout():
|
|
input_channel = SlackInput(
|
|
slack_token="YOUR_SLACK_TOKEN",
|
|
slack_channel="YOUR_SLACK_CHANNEL",
|
|
slack_signing_secret="foobar",
|
|
)
|
|
|
|
request = Mock()
|
|
request.headers = {}
|
|
|
|
start = time.time()
|
|
response = await input_channel.process_message(
|
|
request=request,
|
|
on_new_message=fake_on_new_message_sleep,
|
|
text="",
|
|
sender_id=None,
|
|
metadata=None,
|
|
)
|
|
end = time.time()
|
|
|
|
duration = end - start
|
|
|
|
assert duration < 3
|
|
assert response.status == HTTPStatus.OK
|