2023-04-05 13:36:01 +02:00
|
|
|
from typing import Any, Dict, List
|
2021-05-18 16:59:17 +02:00
|
|
|
from unittest import mock
|
2018-02-12 10:53:36 +01:00
|
|
|
|
2022-04-14 23:31:40 +02:00
|
|
|
from zerver.actions.submessage import do_add_submessage
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.message import MessageDict
|
2018-02-11 14:08:01 +01:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import Message, SubMessage
|
2018-02-11 14:08:01 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TestBasics(ZulipTestCase):
|
|
|
|
def test_get_raw_db_rows(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
stream_name = "Verona"
|
2018-02-11 14:08:01 +01:00
|
|
|
|
|
|
|
message_id = self.send_stream_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
sender=cordelia,
|
2018-02-11 14:08:01 +01:00
|
|
|
stream_name=stream_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_raw_rows() -> List[Dict[str, Any]]:
|
|
|
|
query = SubMessage.get_raw_db_rows([message_id])
|
|
|
|
rows = list(query)
|
|
|
|
return rows
|
|
|
|
|
|
|
|
rows = get_raw_rows()
|
|
|
|
self.assertEqual(rows, [])
|
|
|
|
|
|
|
|
sm1 = SubMessage.objects.create(
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="stuff1",
|
2018-02-11 14:08:01 +01:00
|
|
|
message_id=message_id,
|
|
|
|
sender=cordelia,
|
|
|
|
)
|
|
|
|
|
|
|
|
sm2 = SubMessage.objects.create(
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="stuff2",
|
2018-02-11 14:08:01 +01:00
|
|
|
message_id=message_id,
|
|
|
|
sender=hamlet,
|
|
|
|
)
|
|
|
|
|
|
|
|
expected_data = [
|
|
|
|
dict(
|
|
|
|
id=sm1.id,
|
|
|
|
message_id=message_id,
|
|
|
|
sender_id=cordelia.id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="stuff1",
|
2018-02-11 14:08:01 +01:00
|
|
|
),
|
|
|
|
dict(
|
|
|
|
id=sm2.id,
|
|
|
|
message_id=message_id,
|
|
|
|
sender_id=hamlet.id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="stuff2",
|
2018-02-11 14:08:01 +01:00
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
self.assertEqual(get_raw_rows(), expected_data)
|
2018-02-11 14:09:17 +01:00
|
|
|
|
|
|
|
message = Message.objects.get(id=message_id)
|
|
|
|
message_json = MessageDict.wide_dict(message)
|
2021-02-12 08:20:45 +01:00
|
|
|
rows = message_json["submessages"]
|
|
|
|
rows.sort(key=lambda r: r["id"])
|
2018-02-11 14:09:17 +01:00
|
|
|
self.assertEqual(rows, expected_data)
|
|
|
|
|
|
|
|
msg_rows = MessageDict.get_raw_db_rows([message_id])
|
2021-02-12 08:20:45 +01:00
|
|
|
rows = msg_rows[0]["submessages"]
|
|
|
|
rows.sort(key=lambda r: r["id"])
|
2018-02-11 14:09:17 +01:00
|
|
|
self.assertEqual(rows, expected_data)
|
2018-02-12 10:53:36 +01:00
|
|
|
|
|
|
|
def test_endpoint_errors(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
stream_name = "Verona"
|
2018-02-12 10:53:36 +01:00
|
|
|
message_id = self.send_stream_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
sender=cordelia,
|
2018-02-12 10:53:36 +01:00
|
|
|
stream_name=stream_name,
|
|
|
|
)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(cordelia)
|
2018-02-12 10:53:36 +01:00
|
|
|
|
|
|
|
payload = dict(
|
|
|
|
message_id=message_id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="not json",
|
2018-02-12 10:53:36 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_post("/json/submessage", payload)
|
|
|
|
self.assert_json_error(result, "Invalid json for submessage")
|
2018-02-12 10:53:36 +01:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
hamlet = self.example_user("hamlet")
|
2018-02-12 10:53:36 +01:00
|
|
|
bad_message_id = self.send_personal_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
from_user=hamlet,
|
|
|
|
to_user=hamlet,
|
2018-02-12 10:53:36 +01:00
|
|
|
)
|
|
|
|
payload = dict(
|
|
|
|
message_id=bad_message_id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
|
|
|
content="does not matter",
|
2018-02-12 10:53:36 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_post("/json/submessage", payload)
|
|
|
|
self.assert_json_error(result, "Invalid message(s)")
|
2018-02-12 10:53:36 +01:00
|
|
|
|
2021-06-11 17:36:43 +02:00
|
|
|
def test_original_sender_enforced(self) -> None:
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
stream_name = "Verona"
|
|
|
|
|
|
|
|
message_id = self.send_stream_message(
|
|
|
|
sender=cordelia,
|
|
|
|
stream_name=stream_name,
|
|
|
|
)
|
|
|
|
self.login_user(hamlet)
|
|
|
|
|
|
|
|
payload = dict(
|
|
|
|
message_id=message_id,
|
|
|
|
msg_type="whatever",
|
|
|
|
content="{}",
|
|
|
|
)
|
|
|
|
|
|
|
|
# Hamlet can't just go attaching submessages to Cordelia's
|
|
|
|
# message, even though he does have read access here to the
|
|
|
|
# message itself.
|
|
|
|
result = self.client_post("/json/submessage", payload)
|
|
|
|
self.assert_json_error(result, "You cannot attach a submessage to this message.")
|
|
|
|
|
2021-10-18 16:30:46 +02:00
|
|
|
# Since Hamlet is actually subscribed to the stream, he is welcome
|
2021-06-11 17:36:43 +02:00
|
|
|
# to send submessages to Cordelia once she initiates the "subconversation".
|
|
|
|
do_add_submessage(
|
|
|
|
realm=cordelia.realm,
|
|
|
|
sender_id=cordelia.id,
|
|
|
|
message_id=message_id,
|
|
|
|
msg_type="whatever",
|
|
|
|
content="whatever",
|
|
|
|
)
|
|
|
|
|
|
|
|
result = self.client_post("/json/submessage", payload)
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2018-02-12 10:53:36 +01:00
|
|
|
def test_endpoint_success(self) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
stream_name = "Verona"
|
2018-02-12 10:53:36 +01:00
|
|
|
message_id = self.send_stream_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
sender=cordelia,
|
2018-02-12 10:53:36 +01:00
|
|
|
stream_name=stream_name,
|
|
|
|
)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(cordelia)
|
2018-02-12 10:53:36 +01:00
|
|
|
|
|
|
|
payload = dict(
|
|
|
|
message_id=message_id,
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
content='{"name": "alice", "salary": 20}',
|
2018-02-12 10:53:36 +01:00
|
|
|
)
|
2023-04-05 13:36:01 +02:00
|
|
|
with self.capture_send_event_calls(expected_num_events=1) as events:
|
2021-02-12 08:20:45 +01:00
|
|
|
result = self.client_post("/json/submessage", payload)
|
2018-02-12 10:53:36 +01:00
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2018-05-30 22:41:15 +02:00
|
|
|
submessage = SubMessage.objects.get(message_id=message_id)
|
|
|
|
|
2018-02-12 10:53:36 +01:00
|
|
|
expected_data = dict(
|
|
|
|
message_id=message_id,
|
2018-05-30 22:41:15 +02:00
|
|
|
submessage_id=submessage.id,
|
2021-02-12 08:20:45 +01:00
|
|
|
content=payload["content"],
|
|
|
|
msg_type="whatever",
|
2018-02-12 10:53:36 +01:00
|
|
|
sender_id=cordelia.id,
|
2021-02-12 08:20:45 +01:00
|
|
|
type="submessage",
|
2018-02-12 10:53:36 +01:00
|
|
|
)
|
|
|
|
|
2021-05-27 16:25:23 +02:00
|
|
|
data = events[0]["event"]
|
2018-02-12 10:53:36 +01:00
|
|
|
self.assertEqual(data, expected_data)
|
2021-05-27 16:25:23 +02:00
|
|
|
users = events[0]["users"]
|
2018-02-12 10:53:36 +01:00
|
|
|
self.assertIn(cordelia.id, users)
|
|
|
|
self.assertIn(hamlet.id, users)
|
|
|
|
|
|
|
|
rows = SubMessage.get_raw_db_rows([message_id])
|
2021-05-17 05:41:32 +02:00
|
|
|
self.assert_length(rows, 1)
|
2018-02-12 10:53:36 +01:00
|
|
|
row = rows[0]
|
|
|
|
|
|
|
|
expected_data = dict(
|
2021-02-12 08:20:45 +01:00
|
|
|
id=row["id"],
|
2018-02-12 10:53:36 +01:00
|
|
|
message_id=message_id,
|
|
|
|
content='{"name": "alice", "salary": 20}',
|
2021-02-12 08:20:45 +01:00
|
|
|
msg_type="whatever",
|
2018-02-12 10:53:36 +01:00
|
|
|
sender_id=cordelia.id,
|
|
|
|
)
|
|
|
|
self.assertEqual(row, expected_data)
|
2021-05-18 16:59:17 +02:00
|
|
|
|
|
|
|
def test_submessage_event_sent_after_transaction_commits(self) -> None:
|
|
|
|
"""
|
|
|
|
Tests that `send_event` is hooked to `transaction.on_commit`. This is important, because
|
|
|
|
we don't want to end up holding locks on message rows for too long if the event queue runs
|
|
|
|
into a problem.
|
|
|
|
"""
|
|
|
|
hamlet = self.example_user("hamlet")
|
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
|
|
|
message_id = self.send_stream_message(hamlet, "Denmark")
|
2021-05-18 16:59:17 +02:00
|
|
|
|
2023-04-05 13:36:01 +02:00
|
|
|
with self.capture_send_event_calls(expected_num_events=1):
|
django_api: Extract send_event_on_commit helper.
django-stubs 4.2.1 gives transaction.on_commit a more accurate type
annotation, but this exposed that mypy can’t handle the lambda default
parameters that we use to recapture loop variables such as
for stream_id in public_stream_ids:
peer_user_ids = …
event = …
transaction.on_commit(
lambda event=event, peer_user_ids=peer_user_ids: send_event(
realm, event, peer_user_ids
)
)
https://github.com/python/mypy/issues/15459
A workaround that mypy accepts is
transaction.on_commit(
(
lambda event, peer_user_ids: lambda: send_event(
realm, event, peer_user_ids
)
)(event, peer_user_ids)
)
But that’s kind of ugly and potentially error-prone, so let’s make a
helper function for this very common pattern.
send_event_on_commit(realm, event, peer_user_ids)
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-17 20:53:07 +02:00
|
|
|
with mock.patch("zerver.tornado.django_api.queue_json_publish") as m:
|
2021-05-18 16:59:17 +02:00
|
|
|
m.side_effect = AssertionError(
|
|
|
|
"Events should be sent only after the transaction commits."
|
|
|
|
)
|
|
|
|
do_add_submessage(hamlet.realm, hamlet.id, message_id, "whatever", "whatever")
|
2023-06-07 17:37:16 +02:00
|
|
|
|
|
|
|
def test_fetch_message_containing_submessages(self) -> None:
|
|
|
|
cordelia = self.example_user("cordelia")
|
|
|
|
stream_name = "Verona"
|
|
|
|
message_id = self.send_stream_message(
|
|
|
|
sender=cordelia,
|
|
|
|
stream_name=stream_name,
|
|
|
|
)
|
|
|
|
self.login_user(cordelia)
|
|
|
|
|
|
|
|
payload = dict(
|
|
|
|
message_id=message_id,
|
|
|
|
msg_type="whatever",
|
|
|
|
content='{"name": "alice", "salary": 20}',
|
|
|
|
)
|
|
|
|
self.assert_json_success(self.client_post("/json/submessage", payload))
|
|
|
|
|
|
|
|
result = self.client_get(f"/json/messages/{message_id}")
|
|
|
|
response_dict = self.assert_json_success(result)
|
|
|
|
self.assert_length(response_dict["message"]["submessages"], 1)
|
|
|
|
|
|
|
|
submessage = response_dict["message"]["submessages"][0]
|
|
|
|
expected_data = dict(
|
|
|
|
id=submessage["id"],
|
|
|
|
message_id=message_id,
|
|
|
|
content='{"name": "alice", "salary": 20}',
|
|
|
|
msg_type="whatever",
|
|
|
|
sender_id=cordelia.id,
|
|
|
|
)
|
|
|
|
self.assertEqual(submessage, expected_data)
|