Files
upstream-zulip/zerver/tests/test_event_system.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

1750 lines
68 KiB
Python

import time
from collections.abc import Callable
from typing import Any
from unittest import mock
from urllib.parse import urlsplit
import orjson
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from django.test import override_settings
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from zerver.actions.channel_folders import check_add_channel_folder
from zerver.actions.custom_profile_fields import try_update_realm_custom_profile_field
from zerver.actions.message_send import check_send_message
from zerver.actions.presence import do_update_user_presence
from zerver.actions.streams import do_change_stream_folder
from zerver.actions.user_settings import do_change_avatar_fields, do_change_user_setting
from zerver.actions.users import do_change_user_role
from zerver.lib.event_schema import check_web_reload_client_event
from zerver.lib.events import fetch_initial_state_data, post_process_state
from zerver.lib.exceptions import AccessDeniedError
from zerver.lib.request import RequestVariableMissingError
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import (
HostRequestMock,
dummy_handler,
reset_email_visibility_to_everyone_in_zulip_realm,
stub_event_queue_user_events,
)
from zerver.lib.users import get_users_for_api
from zerver.models import CustomProfileField, UserMessage, UserPresence, UserProfile
from zerver.models.clients import get_client
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
from zerver.models.users import get_system_bot
from zerver.tornado.django_api import EventQueueData
from zerver.tornado.event_queue import (
DEFAULT_EVENT_QUEUE_TIMEOUT_SECS,
MOBILE_EVENT_QUEUE_TIMEOUT_SECS,
allocate_client_descriptor,
clear_client_event_queues_for_testing,
get_client_info_for_message_event,
mark_clients_to_reload,
process_message_event,
send_web_reload_client_events,
)
from zerver.tornado.exceptions import BadEventQueueIdError
from zerver.tornado.views import get_events
from zerver.views.events_register import _default_all_public_streams, _default_narrow
class EventsEndpointTest(ZulipTestCase):
def test_events_register_without_user_agent(self) -> None:
result = self.client_post("/json/register", skip_user_agent=True)
self.assert_json_success(result)
def test_narrows(self) -> None:
user = self.example_user("hamlet")
with mock.patch("zerver.lib.events.request_event_queue", return_value=None) as m:
munge = lambda obj: orjson.dumps(obj).decode()
narrow = [["stream", "devel"], ["is", "mentioned"]]
payload = dict(narrow=munge(narrow))
result = self.api_post(user, "/api/v1/register", payload)
# We want the test to abort before actually fetching data.
self.assert_json_error(result, "Could not allocate event queue")
self.assertEqual(m.call_args.kwargs["narrow"], [["stream", "devel"], ["is", "mentioned"]])
def test_invalid_narrow(self) -> None:
hamlet = self.example_user("hamlet")
narrow = [["stream", "devel", True]]
payload = dict(narrow=orjson.dumps(narrow).decode())
result = self.api_post(hamlet, "/api/v1/register", payload)
self.assert_json_error(result, "narrow[0] is too long (limit: 2 items)")
narrow = [["stream"]]
payload = dict(narrow=orjson.dumps(narrow).decode())
result = self.api_post(hamlet, "/api/v1/register", payload)
self.assert_json_error(result, "narrow[0] is too short (minimum 2 items)")
def test_events_register_endpoint(self) -> None:
# This test is intended to get minimal coverage on the
# events_register code paths
user = self.example_user("hamlet")
with mock.patch("zerver.views.events_register.do_events_register", return_value={}):
result = self.api_post(user, "/api/v1/register")
self.assert_json_success(result)
with mock.patch("zerver.lib.events.request_event_queue", return_value=None):
result = self.api_post(user, "/api/v1/register")
self.assert_json_error(result, "Could not allocate event queue")
return_event_queue = EventQueueData(queue_id="15:11", idle_queue_timeout_secs=600)
return_user_events: list[dict[str, Any]] = []
# We choose realm_emoji somewhat randomly--we want
# a "boring" event type for the purpose of this test.
event_type = "realm_emoji"
test_realm_emoji: dict[str, Any] = {
"id": "1",
"name": "spain",
"source_url": "http://example.com/",
"still_url": None,
"deactivated": False,
"author_id": user.id,
}
test_event = dict(id=6, type=event_type, op="add", emoji=test_realm_emoji)
# Test that call is made to deal with a returning soft deactivated user.
with (
mock.patch("zerver.lib.events.reactivate_user_if_soft_deactivated") as fa,
stub_event_queue_user_events(return_event_queue, return_user_events),
):
result = self.api_post(
user, "/api/v1/register", dict(event_types=orjson.dumps([event_type]).decode())
)
self.assertEqual(fa.call_count, 1)
with stub_event_queue_user_events(return_event_queue, return_user_events):
result = self.api_post(
user, "/api/v1/register", dict(event_types=orjson.dumps([event_type]).decode())
)
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["last_event_id"], -1)
self.assertEqual(result_dict["queue_id"], "15:11")
# Now start simulating returning actual data
return_event_queue.queue_id = "15:12"
return_user_events = [test_event]
with stub_event_queue_user_events(return_event_queue, return_user_events):
result = self.api_post(
user, "/api/v1/register", dict(event_types=orjson.dumps([event_type]).decode())
)
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["last_event_id"], 6)
self.assertEqual(result_dict["queue_id"], "15:12")
# sanity check the data relevant to our event
self.assertEqual(result_dict["realm_emoji"], {"1": test_realm_emoji})
# Now test with `fetch_event_types` not matching the event
return_event_queue.queue_id = "15:13"
with stub_event_queue_user_events(return_event_queue, return_user_events):
result = self.api_post(
user,
"/api/v1/register",
dict(
event_types=orjson.dumps([event_type]).decode(),
fetch_event_types=orjson.dumps(["message"]).decode(),
),
)
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["last_event_id"], 6)
# Check that the message event types data is in there
self.assertIn("max_message_id", result_dict)
# Check that our original event type is not there.
self.assertNotIn(event_type, result_dict)
self.assertEqual(result_dict["queue_id"], "15:13")
# Now test with `fetch_event_types` matching the event
with stub_event_queue_user_events(return_event_queue, return_user_events):
result = self.api_post(
user,
"/api/v1/register",
dict(
fetch_event_types=orjson.dumps([event_type]).decode(),
event_types=orjson.dumps(["message"]).decode(),
),
)
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["last_event_id"], 6)
# Check that we didn't fetch the messages data
self.assertNotIn("max_message_id", result_dict)
# Check that the realm_emoji data is in there.
self.assertIn("realm_emoji", result_dict)
self.assertEqual(result_dict["realm_emoji"], {"1": test_realm_emoji})
self.assertEqual(result_dict["queue_id"], "15:13")
def test_idle_queue_timeout(self) -> None:
user = self.example_user("hamlet")
# Verify response includes idle_queue_timeout_secs.
queue_data = EventQueueData(
queue_id="1", idle_queue_timeout_secs=DEFAULT_EVENT_QUEUE_TIMEOUT_SECS
)
with stub_event_queue_user_events(queue_data, []):
result = self.api_post(user, "/api/v1/register")
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["idle_queue_timeout_secs"], DEFAULT_EVENT_QUEUE_TIMEOUT_SECS)
# "mobile" is accepted.
queue_data = EventQueueData(
queue_id="2", idle_queue_timeout_secs=MOBILE_EVENT_QUEUE_TIMEOUT_SECS
)
with stub_event_queue_user_events(queue_data, []):
result = self.api_post(
user,
"/api/v1/register",
{"idle_queue_timeout": orjson.dumps("mobile").decode()},
)
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["idle_queue_timeout_secs"], MOBILE_EVENT_QUEUE_TIMEOUT_SECS)
# Explicit integer value is accepted.
queue_data = EventQueueData(queue_id="3", idle_queue_timeout_secs=3600)
with stub_event_queue_user_events(queue_data, []):
result = self.api_post(user, "/api/v1/register", {"idle_queue_timeout": 3600})
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["idle_queue_timeout_secs"], 3600)
# Invalid string value is rejected.
result = self.api_post(
user, "/api/v1/register", {"idle_queue_timeout": orjson.dumps("invalid").decode()}
)
self.assert_json_error_contains(result, "idle_queue_timeout")
def test_events_register_spectators(self) -> None:
# Verify that POST /register works for spectators, but not for
# normal users.
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False), self.assert_database_query_count(2):
result = self.client_post("/json/register")
self.assert_json_error(
result,
"Not logged in: API authentication or user session required",
status_code=401,
)
with self.assert_database_query_count(15):
result = self.client_post("/json/register")
result_dict = self.assert_json_success(result)
self.assertEqual(result_dict["queue_id"], None)
self.assertEqual(result_dict["realm_url"], "http://zulip.testserver")
self.assertEqual(result_dict["realm_uri"], "http://zulip.testserver")
result = self.client_post("/json/register")
self.assertEqual(result.status_code, 200)
result = self.client_post("/json/register", dict(client_gravatar="false"))
self.assertEqual(result.status_code, 200)
result = self.client_post("/json/register", dict(client_gravatar="true"))
self.assert_json_error(
result,
"Invalid 'client_gravatar' parameter for anonymous request",
status_code=400,
)
result = self.client_post("/json/register", dict(include_subscribers="true"))
self.assert_json_error(
result,
"Invalid 'include_subscribers' parameter for anonymous request",
status_code=400,
)
def test_channel_folders_for_spectators(self) -> None:
realm = get_realm("zulip")
iago = self.example_user("iago")
frontend_folder = check_add_channel_folder(realm, "Frontend", "", acting_user=iago)
backend_folder = check_add_channel_folder(realm, "Backend", "", acting_user=iago)
result = self.client_post("/json/register")
self.assertEqual(result.status_code, 200)
channel_folders_data = orjson.loads(result.content)["channel_folders"]
self.assert_length(channel_folders_data, 0)
web_public_stream = get_stream("Rome", realm)
do_change_stream_folder(web_public_stream, frontend_folder, acting_user=iago)
public_stream = get_stream("Verona", realm)
do_change_stream_folder(public_stream, backend_folder, acting_user=iago)
result = self.client_post("/json/register")
self.assertEqual(result.status_code, 200)
channel_folders_data = orjson.loads(result.content)["channel_folders"]
self.assert_length(channel_folders_data, 1)
self.assertEqual(channel_folders_data[0]["name"], "Frontend")
def test_events_register_endpoint_all_public_streams_access(self) -> None:
guest_user = self.example_user("polonius")
normal_user = self.example_user("hamlet")
self.assertEqual(guest_user.role, UserProfile.ROLE_GUEST)
self.assertEqual(normal_user.role, UserProfile.ROLE_MEMBER)
with mock.patch("zerver.views.events_register.do_events_register", return_value={}):
result = self.api_post(normal_user, "/api/v1/register", dict(all_public_streams="true"))
self.assert_json_success(result)
with mock.patch("zerver.views.events_register.do_events_register", return_value={}):
result = self.api_post(guest_user, "/api/v1/register", dict(all_public_streams="true"))
self.assert_json_error(result, "User not authorized for this query")
def test_events_get_events_endpoint_guest_cant_use_all_public_streams_param(self) -> None:
"""
This test is meant to execute the very beginning of the codepath
to ensure guest users are immediately disallowed to use the
all_public_streams param. Deeper testing is hard (and not necessary for this case)
due to the codepath expecting AsyncDjangoHandler to be attached to the request,
which doesn't happen in our test setup.
"""
guest_user = self.example_user("polonius")
self.assertEqual(guest_user.role, UserProfile.ROLE_GUEST)
result = self.api_get(guest_user, "/api/v1/events", dict(all_public_streams="true"))
self.assert_json_error(result, "User not authorized for this query")
def test_tornado_endpoint(self) -> None:
# This test is mostly intended to get minimal coverage on the
# /api/internal/notify_tornado endpoint (only used in
# puppeteer tests), so we can have 100% URL coverage, but it
# does exercise a little bit of the codepath.
post_data = dict(
data=orjson.dumps(
dict(
event=dict(
type="other",
),
users=[self.example_user("hamlet").id],
),
).decode(),
)
req = HostRequestMock(post_data)
req.META["REMOTE_ADDR"] = "127.0.0.1"
with self.assertRaises(RequestVariableMissingError) as context:
result = self.client_post_request("/api/internal/notify_tornado", req)
self.assertEqual(str(context.exception), "Missing 'secret' argument")
self.assertEqual(context.exception.http_status_code, 400)
post_data["secret"] = "random"
req = HostRequestMock(post_data, user_profile=None)
req.META["REMOTE_ADDR"] = "127.0.0.1"
with self.assertRaises(AccessDeniedError) as access_denied_error:
result = self.client_post_request("/api/internal/notify_tornado", req)
self.assertEqual(str(access_denied_error.exception), "Access denied")
self.assertEqual(access_denied_error.exception.http_status_code, 403)
post_data["secret"] = settings.SHARED_SECRET
req = HostRequestMock(post_data, tornado_handler=dummy_handler)
req.META["REMOTE_ADDR"] = "127.0.0.1"
result = self.client_post_request("/api/internal/notify_tornado", req)
self.assert_json_success(result)
post_data = dict(secret=settings.SHARED_SECRET)
req = HostRequestMock(post_data, tornado_handler=dummy_handler)
req.META["REMOTE_ADDR"] = "127.0.0.1"
with self.assertRaises(RequestVariableMissingError) as context:
result = self.client_post_request("/api/internal/notify_tornado", req)
self.assertEqual(str(context.exception), "Missing 'data' argument")
self.assertEqual(context.exception.http_status_code, 400)
def test_web_reload_clients(self) -> None:
# Minimal testing of the /api/internal/web_reload_clients endpoint
post_data = {
"client_count": "1",
"immediate": orjson.dumps(False).decode(),
"secret": settings.SHARED_SECRET,
}
req = HostRequestMock(post_data, tornado_handler=dummy_handler)
req.META["REMOTE_ADDR"] = "127.0.0.1"
result = self.client_post_request("/api/internal/web_reload_clients", req)
self.assert_json_success(result)
self.assertEqual(orjson.loads(result.content)["sent_events"], 0)
class GetEventsTest(ZulipTestCase):
def tornado_call(
self,
view_func: Callable[[HttpRequest, UserProfile], HttpResponse],
user_profile: UserProfile,
post_data: dict[str, Any],
) -> HttpResponse:
request = HostRequestMock(post_data, user_profile, tornado_handler=dummy_handler)
return view_func(request, user_profile)
def test_get_events(self) -> None:
user_profile = self.example_user("hamlet")
email = user_profile.email
recipient_user_profile = self.example_user("othello")
recipient_email = recipient_user_profile.email
recipient = self.get_dm_group_recipient(user_profile, recipient_user_profile)
self.login_user(user_profile)
result = self.tornado_call(
get_events,
user_profile,
{
"apply_markdown": orjson.dumps(True).decode(),
"client_gravatar": orjson.dumps(True).decode(),
"event_types": orjson.dumps(["message"]).decode(),
"user_client": "website",
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
recipient_result = self.tornado_call(
get_events,
recipient_user_profile,
{
"apply_markdown": orjson.dumps(True).decode(),
"client_gravatar": orjson.dumps(True).decode(),
"event_types": orjson.dumps(["message"]).decode(),
"user_client": "website",
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(recipient_result)
recipient_queue_id = orjson.loads(recipient_result.content)["queue_id"]
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 0)
local_id = "10.01"
with self.captureOnCommitCallbacks(execute=True):
check_send_message(
sender=user_profile,
client=get_client("whatever"),
recipient_type_name="private",
message_to=[recipient_email],
topic_name=None,
message_content="hello",
local_id=local_id,
sender_queue_id=queue_id,
)
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
self.assertEqual(events[0]["message"]["sender_email"], email)
self.assertEqual(events[0]["local_message_id"], local_id)
self.assertEqual(events[0]["message"]["display_recipient"][0]["is_mirror_dummy"], False)
self.assertEqual(events[0]["message"]["display_recipient"][1]["is_mirror_dummy"], False)
self.assertEqual(events[0]["message"]["recipient_id"], recipient.id)
last_event_id = events[0]["id"]
local_id = "10.02"
with self.captureOnCommitCallbacks(execute=True):
check_send_message(
sender=user_profile,
client=get_client("whatever"),
recipient_type_name="private",
message_to=[recipient_email],
topic_name=None,
message_content="hello",
local_id=local_id,
sender_queue_id=queue_id,
)
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": last_event_id,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
self.assertEqual(events[0]["message"]["sender_email"], email)
self.assertEqual(events[0]["local_message_id"], local_id)
self.assertEqual(events[0]["message"]["recipient_id"], recipient.id)
# Test that the received message in the receiver's event queue
# exists and does not contain a local id
recipient_result = self.tornado_call(
get_events,
recipient_user_profile,
{
"queue_id": recipient_queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
recipient_events = orjson.loads(recipient_result.content)["events"]
self.assert_json_success(recipient_result)
self.assert_length(recipient_events, 2)
self.assertEqual(recipient_events[0]["type"], "message")
self.assertEqual(recipient_events[0]["message"]["sender_email"], email)
self.assertTrue("local_message_id" not in recipient_events[0])
# Incoming DMs show the recipient_id that outgoing DMs would.
self.assertEqual(events[0]["message"]["recipient_id"], recipient.id)
self.assertEqual(recipient_events[1]["type"], "message")
self.assertEqual(recipient_events[1]["message"]["sender_email"], email)
self.assertTrue("local_message_id" not in recipient_events[1])
self.assertEqual(recipient_events[1]["message"]["recipient_id"], recipient.id)
def test_get_events_narrow(self) -> None:
user_profile = self.example_user("hamlet")
self.login_user(user_profile)
do_change_avatar_fields(user_profile, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=None)
self.assertEqual(user_profile.avatar_source, UserProfile.AVATAR_FROM_GRAVATAR)
def get_message(apply_markdown: bool, client_gravatar: bool) -> dict[str, Any]:
result = self.tornado_call(
get_events,
user_profile,
dict(
apply_markdown=orjson.dumps(apply_markdown).decode(),
client_gravatar=orjson.dumps(client_gravatar).decode(),
event_types=orjson.dumps(["message"]).decode(),
narrow=orjson.dumps([["stream", "denmark"]]).decode(),
user_client="website",
dont_block=orjson.dumps(True).decode(),
),
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 0)
self.send_personal_message(user_profile, self.example_user("othello"), "hello")
self.send_stream_message(user_profile, "Denmark", "**hello**")
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
return events[0]["message"]
message = get_message(apply_markdown=False, client_gravatar=False)
self.assertEqual(message["display_recipient"], "Denmark")
self.assertEqual(message["content"], "**hello**")
self.assertEqual(urlsplit(message["avatar_url"]).hostname, "secure.gravatar.com")
message = get_message(apply_markdown=True, client_gravatar=False)
self.assertEqual(message["display_recipient"], "Denmark")
self.assertEqual(message["content"], "<p><strong>hello</strong></p>")
self.assertIn("gravatar.com", message["avatar_url"])
do_change_user_setting(
user_profile,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
message = get_message(apply_markdown=False, client_gravatar=True)
self.assertEqual(message["display_recipient"], "Denmark")
self.assertEqual(message["content"], "**hello**")
self.assertEqual(message["avatar_url"], None)
message = get_message(apply_markdown=True, client_gravatar=True)
self.assertEqual(message["display_recipient"], "Denmark")
self.assertEqual(message["content"], "<p><strong>hello</strong></p>")
self.assertEqual(message["avatar_url"], None)
def test_get_events_guest_narrow_unsubscribed_stream(self) -> None:
guest = self.example_user("polonius")
hamlet = self.example_user("hamlet")
self.login_user(guest)
stream_name = "public_events_stream"
self.subscribe(hamlet, stream_name)
result = self.tornado_call(
get_events,
guest,
dict(
apply_markdown=orjson.dumps(True).decode(),
client_gravatar=orjson.dumps(True).decode(),
event_types=orjson.dumps(["message"]).decode(),
narrow=orjson.dumps([["stream", stream_name]]).decode(),
user_client="website",
dont_block=orjson.dumps(True).decode(),
),
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
def get_events_for_queue() -> list[dict[str, Any]]:
result = self.tornado_call(
get_events,
guest,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(result)
return orjson.loads(result.content)["events"]
# A guest does not receive events for messages in a public
# channel they are not subscribed to, even with a narrow
# matching the message.
self.send_stream_message(hamlet, stream_name, "secret message")
self.assert_length(get_events_for_queue(), 0)
# Once subscribed, the guest receives message events.
self.subscribe(guest, stream_name)
self.send_stream_message(hamlet, stream_name, "public message")
events = get_events_for_queue()
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
self.assertEqual(events[0]["message"]["content"], "<p>public message</p>")
def test_get_events_guest_narrow_unsubscribed_web_public_stream(self) -> None:
guest = self.example_user("polonius")
hamlet = self.example_user("hamlet")
self.login_user(guest)
stream_name = "web_public_events_stream"
self.make_stream(stream_name, guest.realm, is_web_public=True)
self.subscribe(hamlet, stream_name)
result = self.tornado_call(
get_events,
guest,
dict(
apply_markdown=orjson.dumps(True).decode(),
client_gravatar=orjson.dumps(True).decode(),
event_types=orjson.dumps(["message"]).decode(),
narrow=orjson.dumps([["stream", stream_name]]).decode(),
user_client="website",
dont_block=orjson.dumps(True).decode(),
),
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
# A guest can read web-public channels without subscribing.
self.send_stream_message(hamlet, stream_name, "web-public message")
result = self.tornado_call(
get_events,
guest,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(result)
events = orjson.loads(result.content)["events"]
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
self.assertEqual(events[0]["message"]["content"], "<p>web-public message</p>")
def test_get_events_all_public_streams_after_guest_demotion(self) -> None:
hamlet = self.example_user("hamlet")
iago = self.example_user("iago")
self.assertFalse(hamlet.is_guest)
self.login_user(hamlet)
stream_name = "public_events_stream"
self.subscribe(iago, stream_name)
result = self.tornado_call(
get_events,
hamlet,
dict(
apply_markdown=orjson.dumps(True).decode(),
client_gravatar=orjson.dumps(True).decode(),
event_types=orjson.dumps(["message"]).decode(),
all_public_streams=orjson.dumps(True).decode(),
user_client="website",
dont_block=orjson.dumps(True).decode(),
),
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
last_event_id = -1
def get_new_events() -> list[dict[str, Any]]:
nonlocal last_event_id
result = self.tornado_call(
get_events,
hamlet,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": last_event_id,
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(result)
events = orjson.loads(result.content)["events"]
if events:
last_event_id = events[-1]["id"]
return events
# As a member with an `all_public_streams` queue, hamlet
# receives messages for public channels he is not subscribed
# to.
self.send_stream_message(iago, stream_name, "member message")
events = get_new_events()
self.assert_length(events, 1)
self.assertEqual(events[0]["message"]["content"], "<p>member message</p>")
# After hamlet's role is changed to guest, the same queue no
# longer receives messages for public channels he is not
# subscribed to.
do_change_user_role(hamlet, UserProfile.ROLE_GUEST, acting_user=None, notify=False)
self.send_stream_message(iago, stream_name, "secret message")
self.assert_length(get_new_events(), 0)
# Once subscribed, the guest receives message events again.
self.subscribe(hamlet, stream_name)
self.send_stream_message(iago, stream_name, "subscribed message")
events = get_new_events()
self.assert_length(events, 1)
self.assertEqual(events[0]["type"], "message")
self.assertEqual(events[0]["message"]["content"], "<p>subscribed message</p>")
def test_bogus_queue_id(self) -> None:
user = self.example_user("hamlet")
with self.assertRaises(BadEventQueueIdError):
self.tornado_call(
get_events,
user,
{
"queue_id": "hamster",
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
def test_wrong_user_queue_id(self) -> None:
user = self.example_user("hamlet")
wrong_user = self.example_user("othello")
result = self.tornado_call(
get_events,
user,
{
"apply_markdown": orjson.dumps(True).decode(),
"client_gravatar": orjson.dumps(True).decode(),
"event_types": orjson.dumps(["message"]).decode(),
"user_client": "website",
"dont_block": orjson.dumps(True).decode(),
},
)
self.assert_json_success(result)
queue_id = orjson.loads(result.content)["queue_id"]
with self.assertLogs(level="WARNING") as cm, self.assertRaises(BadEventQueueIdError):
self.tornado_call(
get_events,
wrong_user,
{
"queue_id": queue_id,
"user_client": "website",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
self.assertIn("not authorized for queue", cm.output[0])
def test_get_events_custom_profile_fields(self) -> None:
user_profile = self.example_user("iago")
self.login_user(user_profile)
profile_field = CustomProfileField.objects.get(realm=user_profile.realm, name="Pronouns")
def check_pronouns_type_field_supported(
pronouns_field_type_supported: bool, new_name: str
) -> None:
clear_client_event_queues_for_testing()
queue_data = dict(
apply_markdown=True,
all_public_streams=True,
client_type_name="ZulipMobile",
event_types=["custom_profile_fields"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=user_profile.realm.id,
user_profile_id=user_profile.id,
pronouns_field_type_supported=pronouns_field_type_supported,
)
client = allocate_client_descriptor(queue_data)
with self.captureOnCommitCallbacks(execute=True):
try_update_realm_custom_profile_field(
realm=user_profile.realm, field=profile_field, name=new_name
)
result = self.tornado_call(
get_events,
user_profile,
{
"queue_id": client.event_queue.id,
"user_client": "ZulipAndroid",
"last_event_id": -1,
"dont_block": orjson.dumps(True).decode(),
},
)
events = orjson.loads(result.content)["events"]
self.assert_json_success(result)
self.assert_length(events, 1)
[pronouns_field] = (
field for field in events[0]["fields"] if field["id"] == profile_field.id
)
if pronouns_field_type_supported:
expected_type = CustomProfileField.PRONOUNS
else:
expected_type = CustomProfileField.SHORT_TEXT
self.assertEqual(pronouns_field["type"], expected_type)
check_pronouns_type_field_supported(False, "Pronouns field")
check_pronouns_type_field_supported(True, "Pronouns")
class FetchInitialStateDataTest(ZulipTestCase):
# Non-admin users don't have access to all bots
def test_realm_bots_non_admin(self) -> None:
user_profile = self.example_user("cordelia")
self.assertFalse(user_profile.is_realm_admin)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
self.assert_length(result["realm_bots"], 0)
# additionally the API key for a random bot is not present in the data
api_key = self.notification_bot(user_profile.realm).api_key
self.assertNotIn(api_key, str(result))
# Admin users have access to all bots in the realm_bots field
def test_realm_bots_admin(self) -> None:
user_profile = self.example_user("hamlet")
self.set_user_role(user_profile, UserProfile.ROLE_REALM_ADMINISTRATOR)
self.assertTrue(user_profile.is_realm_admin)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
self.assertGreater(len(result["realm_bots"]), 2)
def test_max_message_id_with_no_history(self) -> None:
user_profile = self.example_user("aaron")
# Delete all historical messages for this user
UserMessage.objects.filter(user_profile=user_profile).delete()
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
self.assertEqual(result["max_message_id"], -1)
def test_delivery_email_presence_for_non_admins(self) -> None:
user_profile = self.example_user("aaron")
hamlet = self.example_user("hamlet")
self.assertFalse(user_profile.is_realm_admin)
hamlet = self.example_user("hamlet")
do_change_user_setting(
hamlet,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email)
do_change_user_setting(
hamlet,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None,
)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertIsNone(hamlet_obj["delivery_email"])
def test_delivery_email_presence_for_admins(self) -> None:
user_profile = self.example_user("iago")
hamlet = self.example_user("hamlet")
self.assertTrue(user_profile.is_realm_admin)
hamlet = self.example_user("hamlet")
do_change_user_setting(
hamlet,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
acting_user=None,
)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertEqual(hamlet_obj["delivery_email"], hamlet.delivery_email)
do_change_user_setting(
hamlet,
"email_address_visibility",
UserProfile.EMAIL_ADDRESS_VISIBILITY_ADMINS,
acting_user=None,
)
result = fetch_initial_state_data(user_profile, realm=user_profile.realm)
(hamlet_obj,) = (value for key, value in result["raw_users"].items() if key == hamlet.id)
self.assertIn("delivery_email", hamlet_obj)
def test_user_avatar_url_field_optional(self) -> None:
hamlet = self.example_user("hamlet")
aaron = self.example_user("aaron")
users = [
self.example_user("iago"),
self.example_user("cordelia"),
self.example_user("ZOE"),
self.example_user("othello"),
]
do_change_avatar_fields(hamlet, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=None)
do_change_avatar_fields(aaron, UserProfile.AVATAR_FROM_JDENTICON, acting_user=None)
for user in users:
user.long_term_idle = True
user.save()
long_term_idle_users_ids = [user.id for user in users]
result = fetch_initial_state_data(
user_profile=hamlet,
realm=hamlet.realm,
user_avatar_url_field_optional=True,
)
raw_users = result["raw_users"]
for user_dict in raw_users.values():
if user_dict["user_id"] in long_term_idle_users_ids:
self.assertFalse("avatar_url" in user_dict)
else:
self.assertIsNotNone(user_dict["avatar_url"])
gravatar_users_id = [
user_dict["user_id"]
for user_dict in raw_users.values()
if "avatar_url" in user_dict
and urlsplit(user_dict["avatar_url"]).hostname == "secure.gravatar.com"
]
reset_email_visibility_to_everyone_in_zulip_realm()
# Test again with client_gravatar = True
result = fetch_initial_state_data(
user_profile=hamlet,
realm=hamlet.realm,
client_gravatar=True,
user_avatar_url_field_optional=True,
)
raw_users = result["raw_users"]
for user_dict in raw_users.values():
if user_dict["user_id"] in gravatar_users_id:
self.assertIsNone(user_dict["avatar_url"])
elif user_dict["user_id"] in long_term_idle_users_ids:
self.assertFalse("avatar_url" in user_dict)
else:
# avatar source is Jdenticon
self.assertIsNotNone(user_dict["avatar_url"])
def test_realm_linkifiers_based_on_client_capabilities(self) -> None:
user = self.example_user("iago")
self.login_user(user)
data = {
"pattern": "#(?P<id>[123])",
"url_template": "https://realm.com/my_realm_filter/{id}",
}
post_result = self.client_post("/json/realm/filters", info=data)
self.assert_json_success(post_result)
result = fetch_initial_state_data(
user_profile=user,
realm=user.realm,
linkifier_url_template=True,
)
self.assertEqual(result["realm_filters"], [])
self.assertEqual(result["realm_linkifiers"][-1]["pattern"], "#(?P<id>[123])")
self.assertEqual(
result["realm_linkifiers"][-1]["url_template"],
"https://realm.com/my_realm_filter/{id}",
)
# The default behavior should be `linkifier_url_template=False`
result = fetch_initial_state_data(
user_profile=user,
realm=user.realm,
)
self.assertEqual(result["realm_filters"], [])
self.assertEqual(result["realm_linkifiers"], [])
def test_pronouns_field_type_support(self) -> None:
hamlet = self.example_user("hamlet")
result = fetch_initial_state_data(
user_profile=hamlet,
realm=hamlet.realm,
pronouns_field_type_supported=False,
)
self.assertIn("custom_profile_fields", result)
custom_profile_fields = result["custom_profile_fields"]
[pronouns_field] = (field for field in custom_profile_fields if field["name"] == "Pronouns")
self.assertEqual(pronouns_field["type"], CustomProfileField.SHORT_TEXT)
result = fetch_initial_state_data(
user_profile=hamlet,
realm=hamlet.realm,
pronouns_field_type_supported=True,
)
self.assertIn("custom_profile_fields", result)
custom_profile_fields = result["custom_profile_fields"]
[pronouns_field] = (field for field in custom_profile_fields if field["name"] == "Pronouns")
self.assertEqual(pronouns_field["type"], CustomProfileField.PRONOUNS)
def test_unreads_case_insensitive_topics(self) -> None:
sender = self.example_user("hamlet")
self.login_user(sender)
self.send_stream_message(sender, "Denmark", "**hello**", topic_name="case DOES not MATTER")
self.send_stream_message(sender, "Denmark", "**bye**", topic_name="CASE does NOT matter")
reader = self.example_user("othello")
result = fetch_initial_state_data(
user_profile=reader,
realm=reader.realm,
)
post_process_state(reader, result, False, True)
self.assert_length(result["unread_msgs"]["streams"], 1)
self.assertEqual(result["unread_msgs"]["streams"][0]["topic"], "case DOES not MATTER")
self.assert_length(result["unread_msgs"]["streams"][0]["unread_message_ids"], 2)
class ClientDescriptorsTest(ZulipTestCase):
def test_get_client_info_for_all_public_streams(self) -> None:
hamlet = self.example_user("hamlet")
realm = hamlet.realm
queue_data = dict(
all_public_streams=True,
apply_markdown=True,
client_gravatar=True,
client_type_name="website",
event_types=["message"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=hamlet.id,
)
client = allocate_client_descriptor(queue_data)
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
)
client_info = get_client_info_for_message_event(
message_event,
users=[],
)
self.assert_length(client_info, 1)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["client"].apply_markdown, True)
self.assertEqual(dct["client"].client_gravatar, True)
self.assertEqual(dct["client"].user_profile_id, hamlet.id)
self.assertEqual(dct["flags"], [])
self.assertEqual(dct["is_sender"], False)
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
sender_queue_id=client.event_queue.id,
)
client_info = get_client_info_for_message_event(
message_event,
users=[],
)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["is_sender"], True)
def test_get_client_info_for_narrow_guest(self) -> None:
polonius = self.example_user("polonius")
realm = polonius.realm
queue_data = dict(
all_public_streams=False,
apply_markdown=True,
client_gravatar=True,
client_type_name="website",
event_types=["message"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=polonius.id,
narrow=[["stream", "whatever"]],
)
client = allocate_client_descriptor(queue_data)
# A guest's client in realm_clients_all_streams is skipped,
# identified via `realm_guest_user_ids`.
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
realm_guest_user_ids=[polonius.id],
)
client_info = get_client_info_for_message_event(
message_event,
users=[],
)
self.assert_length(client_info, 0)
# As a subscriber, the guest user instead receives the event
# via `users`, with their user-specific flags.
client_info = get_client_info_for_message_event(
message_event,
users=[dict(id=polonius.id, flags=["mentioned"])],
)
self.assert_length(client_info, 1)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["client"].user_profile_id, polonius.id)
self.assertEqual(dct["flags"], ["mentioned"])
def test_get_client_info_for_all_public_streams_guest(self) -> None:
polonius = self.example_user("polonius")
realm = polonius.realm
queue_data = dict(
all_public_streams=True,
apply_markdown=True,
client_gravatar=True,
client_type_name="website",
event_types=["message"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=polonius.id,
)
client = allocate_client_descriptor(queue_data)
# A guest's client in realm_clients_all_streams is skipped,
# identified via `realm_guest_user_ids`.
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
realm_guest_user_ids=[polonius.id],
)
client_info = get_client_info_for_message_event(
message_event,
users=[],
)
self.assert_length(client_info, 0)
# As a subscriber, the guest user instead receives the event
# via `users`, with their user-specific flags.
client_info = get_client_info_for_message_event(
message_event,
users=[dict(id=polonius.id, flags=["mentioned"])],
)
self.assert_length(client_info, 1)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["client"].user_profile_id, polonius.id)
self.assertEqual(dct["flags"], ["mentioned"])
def test_get_client_info_for_all_public_streams_guest_web_public(self) -> None:
polonius = self.example_user("polonius")
realm = polonius.realm
queue_data = dict(
all_public_streams=True,
apply_markdown=True,
client_gravatar=True,
client_type_name="website",
event_types=["message"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=polonius.id,
)
client = allocate_client_descriptor(queue_data)
# For a web-public channel, a guest's client in
# realm_clients_all_streams is not skipped, since guests can
# read web-public channels without subscribing.
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
is_web_public=True,
realm_guest_user_ids=[polonius.id],
)
client_info = get_client_info_for_message_event(
message_event,
users=[],
)
self.assert_length(client_info, 1)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["client"].user_profile_id, polonius.id)
self.assertEqual(dct["flags"], [])
def test_get_client_info_for_normal_users(self) -> None:
hamlet = self.example_user("hamlet")
cordelia = self.example_user("cordelia")
realm = hamlet.realm
def test_get_info(apply_markdown: bool, client_gravatar: bool) -> None:
clear_client_event_queues_for_testing()
queue_data = dict(
all_public_streams=False,
apply_markdown=apply_markdown,
client_gravatar=client_gravatar,
client_type_name="website",
event_types=["message"],
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=hamlet.id,
)
client = allocate_client_descriptor(queue_data)
message_event = dict(
realm_id=realm.id,
stream_name="whatever",
)
client_info = get_client_info_for_message_event(
message_event,
users=[
dict(id=cordelia.id),
],
)
self.assert_length(client_info, 0)
client_info = get_client_info_for_message_event(
message_event,
users=[
dict(id=cordelia.id),
dict(id=hamlet.id, flags=["mentioned"]),
],
)
self.assert_length(client_info, 1)
dct = client_info[client.event_queue.id]
self.assertEqual(dct["client"].apply_markdown, apply_markdown)
self.assertEqual(dct["client"].client_gravatar, client_gravatar)
self.assertEqual(dct["client"].user_profile_id, hamlet.id)
self.assertEqual(dct["flags"], ["mentioned"])
self.assertEqual(dct["is_sender"], False)
test_get_info(apply_markdown=False, client_gravatar=False)
test_get_info(apply_markdown=True, client_gravatar=False)
test_get_info(apply_markdown=False, client_gravatar=True)
test_get_info(apply_markdown=True, client_gravatar=True)
def test_process_message_event_with_mocked_client_info(self) -> None:
hamlet = self.example_user("hamlet")
class MockClient:
def __init__(
self,
*,
user_profile_id: int,
apply_markdown: bool,
client_gravatar: bool,
) -> None:
self.user_profile_id = user_profile_id
self.apply_markdown = apply_markdown
self.client_gravatar = client_gravatar
self.client_type_name = "whatever"
self.events: list[dict[str, Any]] = []
self.empty_topic_name = True
def accepts_messages(self) -> bool:
return True
def accepts_event(self, event: dict[str, Any]) -> bool:
assert event["type"] == "message"
return True
def add_event(self, event: dict[str, Any]) -> None:
self.events.append(event)
client1 = MockClient(
user_profile_id=hamlet.id,
apply_markdown=True,
client_gravatar=False,
)
client2 = MockClient(
user_profile_id=hamlet.id,
apply_markdown=False,
client_gravatar=False,
)
client3 = MockClient(
user_profile_id=hamlet.id,
apply_markdown=True,
client_gravatar=True,
)
client4 = MockClient(
user_profile_id=hamlet.id,
apply_markdown=False,
client_gravatar=True,
)
client_info = {
"client:1": dict(
client=client1,
flags=["starred"],
),
"client:2": dict(
client=client2,
flags=["has_alert_word"],
),
"client:3": dict(
client=client3,
flags=[],
),
"client:4": dict(
client=client4,
flags=[],
),
}
sender = hamlet
message_event = dict(
message_dict=dict(
id=999,
content="**hello**",
rendered_content="<b>hello</b>",
sender_id=sender.id,
recipient_id=1111,
type="stream",
client="website",
# NOTE: Some of these fields are clutter, but some
# will be useful when we let clients specify
# that they can compute their own gravatar URLs.
sender_email=sender.email,
sender_delivery_email=sender.delivery_email,
sender_realm_id=sender.realm_id,
sender_avatar_source=UserProfile.AVATAR_FROM_GRAVATAR,
sender_avatar_version=1,
sender_is_mirror_dummy=None,
sender_email_address_visibility=UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE,
recipient_type=None,
recipient_type_id=None,
),
)
# Setting users to `[]` bypasses code we don't care about
# for this test--we assume client_info is correct in our mocks,
# and we are interested in how messages are put on event queue.
users: list[dict[str, Any]] = []
with mock.patch(
"zerver.tornado.event_queue.get_client_info_for_message_event", return_value=client_info
):
process_message_event(message_event, users)
# We are not closely examining avatar_url at this point, so
# just sanity check them and then delete the keys so that
# upcoming comparisons work.
for client in [client1, client2]:
message = client.events[0]["message"]
self.assertIn("gravatar.com", message["avatar_url"])
message.pop("avatar_url")
self.assertEqual(
client1.events,
[
dict(
type="message",
message=dict(
type="stream",
sender_id=sender.id,
sender_email=sender.email,
id=999,
recipient_id=1111,
content="<b>hello</b>",
content_type="text/html",
client="website",
),
flags=["starred"],
),
],
)
self.assertEqual(
client2.events,
[
dict(
type="message",
message=dict(
type="stream",
sender_id=sender.id,
sender_email=sender.email,
id=999,
recipient_id=1111,
content="**hello**",
content_type="text/x-markdown",
client="website",
),
flags=["has_alert_word"],
),
],
)
self.assertEqual(
client3.events,
[
dict(
type="message",
message=dict(
type="stream",
sender_id=sender.id,
sender_email=sender.email,
avatar_url=None,
id=999,
recipient_id=1111,
content="<b>hello</b>",
content_type="text/html",
client="website",
),
flags=[],
),
],
)
self.assertEqual(
client4.events,
[
dict(
type="message",
message=dict(
type="stream",
sender_id=sender.id,
sender_email=sender.email,
avatar_url=None,
id=999,
recipient_id=1111,
content="**hello**",
content_type="text/x-markdown",
client="website",
),
flags=[],
),
],
)
class ReloadWebClientsTest(ZulipTestCase):
def test_web_reload_clients(self) -> None:
hamlet = self.example_user("hamlet")
realm = hamlet.realm
clear_client_event_queues_for_testing()
queue_data = dict(
all_public_streams=False,
apply_markdown=True,
client_gravatar=True,
client_type_name="website",
event_types=None,
last_connection_time=time.time(),
queue_timeout=0,
realm_id=realm.id,
user_profile_id=hamlet.id,
)
client = allocate_client_descriptor(queue_data)
send_web_reload_client_events()
self.assert_length(client.event_queue.queue, 0)
mark_clients_to_reload([client.event_queue.id])
send_web_reload_client_events()
self.assert_length(client.event_queue.queue, 1)
reload_event = client.event_queue.queue[0]
check_web_reload_client_event("web_reload_client_event", reload_event)
self.assertEqual(
reload_event,
dict(
type="web_reload_client",
immediate=False,
id=0,
),
)
class FetchQueriesTest(ZulipTestCase):
def test_queries(self) -> None:
user = self.example_user("hamlet")
self.login_user(user)
with (
self.assert_database_query_count(48),
mock.patch("zerver.lib.events.always_want") as want_mock,
):
fetch_initial_state_data(user, realm=user.realm)
expected_counts = dict(
alert_words=1,
channel_folders=1,
custom_profile_fields=1,
default_streams=1,
default_stream_groups=1,
device=1,
drafts=1,
giphy=0,
klipy=0,
tenor=0,
message=1,
muted_topics=1,
muted_users=1,
navigation_views=1,
onboarding_steps=1,
presence=1,
# 2 of the 3 queries here are a single query that is used
# for all the 'realm', 'stream', 'subscription'
# and 'realm_user_groups' event types.
realm=3,
# Similarly, this query is shared with the realm_user total.
realm_billing=1,
realm_bot=1,
realm_domains=1,
realm_embedded_bots=0,
realm_incoming_webhook_bots=0,
realm_emoji=1,
realm_filters=0,
realm_linkifiers=0,
realm_playgrounds=1,
realm_user=4,
realm_user_groups=2,
realm_user_settings_defaults=1,
recent_private_conversations=1,
reminders=1,
saved_snippets=1,
scheduled_messages=1,
starred_messages=1,
# 3 of the 5 queries here are shared with other event types
# as mentioned above.
stream=5,
stop_words=0,
# 3 of the 9 queries here are shared with other event types
# as mentioned above.
subscription=9,
update_message_flags=7,
user_settings=0,
user_status=1,
user_topic=1,
video_calls=0,
)
wanted_event_types = {item[0][0] for item in want_mock.call_args_list}
self.assertEqual(wanted_event_types, set(expected_counts))
for event_type in sorted(wanted_event_types):
count = expected_counts[event_type]
with self.subTest(event_type=event_type), self.assert_database_query_count(count):
if event_type == "update_message_flags":
event_types = ["update_message_flags", "message"]
else:
event_types = [event_type]
fetch_initial_state_data(user, realm=user.realm, event_types=event_types)
class TestEventsRegisterAllPublicStreamsDefaults(ZulipTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.user_profile = self.example_user("hamlet")
self.email = self.user_profile.email
def test_use_passed_all_public_true_default_false(self) -> None:
self.user_profile.default_all_public_streams = False
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, True)
self.assertTrue(result)
def test_use_passed_all_public_true_default(self) -> None:
self.user_profile.default_all_public_streams = True
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, True)
self.assertTrue(result)
def test_use_passed_all_public_false_default_false(self) -> None:
self.user_profile.default_all_public_streams = False
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, False)
self.assertFalse(result)
def test_use_passed_all_public_false_default_true(self) -> None:
self.user_profile.default_all_public_streams = True
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, False)
self.assertFalse(result)
def test_use_true_default_for_none(self) -> None:
self.user_profile.default_all_public_streams = True
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, None)
self.assertTrue(result)
def test_use_false_default_for_none(self) -> None:
self.user_profile.default_all_public_streams = False
self.user_profile.save()
result = _default_all_public_streams(self.user_profile, None)
self.assertFalse(result)
class TestEventsRegisterNarrowDefaults(ZulipTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.user_profile = self.example_user("hamlet")
self.email = self.user_profile.email
self.stream = get_stream("Verona", self.user_profile.realm)
def test_use_passed_narrow_no_default(self) -> None:
self.user_profile.default_events_register_stream_id = None
self.user_profile.save()
result = _default_narrow(self.user_profile, [["stream", "my_stream"]])
self.assertEqual(result, [["stream", "my_stream"]])
def test_use_passed_narrow_with_default(self) -> None:
self.user_profile.default_events_register_stream_id = self.stream.id
self.user_profile.save()
result = _default_narrow(self.user_profile, [["stream", "my_stream"]])
self.assertEqual(result, [["stream", "my_stream"]])
def test_use_default_if_narrow_is_empty(self) -> None:
self.user_profile.default_events_register_stream_id = self.stream.id
self.user_profile.save()
result = _default_narrow(self.user_profile, [])
self.assertEqual(result, [["stream", "Verona"]])
def test_use_narrow_if_default_is_none(self) -> None:
self.user_profile.default_events_register_stream_id = None
self.user_profile.save()
result = _default_narrow(self.user_profile, [])
self.assertEqual(result, [])
class TestGetUserAPIDataSystemBotRealm(ZulipTestCase):
def test_get_users_api_data_on_system_bot_realm(self) -> None:
realm = get_realm(settings.SYSTEM_BOT_REALM)
result = get_users_for_api(
realm,
self.example_user("hamlet"),
client_gravatar=True,
user_avatar_url_field_optional=True,
)
for bot_email in settings.CROSS_REALM_BOT_EMAILS:
bot_profile = get_system_bot(bot_email, realm.id)
self.assertTrue(bot_profile.id in result)
self.assertTrue(result[bot_profile.id]["is_system_bot"])
class TestUserPresenceUpdatesDisabled(ZulipTestCase):
# For this test, we verify do_update_user_presence doesn't send
# events for organizations with more than
# USER_LIMIT_FOR_SENDING_PRESENCE_UPDATE_EVENTS users, unless
# force_send_update is passed.
@override_settings(USER_LIMIT_FOR_SENDING_PRESENCE_UPDATE_EVENTS=3)
def test_presence_events_disabled_on_larger_realm(self) -> None:
with self.capture_send_event_calls(expected_num_events=1):
do_update_user_presence(
self.example_user("cordelia"),
get_client("website"),
timezone_now(),
UserPresence.LEGACY_STATUS_ACTIVE_INT,
force_send_update=True,
)
with self.capture_send_event_calls(expected_num_events=0):
do_update_user_presence(
self.example_user("hamlet"),
get_client("website"),
timezone_now(),
UserPresence.LEGACY_STATUS_ACTIVE_INT,
force_send_update=False,
)