2020-08-19 02:08:08 +02:00
|
|
|
from functools import wraps
|
2022-08-21 04:26:00 +02:00
|
|
|
from typing import Any, Callable, Dict, Optional
|
2020-05-26 07:16:25 +02:00
|
|
|
from unittest import mock
|
2017-05-25 19:48:36 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2017-10-31 11:20:18 +01:00
|
|
|
from django.conf import settings
|
|
|
|
from django.test import override_settings
|
2022-08-21 04:26:00 +02:00
|
|
|
from typing_extensions import Concatenate, ParamSpec
|
2017-10-31 11:20:18 +01:00
|
|
|
|
2022-04-14 23:53:15 +02:00
|
|
|
from zerver.actions.create_user import do_create_user
|
2022-04-14 23:50:10 +02:00
|
|
|
from zerver.actions.message_send import get_service_bot_events
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.bot_config import ConfigError, load_bot_config_template, set_bot_config
|
|
|
|
from zerver.lib.bot_lib import EmbeddedBotEmptyRecipientsList, EmbeddedBotHandler, StateHandler
|
2017-10-31 11:10:56 +01:00
|
|
|
from zerver.lib.bot_storage import StateError
|
2017-05-25 19:48:36 +02:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2022-08-22 19:50:23 +02:00
|
|
|
from zerver.lib.test_helpers import mock_queue_publish
|
2020-07-01 01:50:08 +02:00
|
|
|
from zerver.lib.validator import check_string
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import Recipient, UserProfile, get_realm
|
2017-11-20 14:40:51 +01:00
|
|
|
|
2017-05-25 20:01:31 +02:00
|
|
|
BOT_TYPE_TO_QUEUE_NAME = {
|
2021-02-12 08:20:45 +01:00
|
|
|
UserProfile.OUTGOING_WEBHOOK_BOT: "outgoing_webhooks",
|
|
|
|
UserProfile.EMBEDDED_BOT: "embedded_bots",
|
2017-05-25 20:01:31 +02:00
|
|
|
}
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-09-26 23:55:15 +02:00
|
|
|
class TestServiceBotBasics(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def _get_outgoing_bot(self) -> UserProfile:
|
2017-09-26 23:55:15 +02:00
|
|
|
outgoing_bot = do_create_user(
|
|
|
|
email="bar-bot@zulip.com",
|
|
|
|
password="test",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
full_name="BarBot",
|
|
|
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT,
|
2021-02-12 08:20:45 +01:00
|
|
|
bot_owner=self.example_user("cordelia"),
|
2021-02-06 14:27:06 +01:00
|
|
|
acting_user=None,
|
2017-09-26 23:55:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return outgoing_bot
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_service_events_for_pms(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
sender = self.example_user("hamlet")
|
2021-02-12 08:19:30 +01:00
|
|
|
assert not sender.is_bot
|
2017-09-26 23:55:15 +02:00
|
|
|
|
|
|
|
outgoing_bot = self._get_outgoing_bot()
|
2020-07-05 00:50:18 +02:00
|
|
|
assert outgoing_bot.bot_type is not None
|
2017-09-26 23:55:15 +02:00
|
|
|
|
|
|
|
event_dict = get_service_bot_events(
|
|
|
|
sender=sender,
|
|
|
|
service_bot_tuples=[
|
|
|
|
(outgoing_bot.id, outgoing_bot.bot_type),
|
|
|
|
],
|
2017-10-24 19:25:50 +02:00
|
|
|
active_user_ids={outgoing_bot.id},
|
2017-09-26 23:55:15 +02:00
|
|
|
mentioned_user_ids=set(),
|
|
|
|
recipient_type=Recipient.PERSONAL,
|
|
|
|
)
|
|
|
|
|
|
|
|
expected = dict(
|
|
|
|
outgoing_webhooks=[
|
2021-02-12 08:20:45 +01:00
|
|
|
dict(trigger="private_message", user_profile_id=outgoing_bot.id),
|
2017-09-26 23:55:15 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(event_dict, expected)
|
|
|
|
|
2018-08-13 16:21:10 +02:00
|
|
|
def test_spurious_mentions(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
sender = self.example_user("hamlet")
|
2021-02-12 08:19:30 +01:00
|
|
|
assert not sender.is_bot
|
2018-08-13 16:21:10 +02:00
|
|
|
|
|
|
|
outgoing_bot = self._get_outgoing_bot()
|
2020-07-05 00:50:18 +02:00
|
|
|
assert outgoing_bot.bot_type is not None
|
2018-08-13 16:21:10 +02:00
|
|
|
|
|
|
|
# If outgoing_bot is not in mentioned_user_ids,
|
|
|
|
# we will skip over it. This tests an anomaly
|
|
|
|
# of the code that our query for bots can include
|
|
|
|
# bots that may not actually be mentioned, and it's
|
|
|
|
# easiest to just filter them in get_service_bot_events.
|
|
|
|
event_dict = get_service_bot_events(
|
|
|
|
sender=sender,
|
|
|
|
service_bot_tuples=[
|
|
|
|
(outgoing_bot.id, outgoing_bot.bot_type),
|
|
|
|
],
|
|
|
|
active_user_ids={outgoing_bot.id},
|
|
|
|
mentioned_user_ids=set(),
|
|
|
|
recipient_type=Recipient.STREAM,
|
|
|
|
)
|
|
|
|
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(event_dict, 0)
|
2018-08-13 16:21:10 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_service_events_for_stream_mentions(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
sender = self.example_user("hamlet")
|
2021-02-12 08:19:30 +01:00
|
|
|
assert not sender.is_bot
|
2017-09-26 23:55:15 +02:00
|
|
|
|
|
|
|
outgoing_bot = self._get_outgoing_bot()
|
2020-07-05 00:50:18 +02:00
|
|
|
assert outgoing_bot.bot_type is not None
|
2017-09-26 23:55:15 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
cordelia = self.example_user("cordelia")
|
2018-08-13 16:21:10 +02:00
|
|
|
|
|
|
|
red_herring_bot = self.create_test_bot(
|
2021-02-12 08:20:45 +01:00
|
|
|
short_name="whatever",
|
2018-08-13 16:21:10 +02:00
|
|
|
user_profile=cordelia,
|
|
|
|
)
|
|
|
|
|
2017-09-26 23:55:15 +02:00
|
|
|
event_dict = get_service_bot_events(
|
|
|
|
sender=sender,
|
|
|
|
service_bot_tuples=[
|
|
|
|
(outgoing_bot.id, outgoing_bot.bot_type),
|
2018-08-13 16:21:10 +02:00
|
|
|
(red_herring_bot.id, UserProfile.OUTGOING_WEBHOOK_BOT),
|
2017-09-26 23:55:15 +02:00
|
|
|
],
|
2017-10-24 19:25:50 +02:00
|
|
|
active_user_ids=set(),
|
2017-09-26 23:55:15 +02:00
|
|
|
mentioned_user_ids={outgoing_bot.id},
|
|
|
|
recipient_type=Recipient.STREAM,
|
|
|
|
)
|
|
|
|
|
2017-09-27 14:01:12 +02:00
|
|
|
expected = dict(
|
|
|
|
outgoing_webhooks=[
|
2021-02-12 08:20:45 +01:00
|
|
|
dict(trigger="mention", user_profile_id=outgoing_bot.id),
|
2017-09-27 14:01:12 +02:00
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
self.assertEqual(event_dict, expected)
|
|
|
|
|
2018-06-04 07:24:21 +02:00
|
|
|
def test_service_events_for_private_mentions(self) -> None:
|
|
|
|
"""Service bots should not get access to mentions if they aren't a
|
|
|
|
direct recipient."""
|
2021-02-12 08:20:45 +01:00
|
|
|
sender = self.example_user("hamlet")
|
2021-02-12 08:19:30 +01:00
|
|
|
assert not sender.is_bot
|
2018-06-04 07:24:21 +02:00
|
|
|
|
|
|
|
outgoing_bot = self._get_outgoing_bot()
|
2020-07-05 00:50:18 +02:00
|
|
|
assert outgoing_bot.bot_type is not None
|
2018-06-04 07:24:21 +02:00
|
|
|
|
|
|
|
event_dict = get_service_bot_events(
|
|
|
|
sender=sender,
|
|
|
|
service_bot_tuples=[
|
|
|
|
(outgoing_bot.id, outgoing_bot.bot_type),
|
|
|
|
],
|
|
|
|
active_user_ids=set(),
|
|
|
|
mentioned_user_ids={outgoing_bot.id},
|
|
|
|
recipient_type=Recipient.PERSONAL,
|
|
|
|
)
|
|
|
|
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(event_dict, 0)
|
2018-06-04 07:24:21 +02:00
|
|
|
|
2018-08-13 14:54:45 +02:00
|
|
|
def test_service_events_with_unexpected_bot_type(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
cordelia = self.example_user("cordelia")
|
2018-08-13 14:54:45 +02:00
|
|
|
|
|
|
|
bot = self.create_test_bot(
|
2021-02-12 08:20:45 +01:00
|
|
|
short_name="whatever",
|
2018-08-13 14:54:45 +02:00
|
|
|
user_profile=cordelia,
|
|
|
|
)
|
|
|
|
wrong_bot_type = UserProfile.INCOMING_WEBHOOK_BOT
|
|
|
|
bot.bot_type = wrong_bot_type
|
|
|
|
bot.save()
|
|
|
|
|
2020-07-16 13:32:07 +02:00
|
|
|
with self.assertLogs(level="ERROR") as m:
|
2018-08-13 14:54:45 +02:00
|
|
|
event_dict = get_service_bot_events(
|
|
|
|
sender=hamlet,
|
|
|
|
service_bot_tuples=[
|
|
|
|
(bot.id, wrong_bot_type),
|
|
|
|
],
|
|
|
|
active_user_ids=set(),
|
|
|
|
mentioned_user_ids={bot.id},
|
|
|
|
recipient_type=Recipient.PERSONAL,
|
|
|
|
)
|
|
|
|
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(event_dict, 0)
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertEqual(
|
|
|
|
m.output,
|
|
|
|
[f"ERROR:root:Unexpected bot_type for Service bot id={bot.id}: {wrong_bot_type}"],
|
|
|
|
)
|
|
|
|
|
2018-08-13 14:54:45 +02:00
|
|
|
|
2017-10-16 18:38:46 +02:00
|
|
|
class TestServiceBotStateHandler(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def setUp(self) -> None:
|
2019-10-19 20:47:00 +02:00
|
|
|
super().setUp()
|
2017-10-16 18:38:46 +02:00
|
|
|
self.user_profile = self.example_user("othello")
|
2021-02-12 08:19:30 +01:00
|
|
|
self.bot_profile = do_create_user(
|
|
|
|
email="embedded-bot-1@zulip.com",
|
|
|
|
password="test",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
full_name="EmbeddedBo1",
|
|
|
|
bot_type=UserProfile.EMBEDDED_BOT,
|
|
|
|
bot_owner=self.user_profile,
|
2021-02-06 14:27:06 +01:00
|
|
|
acting_user=None,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
self.second_bot_profile = do_create_user(
|
|
|
|
email="embedded-bot-2@zulip.com",
|
|
|
|
password="test",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
full_name="EmbeddedBot2",
|
|
|
|
bot_type=UserProfile.EMBEDDED_BOT,
|
|
|
|
bot_owner=self.user_profile,
|
2021-02-06 14:27:06 +01:00
|
|
|
acting_user=None,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-10-16 18:38:46 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_basic_storage_and_retrieval(self) -> None:
|
2017-10-20 17:42:57 +02:00
|
|
|
storage = StateHandler(self.bot_profile)
|
2021-02-12 08:20:45 +01:00
|
|
|
storage.put("some key", "some value")
|
|
|
|
storage.put("some other key", "some other value")
|
|
|
|
self.assertEqual(storage.get("some key"), "some value")
|
|
|
|
self.assertEqual(storage.get("some other key"), "some other value")
|
|
|
|
self.assertTrue(storage.contains("some key"))
|
|
|
|
self.assertFalse(storage.contains("nonexistent key"))
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertRaisesMessage(
|
2021-02-12 08:20:45 +01:00
|
|
|
StateError, "Key does not exist.", lambda: storage.get("nonexistent key")
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
storage.put("some key", "a new value")
|
|
|
|
self.assertEqual(storage.get("some key"), "a new value")
|
2017-10-20 17:42:57 +02:00
|
|
|
second_storage = StateHandler(self.second_bot_profile)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertRaises(StateError, lambda: second_storage.get("some key"))
|
|
|
|
second_storage.put("some key", "yet another value")
|
|
|
|
self.assertEqual(storage.get("some key"), "a new value")
|
|
|
|
self.assertEqual(second_storage.get("some key"), "yet another value")
|
2017-10-16 18:38:46 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_marshaling(self) -> None:
|
2017-10-20 17:42:57 +02:00
|
|
|
storage = StateHandler(self.bot_profile)
|
2021-02-12 08:20:45 +01:00
|
|
|
serializable_obj = {"foo": "bar", "baz": [42, "cux"]}
|
|
|
|
storage.put("some key", serializable_obj)
|
|
|
|
self.assertEqual(storage.get("some key"), serializable_obj)
|
2017-10-20 17:24:09 +02:00
|
|
|
|
2017-11-24 10:18:29 +01:00
|
|
|
# Reduce maximal storage size for faster test string construction.
|
2017-10-31 11:20:18 +01:00
|
|
|
@override_settings(USER_STATE_SIZE_LIMIT=100)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_storage_limit(self) -> None:
|
2017-10-20 17:42:57 +02:00
|
|
|
storage = StateHandler(self.bot_profile)
|
2017-10-31 11:20:18 +01:00
|
|
|
|
|
|
|
# Disable marshaling for storing a string whose size is
|
|
|
|
# equivalent to the size of the stored object.
|
2020-07-01 01:50:08 +02:00
|
|
|
storage.marshal = lambda obj: check_string("obj", obj)
|
2017-10-31 11:03:05 +01:00
|
|
|
storage.demarshal = lambda obj: obj
|
2017-10-31 11:20:18 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
key = "capacity-filling entry"
|
|
|
|
storage.put(key, "x" * (settings.USER_STATE_SIZE_LIMIT - len(key)))
|
2017-10-16 18:38:46 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
with self.assertRaisesMessage(
|
|
|
|
StateError,
|
|
|
|
"Request exceeds storage limit by 32 characters. The limit is 100 characters.",
|
|
|
|
):
|
2021-02-12 08:20:45 +01:00
|
|
|
storage.put("too much data", "a few bits too long")
|
2017-10-16 18:38:46 +02:00
|
|
|
|
2017-10-20 17:42:57 +02:00
|
|
|
second_storage = StateHandler(self.second_bot_profile)
|
2021-02-12 08:20:45 +01:00
|
|
|
second_storage.put("another big entry", "x" * (settings.USER_STATE_SIZE_LIMIT - 40))
|
|
|
|
second_storage.put("normal entry", "abcd")
|
2017-10-16 18:38:46 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_entry_removal(self) -> None:
|
2017-10-26 16:02:35 +02:00
|
|
|
storage = StateHandler(self.bot_profile)
|
2021-02-12 08:20:45 +01:00
|
|
|
storage.put("some key", "some value")
|
|
|
|
storage.put("another key", "some value")
|
|
|
|
self.assertTrue(storage.contains("some key"))
|
|
|
|
self.assertTrue(storage.contains("another key"))
|
|
|
|
storage.remove("some key")
|
|
|
|
self.assertFalse(storage.contains("some key"))
|
|
|
|
self.assertTrue(storage.contains("another key"))
|
|
|
|
self.assertRaises(StateError, lambda: storage.remove("some key"))
|
2017-10-26 16:02:35 +02:00
|
|
|
|
python: Convert function type annotations to Python 3 style.
Generated by com2ann (slightly patched to avoid also converting
assignment type annotations, which require Python 3.6), followed by
some manual whitespace adjustment, and six fixes for runtime issues:
- def __init__(self, token: Token, parent: Optional[Node]) -> None:
+ def __init__(self, token: Token, parent: "Optional[Node]") -> None:
-def main(options: argparse.Namespace) -> NoReturn:
+def main(options: argparse.Namespace) -> "NoReturn":
-def fetch_request(url: str, callback: Any, **kwargs: Any) -> Generator[Callable[..., Any], Any, None]:
+def fetch_request(url: str, callback: Any, **kwargs: Any) -> "Generator[Callable[..., Any], Any, None]":
-def assert_server_running(server: subprocess.Popen[bytes], log_file: Optional[str]) -> None:
+def assert_server_running(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> None:
-def server_is_up(server: subprocess.Popen[bytes], log_file: Optional[str]) -> bool:
+def server_is_up(server: "subprocess.Popen[bytes]", log_file: Optional[str]) -> bool:
- method_kwarg_pairs: List[FuncKwargPair],
+ method_kwarg_pairs: "List[FuncKwargPair]",
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-19 03:48:37 +02:00
|
|
|
def test_internal_endpoint(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(self.user_profile)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Store some data.
|
2021-02-12 08:20:45 +01:00
|
|
|
initial_dict = {"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
|
2017-11-20 14:40:51 +01:00
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"storage": orjson.dumps(initial_dict).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_put("/json/bot_storage", params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_success(result)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert the stored data for some keys.
|
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"keys": orjson.dumps(["key 1", "key 3"]).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage", params)
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], {"key 3": "value 3", "key 1": "value 1"})
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert the stored data for all keys.
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage")
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], initial_dict)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Store some more data; update an entry and store a new entry
|
2021-02-12 08:20:45 +01:00
|
|
|
dict_update = {"key 1": "new value", "key 4": "value 4"}
|
2017-11-20 14:40:51 +01:00
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"storage": orjson.dumps(dict_update).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_put("/json/bot_storage", params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_success(result)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert the data was updated.
|
|
|
|
updated_dict = initial_dict.copy()
|
|
|
|
updated_dict.update(dict_update)
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage")
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], updated_dict)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert errors on invalid requests.
|
2020-07-01 01:50:08 +02:00
|
|
|
invalid_params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"keys": ["This is a list, but should be a serialized string."],
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage", invalid_params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_error(result, 'Argument "keys" is not valid JSON.')
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"keys": orjson.dumps(["key 1", "nonexistent key"]).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage", params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_error(result, "Key does not exist.")
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-25 19:05:12 +01:00
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"storage": orjson.dumps({"foo": [1, 2, 3]}).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_put("/json/bot_storage", params)
|
2020-06-21 03:23:55 +02:00
|
|
|
self.assert_json_error(result, "storage contains a value that is not a string")
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Remove some entries.
|
2021-02-12 08:20:45 +01:00
|
|
|
keys_to_remove = ["key 1", "key 2"]
|
2017-11-20 14:40:51 +01:00
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"keys": orjson.dumps(keys_to_remove).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_delete("/json/bot_storage", params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_success(result)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert the entries were removed.
|
|
|
|
for key in keys_to_remove:
|
|
|
|
updated_dict.pop(key)
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage")
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], updated_dict)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Try to remove an existing and a nonexistent key.
|
|
|
|
params = {
|
2021-02-12 08:20:45 +01:00
|
|
|
"keys": orjson.dumps(["key 3", "nonexistent key"]).decode(),
|
2017-11-20 14:40:51 +01:00
|
|
|
}
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_delete("/json/bot_storage", params)
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_error(result, "Key does not exist.")
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-20 14:40:51 +01:00
|
|
|
# Assert an error has been thrown and no entries were removed.
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage")
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], updated_dict)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-24 10:18:29 +01:00
|
|
|
# Remove the entire storage.
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_delete("/json/bot_storage")
|
2017-11-20 14:40:51 +01:00
|
|
|
self.assert_json_success(result)
|
2017-11-24 11:26:12 +01:00
|
|
|
|
2017-11-24 10:18:29 +01:00
|
|
|
# Assert the entire storage has been removed.
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_get("/json/bot_storage")
|
2022-06-07 01:37:01 +02:00
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assertEqual(response_dict["storage"], {})
|
2017-11-20 14:40:51 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2017-11-01 20:51:12 +01:00
|
|
|
class TestServiceBotConfigHandler(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def setUp(self) -> None:
|
2019-10-19 20:47:00 +02:00
|
|
|
super().setUp()
|
2017-11-01 20:51:12 +01:00
|
|
|
self.user_profile = self.example_user("othello")
|
2021-02-12 08:19:30 +01:00
|
|
|
self.bot_profile = self.create_test_bot(
|
2021-02-12 08:20:45 +01:00
|
|
|
"embedded",
|
2021-02-12 08:19:30 +01:00
|
|
|
self.user_profile,
|
2021-02-12 08:20:45 +01:00
|
|
|
full_name="Embedded bot",
|
2021-02-12 08:19:30 +01:00
|
|
|
bot_type=UserProfile.EMBEDDED_BOT,
|
2021-02-12 08:20:45 +01:00
|
|
|
service_name="helloworld",
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-11-01 20:51:12 +01:00
|
|
|
self.bot_handler = EmbeddedBotHandler(self.bot_profile)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_basic_storage_and_retrieval(self) -> None:
|
2018-01-07 19:14:21 +01:00
|
|
|
with self.assertRaises(ConfigError):
|
2021-02-12 08:20:45 +01:00
|
|
|
self.bot_handler.get_config_info("foo")
|
2018-01-07 19:14:21 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(self.bot_handler.get_config_info("foo", optional=True), {})
|
2018-01-07 19:17:25 +01:00
|
|
|
|
2017-11-01 20:51:12 +01:00
|
|
|
config_dict = {"entry 1": "value 1", "entry 2": "value 2"}
|
|
|
|
for key, value in config_dict.items():
|
|
|
|
set_bot_config(self.bot_profile, key, value)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(self.bot_handler.get_config_info("foo"), config_dict)
|
2017-11-01 20:51:12 +01:00
|
|
|
|
|
|
|
config_update = {"entry 2": "new value", "entry 3": "value 3"}
|
|
|
|
for key, value in config_update.items():
|
|
|
|
set_bot_config(self.bot_profile, key, value)
|
|
|
|
config_dict.update(config_update)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(self.bot_handler.get_config_info("foo"), config_dict)
|
2017-11-01 20:51:12 +01:00
|
|
|
|
|
|
|
@override_settings(BOT_CONFIG_SIZE_LIMIT=100)
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_config_entry_limit(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
set_bot_config(self.bot_profile, "some key", "x" * (settings.BOT_CONFIG_SIZE_LIMIT - 8))
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertRaisesMessage(
|
|
|
|
ConfigError,
|
|
|
|
"Cannot store configuration. Request would require 101 characters. "
|
|
|
|
"The current configuration size limit is 100 characters.",
|
|
|
|
lambda: set_bot_config(
|
2021-02-12 08:20:45 +01:00
|
|
|
self.bot_profile, "some key", "x" * (settings.BOT_CONFIG_SIZE_LIMIT - 8 + 1)
|
2021-02-12 08:19:30 +01:00
|
|
|
),
|
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
set_bot_config(self.bot_profile, "some key", "x" * (settings.BOT_CONFIG_SIZE_LIMIT - 20))
|
|
|
|
set_bot_config(self.bot_profile, "another key", "x")
|
2021-02-12 08:19:30 +01:00
|
|
|
self.assertRaisesMessage(
|
|
|
|
ConfigError,
|
|
|
|
"Cannot store configuration. Request would require 116 characters. "
|
|
|
|
"The current configuration size limit is 100 characters.",
|
2021-02-12 08:20:45 +01:00
|
|
|
lambda: set_bot_config(self.bot_profile, "yet another key", "x"),
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-11-01 20:51:12 +01:00
|
|
|
|
2018-01-07 19:21:04 +01:00
|
|
|
def test_load_bot_config_template(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
bot_config = load_bot_config_template("giphy")
|
2018-01-07 19:21:04 +01:00
|
|
|
self.assertTrue(isinstance(bot_config, dict))
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(bot_config, 1)
|
2018-01-07 19:21:04 +01:00
|
|
|
|
|
|
|
def test_load_bot_config_template_for_bot_without_config_data(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
bot_config = load_bot_config_template("converter")
|
2018-01-07 19:21:04 +01:00
|
|
|
self.assertTrue(isinstance(bot_config, dict))
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(bot_config, 0)
|
2018-01-07 19:21:04 +01:00
|
|
|
|
2019-01-07 16:20:21 +01:00
|
|
|
def test_bot_send_pm_with_empty_recipients_list(self) -> None:
|
2021-02-12 08:19:30 +01:00
|
|
|
with self.assertRaisesRegex(
|
2021-02-12 08:20:45 +01:00
|
|
|
EmbeddedBotEmptyRecipientsList, "Message must have recipients!"
|
2021-02-12 08:19:30 +01:00
|
|
|
):
|
2021-02-12 08:20:45 +01:00
|
|
|
self.bot_handler.send_message(message={"type": "private", "to": []})
|
2019-01-07 16:20:21 +01:00
|
|
|
|
2018-01-07 19:21:04 +01:00
|
|
|
|
2022-08-21 04:26:00 +02:00
|
|
|
ParamT = ParamSpec("ParamT")
|
2021-02-16 00:00:26 +01:00
|
|
|
|
|
|
|
|
2022-08-21 04:26:00 +02:00
|
|
|
def for_all_bot_types(
|
|
|
|
test_func: Callable[Concatenate["TestServiceBotEventTriggers", ParamT], None]
|
|
|
|
) -> Callable[Concatenate["TestServiceBotEventTriggers", ParamT], None]:
|
2020-08-19 02:08:08 +02:00
|
|
|
@wraps(test_func)
|
2022-08-21 04:26:00 +02:00
|
|
|
def _wrapped(
|
|
|
|
self: "TestServiceBotEventTriggers", /, *args: ParamT.args, **kwargs: ParamT.kwargs
|
|
|
|
) -> None:
|
2020-08-19 02:08:08 +02:00
|
|
|
for bot_type in BOT_TYPE_TO_QUEUE_NAME:
|
|
|
|
self.bot_profile.bot_type = bot_type
|
|
|
|
self.bot_profile.save()
|
2022-08-21 04:26:00 +02:00
|
|
|
test_func(self, *args, **kwargs)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2022-08-21 04:26:00 +02:00
|
|
|
return _wrapped
|
2020-08-19 02:08:08 +02:00
|
|
|
|
2017-05-25 19:48:36 +02:00
|
|
|
|
2022-08-22 19:50:23 +02:00
|
|
|
def patch_queue_publish(
|
|
|
|
method_to_patch: str,
|
2022-08-22 19:55:20 +02:00
|
|
|
) -> Callable[
|
|
|
|
[Callable[["TestServiceBotEventTriggers", mock.Mock], None]],
|
|
|
|
Callable[["TestServiceBotEventTriggers"], None],
|
|
|
|
]:
|
|
|
|
def inner(
|
|
|
|
func: Callable[["TestServiceBotEventTriggers", mock.Mock], None]
|
|
|
|
) -> Callable[["TestServiceBotEventTriggers"], None]:
|
2022-08-22 19:50:23 +02:00
|
|
|
@wraps(func)
|
2022-08-22 19:55:20 +02:00
|
|
|
def _wrapped(self: "TestServiceBotEventTriggers") -> None:
|
2022-08-22 19:50:23 +02:00
|
|
|
with mock_queue_publish(method_to_patch) as m:
|
2022-08-22 19:55:20 +02:00
|
|
|
func(self, m)
|
2022-08-22 19:50:23 +02:00
|
|
|
|
|
|
|
return _wrapped
|
|
|
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
class TestServiceBotEventTriggers(ZulipTestCase):
|
2017-11-05 10:51:25 +01:00
|
|
|
def setUp(self) -> None:
|
2019-10-19 20:47:00 +02:00
|
|
|
super().setUp()
|
2017-05-25 19:54:06 +02:00
|
|
|
self.user_profile = self.example_user("othello")
|
2021-02-12 08:19:30 +01:00
|
|
|
self.bot_profile = do_create_user(
|
|
|
|
email="foo-bot@zulip.com",
|
|
|
|
password="test",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
full_name="FooBot",
|
|
|
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT,
|
|
|
|
bot_owner=self.user_profile,
|
2021-02-06 14:27:06 +01:00
|
|
|
acting_user=None,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
self.second_bot_profile = do_create_user(
|
|
|
|
email="bar-bot@zulip.com",
|
|
|
|
password="test",
|
|
|
|
realm=get_realm("zulip"),
|
|
|
|
full_name="BarBot",
|
|
|
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT,
|
|
|
|
bot_owner=self.user_profile,
|
2021-02-06 14:27:06 +01:00
|
|
|
acting_user=None,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2017-05-25 19:54:06 +02:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_trigger_on_stream_mention_from_user(self, mock_queue_json_publish: mock.Mock) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
content = "@**FooBot** foo bar!!!"
|
|
|
|
recipient = "Denmark"
|
|
|
|
trigger = "mention"
|
2020-08-19 02:08:08 +02:00
|
|
|
message_type = Recipient._type_names[Recipient.STREAM]
|
|
|
|
|
|
|
|
def check_values_passed(
|
|
|
|
queue_name: Any,
|
|
|
|
trigger_event: Dict[str, Any],
|
|
|
|
processor: Optional[Callable[[Any], None]] = None,
|
|
|
|
) -> None:
|
|
|
|
assert self.bot_profile.bot_type
|
|
|
|
self.assertEqual(queue_name, BOT_TYPE_TO_QUEUE_NAME[self.bot_profile.bot_type])
|
|
|
|
self.assertEqual(trigger_event["message"]["content"], content)
|
|
|
|
self.assertEqual(trigger_event["message"]["display_recipient"], recipient)
|
|
|
|
self.assertEqual(trigger_event["message"]["sender_email"], self.user_profile.email)
|
|
|
|
self.assertEqual(trigger_event["message"]["type"], message_type)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(trigger_event["trigger"], trigger)
|
|
|
|
self.assertEqual(trigger_event["user_profile_id"], self.bot_profile.id)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
mock_queue_json_publish.side_effect = check_values_passed
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
self.send_stream_message(self.user_profile, "Denmark", content)
|
2020-08-19 02:08:08 +02:00
|
|
|
self.assertTrue(mock_queue_json_publish.called)
|
2017-05-25 20:01:31 +02:00
|
|
|
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2021-02-12 08:19:30 +01:00
|
|
|
def test_no_trigger_on_stream_message_without_mention(
|
|
|
|
self, mock_queue_json_publish: mock.Mock
|
|
|
|
) -> None:
|
2020-03-07 11:43:05 +01:00
|
|
|
sender = self.user_profile
|
|
|
|
self.send_stream_message(sender, "Denmark")
|
2017-05-25 20:01:31 +02:00
|
|
|
self.assertFalse(mock_queue_json_publish.called)
|
2017-05-25 19:48:36 +02:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2021-02-12 08:19:30 +01:00
|
|
|
def test_no_trigger_on_stream_mention_from_bot(
|
|
|
|
self, mock_queue_json_publish: mock.Mock
|
|
|
|
) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
self.send_stream_message(self.second_bot_profile, "Denmark", "@**FooBot** foo bar!!!")
|
2020-08-19 02:08:08 +02:00
|
|
|
self.assertFalse(mock_queue_json_publish.called)
|
2017-05-25 19:48:36 +02:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2021-02-12 08:19:30 +01:00
|
|
|
def test_trigger_on_personal_message_from_user(
|
|
|
|
self, mock_queue_json_publish: mock.Mock
|
|
|
|
) -> None:
|
2020-08-19 02:08:08 +02:00
|
|
|
sender = self.user_profile
|
|
|
|
recipient = self.bot_profile
|
|
|
|
|
|
|
|
def check_values_passed(
|
|
|
|
queue_name: Any,
|
|
|
|
trigger_event: Dict[str, Any],
|
|
|
|
processor: Optional[Callable[[Any], None]] = None,
|
|
|
|
) -> None:
|
|
|
|
assert self.bot_profile.bot_type
|
|
|
|
self.assertEqual(queue_name, BOT_TYPE_TO_QUEUE_NAME[self.bot_profile.bot_type])
|
|
|
|
self.assertEqual(trigger_event["user_profile_id"], self.bot_profile.id)
|
|
|
|
self.assertEqual(trigger_event["trigger"], "private_message")
|
|
|
|
self.assertEqual(trigger_event["message"]["sender_email"], sender.email)
|
|
|
|
display_recipients = [
|
|
|
|
trigger_event["message"]["display_recipient"][0]["email"],
|
|
|
|
trigger_event["message"]["display_recipient"][1]["email"],
|
|
|
|
]
|
|
|
|
self.assertTrue(sender.email in display_recipients)
|
|
|
|
self.assertTrue(recipient.email in display_recipients)
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
mock_queue_json_publish.side_effect = check_values_passed
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
self.send_personal_message(sender, recipient, "test")
|
2020-08-19 02:08:08 +02:00
|
|
|
self.assertTrue(mock_queue_json_publish.called)
|
|
|
|
|
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2021-02-12 08:19:30 +01:00
|
|
|
def test_no_trigger_on_personal_message_from_bot(
|
|
|
|
self, mock_queue_json_publish: mock.Mock
|
|
|
|
) -> None:
|
2020-08-19 02:08:08 +02:00
|
|
|
sender = self.second_bot_profile
|
|
|
|
recipient = self.bot_profile
|
|
|
|
self.send_personal_message(sender, recipient)
|
|
|
|
self.assertFalse(mock_queue_json_publish.called)
|
2017-05-25 20:02:13 +02:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_trigger_on_huddle_message_from_user(self, mock_queue_json_publish: mock.Mock) -> None:
|
2020-08-19 02:08:08 +02:00
|
|
|
self.second_bot_profile.bot_type = self.bot_profile.bot_type
|
|
|
|
self.second_bot_profile.save()
|
2017-05-25 20:02:13 +02:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
sender = self.user_profile
|
|
|
|
recipients = [self.bot_profile, self.second_bot_profile]
|
|
|
|
profile_ids = [self.bot_profile.id, self.second_bot_profile.id]
|
|
|
|
|
|
|
|
def check_values_passed(
|
|
|
|
queue_name: Any,
|
|
|
|
trigger_event: Dict[str, Any],
|
|
|
|
processor: Optional[Callable[[Any], None]] = None,
|
|
|
|
) -> None:
|
|
|
|
assert self.bot_profile.bot_type
|
|
|
|
self.assertEqual(queue_name, BOT_TYPE_TO_QUEUE_NAME[self.bot_profile.bot_type])
|
|
|
|
self.assertIn(trigger_event["user_profile_id"], profile_ids)
|
|
|
|
profile_ids.remove(trigger_event["user_profile_id"])
|
|
|
|
self.assertEqual(trigger_event["trigger"], "private_message")
|
|
|
|
self.assertEqual(trigger_event["message"]["sender_email"], sender.email)
|
2021-02-12 08:20:45 +01:00
|
|
|
self.assertEqual(trigger_event["message"]["type"], "private")
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2020-08-19 02:08:08 +02:00
|
|
|
mock_queue_json_publish.side_effect = check_values_passed
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
self.send_huddle_message(sender, recipients, "test")
|
2020-08-19 02:08:08 +02:00
|
|
|
self.assertEqual(mock_queue_json_publish.call_count, 2)
|
|
|
|
|
|
|
|
@for_all_bot_types
|
2022-04-14 23:50:10 +02:00
|
|
|
@patch_queue_publish("zerver.actions.message_send.queue_json_publish")
|
2021-02-12 08:19:30 +01:00
|
|
|
def test_no_trigger_on_huddle_message_from_bot(
|
|
|
|
self, mock_queue_json_publish: mock.Mock
|
|
|
|
) -> None:
|
2020-08-19 02:08:08 +02:00
|
|
|
sender = self.second_bot_profile
|
|
|
|
recipients = [self.user_profile, self.bot_profile]
|
|
|
|
self.send_huddle_message(sender, recipients)
|
|
|
|
self.assertFalse(mock_queue_json_publish.called)
|