zulip/zerver/tests/test_widgets.py

226 lines
7.4 KiB
Python

import re
from typing import Any, Dict
import orjson
from django.core.exceptions import ValidationError
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.validator import check_widget_content
from zerver.lib.widget import get_widget_data
from zerver.models import SubMessage
class WidgetContentTestCase(ZulipTestCase):
def test_validation(self) -> None:
def assert_error(obj: object, msg: str) -> None:
with self.assertRaisesRegex(ValidationError, re.escape(msg)):
check_widget_content(obj)
assert_error(5, "widget_content is not a dict")
assert_error({}, "widget_type is not in widget_content")
assert_error(dict(widget_type="whatever"), "extra_data is not in widget_content")
assert_error(dict(widget_type="zform", extra_data=4), "extra_data is not a dict")
assert_error(dict(widget_type="bogus", extra_data={}), "unknown widget type: bogus")
extra_data: Dict[str, Any] = {}
obj = dict(widget_type="zform", extra_data=extra_data)
assert_error(obj, "zform is missing type field")
extra_data["type"] = "bogus"
assert_error(obj, "unknown zform type: bogus")
extra_data["type"] = "choices"
assert_error(obj, "heading key is missing from extra_data")
extra_data["heading"] = "whatever"
assert_error(obj, "choices key is missing from extra_data")
extra_data["choices"] = 99
assert_error(obj, 'extra_data["choices"] is not a list')
extra_data["choices"] = [99]
assert_error(obj, 'extra_data["choices"][0] is not a dict')
extra_data["choices"] = [
dict(long_name="foo", reply="bar"),
]
assert_error(obj, 'short_name key is missing from extra_data["choices"][0]')
extra_data["choices"] = [
dict(short_name="a", long_name="foo", reply="bar"),
]
check_widget_content(obj)
def test_message_error_handling(self) -> None:
sender = self.example_user("cordelia")
stream_name = "Verona"
payload = dict(
type="stream",
to=stream_name,
client="test suite",
topic="whatever",
content="whatever",
)
payload["widget_content"] = "{{{{{{" # unparsable
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_error_contains(result, "Widgets: API programmer sent invalid JSON")
bogus_data = dict(color="red", foo="bar", x=2)
payload["widget_content"] = orjson.dumps(bogus_data).decode()
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_error_contains(result, "Widgets: widget_type is not in widget_content")
def test_get_widget_data_for_non_widget_messages(self) -> None:
# This is a pretty important test, despite testing the
# "negative" case. We never want widgets to interfere
# with normal messages.
test_messages = [
"",
" ",
"this is an ordinary message",
"/bogus_command",
"/me shrugs",
"use /poll",
]
for message in test_messages:
self.assertEqual(get_widget_data(content=message), (None, None))
# Add a positive check for context
self.assertEqual(get_widget_data(content="/todo"), ("todo", None))
def test_explicit_widget_content(self) -> None:
# Users can send widget_content directly on messages
# using the `widget_content` field.
sender = self.example_user("cordelia")
stream_name = "Verona"
content = "does-not-matter"
zform_data = dict(
type="choices",
heading="Options:",
choices=[],
)
widget_content = dict(
widget_type="zform",
extra_data=zform_data,
)
check_widget_content(widget_content)
payload = dict(
type="stream",
to=stream_name,
client="test suite",
topic="whatever",
content=content,
widget_content=orjson.dumps(widget_content).decode(),
)
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_success(result)
message = self.get_last_message()
self.assertEqual(message.content, content)
expected_submessage_content = dict(
widget_type="zform",
extra_data=zform_data,
)
submessage = SubMessage.objects.get(message_id=message.id)
self.assertEqual(submessage.msg_type, "widget")
self.assertEqual(orjson.loads(submessage.content), expected_submessage_content)
def test_todo(self) -> None:
# This also helps us get test coverage that could apply
# to future widgets.
sender = self.example_user("cordelia")
stream_name = "Verona"
content = "/todo"
payload = dict(
type="stream",
to=stream_name,
client="test suite",
topic="whatever",
content=content,
)
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_success(result)
message = self.get_last_message()
self.assertEqual(message.content, content)
expected_submessage_content = dict(
widget_type="todo",
extra_data=None,
)
submessage = SubMessage.objects.get(message_id=message.id)
self.assertEqual(submessage.msg_type, "widget")
self.assertEqual(orjson.loads(submessage.content), expected_submessage_content)
def test_poll_command_extra_data(self) -> None:
sender = self.example_user("cordelia")
stream_name = "Verona"
# We test for both trailing and leading spaces, along with blank lines
# for the poll options.
content = "/poll What is your favorite color?\n\nRed\nGreen \n\n Blue\n - Yellow"
payload = dict(
type="stream",
to=stream_name,
client="test suite",
topic="whatever",
content=content,
)
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_success(result)
message = self.get_last_message()
self.assertEqual(message.content, content)
expected_submessage_content = dict(
widget_type="poll",
extra_data=dict(
options=["Red", "Green", "Blue", "Yellow"],
question="What is your favorite color?",
),
)
submessage = SubMessage.objects.get(message_id=message.id)
self.assertEqual(submessage.msg_type, "widget")
self.assertEqual(orjson.loads(submessage.content), expected_submessage_content)
# Now don't supply a question.
content = "/poll"
payload["content"] = content
result = self.api_post(sender, "/api/v1/messages", payload)
self.assert_json_success(result)
expected_submessage_content = dict(
widget_type="poll",
extra_data=dict(
options=[],
question="",
),
)
message = self.get_last_message()
self.assertEqual(message.content, content)
submessage = SubMessage.objects.get(message_id=message.id)
self.assertEqual(submessage.msg_type, "widget")
self.assertEqual(orjson.loads(submessage.content), expected_submessage_content)