2017-11-16 19:54:24 +01:00
|
|
|
# See https://zulip.readthedocs.io/en/latest/subsystems/events-system.html for
|
2017-02-12 01:59:28 +01:00
|
|
|
# high-level documentation on how this system works.
|
2020-09-25 21:53:00 +02:00
|
|
|
#
|
|
|
|
# This module is closely integrated with zerver/lib/event_schema.py
|
|
|
|
# and zerver/lib/data_types.py systems for validating the schemas of
|
|
|
|
# events; it also uses the OpenAPI tools to validate our documentation.
|
2019-04-09 04:07:03 +02:00
|
|
|
import copy
|
2017-10-09 16:20:14 +02:00
|
|
|
import sys
|
2020-06-11 00:54:34 +02:00
|
|
|
import time
|
|
|
|
from io import StringIO
|
2020-07-08 14:13:16 +02:00
|
|
|
from typing import Any, Callable, Dict, List, Optional, Set
|
2020-06-11 00:54:34 +02:00
|
|
|
from unittest import mock
|
2016-06-03 08:00:04 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2017-04-15 04:03:56 +02:00
|
|
|
from django.utils.timezone import now as timezone_now
|
2014-01-31 23:23:39 +01:00
|
|
|
|
|
|
|
from zerver.lib.actions import (
|
2020-06-11 00:54:34 +02:00
|
|
|
bulk_add_members_to_user_group,
|
2017-03-24 05:49:23 +01:00
|
|
|
bulk_add_subscriptions,
|
2016-10-20 16:53:22 +02:00
|
|
|
bulk_remove_subscriptions,
|
2016-02-12 21:08:56 +01:00
|
|
|
check_add_realm_emoji,
|
2020-06-11 00:54:34 +02:00
|
|
|
check_add_user_group,
|
|
|
|
check_delete_user_group,
|
2017-03-18 03:50:41 +01:00
|
|
|
check_send_typing_notification,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_add_alert_words,
|
|
|
|
do_add_default_stream,
|
2017-10-08 09:34:59 +02:00
|
|
|
do_add_reaction,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_add_realm_domain,
|
|
|
|
do_add_realm_filter,
|
2017-11-01 18:20:34 +01:00
|
|
|
do_add_streams_to_default_stream_group,
|
2018-02-12 10:53:36 +01:00
|
|
|
do_add_submessage,
|
2017-01-28 19:05:20 +01:00
|
|
|
do_change_avatar_fields,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_change_bot_owner,
|
2014-03-06 16:34:54 +01:00
|
|
|
do_change_default_all_public_streams,
|
|
|
|
do_change_default_events_register_stream,
|
|
|
|
do_change_default_sending_stream,
|
2017-11-14 20:51:34 +01:00
|
|
|
do_change_default_stream_group_description,
|
2017-11-14 21:06:02 +01:00
|
|
|
do_change_default_stream_group_name,
|
2014-01-31 23:23:39 +01:00
|
|
|
do_change_full_name,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_change_icon_source,
|
2019-03-01 15:52:44 +01:00
|
|
|
do_change_logo_source,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_change_notification_settings,
|
2019-06-11 12:43:08 +02:00
|
|
|
do_change_plan_type,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_change_realm_domain,
|
2014-01-31 23:23:39 +01:00
|
|
|
do_change_stream_description,
|
2019-05-02 19:43:27 +02:00
|
|
|
do_change_stream_invite_only,
|
2020-06-14 18:57:02 +02:00
|
|
|
do_change_stream_message_retention_days,
|
2020-02-04 21:50:55 +01:00
|
|
|
do_change_stream_post_policy,
|
2016-07-01 07:26:09 +02:00
|
|
|
do_change_subscription_property,
|
2018-08-02 08:47:13 +02:00
|
|
|
do_change_user_delivery_email,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_change_user_role,
|
2017-11-01 18:20:34 +01:00
|
|
|
do_create_default_stream_group,
|
2019-02-15 19:09:25 +01:00
|
|
|
do_create_multiuse_invite_link,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_create_user,
|
2016-07-12 23:57:16 +02:00
|
|
|
do_deactivate_stream,
|
2014-03-06 16:34:54 +01:00
|
|
|
do_deactivate_user,
|
2019-01-18 10:37:59 +01:00
|
|
|
do_delete_messages,
|
2017-12-14 22:22:17 +01:00
|
|
|
do_invite_users,
|
2017-01-24 01:48:35 +01:00
|
|
|
do_mark_hotspot_as_read,
|
2017-08-30 02:19:34 +02:00
|
|
|
do_mute_topic,
|
2017-02-15 21:06:07 +01:00
|
|
|
do_reactivate_user,
|
2014-03-06 16:34:54 +01:00
|
|
|
do_regenerate_api_key,
|
2014-01-31 23:23:39 +01:00
|
|
|
do_remove_alert_words,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_remove_default_stream,
|
2017-11-01 18:20:34 +01:00
|
|
|
do_remove_default_stream_group,
|
2017-10-08 09:34:59 +02:00
|
|
|
do_remove_reaction,
|
2017-06-09 20:10:43 +02:00
|
|
|
do_remove_realm_domain,
|
2014-01-31 23:23:39 +01:00
|
|
|
do_remove_realm_emoji,
|
|
|
|
do_remove_realm_filter,
|
2017-11-01 18:20:34 +01:00
|
|
|
do_remove_streams_from_default_stream_group,
|
2014-01-31 23:23:39 +01:00
|
|
|
do_rename_stream,
|
2019-02-15 19:09:25 +01:00
|
|
|
do_revoke_multi_use_invite,
|
2017-12-14 22:22:17 +01:00
|
|
|
do_revoke_user_invite,
|
2016-11-02 21:51:56 +01:00
|
|
|
do_set_realm_authentication_methods,
|
2017-03-21 18:08:40 +01:00
|
|
|
do_set_realm_message_editing,
|
2017-06-09 20:50:38 +02:00
|
|
|
do_set_realm_notifications_stream,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_set_realm_property,
|
2017-10-20 16:55:04 +02:00
|
|
|
do_set_realm_signup_notifications_stream,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_set_user_display_setting,
|
2019-11-16 09:26:28 +01:00
|
|
|
do_set_zoom_token,
|
2017-08-30 02:19:34 +02:00
|
|
|
do_unmute_topic,
|
2017-03-24 05:54:20 +01:00
|
|
|
do_update_embedded_data,
|
2014-03-11 15:14:32 +01:00
|
|
|
do_update_message,
|
2017-03-24 03:19:23 +01:00
|
|
|
do_update_message_flags,
|
2018-01-16 20:34:12 +01:00
|
|
|
do_update_outgoing_webhook_service,
|
2020-06-11 00:54:34 +02:00
|
|
|
do_update_user_custom_profile_data_if_changed,
|
|
|
|
do_update_user_group_description,
|
|
|
|
do_update_user_group_name,
|
2017-03-24 05:26:32 +01:00
|
|
|
do_update_user_presence,
|
2019-01-21 18:19:59 +01:00
|
|
|
do_update_user_status,
|
2017-11-14 20:33:09 +01:00
|
|
|
lookup_default_stream_groups,
|
2017-06-09 20:10:43 +02:00
|
|
|
notify_realm_custom_profile_fields,
|
2017-11-14 08:01:50 +01:00
|
|
|
remove_members_from_user_group,
|
2020-06-11 00:54:34 +02:00
|
|
|
try_update_realm_custom_profile_field,
|
2016-11-30 10:42:58 +01:00
|
|
|
)
|
2020-07-08 12:53:52 +02:00
|
|
|
from zerver.lib.event_schema import (
|
2020-07-18 17:11:41 +02:00
|
|
|
check_alert_words,
|
2020-08-06 13:08:42 +02:00
|
|
|
check_attachment_add,
|
|
|
|
check_attachment_remove,
|
|
|
|
check_attachment_update,
|
2020-07-18 17:02:28 +02:00
|
|
|
check_custom_profile_fields,
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups,
|
2020-08-01 14:36:13 +02:00
|
|
|
check_default_streams,
|
2020-08-14 15:12:27 +02:00
|
|
|
check_delete_message,
|
2020-08-16 17:26:24 +02:00
|
|
|
check_has_zoom_token,
|
2020-08-05 19:56:34 +02:00
|
|
|
check_hotspots,
|
2020-07-18 16:33:03 +02:00
|
|
|
check_invites_changed,
|
2020-07-10 16:10:58 +02:00
|
|
|
check_message,
|
2020-08-06 20:31:12 +02:00
|
|
|
check_muted_topics,
|
2020-08-13 19:29:07 +02:00
|
|
|
check_presence,
|
2020-08-17 15:11:19 +02:00
|
|
|
check_reaction_add,
|
|
|
|
check_reaction_remove,
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add,
|
2020-07-08 21:06:22 +02:00
|
|
|
check_realm_bot_delete,
|
|
|
|
check_realm_bot_remove,
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update,
|
2020-08-17 16:07:25 +02:00
|
|
|
check_realm_domains_add,
|
|
|
|
check_realm_domains_change,
|
|
|
|
check_realm_domains_remove,
|
2020-08-18 15:16:02 +02:00
|
|
|
check_realm_emoji_update,
|
2020-08-05 23:54:26 +02:00
|
|
|
check_realm_export,
|
2020-08-08 15:08:29 +02:00
|
|
|
check_realm_filters,
|
2020-07-08 12:53:52 +02:00
|
|
|
check_realm_update,
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict,
|
2020-08-14 02:14:06 +02:00
|
|
|
check_realm_user_add,
|
2020-08-18 18:38:41 +02:00
|
|
|
check_realm_user_remove,
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
check_realm_user_update,
|
2020-07-08 12:53:52 +02:00
|
|
|
check_stream_create,
|
2020-08-01 14:42:06 +02:00
|
|
|
check_stream_delete,
|
2020-07-08 13:35:37 +02:00
|
|
|
check_stream_update,
|
2020-07-18 16:27:59 +02:00
|
|
|
check_submessage,
|
2020-07-08 14:13:16 +02:00
|
|
|
check_subscription_add,
|
2020-07-08 15:04:35 +02:00
|
|
|
check_subscription_peer_add,
|
|
|
|
check_subscription_peer_remove,
|
2020-07-08 14:20:25 +02:00
|
|
|
check_subscription_remove,
|
2020-08-17 14:19:09 +02:00
|
|
|
check_subscription_update,
|
2020-07-18 16:39:06 +02:00
|
|
|
check_typing_start,
|
2020-08-27 22:10:07 +02:00
|
|
|
check_typing_stop,
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_display_settings,
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications,
|
2020-07-10 18:35:58 +02:00
|
|
|
check_update_message,
|
|
|
|
check_update_message_embedded,
|
2020-08-18 18:08:39 +02:00
|
|
|
check_update_message_flags_add,
|
|
|
|
check_update_message_flags_remove,
|
2020-07-18 17:19:30 +02:00
|
|
|
check_user_group_add,
|
2020-08-14 13:18:52 +02:00
|
|
|
check_user_group_add_members,
|
2020-08-14 13:38:36 +02:00
|
|
|
check_user_group_remove,
|
2020-08-14 13:34:34 +02:00
|
|
|
check_user_group_remove_members,
|
2020-08-14 13:50:55 +02:00
|
|
|
check_user_group_update,
|
2020-07-18 17:15:23 +02:00
|
|
|
check_user_status,
|
2020-07-08 12:53:52 +02:00
|
|
|
)
|
2020-06-29 13:11:26 +02:00
|
|
|
from zerver.lib.events import apply_events, fetch_initial_state_data, post_process_state
|
2020-06-25 15:00:33 +02:00
|
|
|
from zerver.lib.markdown import MentionData
|
2020-06-29 13:19:17 +02:00
|
|
|
from zerver.lib.message import render_markdown
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.test_classes import ZulipTestCase
|
|
|
|
from zerver.lib.test_helpers import (
|
|
|
|
create_dummy_file,
|
|
|
|
get_subscription,
|
|
|
|
get_test_image_file,
|
|
|
|
reset_emails_in_zulip_realm,
|
|
|
|
stdout_suppressed,
|
2016-11-10 19:30:09 +01:00
|
|
|
)
|
2020-07-10 18:35:58 +02:00
|
|
|
from zerver.lib.topic import TOPIC_NAME
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.models import (
|
|
|
|
Attachment,
|
|
|
|
Message,
|
|
|
|
MultiuseInvite,
|
|
|
|
PreregistrationUser,
|
|
|
|
Realm,
|
|
|
|
RealmAuditLog,
|
|
|
|
RealmDomain,
|
|
|
|
Service,
|
|
|
|
Stream,
|
|
|
|
UserGroup,
|
|
|
|
UserMessage,
|
|
|
|
UserPresence,
|
|
|
|
UserProfile,
|
|
|
|
get_client,
|
|
|
|
get_stream,
|
|
|
|
get_user_by_delivery_email,
|
2014-02-04 20:52:02 +01:00
|
|
|
)
|
2020-07-27 16:22:31 +02:00
|
|
|
from zerver.openapi.openapi import validate_against_openapi_schema
|
2017-10-12 01:37:44 +02:00
|
|
|
from zerver.tornado.event_queue import (
|
|
|
|
allocate_client_descriptor,
|
|
|
|
clear_client_event_queues_for_testing,
|
|
|
|
)
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-05-22 23:02:24 +02:00
|
|
|
|
2020-06-27 17:03:37 +02:00
|
|
|
class BaseAction(ZulipTestCase):
|
2020-09-28 21:35:55 +02:00
|
|
|
"""Core class for verifying the apply_event race handling logic as
|
|
|
|
well as the event formatting logic of any function using send_event.
|
|
|
|
|
|
|
|
See https://zulip.readthedocs.io/en/latest/subsystems/events-system.html#testing
|
|
|
|
for extensive design details for this testing system.
|
|
|
|
"""
|
2017-11-05 10:51:25 +01:00
|
|
|
def setUp(self) -> None:
|
2017-10-27 08:28:23 +02:00
|
|
|
super().setUp()
|
2017-05-24 08:33:30 +02:00
|
|
|
self.user_profile = self.example_user('hamlet')
|
2014-02-26 19:55:29 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
def verify_action(self,
|
|
|
|
action: Callable[[], object],
|
2021-01-17 18:05:33 +01:00
|
|
|
*,
|
2020-06-27 17:32:39 +02:00
|
|
|
event_types: Optional[List[str]]=None,
|
|
|
|
include_subscribers: bool=True,
|
|
|
|
state_change_expected: bool=True,
|
|
|
|
notification_settings_null: bool=False,
|
|
|
|
client_gravatar: bool=True,
|
|
|
|
user_avatar_url_field_optional: bool=False,
|
|
|
|
slim_presence: bool=False,
|
2020-10-14 13:48:24 +02:00
|
|
|
include_streams: bool=True,
|
2020-06-27 17:32:39 +02:00
|
|
|
num_events: int=1,
|
|
|
|
bulk_message_deletion: bool=True) -> List[Dict[str, Any]]:
|
2017-10-12 01:37:44 +02:00
|
|
|
'''
|
|
|
|
Make sure we have a clean slate of client descriptors for these tests.
|
|
|
|
If we don't do this, then certain failures will only manifest when you
|
2018-08-10 22:43:58 +02:00
|
|
|
run multiple tests within a single test function.
|
2019-03-01 18:21:31 +01:00
|
|
|
|
|
|
|
See also https://zulip.readthedocs.io/en/latest/subsystems/events-system.html#testing
|
|
|
|
for details on the design of this test system.
|
2017-10-12 01:37:44 +02:00
|
|
|
'''
|
|
|
|
clear_client_event_queues_for_testing()
|
|
|
|
|
2014-01-28 18:11:08 +01:00
|
|
|
client = allocate_client_descriptor(
|
|
|
|
dict(user_profile_id = self.user_profile.id,
|
2017-01-03 21:04:55 +01:00
|
|
|
realm_id = self.user_profile.realm_id,
|
2014-01-28 18:11:08 +01:00
|
|
|
event_types = event_types,
|
|
|
|
client_type_name = "website",
|
|
|
|
apply_markdown = True,
|
2017-10-31 18:36:18 +01:00
|
|
|
client_gravatar = client_gravatar,
|
2020-02-02 17:29:05 +01:00
|
|
|
slim_presence = slim_presence,
|
2014-01-28 18:11:08 +01:00
|
|
|
all_public_streams = False,
|
|
|
|
queue_timeout = 600,
|
|
|
|
last_connection_time = time.time(),
|
2020-06-11 12:12:12 +02:00
|
|
|
narrow = [],
|
|
|
|
bulk_message_deletion = bulk_message_deletion)
|
2017-01-24 06:34:26 +01:00
|
|
|
)
|
2020-06-10 13:47:08 +02:00
|
|
|
|
2014-01-31 23:23:39 +01:00
|
|
|
# hybrid_state = initial fetch state + re-applying events triggered by our action
|
|
|
|
# normal_state = do action then fetch at the end (the "normal" code path)
|
2017-11-02 20:55:44 +01:00
|
|
|
hybrid_state = fetch_initial_state_data(
|
2021-01-17 17:58:50 +01:00
|
|
|
self.user_profile,
|
|
|
|
event_types=event_types,
|
2019-11-05 21:17:15 +01:00
|
|
|
client_gravatar=client_gravatar,
|
2020-06-13 10:10:05 +02:00
|
|
|
user_avatar_url_field_optional=user_avatar_url_field_optional,
|
2020-02-02 17:29:05 +01:00
|
|
|
slim_presence=slim_presence,
|
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
|
|
|
include_subscribers=include_subscribers,
|
2020-10-14 13:48:24 +02:00
|
|
|
include_streams=include_streams,
|
2017-11-02 20:55:44 +01:00
|
|
|
)
|
2014-01-31 23:23:39 +01:00
|
|
|
action()
|
|
|
|
events = client.event_queue.contents()
|
2020-07-27 16:22:31 +02:00
|
|
|
content = {
|
|
|
|
'queue_id': '123.12',
|
2020-08-07 01:09:47 +02:00
|
|
|
# The JSON wrapper helps in converting tuples to lists
|
2020-08-01 01:25:34 +02:00
|
|
|
# as tuples aren't valid JSON structure.
|
2020-08-07 01:09:47 +02:00
|
|
|
'events': orjson.loads(orjson.dumps(events)),
|
2020-07-27 16:22:31 +02:00
|
|
|
'msg': '',
|
|
|
|
'result': 'success'
|
|
|
|
}
|
2020-10-22 23:45:38 +02:00
|
|
|
validate_against_openapi_schema(content, '/events', 'get', '200', display_brief_error=True)
|
2018-12-07 02:38:10 +01:00
|
|
|
self.assertEqual(len(events), num_events)
|
2019-04-09 04:07:03 +02:00
|
|
|
initial_state = copy.deepcopy(hybrid_state)
|
2019-02-13 10:22:16 +01:00
|
|
|
post_process_state(self.user_profile, initial_state, notification_settings_null)
|
2020-08-07 01:09:47 +02:00
|
|
|
before = orjson.dumps(initial_state)
|
2021-01-19 15:52:45 +01:00
|
|
|
apply_events(
|
|
|
|
self.user_profile,
|
|
|
|
state=hybrid_state,
|
|
|
|
events=events,
|
|
|
|
fetch_event_types=None,
|
|
|
|
client_gravatar=client_gravatar,
|
|
|
|
slim_presence=slim_presence,
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
)
|
2019-02-13 10:22:16 +01:00
|
|
|
post_process_state(self.user_profile, hybrid_state, notification_settings_null)
|
2020-08-07 01:09:47 +02:00
|
|
|
after = orjson.dumps(hybrid_state)
|
2017-02-21 19:35:17 +01:00
|
|
|
|
|
|
|
if state_change_expected:
|
2019-05-09 02:38:29 +02:00
|
|
|
if before == after: # nocoverage
|
2020-08-07 01:09:47 +02:00
|
|
|
print(orjson.dumps(initial_state, option=orjson.OPT_INDENT_2).decode())
|
2019-05-09 02:38:29 +02:00
|
|
|
print(events)
|
2017-03-05 08:12:19 +01:00
|
|
|
raise AssertionError('Test does not exercise enough code -- events do not change state.')
|
2017-02-21 19:35:17 +01:00
|
|
|
else:
|
2019-04-09 04:07:03 +02:00
|
|
|
try:
|
|
|
|
self.match_states(initial_state, copy.deepcopy(hybrid_state), events)
|
|
|
|
except AssertionError: # nocoverage
|
2017-03-05 08:12:19 +01:00
|
|
|
raise AssertionError('Test is invalid--state actually does change here.')
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-11-02 20:55:44 +01:00
|
|
|
normal_state = fetch_initial_state_data(
|
2021-01-17 17:58:50 +01:00
|
|
|
self.user_profile,
|
|
|
|
event_types=event_types,
|
2019-11-05 21:17:15 +01:00
|
|
|
client_gravatar=client_gravatar,
|
2020-06-13 10:10:05 +02:00
|
|
|
user_avatar_url_field_optional=user_avatar_url_field_optional,
|
2020-02-02 17:29:05 +01:00
|
|
|
slim_presence=slim_presence,
|
2019-04-09 04:07:03 +02:00
|
|
|
include_subscribers=include_subscribers,
|
2020-10-14 13:48:24 +02:00
|
|
|
include_streams=include_streams,
|
2017-11-02 20:55:44 +01:00
|
|
|
)
|
2019-02-13 10:22:16 +01:00
|
|
|
post_process_state(self.user_profile, normal_state, notification_settings_null)
|
2017-10-06 21:24:56 +02:00
|
|
|
self.match_states(hybrid_state, normal_state, events)
|
2014-02-04 20:52:02 +01:00
|
|
|
return events
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def match_states(self, state1: Dict[str, Any], state2: Dict[str, Any],
|
|
|
|
events: List[Dict[str, Any]]) -> None:
|
|
|
|
def normalize(state: Dict[str, Any]) -> None:
|
2017-09-13 20:00:36 +02:00
|
|
|
for u in state['never_subscribed']:
|
|
|
|
if 'subscribers' in u:
|
|
|
|
u['subscribers'].sort()
|
2016-07-01 01:52:51 +02:00
|
|
|
for u in state['subscriptions']:
|
2017-02-20 08:30:09 +01:00
|
|
|
if 'subscribers' in u:
|
|
|
|
u['subscribers'].sort()
|
2014-02-04 19:09:30 +01:00
|
|
|
state['subscriptions'] = {u['name']: u for u in state['subscriptions']}
|
|
|
|
state['unsubscribed'] = {u['name']: u for u in state['unsubscribed']}
|
2014-02-26 00:12:14 +01:00
|
|
|
if 'realm_bots' in state:
|
|
|
|
state['realm_bots'] = {u['email']: u for u in state['realm_bots']}
|
2014-02-04 19:09:30 +01:00
|
|
|
normalize(state1)
|
|
|
|
normalize(state2)
|
2017-10-06 21:24:56 +02:00
|
|
|
|
|
|
|
# If this assertions fails, we have unusual problems.
|
|
|
|
self.assertEqual(state1.keys(), state2.keys())
|
|
|
|
|
|
|
|
# The far more likely scenario is that some section of
|
2017-10-06 23:08:41 +02:00
|
|
|
# our enormous payload does not get updated properly. We
|
2017-10-06 21:24:56 +02:00
|
|
|
# want the diff here to be developer-friendly, hence
|
|
|
|
# the somewhat tedious code to provide useful output.
|
2017-10-06 22:59:26 +02:00
|
|
|
if state1 != state2: # nocoverage
|
2017-10-06 21:24:56 +02:00
|
|
|
print('\n---States DO NOT MATCH---')
|
|
|
|
print('\nEVENTS:\n')
|
|
|
|
|
|
|
|
# Printing out the events is a big help to
|
|
|
|
# developers.
|
|
|
|
import json
|
|
|
|
for event in events:
|
|
|
|
print(json.dumps(event, indent=4))
|
|
|
|
|
|
|
|
print('\nMISMATCHES:\n')
|
|
|
|
for k in state1:
|
|
|
|
if state1[k] != state2[k]:
|
|
|
|
print('\nkey = ' + k)
|
|
|
|
try:
|
|
|
|
self.assertEqual({k: state1[k]}, {k: state2[k]})
|
|
|
|
except AssertionError as e:
|
|
|
|
print(e)
|
|
|
|
print('''
|
|
|
|
NOTE:
|
|
|
|
|
|
|
|
This is an advanced test that verifies how
|
|
|
|
we apply events after fetching data. If you
|
|
|
|
do not know how to debug it, you can ask for
|
|
|
|
help on chat.
|
|
|
|
''')
|
|
|
|
|
2017-10-09 16:20:14 +02:00
|
|
|
sys.stdout.flush()
|
2017-10-06 21:24:56 +02:00
|
|
|
raise AssertionError('Mismatching states')
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2020-06-27 17:03:37 +02:00
|
|
|
class NormalActionsTest(BaseAction):
|
2020-07-05 03:34:30 +02:00
|
|
|
def create_bot(self, email: str, **extras: Any) -> UserProfile:
|
2020-06-27 17:03:37 +02:00
|
|
|
return self.create_test_bot(email, self.user_profile, **extras)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_mentioned_send_message_events(self) -> None:
|
2017-07-21 20:31:25 +02:00
|
|
|
user = self.example_user('hamlet')
|
|
|
|
|
2017-08-10 10:58:39 +02:00
|
|
|
for i in range(3):
|
|
|
|
content = 'mentioning... @**' + user.full_name + '** hello ' + str(i)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(self.example_user('cordelia'),
|
2017-10-28 16:40:28 +02:00
|
|
|
"Verona",
|
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),
|
2017-08-10 10:58:39 +02:00
|
|
|
|
|
|
|
)
|
2017-07-21 20:31:25 +02:00
|
|
|
|
2019-08-26 05:11:18 +02:00
|
|
|
def test_wildcard_mentioned_send_message_events(self) -> None:
|
|
|
|
for i in range(3):
|
|
|
|
content = 'mentioning... @**all** hello ' + str(i)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(self.example_user('cordelia'),
|
2019-08-26 05:11:18 +02:00
|
|
|
"Verona",
|
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),
|
2019-08-26 05:11:18 +02:00
|
|
|
|
|
|
|
)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_pm_send_message_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_personal_message(self.example_user('cordelia'),
|
|
|
|
self.example_user('hamlet'),
|
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
|
|
|
'hola'),
|
2017-05-23 03:02:01 +02:00
|
|
|
|
|
|
|
)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_huddle_send_message_events(self) -> None:
|
2017-05-23 03:02:01 +02:00
|
|
|
huddle = [
|
2020-03-07 11:43:05 +01:00
|
|
|
self.example_user('hamlet'),
|
|
|
|
self.example_user('othello'),
|
2017-05-23 03:02:01 +02:00
|
|
|
]
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_huddle_message(self.example_user('cordelia'),
|
2017-10-28 16:40:28 +02:00
|
|
|
huddle,
|
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
|
|
|
'hola'),
|
2017-05-23 03:02:01 +02:00
|
|
|
|
|
|
|
)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_stream_send_message_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(self.example_user("hamlet"), "Verona", "hello"),
|
2017-10-31 18:36:18 +01:00
|
|
|
client_gravatar=False,
|
|
|
|
)
|
2020-07-10 16:10:58 +02:00
|
|
|
check_message('events[0]', events[0])
|
|
|
|
assert isinstance(events[0]['message']['avatar_url'], str)
|
2017-04-20 17:31:41 +02:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(self.example_user("hamlet"), "Verona", "hello"),
|
2017-10-31 18:36:18 +01:00
|
|
|
client_gravatar=True,
|
2017-02-21 19:35:17 +01:00
|
|
|
)
|
2020-07-10 16:10:58 +02:00
|
|
|
check_message('events[0]', events[0])
|
|
|
|
assert events[0]['message']['avatar_url'] is None
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-03-24 05:54:20 +01:00
|
|
|
# Verify message editing
|
2016-06-21 21:34:41 +02:00
|
|
|
message = Message.objects.order_by('-id')[0]
|
2014-03-11 15:14:32 +01:00
|
|
|
topic = 'new_topic'
|
|
|
|
propagate_mode = 'change_all'
|
|
|
|
content = 'new content'
|
2016-10-04 18:32:46 +02:00
|
|
|
rendered_content = render_markdown(message, content)
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
prior_mention_user_ids: Set[int] = set()
|
|
|
|
mentioned_user_ids: Set[int] = set()
|
2019-11-28 11:26:57 +01:00
|
|
|
mention_data = MentionData(
|
|
|
|
realm_id=self.user_profile.realm_id,
|
|
|
|
content=content,
|
|
|
|
)
|
Notify offline users about edited stream messages.
We now do push notifications and missed message emails
for offline users who are subscribed to the stream for
a message that has been edited, but we short circuit
the offline-notification logic for any user who presumably
would have already received a notification on the original
message.
This effectively boils down to sending notifications to newly
mentioned users. The motivating use case here is that you
forget to mention somebody in a message, and then you edit
the message to mention the person. If they are offline, they
will now get pushed notifications and missed message emails,
with some minor caveats.
We try to mostly use the same techniques here as the
send-message code path, and we share common code with the
send-message path once we get to the Tornado layer and call
maybe_enqueue_notifications.
The major places where we differ are in a function called
maybe_enqueue_notifications_for_message_update, and the top
of that function short circuits a bunch of cases where we
can mostly assume that the original message had an offline
notification.
We can expect a couple changes in the future:
* Requirements may change here, and it might make sense
to send offline notifications on the update side even
in circumstances where the original message had a
notification.
* We may track more notifications in a DB model, which
may simplify our short-circuit logic.
In the view/action layer, we already had two separate codepaths
for send-message and update-message, but this mostly echoes
what the send-message path does in terms of collecting data
about recipients.
2017-10-03 16:25:12 +02:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_message(
|
|
|
|
self.user_profile,
|
|
|
|
message,
|
|
|
|
None,
|
|
|
|
topic,
|
|
|
|
propagate_mode,
|
|
|
|
False,
|
|
|
|
False,
|
|
|
|
content,
|
|
|
|
rendered_content,
|
|
|
|
prior_mention_user_ids,
|
|
|
|
mentioned_user_ids,
|
|
|
|
mention_data),
|
2017-05-23 03:02:01 +02:00
|
|
|
state_change_expected=True,
|
2017-02-21 19:35:17 +01:00
|
|
|
)
|
2020-07-10 18:35:58 +02:00
|
|
|
check_update_message(
|
|
|
|
'events[0]',
|
|
|
|
events[0],
|
|
|
|
has_content=True,
|
|
|
|
has_topic=True,
|
|
|
|
has_new_stream_id=False,
|
|
|
|
)
|
2017-03-24 05:54:20 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-03-24 05:54:20 +01:00
|
|
|
lambda: do_update_embedded_data(self.user_profile, message,
|
2020-04-09 21:51:58 +02:00
|
|
|
"embed_content", "<p>embed_content</p>"),
|
2017-03-24 05:54:20 +01:00
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-10 18:35:58 +02:00
|
|
|
check_update_message_embedded('events[0]', events[0])
|
2017-03-24 05:54:20 +01:00
|
|
|
|
2020-07-06 09:30:59 +02:00
|
|
|
# Verify move topic to different stream.
|
|
|
|
|
|
|
|
# Send 2 messages in "test" topic.
|
|
|
|
self.send_stream_message(self.user_profile, "Verona")
|
|
|
|
message_id = self.send_stream_message(self.user_profile, "Verona")
|
|
|
|
message = Message.objects.get(id=message_id)
|
|
|
|
topic = 'new_topic'
|
|
|
|
stream = get_stream("Denmark", self.user_profile.realm)
|
|
|
|
propagate_mode = 'change_all'
|
|
|
|
prior_mention_user_ids = set()
|
|
|
|
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_message(
|
|
|
|
self.user_profile,
|
|
|
|
message,
|
|
|
|
stream,
|
|
|
|
topic,
|
|
|
|
propagate_mode,
|
|
|
|
True,
|
|
|
|
True,
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
set(),
|
|
|
|
set(),
|
|
|
|
None),
|
|
|
|
state_change_expected=True,
|
|
|
|
# There are 3 events generated for this action
|
|
|
|
# * update_message: For updating existing messages
|
|
|
|
# * 2 new message events: Breadcrumb messages in the new and old topics.
|
|
|
|
num_events=3,
|
|
|
|
)
|
2020-07-10 18:35:58 +02:00
|
|
|
check_update_message(
|
|
|
|
'events[0]',
|
|
|
|
events[0],
|
|
|
|
has_content=False,
|
|
|
|
has_topic=True,
|
|
|
|
has_new_stream_id=True,
|
|
|
|
)
|
2020-07-06 09:30:59 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_update_message_flags(self) -> None:
|
2017-03-24 03:19:23 +01:00
|
|
|
# Test message flag update events
|
2017-10-28 16:40:28 +02:00
|
|
|
message = self.send_personal_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
self.example_user("cordelia"),
|
|
|
|
self.example_user("hamlet"),
|
2017-10-28 16:40:28 +02:00
|
|
|
"hello",
|
|
|
|
)
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2018-03-14 00:05:55 +01:00
|
|
|
lambda: do_update_message_flags(user_profile, get_client("website"), 'add', 'starred', [message]),
|
2018-08-14 23:57:20 +02:00
|
|
|
state_change_expected=True,
|
2017-03-24 03:19:23 +01:00
|
|
|
)
|
2020-08-18 18:08:39 +02:00
|
|
|
check_update_message_flags_add("events[0]", events[0])
|
2020-07-17 09:13:10 +02:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2018-03-14 00:05:55 +01:00
|
|
|
lambda: do_update_message_flags(user_profile, get_client("website"), 'remove', 'starred', [message]),
|
2018-08-14 23:57:20 +02:00
|
|
|
state_change_expected=True,
|
2017-03-24 03:19:23 +01:00
|
|
|
)
|
2020-08-18 18:08:39 +02:00
|
|
|
check_update_message_flags_remove("events[0]", events[0])
|
2017-03-24 03:19:23 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_update_read_flag_removes_unread_msg_ids(self) -> None:
|
2017-05-23 03:02:01 +02:00
|
|
|
|
|
|
|
user_profile = self.example_user('hamlet')
|
2017-07-21 20:31:25 +02:00
|
|
|
mention = '@**' + user_profile.full_name + '**'
|
|
|
|
|
|
|
|
for content in ['hello', mention]:
|
2017-10-28 16:40:28 +02:00
|
|
|
message = self.send_stream_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
self.example_user('cordelia'),
|
2017-07-21 20:31:25 +02:00
|
|
|
"Verona",
|
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,
|
2017-07-21 20:31:25 +02:00
|
|
|
)
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2018-03-14 00:05:55 +01:00
|
|
|
lambda: do_update_message_flags(user_profile, get_client("website"), 'add', 'read', [message]),
|
2017-07-21 20:31:25 +02:00
|
|
|
state_change_expected=True,
|
|
|
|
)
|
2017-05-23 03:02:01 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_send_message_to_existing_recipient(self) -> None:
|
2020-03-07 11:43:05 +01:00
|
|
|
sender = self.example_user('cordelia')
|
2017-10-28 16:40:28 +02:00
|
|
|
self.send_stream_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
sender,
|
2017-05-23 03:02:01 +02:00
|
|
|
"Verona",
|
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
|
|
|
"hello 1",
|
2017-05-23 03:02:01 +02:00
|
|
|
)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(sender, "Verona", "hello 2"),
|
2017-05-23 03:02:01 +02:00
|
|
|
state_change_expected=True,
|
|
|
|
)
|
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_add_reaction(self) -> None:
|
2020-03-07 11:43:05 +01:00
|
|
|
message_id = self.send_stream_message(self.example_user("hamlet"), "Verona", "hello")
|
2017-10-08 09:34:59 +02:00
|
|
|
message = Message.objects.get(id=message_id)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-10-08 09:34:59 +02:00
|
|
|
lambda: do_add_reaction(
|
|
|
|
self.user_profile, message, "tada", "1f389", "unicode_emoji"),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-08-17 15:11:19 +02:00
|
|
|
check_reaction_add("events[0]", events[0])
|
2017-10-08 09:34:59 +02:00
|
|
|
|
2018-02-12 10:53:36 +01:00
|
|
|
def test_add_submessage(self) -> None:
|
|
|
|
cordelia = self.example_user('cordelia')
|
|
|
|
stream_name = 'Verona'
|
|
|
|
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-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2018-02-12 10:53:36 +01:00
|
|
|
lambda: do_add_submessage(
|
2018-11-02 23:33:54 +01:00
|
|
|
realm=cordelia.realm,
|
2018-02-12 10:53:36 +01:00
|
|
|
sender_id=cordelia.id,
|
|
|
|
message_id=message_id,
|
|
|
|
msg_type='whatever',
|
|
|
|
content='"stuff"',
|
|
|
|
),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 16:27:59 +02:00
|
|
|
check_submessage('events[0]', events[0])
|
2018-02-12 10:53:36 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_remove_reaction(self) -> None:
|
2020-03-07 11:43:05 +01:00
|
|
|
message_id = self.send_stream_message(self.example_user("hamlet"), "Verona", "hello")
|
2017-10-08 09:34:59 +02:00
|
|
|
message = Message.objects.get(id=message_id)
|
|
|
|
do_add_reaction(self.user_profile, message, "tada", "1f389", "unicode_emoji")
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-10-08 09:34:59 +02:00
|
|
|
lambda: do_remove_reaction(
|
|
|
|
self.user_profile, message, "1f389", "unicode_emoji"),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-08-17 15:11:19 +02:00
|
|
|
check_reaction_remove("events[0]", events[0])
|
2017-10-08 09:34:59 +02:00
|
|
|
|
2017-12-14 22:22:17 +01:00
|
|
|
def test_invite_user_event(self) -> None:
|
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-12-14 22:22:17 +01:00
|
|
|
lambda: do_invite_users(self.user_profile, ["foo@zulip.com"], streams, False),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 16:33:03 +02:00
|
|
|
check_invites_changed('events[0]', events[0])
|
2017-12-14 22:22:17 +01:00
|
|
|
|
2019-02-15 19:09:25 +01:00
|
|
|
def test_create_multiuse_invite_event(self) -> None:
|
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Denmark", "Verona"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-02-15 19:09:25 +01:00
|
|
|
lambda: do_create_multiuse_invite_link(self.user_profile, PreregistrationUser.INVITE_AS['MEMBER'], streams),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 16:33:03 +02:00
|
|
|
check_invites_changed('events[0]', events[0])
|
2019-02-15 19:09:25 +01:00
|
|
|
|
2017-12-14 22:22:17 +01:00
|
|
|
def test_revoke_user_invite_event(self) -> None:
|
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Denmark", "Verona"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, False)
|
|
|
|
prereg_users = PreregistrationUser.objects.filter(referred_by__realm=self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-12-14 22:22:17 +01:00
|
|
|
lambda: do_revoke_user_invite(prereg_users[0]),
|
|
|
|
state_change_expected=False,
|
2019-02-15 19:09:25 +01:00
|
|
|
)
|
2020-07-18 16:33:03 +02:00
|
|
|
check_invites_changed('events[0]', events[0])
|
2019-02-15 19:09:25 +01:00
|
|
|
|
|
|
|
def test_revoke_multiuse_invite_event(self) -> None:
|
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Denmark", "Verona"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
do_create_multiuse_invite_link(self.user_profile, PreregistrationUser.INVITE_AS['MEMBER'], streams)
|
|
|
|
|
|
|
|
multiuse_object = MultiuseInvite.objects.get()
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-02-15 19:09:25 +01:00
|
|
|
lambda: do_revoke_multi_use_invite(multiuse_object),
|
|
|
|
state_change_expected=False,
|
2017-12-14 22:22:17 +01:00
|
|
|
)
|
2020-07-18 16:33:03 +02:00
|
|
|
check_invites_changed('events[0]', events[0])
|
2017-12-14 22:22:17 +01:00
|
|
|
|
|
|
|
def test_invitation_accept_invite_event(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
2017-12-14 22:22:17 +01:00
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Denmark", "Scotland"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
|
|
|
|
do_invite_users(self.user_profile, ["foo@zulip.com"], streams, False)
|
2019-11-23 18:15:53 +01:00
|
|
|
prereg_user = PreregistrationUser.objects.get(email="foo@zulip.com")
|
2017-12-14 22:22:17 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-07-16 14:10:43 +02:00
|
|
|
lambda: do_create_user(
|
|
|
|
'foo@zulip.com',
|
|
|
|
'password',
|
|
|
|
self.user_profile.realm,
|
|
|
|
'full name',
|
|
|
|
prereg_user=prereg_user,
|
|
|
|
),
|
2017-12-14 22:22:17 +01:00
|
|
|
state_change_expected=True,
|
2020-10-26 13:16:10 +01:00
|
|
|
num_events=4,
|
2017-12-14 22:22:17 +01:00
|
|
|
)
|
|
|
|
|
2020-10-26 13:16:10 +01:00
|
|
|
check_invites_changed('events[3]', events[3])
|
2017-12-14 22:22:17 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_typing_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-03-18 03:50:41 +01:00
|
|
|
lambda: check_send_typing_notification(
|
2020-02-22 13:38:09 +01:00
|
|
|
self.user_profile, [self.example_user("cordelia").id], "start"),
|
2017-03-18 03:50:41 +01:00
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 16:39:06 +02:00
|
|
|
check_typing_start('events[0]', events[0])
|
2020-08-27 22:10:07 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: check_send_typing_notification(
|
|
|
|
self.user_profile, [self.example_user("cordelia").id], "stop"),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
|
|
|
check_typing_stop('events[0]', events[0])
|
2017-03-18 03:50:41 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_custom_profile_fields_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-03-17 10:07:22 +01:00
|
|
|
lambda: notify_realm_custom_profile_fields(
|
2017-12-11 07:24:44 +01:00
|
|
|
self.user_profile.realm, 'add'),
|
2017-03-17 10:07:22 +01:00
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 17:02:28 +02:00
|
|
|
check_custom_profile_fields('events[0]', events[0])
|
2018-03-31 07:30:24 +02:00
|
|
|
|
|
|
|
realm = self.user_profile.realm
|
|
|
|
field = realm.customprofilefield_set.get(realm=realm, name='Biography')
|
|
|
|
name = field.name
|
|
|
|
hint = 'Biography of the user'
|
|
|
|
try_update_realm_custom_profile_field(realm, field, name, hint=hint)
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2018-03-31 07:30:24 +02:00
|
|
|
lambda: notify_realm_custom_profile_fields(
|
|
|
|
self.user_profile.realm, 'add'),
|
|
|
|
state_change_expected=False,
|
|
|
|
)
|
2020-07-18 17:02:28 +02:00
|
|
|
check_custom_profile_fields('events[0]', events[0])
|
2017-03-17 10:07:22 +01:00
|
|
|
|
2018-07-09 11:49:08 +02:00
|
|
|
def test_custom_profile_field_data_events(self) -> None:
|
2019-03-07 21:29:16 +01:00
|
|
|
field_id = self.user_profile.realm.customprofilefield_set.get(
|
|
|
|
realm=self.user_profile.realm, name='Biography').id
|
2018-07-09 11:49:08 +02:00
|
|
|
field = {
|
|
|
|
"id": field_id,
|
|
|
|
"value": "New value",
|
|
|
|
}
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_custom_profile_data_if_changed(
|
|
|
|
self.user_profile,
|
|
|
|
[field]))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], "custom_profile_field")
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(
|
|
|
|
events[0]['person']['custom_profile_field'].keys(),
|
|
|
|
{"id", "value", "rendered_value"}
|
|
|
|
)
|
2018-07-09 11:49:08 +02:00
|
|
|
|
2018-08-09 14:02:32 +02:00
|
|
|
# Test we pass correct stringify value in custom-user-field data event
|
2019-03-07 21:29:16 +01:00
|
|
|
field_id = self.user_profile.realm.customprofilefield_set.get(
|
|
|
|
realm=self.user_profile.realm, name='Mentor').id
|
2018-08-09 14:02:32 +02:00
|
|
|
field = {
|
|
|
|
"id": field_id,
|
|
|
|
"value": [self.example_user("ZOE").id],
|
|
|
|
}
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_custom_profile_data_if_changed(
|
|
|
|
self.user_profile,
|
|
|
|
[field]))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], "custom_profile_field")
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(
|
|
|
|
events[0]['person']['custom_profile_field'].keys(),
|
|
|
|
{"id", "value"}
|
|
|
|
)
|
2018-08-09 14:02:32 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_presence_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_presence(
|
|
|
|
self.user_profile,
|
|
|
|
get_client("website"),
|
|
|
|
timezone_now(),
|
|
|
|
UserPresence.ACTIVE),
|
2020-02-02 17:29:05 +01:00
|
|
|
slim_presence=False)
|
2020-08-13 19:29:07 +02:00
|
|
|
|
|
|
|
check_presence(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_email=True,
|
|
|
|
presence_key="website",
|
|
|
|
status="active",
|
|
|
|
)
|
2020-02-02 17:29:05 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_presence(
|
|
|
|
self.example_user('cordelia'),
|
|
|
|
get_client("website"),
|
|
|
|
timezone_now(),
|
|
|
|
UserPresence.ACTIVE),
|
2020-02-02 17:29:05 +01:00
|
|
|
slim_presence=True)
|
2017-04-25 11:50:30 +02:00
|
|
|
|
2020-08-13 19:29:07 +02:00
|
|
|
check_presence(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_email=False,
|
|
|
|
presence_key="website",
|
|
|
|
status="active",
|
|
|
|
)
|
2020-02-03 17:09:18 +01:00
|
|
|
|
2020-08-13 19:29:07 +02:00
|
|
|
def test_presence_events_multiple_clients(self) -> None:
|
2020-03-10 11:48:26 +01:00
|
|
|
self.api_post(self.user_profile, "/api/v1/users/me/presence", {'status': 'idle'},
|
2017-12-14 19:02:31 +01:00
|
|
|
HTTP_USER_AGENT="ZulipAndroid/1.0")
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
|
|
|
lambda: do_update_user_presence(
|
|
|
|
self.user_profile,
|
|
|
|
get_client("website"),
|
|
|
|
timezone_now(),
|
|
|
|
UserPresence.ACTIVE))
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_presence(
|
|
|
|
self.user_profile,
|
|
|
|
get_client("ZulipAndroid/1.0"),
|
|
|
|
timezone_now(),
|
|
|
|
UserPresence.IDLE))
|
2020-08-13 19:29:07 +02:00
|
|
|
|
|
|
|
check_presence(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_email=True,
|
|
|
|
presence_key="ZulipAndroid/1.0",
|
|
|
|
status="idle",
|
|
|
|
)
|
2017-03-24 05:26:32 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_register_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: self.register("test1@zulip.com", "test1"))
|
2017-03-24 05:49:23 +01:00
|
|
|
self.assert_length(events, 1)
|
2020-08-14 02:14:06 +02:00
|
|
|
check_realm_user_add('events[0]', events[0])
|
2018-12-06 23:17:46 +01:00
|
|
|
new_user_profile = get_user_by_delivery_email("test1@zulip.com", self.user_profile.realm)
|
2020-03-12 14:17:25 +01:00
|
|
|
self.assertEqual(new_user_profile.delivery_email, "test1@zulip.com")
|
2018-12-06 23:17:46 +01:00
|
|
|
|
|
|
|
def test_register_events_email_address_visibility(self) -> None:
|
|
|
|
do_set_realm_property(self.user_profile.realm, "email_address_visibility",
|
|
|
|
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: self.register("test1@zulip.com", "test1"))
|
2018-12-06 23:17:46 +01:00
|
|
|
self.assert_length(events, 1)
|
2020-08-14 02:14:06 +02:00
|
|
|
check_realm_user_add('events[0]', events[0])
|
2018-12-06 23:17:46 +01:00
|
|
|
new_user_profile = get_user_by_delivery_email("test1@zulip.com", self.user_profile.realm)
|
2020-06-10 06:41:04 +02:00
|
|
|
self.assertEqual(new_user_profile.email, f"user{new_user_profile.id}@zulip.testserver")
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_alert_words_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_add_alert_words(self.user_profile, ["alert_word"]))
|
2020-07-18 17:11:41 +02:00
|
|
|
check_alert_words('events[0]', events[0])
|
2014-03-06 17:07:43 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_alert_words(self.user_profile, ["alert_word"]))
|
2020-07-18 17:11:41 +02:00
|
|
|
check_alert_words('events[0]', events[0])
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2018-12-18 17:17:08 +01:00
|
|
|
def test_away_events(self) -> None:
|
|
|
|
client = get_client("website")
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_status(
|
|
|
|
user_profile=self.user_profile,
|
|
|
|
away=True,
|
|
|
|
status_text='out to lunch',
|
|
|
|
client_id=client.id))
|
|
|
|
|
2021-01-20 19:53:11 +01:00
|
|
|
check_user_status("events[0]", events[0], {"away", "status_text"})
|
2018-12-18 17:17:08 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_status(
|
|
|
|
user_profile=self.user_profile,
|
|
|
|
away=False,
|
|
|
|
status_text='',
|
|
|
|
client_id=client.id))
|
|
|
|
|
2021-01-20 19:53:11 +01:00
|
|
|
check_user_status("events[0]", events[0], {"away", "status_text"})
|
|
|
|
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_status(
|
|
|
|
user_profile=self.user_profile,
|
|
|
|
away=True,
|
|
|
|
status_text=None,
|
|
|
|
client_id=client.id))
|
|
|
|
|
|
|
|
check_user_status("events[0]", events[0], {"away"})
|
|
|
|
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_status(
|
|
|
|
user_profile=self.user_profile,
|
|
|
|
away=None,
|
|
|
|
status_text="at the beach",
|
|
|
|
client_id=client.id))
|
|
|
|
|
|
|
|
check_user_status("events[0]", events[0], {"status_text"})
|
2018-12-18 17:17:08 +01:00
|
|
|
|
2017-11-14 07:31:31 +01:00
|
|
|
def test_user_group_events(self) -> None:
|
|
|
|
othello = self.example_user('othello')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: check_add_user_group(
|
|
|
|
self.user_profile.realm,
|
|
|
|
'backend',
|
|
|
|
[othello],
|
|
|
|
'Backend team'))
|
2020-07-18 17:19:30 +02:00
|
|
|
check_user_group_add('events[0]', events[0])
|
2017-11-14 07:31:31 +01:00
|
|
|
|
2017-11-14 08:00:18 +01:00
|
|
|
# Test name update
|
|
|
|
backend = UserGroup.objects.get(name='backend')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_group_name(backend, 'backendteam'))
|
2020-08-14 13:50:55 +02:00
|
|
|
check_user_group_update('events[0]', events[0], 'name')
|
2017-11-14 08:00:18 +01:00
|
|
|
|
2017-11-14 08:00:53 +01:00
|
|
|
# Test description update
|
|
|
|
description = "Backend team to deal with backend code."
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_update_user_group_description(backend, description))
|
2020-08-14 13:50:55 +02:00
|
|
|
check_user_group_update('events[0]', events[0], 'description')
|
2017-11-14 08:00:53 +01:00
|
|
|
|
2017-11-14 08:01:39 +01:00
|
|
|
# Test add members
|
|
|
|
hamlet = self.example_user('hamlet')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: bulk_add_members_to_user_group(backend, [hamlet]))
|
2020-08-14 13:18:52 +02:00
|
|
|
check_user_group_add_members('events[0]', events[0])
|
2017-11-14 08:01:39 +01:00
|
|
|
|
2017-11-14 08:01:50 +01:00
|
|
|
# Test remove members
|
|
|
|
hamlet = self.example_user('hamlet')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: remove_members_from_user_group(backend, [hamlet]))
|
2020-08-14 13:34:34 +02:00
|
|
|
check_user_group_remove_members('events[0]', events[0])
|
2017-11-14 08:01:50 +01:00
|
|
|
|
2020-08-14 13:38:36 +02:00
|
|
|
# Test remove event
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: check_delete_user_group(backend.id, othello))
|
2020-08-14 13:38:36 +02:00
|
|
|
check_user_group_remove('events[0]', events[0])
|
2017-11-15 08:09:49 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_default_stream_groups_events(self) -> None:
|
2017-11-01 18:20:34 +01:00
|
|
|
streams = []
|
|
|
|
for stream_name in ["Scotland", "Verona", "Denmark"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_create_default_stream_group(
|
|
|
|
self.user_profile.realm,
|
|
|
|
"group1",
|
|
|
|
"This is group1",
|
|
|
|
streams))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-01 18:20:34 +01:00
|
|
|
|
2017-11-14 20:33:09 +01:00
|
|
|
group = lookup_default_stream_groups(["group1"], self.user_profile.realm)[0]
|
2017-11-01 18:20:34 +01:00
|
|
|
venice_stream = get_stream("Venice", self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_add_streams_to_default_stream_group(
|
|
|
|
self.user_profile.realm,
|
|
|
|
group,
|
|
|
|
[venice_stream]))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-01 18:20:34 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_streams_from_default_stream_group(
|
|
|
|
self.user_profile.realm,
|
|
|
|
group,
|
|
|
|
[venice_stream]))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-01 18:20:34 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_default_stream_group_description(
|
|
|
|
self.user_profile.realm,
|
|
|
|
group,
|
|
|
|
"New description"))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-14 20:51:34 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_default_stream_group_name(
|
|
|
|
self.user_profile.realm,
|
|
|
|
group,
|
|
|
|
"New Group Name"))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-14 21:06:02 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_default_stream_group(self.user_profile.realm, group))
|
2020-08-01 14:33:03 +02:00
|
|
|
check_default_stream_groups('events[0]', events[0])
|
2017-11-01 18:20:34 +01:00
|
|
|
|
2019-03-01 01:26:57 +01:00
|
|
|
def test_default_stream_group_events_guest(self) -> None:
|
|
|
|
streams = []
|
|
|
|
for stream_name in ["Scotland", "Verona", "Denmark"]:
|
|
|
|
streams.append(get_stream(stream_name, self.user_profile.realm))
|
|
|
|
|
|
|
|
do_create_default_stream_group(self.user_profile.realm, "group1",
|
|
|
|
"This is group1", streams)
|
|
|
|
group = lookup_default_stream_groups(["group1"], self.user_profile.realm)[0]
|
|
|
|
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_GUEST)
|
2019-03-01 01:26:57 +01:00
|
|
|
venice_stream = get_stream("Venice", self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
|
|
|
lambda: do_add_streams_to_default_stream_group(
|
|
|
|
self.user_profile.realm,
|
|
|
|
group,
|
|
|
|
[venice_stream]),
|
|
|
|
state_change_expected = False,
|
|
|
|
num_events=0)
|
2019-03-01 01:26:57 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_default_streams_events(self) -> None:
|
2017-01-30 04:23:08 +01:00
|
|
|
stream = get_stream("Scotland", self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_add_default_stream(stream))
|
2020-08-01 14:36:13 +02:00
|
|
|
check_default_streams('events[0]', events[0])
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_default_stream(stream))
|
2020-08-01 14:36:13 +02:00
|
|
|
check_default_streams('events[0]', events[0])
|
2016-05-20 22:08:42 +02:00
|
|
|
|
2019-03-01 01:26:57 +01:00
|
|
|
def test_default_streams_events_guest(self) -> None:
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_GUEST)
|
2019-03-01 01:26:57 +01:00
|
|
|
stream = get_stream("Scotland", self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
|
|
|
lambda: do_add_default_stream(stream),
|
|
|
|
state_change_expected = False,
|
|
|
|
num_events=0)
|
|
|
|
self.verify_action(
|
|
|
|
lambda: do_remove_default_stream(stream),
|
|
|
|
state_change_expected = False,
|
|
|
|
num_events=0)
|
2019-03-01 01:26:57 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_muted_topics_events(self) -> None:
|
2017-08-30 02:19:34 +02:00
|
|
|
stream = get_stream('Denmark', self.user_profile.realm)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_mute_topic(
|
|
|
|
self.user_profile,
|
|
|
|
stream,
|
|
|
|
"topic"))
|
2020-08-06 20:31:12 +02:00
|
|
|
check_muted_topics('events[0]', events[0])
|
2017-03-24 05:32:50 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_unmute_topic(
|
|
|
|
self.user_profile,
|
|
|
|
stream,
|
|
|
|
"topic"))
|
2020-08-06 20:31:12 +02:00
|
|
|
check_muted_topics('events[0]', events[0])
|
2017-03-24 05:32:50 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_avatar_fields(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-29 12:47:44 +02:00
|
|
|
lambda: do_change_avatar_fields(self.user_profile, UserProfile.AVATAR_FROM_USER, acting_user=self.user_profile),
|
2017-02-21 21:37:16 +01:00
|
|
|
)
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], "avatar_fields")
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
assert isinstance(events[0]['person']['avatar_url'], str)
|
|
|
|
assert isinstance(events[0]['person']['avatar_url_medium'], str)
|
2017-02-21 21:37:16 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-29 12:47:44 +02:00
|
|
|
lambda: do_change_avatar_fields(self.user_profile, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=self.user_profile),
|
2018-02-05 21:42:54 +01:00
|
|
|
)
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], "avatar_fields")
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(events[0]['person']['avatar_url'], None)
|
|
|
|
self.assertEqual(events[0]['person']['avatar_url_medium'], None)
|
2018-02-05 21:42:54 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_full_name(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_full_name(
|
|
|
|
self.user_profile,
|
|
|
|
'Sir Hamlet',
|
|
|
|
self.user_profile))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], 'full_name')
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2018-08-02 08:47:13 +02:00
|
|
|
def test_change_user_delivery_email_email_address_visibilty_admins(self) -> None:
|
|
|
|
do_set_realm_property(self.user_profile.realm, "email_address_visibility",
|
|
|
|
Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
|
2018-12-06 23:17:46 +01:00
|
|
|
# Important: We need to refresh from the database here so that
|
|
|
|
# we don't have a stale UserProfile object with an old value
|
|
|
|
# for email being passed into this next function.
|
|
|
|
self.user_profile.refresh_from_db()
|
2018-08-02 08:47:13 +02:00
|
|
|
action = lambda: do_change_user_delivery_email(self.user_profile, 'newhamlet@zulip.com')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
num_events=2,
|
|
|
|
client_gravatar=False)
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], "delivery_email")
|
|
|
|
check_realm_user_update('events[1]', events[1], "avatar_fields")
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
assert isinstance(events[1]['person']['avatar_url'], str)
|
|
|
|
assert isinstance(events[1]['person']['avatar_url_medium'], str)
|
2018-08-02 08:47:13 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_realm_authentication_methods(self) -> None:
|
|
|
|
def fake_backends() -> Any:
|
2017-02-21 19:35:17 +01:00
|
|
|
backends = (
|
|
|
|
'zproject.backends.DevAuthBackend',
|
|
|
|
'zproject.backends.EmailAuthBackend',
|
|
|
|
'zproject.backends.GitHubAuthBackend',
|
2019-02-02 16:51:26 +01:00
|
|
|
'zproject.backends.GoogleAuthBackend',
|
2017-02-21 19:35:17 +01:00
|
|
|
'zproject.backends.ZulipLDAPAuthBackend',
|
|
|
|
)
|
|
|
|
return self.settings(AUTHENTICATION_BACKENDS=backends)
|
|
|
|
|
2016-11-02 21:51:56 +01:00
|
|
|
# Test transitions; any new backends should be tested with T/T/T/F/T
|
|
|
|
for (auth_method_dict) in \
|
|
|
|
({'Google': True, 'Email': True, 'GitHub': True, 'LDAP': False, 'Dev': False},
|
2016-12-03 00:04:17 +01:00
|
|
|
{'Google': True, 'Email': True, 'GitHub': False, 'LDAP': False, 'Dev': False},
|
|
|
|
{'Google': True, 'Email': False, 'GitHub': False, 'LDAP': False, 'Dev': False},
|
|
|
|
{'Google': True, 'Email': False, 'GitHub': True, 'LDAP': False, 'Dev': False},
|
|
|
|
{'Google': False, 'Email': False, 'GitHub': False, 'LDAP': False, 'Dev': True},
|
|
|
|
{'Google': False, 'Email': False, 'GitHub': True, 'LDAP': False, 'Dev': True},
|
|
|
|
{'Google': False, 'Email': True, 'GitHub': True, 'LDAP': True, 'Dev': False}):
|
2017-02-21 19:35:17 +01:00
|
|
|
with fake_backends():
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-02-21 19:35:17 +01:00
|
|
|
lambda: do_set_realm_authentication_methods(
|
|
|
|
self.user_profile.realm,
|
|
|
|
auth_method_dict))
|
|
|
|
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict('events[0]', events[0])
|
2016-11-02 21:51:56 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_pin_stream(self) -> None:
|
2017-03-05 01:30:48 +01:00
|
|
|
stream = get_stream("Denmark", self.user_profile.realm)
|
|
|
|
sub = get_subscription(stream.name, self.user_profile)
|
2017-02-21 19:35:17 +01:00
|
|
|
do_change_subscription_property(self.user_profile, sub, stream, "pin_to_top", False)
|
|
|
|
for pinned in (True, False):
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_subscription_property(
|
|
|
|
self.user_profile,
|
|
|
|
sub,
|
|
|
|
stream,
|
|
|
|
"pin_to_top",
|
|
|
|
pinned))
|
2020-08-17 14:19:09 +02:00
|
|
|
check_subscription_update(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
property="pin_to_top",
|
|
|
|
value=pinned,
|
|
|
|
)
|
2016-07-01 07:26:09 +02:00
|
|
|
|
2019-02-13 10:22:16 +01:00
|
|
|
def test_change_stream_notification_settings(self) -> None:
|
|
|
|
for setting_name in ['email_notifications']:
|
2020-08-17 14:06:06 +02:00
|
|
|
stream = get_stream("Denmark", self.user_profile.realm)
|
|
|
|
sub = get_subscription(stream.name, self.user_profile)
|
2019-02-13 10:22:16 +01:00
|
|
|
|
2020-08-17 14:06:06 +02:00
|
|
|
# First test with notification_settings_null enabled
|
|
|
|
for value in (True, False):
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_subscription_property(
|
|
|
|
self.user_profile,
|
|
|
|
sub,
|
|
|
|
stream,
|
|
|
|
setting_name, value),
|
|
|
|
notification_settings_null=True)
|
2020-08-17 14:19:09 +02:00
|
|
|
check_subscription_update(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
property=setting_name,
|
|
|
|
value=value,
|
|
|
|
)
|
2020-08-17 14:06:06 +02:00
|
|
|
|
|
|
|
for value in (True, False):
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_subscription_property(
|
|
|
|
self.user_profile,
|
|
|
|
sub,
|
|
|
|
stream,
|
|
|
|
setting_name,
|
|
|
|
value))
|
2020-08-17 14:19:09 +02:00
|
|
|
check_subscription_update(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
property=setting_name,
|
|
|
|
value=value,
|
|
|
|
)
|
2019-02-13 10:22:16 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_realm_message_edit_settings(self) -> None:
|
2016-07-08 02:25:55 +02:00
|
|
|
# Test every transition among the four possibilities {T,F} x {0, non-0}
|
|
|
|
for (allow_message_editing, message_content_edit_limit_seconds) in \
|
2017-10-07 00:29:18 +02:00
|
|
|
((True, 0), (False, 0), (False, 1234),
|
|
|
|
(True, 600), (False, 0), (True, 1234)):
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-03-21 18:08:40 +01:00
|
|
|
lambda: do_set_realm_message_editing(self.user_profile.realm,
|
|
|
|
allow_message_editing,
|
2017-12-03 00:50:48 +01:00
|
|
|
message_content_edit_limit_seconds,
|
|
|
|
False))
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict('events[0]', events[0])
|
2016-06-21 21:34:41 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_realm_notifications_stream(self) -> None:
|
2017-06-09 20:50:38 +02:00
|
|
|
|
|
|
|
stream = get_stream("Rome", self.user_profile.realm)
|
|
|
|
|
|
|
|
for notifications_stream, notifications_stream_id in ((stream, stream.id), (None, -1)):
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-06-09 20:50:38 +02:00
|
|
|
lambda: do_set_realm_notifications_stream(self.user_profile.realm,
|
|
|
|
notifications_stream,
|
|
|
|
notifications_stream_id))
|
2020-07-23 17:38:53 +02:00
|
|
|
check_realm_update('events[0]', events[0], 'notifications_stream_id')
|
2017-06-09 20:50:38 +02:00
|
|
|
|
2017-10-20 16:55:04 +02:00
|
|
|
def test_change_realm_signup_notifications_stream(self) -> None:
|
|
|
|
stream = get_stream("Rome", self.user_profile.realm)
|
|
|
|
|
|
|
|
for signup_notifications_stream, signup_notifications_stream_id in ((stream, stream.id), (None, -1)):
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2017-10-20 16:55:04 +02:00
|
|
|
lambda: do_set_realm_signup_notifications_stream(self.user_profile.realm,
|
|
|
|
signup_notifications_stream,
|
|
|
|
signup_notifications_stream_id))
|
2020-07-23 17:38:53 +02:00
|
|
|
check_realm_update('events[0]', events[0], 'signup_notifications_stream_id')
|
2017-10-20 16:55:04 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_is_admin(self) -> None:
|
2020-03-12 14:17:25 +01:00
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
|
|
|
# Important: We need to refresh from the database here so that
|
|
|
|
# we don't have a stale UserProfile object with an old value
|
|
|
|
# for email being passed into this next function.
|
|
|
|
self.user_profile.refresh_from_db()
|
|
|
|
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_MEMBER)
|
|
|
|
for role in [UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_MEMBER]:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_user_role(self.user_profile, role))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], 'role')
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(events[0]['person']['role'], role)
|
2020-06-03 19:49:45 +02:00
|
|
|
|
|
|
|
def test_change_is_owner(self) -> None:
|
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
|
|
|
# Important: We need to refresh from the database here so that
|
|
|
|
# we don't have a stale UserProfile object with an old value
|
|
|
|
# for email being passed into this next function.
|
|
|
|
self.user_profile.refresh_from_db()
|
|
|
|
|
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_MEMBER)
|
|
|
|
for role in [UserProfile.ROLE_REALM_OWNER, UserProfile.ROLE_MEMBER]:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_user_role(self.user_profile, role))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], 'role')
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(events[0]['person']['role'], role)
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2020-06-01 02:31:30 +02:00
|
|
|
def test_change_is_guest(self) -> None:
|
|
|
|
reset_emails_in_zulip_realm()
|
|
|
|
|
|
|
|
# Important: We need to refresh from the database here so that
|
|
|
|
# we don't have a stale UserProfile object with an old value
|
|
|
|
# for email being passed into this next function.
|
|
|
|
self.user_profile.refresh_from_db()
|
|
|
|
|
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_MEMBER)
|
|
|
|
for role in [UserProfile.ROLE_GUEST, UserProfile.ROLE_MEMBER]:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_user_role(self.user_profile, role))
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[0]', events[0], 'role')
|
event_schema: Extract check_realm_user_update.
This a pretty big commit, but I really wanted it
to be atomic.
All realm_user/update events look the same from
the top:
_check_realm_user_update = check_events_dict(
required_keys=[
("type", equals("realm_user")),
("op", equals("update")),
("person", _check_realm_user_person),
]
)
And then we have a bunch of fields for person that
are optional, and we usually only send user_id plus
one other field, with the exception of avatar-related
events:
_check_realm_user_person = check_dict_only(
required_keys=[
# vertical formatting
("user_id", check_int),
],
optional_keys=[
("avatar_source", check_string),
("avatar_url", check_none_or(check_string)),
("avatar_url_medium", check_none_or(check_string)),
("avatar_version", check_int),
("bot_owner_id", check_int),
("custom_profile_field", _check_custom_profile_field),
("delivery_email", check_string),
("full_name", check_string),
("role", check_int_in(UserProfile.ROLE_TYPES)),
("email", check_string),
("user_id", check_int),
("timezone", check_string),
],
)
I would start the code review by just skimming the changes
to event_schema.py, to get the big picture of the complexity
here. Basically the schema is just the combined superset of
all the individual schemas that we remove from test_events.
Then I would read test_events.py.
The simplest diffs are basically of this form:
- schema_checker = check_events_dict([
- ('type', equals('realm_user')),
- ('op', equals('update')),
- ('person', check_dict_only([
- ('role', check_int_in(UserProfile.ROLE_TYPES)),
- ('user_id', check_int),
- ])),
- ])
# ...
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
Instead of a custom schema checker, we use the "superset"
schema checker, but then we pass in the set of fields that we
expect to be there. Note that 'user_id' is always there.
So most of the heavy lifting happens in this new function
in event_schema.py:
def check_realm_user_update(
var_name: str, event: Dict[str, Any], optional_fields: Set[str],
) -> None:
_check_realm_user_update(var_name, event)
keys = set(event["person"].keys()) - {"user_id"}
assert optional_fields == keys
But we still do some more custom checks in test_events.py.
custom profile fields: check keys of custom_profile_field
def test_custom_profile_field_data_events(self) -> None:
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value", "rendered_value"}
+ )
+ check_realm_user_update('events[0]', events[0], {"custom_profile_field"})
+ self.assertEqual(
+ events[0]['person']['custom_profile_field'].keys(),
+ {"id", "value"}
+ )
avatar fields: check more specific types, since the superset
schema has check_none_or(check_string)
def test_change_avatar_fields(self) -> None:
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ assert isinstance(events[0]['person']['avatar_url'], str)
+ assert isinstance(events[0]['person']['avatar_url_medium'], str)
+ check_realm_user_update('events[0]', events[0], avatar_fields)
+ self.assertEqual(events[0]['person']['avatar_url'], None)
+ self.assertEqual(events[0]['person']['avatar_url_medium'], None)
Also note that avatar_fields is a set of four fields that
are set in event_schema.
full name: no extra work!
def test_change_full_name(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'full_name'})
test_change_user_delivery_email_email_address_visibilty_admins:
no extra work for delivery_email
check avatar fields more directly
roles (several examples) -- actually check the specific role
def test_change_realm_authentication_methods(self) -> None:
- schema_checker('events[0]', events[0])
+ check_realm_user_update('events[0]', events[0], {'role'})
+ self.assertEqual(events[0]['person']['role'], role)
bot_owner_id: no extra work!
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
- change_bot_owner_checker_user('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"bot_owner_id"})
timezone: no extra work!
- timezone_schema_checker('events[1]', events[1])
+ check_realm_user_update('events[1]', events[1], {"email", "timezone"})
2020-07-23 16:04:06 +02:00
|
|
|
self.assertEqual(events[0]['person']['role'], role)
|
2020-06-01 02:31:30 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_notification_settings(self) -> None:
|
2017-05-23 03:19:21 +02:00
|
|
|
for notification_setting, v in self.user_profile.notification_setting_types.items():
|
2019-06-29 22:00:44 +02:00
|
|
|
if notification_setting in ["notification_sound", "desktop_icon_count_display"]:
|
|
|
|
# These settings are tested in their own tests.
|
2018-01-11 21:36:11 +01:00
|
|
|
continue
|
|
|
|
|
2020-07-12 23:41:53 +02:00
|
|
|
do_change_notification_settings(self.user_profile, notification_setting, False,
|
|
|
|
acting_user=self.user_profile)
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2017-05-23 03:19:21 +02:00
|
|
|
for setting_value in [True, False]:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_notification_settings(
|
|
|
|
self.user_profile,
|
|
|
|
notification_setting,
|
|
|
|
setting_value,
|
2020-07-12 23:41:53 +02:00
|
|
|
acting_user=self.user_profile))
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications('events[0]', events[0], setting_value)
|
2016-12-08 21:06:23 +01:00
|
|
|
|
2019-02-13 10:22:16 +01:00
|
|
|
# Also test with notification_settings_null=True
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-02-13 10:22:16 +01:00
|
|
|
lambda: do_change_notification_settings(
|
2020-07-12 23:41:53 +02:00
|
|
|
self.user_profile, notification_setting, setting_value,
|
|
|
|
acting_user=self.user_profile),
|
2019-02-13 10:22:16 +01:00
|
|
|
notification_settings_null=True,
|
|
|
|
state_change_expected=False)
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications('events[0]', events[0], setting_value)
|
2019-02-13 10:22:16 +01:00
|
|
|
|
2018-01-11 21:36:11 +01:00
|
|
|
def test_change_notification_sound(self) -> None:
|
|
|
|
notification_setting = "notification_sound"
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_notification_settings(
|
|
|
|
self.user_profile,
|
|
|
|
notification_setting,
|
2020-07-12 23:41:53 +02:00
|
|
|
'ding'))
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications('events[0]', events[0], 'ding')
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2019-06-29 22:00:44 +02:00
|
|
|
def test_change_desktop_icon_count_display(self) -> None:
|
|
|
|
notification_setting = "desktop_icon_count_display"
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_notification_settings(
|
|
|
|
self.user_profile,
|
|
|
|
notification_setting,
|
|
|
|
2,
|
2020-07-12 23:41:53 +02:00
|
|
|
acting_user=self.user_profile))
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications('events[0]', events[0], 2)
|
2019-06-29 22:00:44 +02:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_notification_settings(
|
|
|
|
self.user_profile,
|
|
|
|
notification_setting,
|
|
|
|
1,
|
2020-07-12 23:41:53 +02:00
|
|
|
acting_user=self.user_profile))
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_global_notifications('events[0]', events[0], 1)
|
2019-06-29 22:00:44 +02:00
|
|
|
|
2019-06-11 12:43:08 +02:00
|
|
|
def test_realm_update_plan_type(self) -> None:
|
|
|
|
realm = self.user_profile.realm
|
2019-06-12 08:56:28 +02:00
|
|
|
|
2021-01-17 17:58:50 +01:00
|
|
|
state_data = fetch_initial_state_data(self.user_profile)
|
2019-06-12 08:56:28 +02:00
|
|
|
self.assertEqual(state_data['realm_plan_type'], Realm.SELF_HOSTED)
|
2020-05-08 13:30:34 +02:00
|
|
|
self.assertEqual(state_data['zulip_plan_is_not_limited'], True)
|
2019-06-12 08:56:28 +02:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_plan_type(realm, Realm.LIMITED))
|
2020-07-23 18:01:27 +02:00
|
|
|
check_realm_update('events[0]', events[0], 'plan_type')
|
2019-06-12 08:56:28 +02:00
|
|
|
|
2021-01-17 17:58:50 +01:00
|
|
|
state_data = fetch_initial_state_data(self.user_profile)
|
2019-06-12 08:56:28 +02:00
|
|
|
self.assertEqual(state_data['realm_plan_type'], Realm.LIMITED)
|
2020-05-08 13:30:34 +02:00
|
|
|
self.assertEqual(state_data['zulip_plan_is_not_limited'], False)
|
2019-06-11 12:43:08 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_realm_emoji_events(self) -> None:
|
2018-03-11 18:55:20 +01:00
|
|
|
author = self.example_user('iago')
|
|
|
|
with get_test_image_file('img.png') as img_file:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: check_add_realm_emoji(
|
|
|
|
self.user_profile.realm,
|
|
|
|
"my_emoji",
|
|
|
|
author,
|
|
|
|
img_file))
|
|
|
|
|
2020-08-18 15:16:02 +02:00
|
|
|
check_realm_emoji_update('events[0]', events[0])
|
2014-03-06 17:07:43 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_realm_emoji(self.user_profile.realm, "my_emoji"))
|
2020-08-18 15:16:02 +02:00
|
|
|
check_realm_emoji_update('events[0]', events[0])
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_realm_filter_events(self) -> None:
|
2020-06-24 21:36:27 +02:00
|
|
|
regex = "#(?P<id>[123])"
|
|
|
|
url = "https://realm.com/my_realm_filter/%(id)s"
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_add_realm_filter(self.user_profile.realm, regex, url))
|
2020-08-08 15:08:29 +02:00
|
|
|
check_realm_filters('events[0]', events[0])
|
2014-03-06 17:07:43 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_realm_filter(self.user_profile.realm, "#(?P<id>[123])"))
|
2020-08-08 15:08:29 +02:00
|
|
|
check_realm_filters('events[0]', events[0])
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_realm_domain_events(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_add_realm_domain(self.user_profile.realm, 'zulip.org', False))
|
2016-12-26 19:19:02 +01:00
|
|
|
|
2020-08-17 16:07:25 +02:00
|
|
|
check_realm_domains_add('events[0]', events[0])
|
|
|
|
self.assertEqual(events[0]["realm_domain"]["domain"], "zulip.org")
|
|
|
|
self.assertEqual(events[0]["realm_domain"]["allow_subdomains"], False)
|
|
|
|
|
2019-03-07 21:29:16 +01:00
|
|
|
test_domain = RealmDomain.objects.get(realm=self.user_profile.realm,
|
|
|
|
domain='zulip.org')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_change_realm_domain(test_domain, True))
|
2017-02-09 22:44:03 +01:00
|
|
|
|
2020-08-17 16:07:25 +02:00
|
|
|
check_realm_domains_change("events[0]", events[0])
|
|
|
|
self.assertEqual(events[0]["realm_domain"]["domain"], "zulip.org")
|
|
|
|
self.assertEqual(events[0]["realm_domain"]["allow_subdomains"], True)
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_remove_realm_domain(test_domain))
|
2020-08-17 16:07:25 +02:00
|
|
|
|
|
|
|
check_realm_domains_remove("events[0]", events[0])
|
|
|
|
self.assertEqual(events[0]["domain"], "zulip.org")
|
2016-12-26 19:19:02 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_create_bot(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
action = lambda: self.create_bot('test')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add('events[1]', events[1])
|
2018-01-30 17:10:10 +01:00
|
|
|
|
|
|
|
action = lambda: self.create_bot('test_outgoing_webhook',
|
bots: Prevent bots from having duplicate full names.
Bots are not allowed to use the same name as
other users in the realm (either bot or human).
This is kind of a big commit, but I wanted to
combine the post/patch (aka add/edit) checks
into one commit, since it's a change in policy
that affects both codepaths.
A lot of the noise is in tests. We had good
coverage on the previous code, including some places
like event testing where we were expediently
not bothering to use different names for
different bots in some longer tests. And then
of course I test some new scenarios that are relevant
with the new policy.
There are two new functions:
check_bot_name_available:
very simple Django query
check_change_bot_full_name:
this diverges from the 3-line
check_change_full_name, where the latter
is still used for the "humans" use case
And then we just call those in appropriate places.
Note that there is still a loophole here
where you can get two bots with the same
name if you reactivate a bot named Fred
that was inactive when the second bot named
Fred was created. Also, we don't attempt
to fix historical data. So this commit
shouldn't be considered any kind of lockdown,
it's just meant to help people from
inadvertently creating two bots of the same
name where they don't intend to. For more
context, we are continuing to allow two
human users in the same realm to have the
same full name, and our code should generally
be tolerant of that possibility. (A good
example is our new mention syntax, which disambiguates
same-named people using ids.)
It's also worth noting that our web app client
doesn't try to scrub full_name from its payload in
situations where the user has actually only modified other
fields in the "Edit bot" UI. Starting here
we just handle this on the server, since it's
easy to fix there, and even if we fixed it in the web
app, there's no guarantee that other clients won't be
just as brute force. It wasn't exactly broken before,
but we'd needlessly write rows to audit tables.
Fixes #10509
2018-09-27 19:25:18 +02:00
|
|
|
full_name='Outgoing Webhook Bot',
|
2020-08-07 01:09:47 +02:00
|
|
|
payload_url=orjson.dumps('https://foo.bar.com').decode(),
|
2018-01-30 17:10:10 +01:00
|
|
|
interface_type=Service.GENERIC,
|
|
|
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2018-01-30 17:10:10 +01:00
|
|
|
# The third event is the second call of notify_created_bot, which contains additional
|
|
|
|
# data for services (in contrast to the first call).
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add('events[1]', events[1])
|
2014-02-26 00:12:14 +01:00
|
|
|
|
2018-01-30 19:21:13 +01:00
|
|
|
action = lambda: self.create_bot('test_embedded',
|
bots: Prevent bots from having duplicate full names.
Bots are not allowed to use the same name as
other users in the realm (either bot or human).
This is kind of a big commit, but I wanted to
combine the post/patch (aka add/edit) checks
into one commit, since it's a change in policy
that affects both codepaths.
A lot of the noise is in tests. We had good
coverage on the previous code, including some places
like event testing where we were expediently
not bothering to use different names for
different bots in some longer tests. And then
of course I test some new scenarios that are relevant
with the new policy.
There are two new functions:
check_bot_name_available:
very simple Django query
check_change_bot_full_name:
this diverges from the 3-line
check_change_full_name, where the latter
is still used for the "humans" use case
And then we just call those in appropriate places.
Note that there is still a loophole here
where you can get two bots with the same
name if you reactivate a bot named Fred
that was inactive when the second bot named
Fred was created. Also, we don't attempt
to fix historical data. So this commit
shouldn't be considered any kind of lockdown,
it's just meant to help people from
inadvertently creating two bots of the same
name where they don't intend to. For more
context, we are continuing to allow two
human users in the same realm to have the
same full name, and our code should generally
be tolerant of that possibility. (A good
example is our new mention syntax, which disambiguates
same-named people using ids.)
It's also worth noting that our web app client
doesn't try to scrub full_name from its payload in
situations where the user has actually only modified other
fields in the "Edit bot" UI. Starting here
we just handle this on the server, since it's
easy to fix there, and even if we fixed it in the web
app, there's no guarantee that other clients won't be
just as brute force. It wasn't exactly broken before,
but we'd needlessly write rows to audit tables.
Fixes #10509
2018-09-27 19:25:18 +02:00
|
|
|
full_name='Embedded Bot',
|
2018-01-30 19:21:13 +01:00
|
|
|
service_name='helloworld',
|
2020-08-07 01:09:47 +02:00
|
|
|
config_data=orjson.dumps({'foo': 'bar'}).decode(),
|
2018-01-30 19:21:13 +01:00
|
|
|
bot_type=UserProfile.EMBEDDED_BOT)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add('events[1]', events[1])
|
2018-01-30 19:21:13 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_full_name(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-04-07 07:28:28 +02:00
|
|
|
action = lambda: do_change_full_name(bot, 'New Bot Name', self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[1]', events[1], 'full_name')
|
2014-02-26 19:55:29 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_regenerate_bot_api_key(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-04-06 12:27:58 +02:00
|
|
|
action = lambda: do_regenerate_api_key(bot, self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'api_key')
|
2014-02-26 20:17:19 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_avatar_source(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2020-06-29 12:47:44 +02:00
|
|
|
action = lambda: do_change_avatar_fields(bot, bot.AVATAR_FROM_USER, acting_user=self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'avatar_url')
|
2017-03-26 08:17:48 +02:00
|
|
|
self.assertEqual(events[1]['type'], 'realm_user')
|
2014-02-26 21:05:10 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_realm_icon_source(self) -> None:
|
2019-03-07 21:29:16 +01:00
|
|
|
action = lambda: do_change_icon_source(self.user_profile.realm, Realm.ICON_UPLOADED)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, state_change_expected=True)
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict('events[0]', events[0])
|
2017-02-21 03:41:20 +01:00
|
|
|
|
2019-03-01 15:52:44 +01:00
|
|
|
def test_change_realm_day_mode_logo_source(self) -> None:
|
2020-06-29 12:35:58 +02:00
|
|
|
action = lambda: do_change_logo_source(self.user_profile.realm, Realm.LOGO_UPLOADED, False, acting_user=self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, state_change_expected=True)
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict('events[0]', events[0])
|
2019-03-01 15:52:44 +01:00
|
|
|
|
|
|
|
def test_change_realm_night_mode_logo_source(self) -> None:
|
2020-06-29 12:35:58 +02:00
|
|
|
action = lambda: do_change_logo_source(self.user_profile.realm, Realm.LOGO_UPLOADED, True, acting_user=self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, state_change_expected=True)
|
2020-08-16 14:52:09 +02:00
|
|
|
check_realm_update_dict('events[0]', events[0])
|
2019-03-01 15:52:44 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_default_all_public_streams(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-02-21 19:35:17 +01:00
|
|
|
action = lambda: do_change_default_all_public_streams(bot, True)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'default_all_public_streams')
|
2014-02-26 21:15:31 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_default_sending_stream(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-02-21 19:35:17 +01:00
|
|
|
stream = get_stream("Rome", bot.realm)
|
2017-03-24 03:04:13 +01:00
|
|
|
|
2017-02-21 19:35:17 +01:00
|
|
|
action = lambda: do_change_default_sending_stream(bot, stream)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'default_sending_stream')
|
2014-02-26 21:23:18 +01:00
|
|
|
|
2017-03-24 03:04:13 +01:00
|
|
|
action = lambda: do_change_default_sending_stream(bot, None)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'default_sending_stream')
|
2017-03-24 03:04:13 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_default_events_register_stream(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-02-21 19:35:17 +01:00
|
|
|
stream = get_stream("Rome", bot.realm)
|
2017-03-24 03:04:13 +01:00
|
|
|
|
2017-02-21 19:35:17 +01:00
|
|
|
action = lambda: do_change_default_events_register_stream(bot, stream)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'default_events_register_stream')
|
2017-02-24 06:36:54 +01:00
|
|
|
|
2017-03-24 03:04:13 +01:00
|
|
|
action = lambda: do_change_default_events_register_stream(bot, None)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'default_events_register_stream')
|
2017-03-24 03:04:13 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_change_bot_owner(self) -> None:
|
2017-05-07 17:21:26 +02:00
|
|
|
self.user_profile = self.example_user('iago')
|
|
|
|
owner = self.example_user('hamlet')
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-03-31 17:27:08 +02:00
|
|
|
action = lambda: do_change_bot_owner(bot, owner, self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'owner_id')
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[1]', events[1], "bot_owner_id")
|
2014-02-26 21:34:12 +01:00
|
|
|
|
2018-03-06 22:32:03 +01:00
|
|
|
self.user_profile = self.example_user('aaron')
|
|
|
|
owner = self.example_user('hamlet')
|
bots: Prevent bots from having duplicate full names.
Bots are not allowed to use the same name as
other users in the realm (either bot or human).
This is kind of a big commit, but I wanted to
combine the post/patch (aka add/edit) checks
into one commit, since it's a change in policy
that affects both codepaths.
A lot of the noise is in tests. We had good
coverage on the previous code, including some places
like event testing where we were expediently
not bothering to use different names for
different bots in some longer tests. And then
of course I test some new scenarios that are relevant
with the new policy.
There are two new functions:
check_bot_name_available:
very simple Django query
check_change_bot_full_name:
this diverges from the 3-line
check_change_full_name, where the latter
is still used for the "humans" use case
And then we just call those in appropriate places.
Note that there is still a loophole here
where you can get two bots with the same
name if you reactivate a bot named Fred
that was inactive when the second bot named
Fred was created. Also, we don't attempt
to fix historical data. So this commit
shouldn't be considered any kind of lockdown,
it's just meant to help people from
inadvertently creating two bots of the same
name where they don't intend to. For more
context, we are continuing to allow two
human users in the same realm to have the
same full name, and our code should generally
be tolerant of that possibility. (A good
example is our new mention syntax, which disambiguates
same-named people using ids.)
It's also worth noting that our web app client
doesn't try to scrub full_name from its payload in
situations where the user has actually only modified other
fields in the "Edit bot" UI. Starting here
we just handle this on the server, since it's
easy to fix there, and even if we fixed it in the web
app, there's no guarantee that other clients won't be
just as brute force. It wasn't exactly broken before,
but we'd needlessly write rows to audit tables.
Fixes #10509
2018-09-27 19:25:18 +02:00
|
|
|
bot = self.create_bot('test1', full_name='Test1 Testerson')
|
2018-03-06 22:32:03 +01:00
|
|
|
action = lambda: do_change_bot_owner(bot, owner, self.user_profile)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 21:06:22 +02:00
|
|
|
check_realm_bot_delete('events[0]', events[0])
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[1]', events[1], "bot_owner_id")
|
2018-03-06 22:32:03 +01:00
|
|
|
|
|
|
|
previous_owner = self.example_user('aaron')
|
|
|
|
self.user_profile = self.example_user('hamlet')
|
bots: Prevent bots from having duplicate full names.
Bots are not allowed to use the same name as
other users in the realm (either bot or human).
This is kind of a big commit, but I wanted to
combine the post/patch (aka add/edit) checks
into one commit, since it's a change in policy
that affects both codepaths.
A lot of the noise is in tests. We had good
coverage on the previous code, including some places
like event testing where we were expediently
not bothering to use different names for
different bots in some longer tests. And then
of course I test some new scenarios that are relevant
with the new policy.
There are two new functions:
check_bot_name_available:
very simple Django query
check_change_bot_full_name:
this diverges from the 3-line
check_change_full_name, where the latter
is still used for the "humans" use case
And then we just call those in appropriate places.
Note that there is still a loophole here
where you can get two bots with the same
name if you reactivate a bot named Fred
that was inactive when the second bot named
Fred was created. Also, we don't attempt
to fix historical data. So this commit
shouldn't be considered any kind of lockdown,
it's just meant to help people from
inadvertently creating two bots of the same
name where they don't intend to. For more
context, we are continuing to allow two
human users in the same realm to have the
same full name, and our code should generally
be tolerant of that possibility. (A good
example is our new mention syntax, which disambiguates
same-named people using ids.)
It's also worth noting that our web app client
doesn't try to scrub full_name from its payload in
situations where the user has actually only modified other
fields in the "Edit bot" UI. Starting here
we just handle this on the server, since it's
easy to fix there, and even if we fixed it in the web
app, there's no guarantee that other clients won't be
just as brute force. It wasn't exactly broken before,
but we'd needlessly write rows to audit tables.
Fixes #10509
2018-09-27 19:25:18 +02:00
|
|
|
bot = self.create_test_bot('test2', previous_owner, full_name='Test2 Testerson')
|
2018-03-06 22:32:03 +01:00
|
|
|
action = lambda: do_change_bot_owner(bot, self.user_profile, previous_owner)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add('events[0]', events[0])
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[1]', events[1], "bot_owner_id")
|
2018-03-06 22:32:03 +01: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_do_update_outgoing_webhook_service(self) -> None:
|
2018-01-16 20:34:12 +01:00
|
|
|
self.user_profile = self.example_user('iago')
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_test_bot('test', self.user_profile,
|
|
|
|
full_name='Test Bot',
|
|
|
|
bot_type=UserProfile.OUTGOING_WEBHOOK_BOT,
|
2020-08-07 01:09:47 +02:00
|
|
|
payload_url=orjson.dumps('http://hostname.domain2.com').decode(),
|
2018-01-30 17:08:35 +01:00
|
|
|
interface_type=Service.GENERIC,
|
|
|
|
)
|
2018-01-16 20:34:12 +01:00
|
|
|
action = lambda: do_update_outgoing_webhook_service(bot, 2, 'http://hostname.domain2.com')
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action)
|
2020-07-08 17:47:56 +02:00
|
|
|
check_realm_bot_update('events[0]', events[0], 'services')
|
2018-01-16 20:34:12 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_do_deactivate_user(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2014-02-26 22:27:51 +01:00
|
|
|
action = lambda: do_deactivate_user(bot)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-08-18 18:38:41 +02:00
|
|
|
check_realm_user_remove("events[0]", events[0])
|
2020-07-08 21:06:22 +02:00
|
|
|
check_realm_bot_remove('events[1]', events[1])
|
2014-02-26 22:27:51 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_do_reactivate_user(self) -> None:
|
2018-01-30 17:08:35 +01:00
|
|
|
bot = self.create_bot('test')
|
2017-02-15 21:06:07 +01:00
|
|
|
do_deactivate_user(bot)
|
|
|
|
action = lambda: do_reactivate_user(bot)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(action, num_events=2)
|
2020-07-08 17:07:29 +02:00
|
|
|
check_realm_bot_add('events[1]', events[1])
|
2017-01-24 01:48:35 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_do_mark_hotspot_as_read(self) -> None:
|
2017-10-12 17:13:02 +02:00
|
|
|
self.user_profile.tutorial_status = UserProfile.TUTORIAL_WAITING
|
|
|
|
self.user_profile.save(update_fields=['tutorial_status'])
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_mark_hotspot_as_read(self.user_profile, 'intro_reply'))
|
2020-08-05 19:56:34 +02:00
|
|
|
check_hotspots('events[0]', events[0])
|
2017-02-15 21:06:07 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_rename_stream(self) -> None:
|
2020-12-01 14:13:09 +01:00
|
|
|
for i, include_streams in enumerate([True, False]):
|
|
|
|
old_name = f'old name{i}'
|
|
|
|
new_name = f'new name{i}'
|
|
|
|
|
|
|
|
stream = self.make_stream(old_name)
|
|
|
|
self.subscribe(self.user_profile, stream.name)
|
|
|
|
action = lambda: do_rename_stream(stream, new_name, self.user_profile)
|
|
|
|
events = self.verify_action(action, num_events=3, include_streams=include_streams)
|
|
|
|
|
|
|
|
check_stream_update('events[0]', events[0])
|
|
|
|
self.assertEqual(events[0]['name'], old_name)
|
|
|
|
|
|
|
|
check_stream_update('events[1]', events[1])
|
|
|
|
self.assertEqual(events[1]['name'], old_name)
|
|
|
|
|
|
|
|
check_message('events[2]', events[2])
|
|
|
|
|
|
|
|
fields = dict(
|
|
|
|
sender_email='notification-bot@zulip.com',
|
|
|
|
display_recipient=new_name,
|
|
|
|
sender_full_name='Notification Bot',
|
|
|
|
is_me_message=False,
|
|
|
|
type='stream',
|
|
|
|
client='Internal',
|
|
|
|
)
|
2020-07-10 16:10:58 +02:00
|
|
|
|
2020-12-01 14:13:09 +01:00
|
|
|
fields[TOPIC_NAME] = 'stream events'
|
2020-07-10 16:10:58 +02:00
|
|
|
|
2020-12-01 14:13:09 +01:00
|
|
|
msg = events[2]['message']
|
|
|
|
for k, v in fields.items():
|
|
|
|
self.assertEqual(msg[k], v)
|
2014-01-31 23:23:39 +01:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_deactivate_stream_neversubscribed(self) -> None:
|
2020-12-01 14:13:09 +01:00
|
|
|
for i, include_streams in enumerate([True, False]):
|
|
|
|
stream = self.make_stream(f"stream{i}")
|
|
|
|
action = lambda: do_deactivate_stream(stream)
|
|
|
|
events = self.verify_action(action, include_streams=include_streams)
|
|
|
|
check_stream_delete('events[0]', events[0])
|
2016-07-12 23:57:16 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_subscribe_other_user_never_subscribed(self) -> None:
|
2020-12-01 14:13:09 +01:00
|
|
|
for i, include_streams in enumerate([True, False]):
|
|
|
|
action = lambda: self.subscribe(self.example_user("othello"), f"test_stream{i}")
|
|
|
|
events = self.verify_action(action, num_events=2, include_streams=True)
|
|
|
|
check_subscription_peer_add('events[1]', events[1])
|
2016-07-12 23:57:16 +02:00
|
|
|
|
2020-08-31 18:33:09 +02:00
|
|
|
def test_remove_other_user_never_subscribed(self) -> None:
|
|
|
|
self.subscribe(self.example_user("othello"), "test_stream")
|
|
|
|
stream = get_stream("test_stream", self.user_profile.realm)
|
|
|
|
|
|
|
|
action = lambda: bulk_remove_subscriptions(
|
|
|
|
[self.example_user('othello')],
|
|
|
|
[stream],
|
|
|
|
get_client("website"))
|
|
|
|
events = self.verify_action(action)
|
|
|
|
check_subscription_peer_remove('events[0]', events[0])
|
|
|
|
|
2018-04-02 00:21:21 +02:00
|
|
|
def test_do_delete_message_stream(self) -> None:
|
2020-06-11 12:12:12 +02:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id = self.send_stream_message(hamlet, "Verona")
|
|
|
|
msg_id_2 = self.send_stream_message(hamlet, "Verona")
|
|
|
|
messages = [
|
|
|
|
Message.objects.get(id=msg_id),
|
|
|
|
Message.objects.get(id=msg_id_2)
|
|
|
|
]
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-11 12:12:12 +02:00
|
|
|
lambda: do_delete_messages(self.user_profile.realm, messages),
|
|
|
|
state_change_expected=True,
|
|
|
|
)
|
2020-08-14 15:12:27 +02:00
|
|
|
check_delete_message(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
message_type="stream",
|
|
|
|
num_message_ids=2,
|
|
|
|
is_legacy=False,
|
|
|
|
)
|
2020-06-11 12:12:12 +02:00
|
|
|
|
|
|
|
def test_do_delete_message_stream_legacy(self) -> None:
|
|
|
|
"""
|
|
|
|
Test for legacy method of deleting messages which
|
|
|
|
sends an event per message to delete to the client.
|
|
|
|
"""
|
2020-03-07 11:43:05 +01:00
|
|
|
hamlet = self.example_user('hamlet')
|
|
|
|
msg_id = self.send_stream_message(hamlet, "Verona")
|
2020-06-11 12:12:12 +02:00
|
|
|
msg_id_2 = self.send_stream_message(hamlet, "Verona")
|
|
|
|
messages = [
|
|
|
|
Message.objects.get(id=msg_id),
|
|
|
|
Message.objects.get(id=msg_id_2)
|
|
|
|
]
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-11 12:12:12 +02:00
|
|
|
lambda: do_delete_messages(self.user_profile.realm, messages),
|
|
|
|
state_change_expected=True, bulk_message_deletion=False,
|
|
|
|
num_events=2
|
|
|
|
)
|
2020-08-14 15:12:27 +02:00
|
|
|
check_delete_message(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
message_type="stream",
|
|
|
|
num_message_ids=1,
|
|
|
|
is_legacy=True,
|
|
|
|
)
|
2020-06-11 12:12:12 +02:00
|
|
|
|
|
|
|
def test_do_delete_message_personal(self) -> None:
|
|
|
|
msg_id = self.send_personal_message(
|
|
|
|
self.example_user("cordelia"),
|
|
|
|
self.user_profile,
|
|
|
|
"hello",
|
|
|
|
)
|
2017-05-14 21:14:26 +02:00
|
|
|
message = Message.objects.get(id=msg_id)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-11-12 21:20:31 +01:00
|
|
|
lambda: do_delete_messages(self.user_profile.realm, [message]),
|
2017-05-14 21:14:26 +02:00
|
|
|
state_change_expected=True,
|
|
|
|
)
|
2020-08-14 15:12:27 +02:00
|
|
|
check_delete_message(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
message_type="private",
|
|
|
|
num_message_ids=1,
|
|
|
|
is_legacy=False,
|
|
|
|
)
|
2017-05-14 21:14:26 +02:00
|
|
|
|
2020-06-11 12:12:12 +02:00
|
|
|
def test_do_delete_message_personal_legacy(self) -> None:
|
2018-04-02 00:21:21 +02:00
|
|
|
msg_id = self.send_personal_message(
|
2020-03-07 11:43:05 +01:00
|
|
|
self.example_user("cordelia"),
|
|
|
|
self.user_profile,
|
2018-04-02 00:21:21 +02:00
|
|
|
"hello",
|
|
|
|
)
|
|
|
|
message = Message.objects.get(id=msg_id)
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-11-12 21:20:31 +01:00
|
|
|
lambda: do_delete_messages(self.user_profile.realm, [message]),
|
2020-06-11 12:12:12 +02:00
|
|
|
state_change_expected=True, bulk_message_deletion=False
|
2018-04-02 00:21:21 +02:00
|
|
|
)
|
2020-08-14 15:12:27 +02:00
|
|
|
check_delete_message(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
message_type="private",
|
|
|
|
num_message_ids=1,
|
|
|
|
is_legacy=True,
|
|
|
|
)
|
2018-04-02 00:21:21 +02:00
|
|
|
|
2017-11-05 10:51:25 +01:00
|
|
|
def test_do_delete_message_no_max_id(self) -> None:
|
2017-05-14 21:14:26 +02:00
|
|
|
user_profile = self.example_user('aaron')
|
|
|
|
# Delete all historical messages for this user
|
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
UserMessage.objects.filter(user_profile=user_profile).delete()
|
2020-03-07 11:43:05 +01:00
|
|
|
msg_id = self.send_stream_message(user_profile, "Verona")
|
2017-05-14 21:14:26 +02:00
|
|
|
message = Message.objects.get(id=msg_id)
|
2020-06-27 17:32:39 +02:00
|
|
|
self.verify_action(
|
2019-11-12 21:20:31 +01:00
|
|
|
lambda: do_delete_messages(self.user_profile.realm, [message]),
|
2017-05-14 21:14:26 +02:00
|
|
|
state_change_expected=True,
|
|
|
|
)
|
2021-01-17 17:58:50 +01:00
|
|
|
result = fetch_initial_state_data(user_profile)
|
2017-05-14 21:14:26 +02:00
|
|
|
self.assertEqual(result['max_message_id'], -1)
|
|
|
|
|
2018-05-04 22:57:36 +02:00
|
|
|
def test_add_attachment(self) -> None:
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login('hamlet')
|
2018-05-04 22:57:36 +02:00
|
|
|
fp = StringIO("zulip!")
|
|
|
|
fp.name = "zulip.txt"
|
2020-07-05 03:34:30 +02:00
|
|
|
uri = None
|
2018-05-04 22:57:36 +02:00
|
|
|
|
|
|
|
def do_upload() -> None:
|
2020-07-05 03:34:30 +02:00
|
|
|
nonlocal uri
|
2018-05-04 22:57:36 +02:00
|
|
|
result = self.client_post("/json/user_uploads", {'file': fp})
|
|
|
|
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.assertIn("uri", result.json())
|
|
|
|
uri = result.json()["uri"]
|
|
|
|
base = '/user_uploads/'
|
|
|
|
self.assertEqual(base, uri[:len(base)])
|
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2018-05-04 22:57:36 +02:00
|
|
|
lambda: do_upload(),
|
|
|
|
num_events=1, state_change_expected=False)
|
2020-08-06 13:08:42 +02:00
|
|
|
|
|
|
|
check_attachment_add("events[0]", events[0])
|
|
|
|
self.assertEqual(events[0]["upload_space_used"], 6)
|
2018-05-04 22:57:36 +02:00
|
|
|
|
|
|
|
# Verify that the DB has the attachment marked as unclaimed
|
|
|
|
entry = Attachment.objects.get(file_name='zulip.txt')
|
|
|
|
self.assertEqual(entry.is_claimed(), False)
|
|
|
|
|
2019-12-13 03:56:59 +01:00
|
|
|
hamlet = self.example_user("hamlet")
|
|
|
|
self.subscribe(hamlet, "Denmark")
|
2020-07-05 03:34:30 +02:00
|
|
|
assert uri is not None
|
|
|
|
body = f"First message ...[zulip.txt](http://{hamlet.realm.host}" + uri + ")"
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-03-07 11:43:05 +01:00
|
|
|
lambda: self.send_stream_message(self.example_user("hamlet"), "Denmark", body, "test"),
|
2018-05-04 22:57:36 +02:00
|
|
|
num_events=2)
|
|
|
|
|
2020-08-06 13:08:42 +02:00
|
|
|
check_attachment_update("events[0]", events[0])
|
|
|
|
self.assertEqual(events[0]["upload_space_used"], 6)
|
2018-05-04 22:57:36 +02:00
|
|
|
|
2020-08-06 13:08:42 +02:00
|
|
|
# Now remove the attachment
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-10 06:41:04 +02:00
|
|
|
lambda: self.client_delete(f"/json/attachments/{entry.id}"),
|
2018-05-04 22:57:36 +02:00
|
|
|
num_events=1, state_change_expected=False)
|
2020-08-06 13:08:42 +02:00
|
|
|
|
|
|
|
check_attachment_remove("events[0]", events[0])
|
|
|
|
self.assertEqual(events[0]["upload_space_used"], 0)
|
2018-05-04 22:57:36 +02:00
|
|
|
|
2019-08-02 00:14:58 +02:00
|
|
|
def test_notify_realm_export(self) -> None:
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2020-03-06 18:40:46 +01:00
|
|
|
self.login_user(self.user_profile)
|
2019-08-07 21:49:54 +02:00
|
|
|
|
|
|
|
with mock.patch('zerver.lib.export.do_export_realm',
|
|
|
|
return_value=create_dummy_file('test-export.tar.gz')):
|
2020-07-26 01:58:11 +02:00
|
|
|
with stdout_suppressed(), self.assertLogs(level='INFO') as info_logs:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2019-09-13 08:40:35 +02:00
|
|
|
lambda: self.client_post('/json/export/realm'),
|
2020-04-16 23:00:24 +02:00
|
|
|
state_change_expected=True, num_events=3)
|
2020-07-26 01:58:11 +02:00
|
|
|
self.assertTrue(
|
|
|
|
'INFO:root:Completed data export for zulip in' in info_logs.output[0]
|
|
|
|
)
|
2019-08-07 21:49:54 +02:00
|
|
|
|
2020-08-05 23:54:26 +02:00
|
|
|
# We get two realm_export events for this action, where the first
|
|
|
|
# is missing the export_url (because it's pending).
|
|
|
|
check_realm_export(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_export_url=False,
|
|
|
|
has_deleted_timestamp=False,
|
|
|
|
has_failed_timestamp=False,
|
|
|
|
)
|
2020-04-16 23:00:24 +02:00
|
|
|
|
2020-08-05 23:54:26 +02:00
|
|
|
check_realm_export(
|
|
|
|
"events[2]",
|
|
|
|
events[2],
|
|
|
|
has_export_url=True,
|
|
|
|
has_deleted_timestamp=False,
|
|
|
|
has_failed_timestamp=False,
|
|
|
|
)
|
2019-03-27 00:57:33 +01:00
|
|
|
|
2019-08-01 19:59:36 +02:00
|
|
|
# Now we check the deletion of the export.
|
|
|
|
audit_log_entry = RealmAuditLog.objects.filter(
|
2019-09-26 03:20:56 +02:00
|
|
|
event_type=RealmAuditLog.REALM_EXPORTED).first()
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-06-09 00:25:09 +02:00
|
|
|
lambda: self.client_delete(f'/json/export/realm/{audit_log_entry.id}'),
|
2019-08-01 19:59:36 +02:00
|
|
|
state_change_expected=False, num_events=1)
|
|
|
|
|
2020-08-05 23:54:26 +02:00
|
|
|
check_realm_export(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_export_url=False,
|
|
|
|
has_deleted_timestamp=True,
|
|
|
|
has_failed_timestamp=False,
|
|
|
|
)
|
2020-04-16 23:00:24 +02:00
|
|
|
|
2020-08-05 23:54:26 +02:00
|
|
|
def test_notify_realm_export_on_failure(self) -> None:
|
2020-05-21 00:13:06 +02:00
|
|
|
do_change_user_role(self.user_profile, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
2020-04-16 23:00:24 +02:00
|
|
|
self.login_user(self.user_profile)
|
|
|
|
|
|
|
|
with mock.patch('zerver.lib.export.do_export_realm',
|
2020-07-19 16:09:17 +02:00
|
|
|
side_effect=Exception("test")), \
|
|
|
|
self.assertLogs(level="ERROR") as error_log:
|
2020-04-16 23:00:24 +02:00
|
|
|
with stdout_suppressed():
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
2020-04-16 23:00:24 +02:00
|
|
|
lambda: self.client_post('/json/export/realm'),
|
|
|
|
state_change_expected=False, num_events=2)
|
|
|
|
|
2020-07-19 16:09:17 +02:00
|
|
|
# Log is of following format: "ERROR:root:Data export for zulip failed after 0.004499673843383789"
|
|
|
|
# Where last floating number is time and will vary in each test hence the following assertion is
|
|
|
|
# independent of time bit by not matching exact log but only part of it.
|
|
|
|
self.assertTrue("ERROR:root:Data export for zulip failed after" in error_log.output[0])
|
|
|
|
|
2020-08-05 23:54:26 +02:00
|
|
|
# We get two events for the export.
|
|
|
|
check_realm_export(
|
|
|
|
"events[0]",
|
|
|
|
events[0],
|
|
|
|
has_export_url=False,
|
|
|
|
has_deleted_timestamp=False,
|
|
|
|
has_failed_timestamp=False,
|
|
|
|
)
|
|
|
|
check_realm_export(
|
|
|
|
"events[1]",
|
|
|
|
events[1],
|
|
|
|
has_export_url=False,
|
|
|
|
has_deleted_timestamp=False,
|
|
|
|
has_failed_timestamp=True,
|
|
|
|
)
|
2020-04-16 23:00:24 +02:00
|
|
|
|
2019-11-16 09:26:28 +01:00
|
|
|
def test_has_zoom_token(self) -> None:
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
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
|
|
|
lambda: do_set_zoom_token(self.user_profile, {'access_token': 'token'}),
|
2019-11-16 09:26:28 +01:00
|
|
|
)
|
2020-08-16 17:26:24 +02:00
|
|
|
check_has_zoom_token('events[0]', events[0], value=True)
|
2019-11-16 09:26:28 +01:00
|
|
|
|
2020-06-27 17:32:39 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_set_zoom_token(self.user_profile, None))
|
2020-08-16 17:26:24 +02:00
|
|
|
check_has_zoom_token('events[0]', events[0], value=False)
|
2020-06-27 18:06:51 +02:00
|
|
|
|
|
|
|
class RealmPropertyActionTest(BaseAction):
|
|
|
|
def do_set_realm_property_test(self, name: str) -> None:
|
|
|
|
bool_tests: List[bool] = [True, False, True]
|
|
|
|
test_values: Dict[str, Any] = dict(
|
|
|
|
default_language=['es', 'de', 'en'],
|
|
|
|
description=['Realm description', 'New description'],
|
|
|
|
digest_weekday=[0, 1, 2],
|
|
|
|
message_retention_days=[10, 20],
|
|
|
|
name=['Zulip', 'New Name'],
|
|
|
|
waiting_period_threshold=[10, 20],
|
|
|
|
create_stream_policy=[3, 2, 1],
|
|
|
|
invite_to_stream_policy=[3, 2, 1],
|
|
|
|
private_message_policy=[2, 1],
|
|
|
|
user_group_edit_policy=[1, 2],
|
2020-10-02 13:07:40 +02:00
|
|
|
wildcard_mention_policy=[6, 5, 4, 3, 2, 1],
|
2020-06-27 18:06:51 +02:00
|
|
|
email_address_visibility=[Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS],
|
|
|
|
bot_creation_policy=[Realm.BOT_CREATION_EVERYONE],
|
|
|
|
video_chat_provider=[
|
|
|
|
Realm.VIDEO_CHAT_PROVIDERS['jitsi_meet']['id'],
|
|
|
|
],
|
|
|
|
default_code_block_language=['python', 'javascript'],
|
2020-07-12 21:26:45 +02:00
|
|
|
message_content_delete_limit_seconds=[1000, 1100, 1200]
|
2020-06-27 18:06:51 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
vals = test_values.get(name)
|
|
|
|
property_type = Realm.property_types[name]
|
|
|
|
if property_type is bool:
|
|
|
|
vals = bool_tests
|
|
|
|
|
|
|
|
if vals is None:
|
|
|
|
raise AssertionError(f'No test created for {name}')
|
2020-06-29 15:34:19 +02:00
|
|
|
now = timezone_now()
|
|
|
|
do_set_realm_property(self.user_profile.realm, name, vals[0], acting_user=self.user_profile)
|
|
|
|
self.assertEqual(RealmAuditLog.objects.filter(realm=self.user_profile.realm, event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
|
|
|
event_time__gte=now, acting_user=self.user_profile).count(), 1)
|
|
|
|
for count, val in enumerate(vals[1:]):
|
|
|
|
now = timezone_now()
|
2020-06-27 18:06:51 +02:00
|
|
|
state_change_expected = True
|
|
|
|
events = self.verify_action(
|
2020-06-29 15:34:19 +02:00
|
|
|
lambda: do_set_realm_property(self.user_profile.realm, name, val, acting_user=self.user_profile),
|
2020-06-27 18:06:51 +02:00
|
|
|
state_change_expected=state_change_expected)
|
2020-06-29 15:34:19 +02:00
|
|
|
|
|
|
|
old_value = vals[count]
|
|
|
|
self.assertEqual(RealmAuditLog.objects.filter(
|
|
|
|
realm=self.user_profile.realm, event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
|
|
|
event_time__gte=now, acting_user=self.user_profile,
|
2020-08-07 01:09:47 +02:00
|
|
|
extra_data=orjson.dumps({
|
2020-08-07 21:39:24 +02:00
|
|
|
RealmAuditLog.OLD_VALUE: old_value,
|
|
|
|
RealmAuditLog.NEW_VALUE: val,
|
|
|
|
'property': name,
|
2020-08-07 01:09:47 +02:00
|
|
|
}).decode()).count(), 1)
|
2020-07-23 17:38:53 +02:00
|
|
|
check_realm_update('events[0]', events[0], name)
|
2020-06-27 18:06:51 +02:00
|
|
|
|
|
|
|
def test_change_realm_property(self) -> None:
|
|
|
|
for prop in Realm.property_types:
|
|
|
|
with self.settings(SEND_DIGEST_EMAILS=True):
|
|
|
|
self.do_set_realm_property_test(prop)
|
2020-06-27 19:04:32 +02:00
|
|
|
|
|
|
|
class UserDisplayActionTest(BaseAction):
|
|
|
|
def do_set_user_display_settings_test(self, setting_name: str) -> None:
|
|
|
|
"""Test updating each setting in UserProfile.property_types dict."""
|
|
|
|
|
|
|
|
test_changes: Dict[str, Any] = dict(
|
|
|
|
emojiset = ['twitter'],
|
|
|
|
default_language = ['es', 'de', 'en'],
|
2020-10-27 01:41:00 +01:00
|
|
|
timezone = ['America/Denver', 'Pacific/Pago_Pago', 'Pacific/Galapagos', ''],
|
2020-06-27 19:04:32 +02:00
|
|
|
demote_inactive_streams = [2, 3, 1],
|
|
|
|
color_scheme = [2, 3, 1]
|
|
|
|
)
|
|
|
|
|
|
|
|
num_events = 1
|
|
|
|
if setting_name == "timezone":
|
|
|
|
num_events = 2
|
|
|
|
values = test_changes.get(setting_name)
|
2020-07-08 15:29:13 +02:00
|
|
|
|
|
|
|
property_type = UserProfile.property_types[setting_name]
|
2020-06-27 19:04:32 +02:00
|
|
|
if property_type is bool:
|
|
|
|
if getattr(self.user_profile, setting_name) is False:
|
|
|
|
values = [True, False, True]
|
|
|
|
else:
|
|
|
|
values = [False, True, False]
|
2020-07-08 15:29:13 +02:00
|
|
|
|
2020-06-27 19:04:32 +02:00
|
|
|
if values is None:
|
|
|
|
raise AssertionError(f'No test created for {setting_name}')
|
|
|
|
|
|
|
|
for value in values:
|
|
|
|
events = self.verify_action(
|
|
|
|
lambda: do_set_user_display_setting(
|
|
|
|
self.user_profile,
|
|
|
|
setting_name,
|
|
|
|
value),
|
|
|
|
num_events=num_events)
|
|
|
|
|
2020-07-08 15:29:13 +02:00
|
|
|
check_update_display_settings('events[0]', events[0])
|
2020-06-27 19:04:32 +02:00
|
|
|
|
|
|
|
if setting_name == "timezone":
|
2020-08-05 12:31:09 +02:00
|
|
|
check_realm_user_update('events[1]', events[1], "timezone")
|
2020-06-27 19:04:32 +02:00
|
|
|
|
|
|
|
def test_set_user_display_settings(self) -> None:
|
|
|
|
for prop in UserProfile.property_types:
|
|
|
|
self.do_set_user_display_settings_test(prop)
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
class SubscribeActionTest(BaseAction):
|
|
|
|
def test_subscribe_events(self) -> None:
|
|
|
|
self.do_test_subscribe_events(include_subscribers=True)
|
|
|
|
|
|
|
|
def test_subscribe_events_no_include_subscribers(self) -> None:
|
|
|
|
self.do_test_subscribe_events(include_subscribers=False)
|
|
|
|
|
|
|
|
def do_test_subscribe_events(self, include_subscribers: bool) -> None:
|
|
|
|
# Subscribe to a totally new stream, so it's just Hamlet on it
|
|
|
|
action: Callable[[], object] = lambda: self.subscribe(self.example_user("hamlet"), "test_stream")
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
2020-09-01 13:08:42 +02:00
|
|
|
event_types=["subscription"],
|
2020-06-28 13:20:01 +02:00
|
|
|
include_subscribers=include_subscribers)
|
2021-01-20 15:14:52 +01:00
|
|
|
check_subscription_add('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
# Add another user to that totally new stream
|
|
|
|
action = lambda: self.subscribe(self.example_user("othello"), "test_stream")
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
state_change_expected=include_subscribers)
|
2020-07-08 15:04:35 +02:00
|
|
|
check_subscription_peer_add('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
stream = get_stream("test_stream", self.user_profile.realm)
|
|
|
|
|
2020-08-31 16:42:16 +02:00
|
|
|
# Now remove the first user, to test the normal unsubscribe flow and
|
|
|
|
# 'peer_remove' event for subscribed streams.
|
2020-06-28 13:20:01 +02:00
|
|
|
action = lambda: bulk_remove_subscriptions(
|
|
|
|
[self.example_user('othello')],
|
|
|
|
[stream],
|
|
|
|
get_client("website"))
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
state_change_expected=include_subscribers)
|
2020-07-08 15:04:35 +02:00
|
|
|
check_subscription_peer_remove('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
2020-10-22 13:40:24 +02:00
|
|
|
# Now remove the user himself, to test the 'remove' event flow
|
2020-06-28 13:20:01 +02:00
|
|
|
action = lambda: bulk_remove_subscriptions(
|
|
|
|
[self.example_user('hamlet')],
|
|
|
|
[stream],
|
|
|
|
get_client("website"))
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
2020-10-14 13:48:24 +02:00
|
|
|
include_streams=False,
|
|
|
|
num_events=2)
|
2020-07-08 14:20:25 +02:00
|
|
|
check_subscription_remove('events[0]', events[0])
|
|
|
|
self.assertEqual(len(events[0]['subscriptions']), 1)
|
|
|
|
self.assertEqual(
|
|
|
|
events[0]['subscriptions'][0]['name'],
|
|
|
|
'test_stream',
|
|
|
|
)
|
2020-06-28 13:20:01 +02:00
|
|
|
|
2020-09-01 07:46:12 +02:00
|
|
|
# Subscribe other user to test 'peer_add' event flow for unsubscribed stream.
|
|
|
|
action = lambda: self.subscribe(self.example_user("iago"), "test_stream")
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
event_types=["subscription"],
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
state_change_expected=include_subscribers)
|
|
|
|
check_subscription_peer_add('events[0]', events[0])
|
2020-08-31 16:42:16 +02:00
|
|
|
|
|
|
|
# Remove the user to test 'peer_remove' event flow for unsubscribed stream.
|
|
|
|
action = lambda: bulk_remove_subscriptions(
|
|
|
|
[self.example_user('iago')],
|
|
|
|
[stream],
|
|
|
|
get_client("website"))
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
state_change_expected=include_subscribers)
|
|
|
|
check_subscription_peer_remove('events[0]', events[0])
|
|
|
|
|
2020-06-28 13:20:01 +02:00
|
|
|
# Now resubscribe a user, to make sure that works on a vacated stream
|
|
|
|
action = lambda: self.subscribe(self.example_user("hamlet"), "test_stream")
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
2020-10-14 13:48:24 +02:00
|
|
|
include_streams=False,
|
|
|
|
num_events=1)
|
2021-01-20 15:14:52 +01:00
|
|
|
check_subscription_add('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
action = lambda: do_change_stream_description(stream, 'new description')
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers)
|
2020-07-08 13:35:37 +02:00
|
|
|
check_stream_update('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
# Update stream privacy
|
|
|
|
action = lambda: do_change_stream_invite_only(stream, True, history_public_to_subscribers=True)
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers)
|
2020-07-08 13:35:37 +02:00
|
|
|
check_stream_update('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
# Update stream stream_post_policy property
|
|
|
|
action = lambda: do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_ADMINS)
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers, num_events=2)
|
2020-07-08 13:35:37 +02:00
|
|
|
check_stream_update('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
action = lambda: do_change_stream_message_retention_days(stream, -1)
|
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers, num_events=1)
|
2020-07-08 13:35:37 +02:00
|
|
|
check_stream_update('events[0]', events[0])
|
2020-06-28 13:20:01 +02:00
|
|
|
|
|
|
|
# Subscribe to a totally new invite-only stream, so it's just Hamlet on it
|
|
|
|
stream = self.make_stream("private", self.user_profile.realm, invite_only=True)
|
2020-08-05 11:57:45 +02:00
|
|
|
stream.message_retention_days = 10
|
|
|
|
stream.save()
|
|
|
|
|
2020-06-28 13:20:01 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2020-10-13 15:16:27 +02:00
|
|
|
action = lambda: bulk_add_subscriptions(user_profile.realm, [stream], [user_profile])
|
2020-06-28 13:20:01 +02:00
|
|
|
events = self.verify_action(
|
|
|
|
action,
|
|
|
|
include_subscribers=include_subscribers,
|
|
|
|
num_events=2)
|
2020-07-08 12:53:52 +02:00
|
|
|
check_stream_create('events[0]', events[0])
|
2021-01-20 15:14:52 +01:00
|
|
|
check_subscription_add('events[1]', events[1])
|
2020-08-05 11:57:45 +02:00
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
events[0]['streams'][0]['message_retention_days'],
|
|
|
|
10,
|
|
|
|
)
|