2017-03-08 12:28:24 +01:00
import random
import re
markdown: Fix use of pure_markdown for non-pure markdown rendering.
`render_markdown_path` renders Markdown, and also (since baff121115a1)
runs Jinja2 on the resulting HTML.
The `pure_markdown` flag was added in 0a99fa2fd669, and did two
things: retried the path directly in the filesystem if it wasn't found
by the Jinja2 resolver, and also skipped the subsequent Jinja2
templating step (regardless of where the content was found). In this
context, the name `pure_markdown` made some sense. The only two
callsites were the TOS and privacy policy renders, which might have
had user-supplied arbitrary paths, and we wished to handle absolute
paths in addition to ones inside `templates/`.
Unfortunately, the follow-up of 01bd55bbcbf7 did not refactor the
logic -- it changed it, by making `pure_markdown` only do the former
of the two behaviors. Passing `pure_markdown=True` after that commit
still caused it to always run Jinja2, but allowed it to look elsewhere
in the filesystem.
This set the stage for calls, such as the one introduced in
dedea237456b, which passed both a context for Jinja2, as well as
`pure_markdown=True` implying that Jinja2 was not to be used.
Split the two previous behaviors of the `pure_markdown` flag, and use
pre-existing data to control them, rather than an explicit flag. For
handling policy information which is stored at an absolute path
outside of the template root, we switch to using the template search
path if and only if the path is relative. This also closes the
potential inconsistency based on CWD when `pure_markdown=True` was
passed and the path was relative, not absolute.
Decide whether to run Jinja2 based on if a context is passed in at
all. This restores the behavior in the initial 0a99fa2fd669 where a
call to `rendar_markdown_path` could be made to just render markdown,
and not some other unmentioned and unrelated templating language as
well.
2023-03-10 02:47:44 +01:00
import tempfile
2022-04-17 01:47:25 +02:00
from datetime import datetime , timedelta , timezone
2020-06-05 23:26:35 +02:00
from email . headerregistry import Address
2023-05-11 02:34:34 +02:00
from typing import List , Optional , Sequence , Union
2021-06-10 15:20:47 +02:00
from unittest import mock
2020-06-11 00:54:34 +02:00
from unittest . mock import patch
2017-03-08 12:28:24 +01:00
2020-06-11 00:54:34 +02:00
import ldap
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
import lxml . html
2020-08-07 01:09:47 +02:00
import orjson
2017-03-08 12:28:24 +01:00
from django . conf import settings
from django . core import mail
2022-07-01 03:05:12 +02:00
from django . core . mail . message import EmailMultiAlternatives
2017-03-08 12:28:24 +01:00
from django . test import override_settings
2021-09-09 21:51:55 +02:00
from django . utils . timezone import now as timezone_now
2019-10-05 03:54:48 +02:00
from django_auth_ldap . config import LDAPSearch
2023-05-11 02:34:34 +02:00
from django_stubs_ext import StrPromise
2017-03-08 12:28:24 +01:00
2023-01-14 20:36:37 +01:00
from zerver . actions . create_user import do_create_user
2022-12-14 06:45:55 +01:00
from zerver . actions . user_groups import check_add_user_group
2022-04-14 23:49:26 +02:00
from zerver . actions . user_settings import do_change_user_setting
2023-07-05 11:59:56 +02:00
from zerver . actions . user_topics import do_set_user_topic_visibility_policy
2022-04-14 23:48:28 +02:00
from zerver . actions . users import do_change_user_role
2020-06-11 00:54:34 +02:00
from zerver . lib . email_notifications import (
2023-07-12 19:05:57 +02:00
MissedMessageData ,
2020-06-11 00:54:34 +02:00
enqueue_welcome_emails ,
fix_emojis ,
2020-07-15 01:21:28 +02:00
fix_spoilers_in_html ,
2023-03-15 18:09:26 +01:00
get_onboarding_email_schedule ,
2020-06-11 00:54:34 +02:00
handle_missedmessage_emails ,
2023-01-14 20:36:37 +01:00
include_realm_name_in_missedmessage_emails_subject ,
2020-06-11 00:54:34 +02:00
relative_to_full_url ,
2023-06-30 13:27:25 +02:00
send_account_registered_email ,
2017-05-08 17:54:11 +02:00
)
2021-09-09 21:51:55 +02:00
from zerver . lib . send_email import FromAddress , deliver_scheduled_emails , send_custom_email
2020-06-11 00:54:34 +02:00
from zerver . lib . test_classes import ZulipTestCase
2023-07-05 11:59:56 +02:00
from zerver . models import (
Realm ,
ScheduledEmail ,
UserMessage ,
UserProfile ,
UserTopic ,
2023-07-14 12:37:29 +02:00
get_name_keyed_dict_for_active_realm_emoji ,
2023-07-05 11:59:56 +02:00
get_realm ,
get_stream ,
)
2020-06-11 00:54:34 +02:00
2017-03-08 12:28:24 +01:00
2020-04-09 13:39:16 +02:00
class TestCustomEmails ( ZulipTestCase ) :
def test_send_custom_email_argument ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
email_subject = " subject_test "
reply_to = " reply_to_test "
2020-04-09 13:39:16 +02:00
from_name = " from_name_test "
markdown: Fix use of pure_markdown for non-pure markdown rendering.
`render_markdown_path` renders Markdown, and also (since baff121115a1)
runs Jinja2 on the resulting HTML.
The `pure_markdown` flag was added in 0a99fa2fd669, and did two
things: retried the path directly in the filesystem if it wasn't found
by the Jinja2 resolver, and also skipped the subsequent Jinja2
templating step (regardless of where the content was found). In this
context, the name `pure_markdown` made some sense. The only two
callsites were the TOS and privacy policy renders, which might have
had user-supplied arbitrary paths, and we wished to handle absolute
paths in addition to ones inside `templates/`.
Unfortunately, the follow-up of 01bd55bbcbf7 did not refactor the
logic -- it changed it, by making `pure_markdown` only do the former
of the two behaviors. Passing `pure_markdown=True` after that commit
still caused it to always run Jinja2, but allowed it to look elsewhere
in the filesystem.
This set the stage for calls, such as the one introduced in
dedea237456b, which passed both a context for Jinja2, as well as
`pure_markdown=True` implying that Jinja2 was not to be used.
Split the two previous behaviors of the `pure_markdown` flag, and use
pre-existing data to control them, rather than an explicit flag. For
handling policy information which is stored at an absolute path
outside of the template root, we switch to using the template search
path if and only if the path is relative. This also closes the
potential inconsistency based on CWD when `pure_markdown=True` was
passed and the path was relative, not absolute.
Decide whether to run Jinja2 based on if a context is passed in at
all. This restores the behavior in the initial 0a99fa2fd669 where a
call to `rendar_markdown_path` could be made to just render markdown,
and not some other unmentioned and unrelated templating language as
well.
2023-03-10 02:47:44 +01:00
with tempfile . NamedTemporaryFile ( ) as markdown_template :
markdown_template . write ( b " # Some heading \n \n Some content \n {{ realm_name }} " )
markdown_template . flush ( )
send_custom_email (
[ hamlet ] ,
options = {
" markdown_template_path " : markdown_template . name ,
" reply_to " : reply_to ,
" subject " : email_subject ,
" from_name " : from_name ,
" dry_run " : False ,
} ,
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2020-04-09 13:39:16 +02:00
msg = mail . outbox [ 0 ]
self . assertEqual ( msg . subject , email_subject )
2021-05-17 05:41:32 +02:00
self . assert_length ( msg . reply_to , 1 )
2020-04-09 13:39:16 +02:00
self . assertEqual ( msg . reply_to [ 0 ] , reply_to )
self . assertNotIn ( " { % block content % } " , msg . body )
markdown: Fix use of pure_markdown for non-pure markdown rendering.
`render_markdown_path` renders Markdown, and also (since baff121115a1)
runs Jinja2 on the resulting HTML.
The `pure_markdown` flag was added in 0a99fa2fd669, and did two
things: retried the path directly in the filesystem if it wasn't found
by the Jinja2 resolver, and also skipped the subsequent Jinja2
templating step (regardless of where the content was found). In this
context, the name `pure_markdown` made some sense. The only two
callsites were the TOS and privacy policy renders, which might have
had user-supplied arbitrary paths, and we wished to handle absolute
paths in addition to ones inside `templates/`.
Unfortunately, the follow-up of 01bd55bbcbf7 did not refactor the
logic -- it changed it, by making `pure_markdown` only do the former
of the two behaviors. Passing `pure_markdown=True` after that commit
still caused it to always run Jinja2, but allowed it to look elsewhere
in the filesystem.
This set the stage for calls, such as the one introduced in
dedea237456b, which passed both a context for Jinja2, as well as
`pure_markdown=True` implying that Jinja2 was not to be used.
Split the two previous behaviors of the `pure_markdown` flag, and use
pre-existing data to control them, rather than an explicit flag. For
handling policy information which is stored at an absolute path
outside of the template root, we switch to using the template search
path if and only if the path is relative. This also closes the
potential inconsistency based on CWD when `pure_markdown=True` was
passed and the path was relative, not absolute.
Decide whether to run Jinja2 based on if a context is passed in at
all. This restores the behavior in the initial 0a99fa2fd669 where a
call to `rendar_markdown_path` could be made to just render markdown,
and not some other unmentioned and unrelated templating language as
well.
2023-03-10 02:47:44 +01:00
self . assertIn ( " # Some heading " , msg . body )
self . assertIn ( " Zulip Dev " , msg . body )
assert isinstance ( msg , EmailMultiAlternatives )
self . assertIn ( " Some heading</h1> " , str ( msg . alternatives [ 0 ] [ 0 ] ) )
2020-04-09 13:39:16 +02:00
2021-12-15 02:09:12 +01:00
def test_send_custom_email_remote_server ( self ) - > None :
email_subject = " subject_test "
reply_to = " reply_to_test "
from_name = " from_name_test "
contact_email = " zulip-admin@example.com "
markdown_template_path = " templates/corporate/policies/index.md "
send_custom_email (
[ ] ,
target_emails = [ contact_email ] ,
options = {
" markdown_template_path " : markdown_template_path ,
" reply_to " : reply_to ,
" subject " : email_subject ,
" from_name " : from_name ,
" dry_run " : False ,
} ,
)
self . assert_length ( mail . outbox , 1 )
msg = mail . outbox [ 0 ]
self . assertEqual ( msg . subject , email_subject )
self . assertEqual ( msg . to , [ contact_email ] )
self . assert_length ( msg . reply_to , 1 )
self . assertEqual ( msg . reply_to [ 0 ] , reply_to )
self . assertNotIn ( " { % block content % } " , msg . body )
# Verify that the HTML version contains the footer.
markdown: Fix use of pure_markdown for non-pure markdown rendering.
`render_markdown_path` renders Markdown, and also (since baff121115a1)
runs Jinja2 on the resulting HTML.
The `pure_markdown` flag was added in 0a99fa2fd669, and did two
things: retried the path directly in the filesystem if it wasn't found
by the Jinja2 resolver, and also skipped the subsequent Jinja2
templating step (regardless of where the content was found). In this
context, the name `pure_markdown` made some sense. The only two
callsites were the TOS and privacy policy renders, which might have
had user-supplied arbitrary paths, and we wished to handle absolute
paths in addition to ones inside `templates/`.
Unfortunately, the follow-up of 01bd55bbcbf7 did not refactor the
logic -- it changed it, by making `pure_markdown` only do the former
of the two behaviors. Passing `pure_markdown=True` after that commit
still caused it to always run Jinja2, but allowed it to look elsewhere
in the filesystem.
This set the stage for calls, such as the one introduced in
dedea237456b, which passed both a context for Jinja2, as well as
`pure_markdown=True` implying that Jinja2 was not to be used.
Split the two previous behaviors of the `pure_markdown` flag, and use
pre-existing data to control them, rather than an explicit flag. For
handling policy information which is stored at an absolute path
outside of the template root, we switch to using the template search
path if and only if the path is relative. This also closes the
potential inconsistency based on CWD when `pure_markdown=True` was
passed and the path was relative, not absolute.
Decide whether to run Jinja2 based on if a context is passed in at
all. This restores the behavior in the initial 0a99fa2fd669 where a
call to `rendar_markdown_path` could be made to just render markdown,
and not some other unmentioned and unrelated templating language as
well.
2023-03-10 02:47:44 +01:00
assert isinstance ( msg , EmailMultiAlternatives )
2021-12-15 02:09:12 +01:00
self . assertIn (
" You are receiving this email to update you about important changes to Zulip " ,
markdown: Fix use of pure_markdown for non-pure markdown rendering.
`render_markdown_path` renders Markdown, and also (since baff121115a1)
runs Jinja2 on the resulting HTML.
The `pure_markdown` flag was added in 0a99fa2fd669, and did two
things: retried the path directly in the filesystem if it wasn't found
by the Jinja2 resolver, and also skipped the subsequent Jinja2
templating step (regardless of where the content was found). In this
context, the name `pure_markdown` made some sense. The only two
callsites were the TOS and privacy policy renders, which might have
had user-supplied arbitrary paths, and we wished to handle absolute
paths in addition to ones inside `templates/`.
Unfortunately, the follow-up of 01bd55bbcbf7 did not refactor the
logic -- it changed it, by making `pure_markdown` only do the former
of the two behaviors. Passing `pure_markdown=True` after that commit
still caused it to always run Jinja2, but allowed it to look elsewhere
in the filesystem.
This set the stage for calls, such as the one introduced in
dedea237456b, which passed both a context for Jinja2, as well as
`pure_markdown=True` implying that Jinja2 was not to be used.
Split the two previous behaviors of the `pure_markdown` flag, and use
pre-existing data to control them, rather than an explicit flag. For
handling policy information which is stored at an absolute path
outside of the template root, we switch to using the template search
path if and only if the path is relative. This also closes the
potential inconsistency based on CWD when `pure_markdown=True` was
passed and the path was relative, not absolute.
Decide whether to run Jinja2 based on if a context is passed in at
all. This restores the behavior in the initial 0a99fa2fd669 where a
call to `rendar_markdown_path` could be made to just render markdown,
and not some other unmentioned and unrelated templating language as
well.
2023-03-10 02:47:44 +01:00
str ( msg . alternatives [ 0 ] [ 0 ] ) ,
2021-12-15 02:09:12 +01:00
)
2020-04-09 13:39:16 +02:00
def test_send_custom_email_headers ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2021-02-12 08:19:30 +01:00
markdown_template_path = (
2023-04-05 11:19:58 +02:00
" zerver/tests/fixtures/email/custom_emails/email_base_headers_test.html "
2021-02-12 08:19:30 +01:00
)
send_custom_email (
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2020-04-09 13:39:16 +02:00
msg = mail . outbox [ 0 ]
2021-05-10 07:02:14 +02:00
self . assertEqual ( msg . subject , " Test subject " )
2020-04-09 13:39:16 +02:00
self . assertFalse ( msg . reply_to )
2021-02-12 08:20:45 +01:00
self . assertEqual ( " Test body " , msg . body )
2020-04-09 13:39:16 +02:00
def test_send_custom_email_no_argument ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2020-04-09 13:39:16 +02:00
from_name = " from_name_test "
2021-02-12 08:20:45 +01:00
email_subject = " subject_test "
2023-04-05 11:19:58 +02:00
markdown_template_path = (
" zerver/tests/fixtures/email/custom_emails/email_base_headers_no_headers_test.html "
)
2020-04-09 13:39:16 +02:00
2022-11-17 09:30:48 +01:00
from zerver . lib . send_email import NoEmailArgumentError
2020-04-09 13:39:16 +02:00
2021-02-12 08:19:30 +01:00
self . assertRaises (
2022-11-17 09:30:48 +01:00
NoEmailArgumentError ,
2021-02-12 08:19:30 +01:00
send_custom_email ,
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
" from_name " : from_name ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
self . assertRaises (
2022-11-17 09:30:48 +01:00
NoEmailArgumentError ,
2021-02-12 08:19:30 +01:00
send_custom_email ,
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
" subject " : email_subject ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
2020-04-09 13:39:16 +02:00
def test_send_custom_email_doubled_arguments ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2020-04-09 13:39:16 +02:00
from_name = " from_name_test "
2021-02-12 08:20:45 +01:00
email_subject = " subject_test "
2021-02-12 08:19:30 +01:00
markdown_template_path = (
2023-04-05 11:19:58 +02:00
" zerver/tests/fixtures/email/custom_emails/email_base_headers_test.html "
2021-02-12 08:19:30 +01:00
)
2020-04-09 13:39:16 +02:00
2022-11-17 09:30:48 +01:00
from zerver . lib . send_email import DoubledEmailArgumentError
2020-04-09 13:39:16 +02:00
2021-02-12 08:19:30 +01:00
self . assertRaises (
2022-11-17 09:30:48 +01:00
DoubledEmailArgumentError ,
2021-02-12 08:19:30 +01:00
send_custom_email ,
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
" subject " : email_subject ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
self . assertRaises (
2022-11-17 09:30:48 +01:00
DoubledEmailArgumentError ,
2021-02-12 08:19:30 +01:00
send_custom_email ,
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
" from_name " : from_name ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
2020-04-09 13:39:16 +02:00
2020-04-18 15:51:29 +02:00
def test_send_custom_email_admins_only ( self ) - > None :
2021-02-12 08:20:45 +01:00
admin_user = self . example_user ( " hamlet " )
2021-03-27 05:13:46 +01:00
do_change_user_role ( admin_user , UserProfile . ROLE_REALM_ADMINISTRATOR , acting_user = None )
2020-04-18 15:51:29 +02:00
2021-02-12 08:20:45 +01:00
non_admin_user = self . example_user ( " cordelia " )
2020-04-18 15:51:29 +02:00
2021-02-12 08:19:30 +01:00
markdown_template_path = (
2023-04-05 11:19:58 +02:00
" zerver/tests/fixtures/email/custom_emails/email_base_headers_test.html "
2021-02-12 08:19:30 +01:00
)
send_custom_email (
[ admin_user , non_admin_user ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-02-12 08:19:30 +01:00
" markdown_template_path " : markdown_template_path ,
" admins_only " : True ,
2021-04-07 01:27:02 +02:00
" dry_run " : False ,
2021-02-12 08:19:30 +01:00
} ,
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2020-04-18 15:51:29 +02:00
self . assertIn ( admin_user . delivery_email , mail . outbox [ 0 ] . to [ 0 ] )
2021-04-07 01:27:02 +02:00
def test_send_custom_email_dry_run ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
email_subject = " subject_test "
reply_to = " reply_to_test "
from_name = " from_name_test "
markdown_template_path = " templates/zerver/tests/markdown/test_nested_code_blocks.md "
with patch ( " builtins.print " ) as _ :
send_custom_email (
[ hamlet ] ,
2021-12-15 01:45:35 +01:00
options = {
2021-04-07 01:27:02 +02:00
" markdown_template_path " : markdown_template_path ,
" reply_to " : reply_to ,
" subject " : email_subject ,
" from_name " : from_name ,
" dry_run " : True ,
} ,
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 0 )
2021-04-07 01:27:02 +02:00
2020-04-09 13:39:16 +02:00
2018-11-14 12:46:56 +01:00
class TestFollowupEmails ( ZulipTestCase ) :
def test_day1_email_context ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
2023-06-30 13:27:25 +02:00
send_account_registered_email ( hamlet )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = hamlet ) . order_by (
" scheduled_timestamp "
)
2020-08-07 01:09:47 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
2018-11-14 12:46:56 +01:00
self . assertEqual ( email_data [ " context " ] [ " email " ] , self . example_email ( " hamlet " ) )
self . assertEqual ( email_data [ " context " ] [ " is_realm_admin " ] , False )
2023-02-03 02:16:43 +01:00
self . assertEqual (
email_data [ " context " ] [ " getting_user_started_link " ] ,
" http://zulip.testserver/help/getting-started-with-zulip " ,
)
2018-11-14 12:46:56 +01:00
self . assertNotIn ( " ldap_username " , email_data [ " context " ] )
ScheduledEmail . objects . all ( ) . delete ( )
iago = self . example_user ( " iago " )
2023-06-30 13:27:25 +02:00
send_account_registered_email ( iago )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = iago ) . order_by ( " scheduled_timestamp " )
2020-08-07 01:09:47 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
2018-11-14 12:46:56 +01:00
self . assertEqual ( email_data [ " context " ] [ " email " ] , self . example_email ( " iago " ) )
self . assertEqual ( email_data [ " context " ] [ " is_realm_admin " ] , True )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2023-02-03 02:16:43 +01:00
email_data [ " context " ] [ " getting_organization_started_link " ] ,
2021-02-12 08:19:30 +01:00
" http://zulip.testserver/help/getting-your-organization-started-with-zulip " ,
)
2023-02-03 02:16:43 +01:00
self . assertEqual (
email_data [ " context " ] [ " getting_user_started_link " ] ,
" http://zulip.testserver/help/getting-started-with-zulip " ,
)
2018-11-14 12:46:56 +01:00
self . assertNotIn ( " ldap_username " , email_data [ " context " ] )
2018-11-29 16:32:17 +01:00
# See https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#ldap-including-active-directory
# for case details.
2021-02-12 08:19:30 +01:00
@override_settings (
AUTHENTICATION_BACKENDS = (
2021-02-12 08:20:45 +01:00
" zproject.backends.ZulipLDAPAuthBackend " ,
" zproject.backends.ZulipDummyBackend " ,
2021-02-12 08:19:30 +01:00
) ,
# configure email search for email address in the uid attribute:
AUTH_LDAP_REVERSE_EMAIL_SEARCH = LDAPSearch (
" ou=users,dc=zulip,dc=com " , ldap . SCOPE_ONELEVEL , " (uid= %(email)s ) "
) ,
)
2018-11-29 16:32:17 +01:00
def test_day1_email_ldap_case_a_login_credentials ( self ) - > None :
2019-10-16 18:27:53 +02:00
self . init_default_ldap_database ( )
2021-02-12 08:20:45 +01:00
ldap_user_attr_map = { " full_name " : " cn " }
2019-10-16 18:27:53 +02:00
with self . settings ( AUTH_LDAP_USER_ATTR_MAP = ldap_user_attr_map ) :
2021-02-12 08:19:30 +01:00
self . login_with_return (
" newuser_email_as_uid@zulip.com " ,
self . ldap_password ( " newuser_email_as_uid@zulip.com " ) ,
)
2020-03-12 14:17:25 +01:00
user = UserProfile . objects . get ( delivery_email = " newuser_email_as_uid@zulip.com " )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = user ) . order_by (
" scheduled_timestamp "
)
2018-11-29 16:32:17 +01:00
2023-03-15 20:18:09 +01:00
self . assert_length ( scheduled_emails , 3 )
2020-08-07 01:09:47 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
2018-11-29 16:32:17 +01:00
self . assertEqual ( email_data [ " context " ] [ " ldap " ] , True )
2021-02-12 08:19:30 +01:00
self . assertEqual (
email_data [ " context " ] [ " ldap_username " ] , " newuser_email_as_uid@zulip.com "
)
@override_settings (
AUTHENTICATION_BACKENDS = (
2021-02-12 08:20:45 +01:00
" zproject.backends.ZulipLDAPAuthBackend " ,
" zproject.backends.ZulipDummyBackend " ,
2021-02-12 08:19:30 +01:00
)
)
2018-11-29 16:32:17 +01:00
def test_day1_email_ldap_case_b_login_credentials ( self ) - > None :
2019-10-16 18:27:53 +02:00
self . init_default_ldap_database ( )
2021-02-12 08:20:45 +01:00
ldap_user_attr_map = { " full_name " : " cn " }
2018-11-14 12:46:56 +01:00
with self . settings (
2021-02-12 08:20:45 +01:00
LDAP_APPEND_DOMAIN = " zulip.com " ,
2021-02-12 08:19:30 +01:00
AUTH_LDAP_USER_ATTR_MAP = ldap_user_attr_map ,
2019-10-16 18:27:53 +02:00
) :
2020-02-19 19:40:49 +01:00
self . login_with_return ( " newuser@zulip.com " , self . ldap_password ( " newuser " ) )
2018-11-29 16:32:17 +01:00
2020-03-12 14:17:25 +01:00
user = UserProfile . objects . get ( delivery_email = " newuser@zulip.com " )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = user ) . order_by (
" scheduled_timestamp "
)
self . assert_length ( scheduled_emails , 3 )
2020-08-07 01:09:47 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
2018-11-29 16:32:17 +01:00
self . assertEqual ( email_data [ " context " ] [ " ldap " ] , True )
self . assertEqual ( email_data [ " context " ] [ " ldap_username " ] , " newuser " )
2021-02-12 08:19:30 +01:00
@override_settings (
AUTHENTICATION_BACKENDS = (
2021-02-12 08:20:45 +01:00
" zproject.backends.ZulipLDAPAuthBackend " ,
" zproject.backends.ZulipDummyBackend " ,
2021-02-12 08:19:30 +01:00
)
)
2018-11-29 16:32:17 +01:00
def test_day1_email_ldap_case_c_login_credentials ( self ) - > None :
2019-10-16 18:27:53 +02:00
self . init_default_ldap_database ( )
2021-02-12 08:20:45 +01:00
ldap_user_attr_map = { " full_name " : " cn " }
2018-11-29 16:32:17 +01:00
with self . settings (
2021-02-12 08:20:45 +01:00
LDAP_EMAIL_ATTR = " mail " ,
2021-02-12 08:19:30 +01:00
AUTH_LDAP_USER_ATTR_MAP = ldap_user_attr_map ,
2019-10-16 18:27:53 +02:00
) :
2020-02-19 19:40:49 +01:00
self . login_with_return ( " newuser_with_email " , self . ldap_password ( " newuser_with_email " ) )
2020-03-12 14:17:25 +01:00
user = UserProfile . objects . get ( delivery_email = " newuser_email@zulip.com " )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = user ) . order_by (
" scheduled_timestamp "
)
self . assert_length ( scheduled_emails , 3 )
2020-08-07 01:09:47 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
2018-11-29 16:32:17 +01:00
self . assertEqual ( email_data [ " context " ] [ " ldap " ] , True )
2019-11-05 02:29:03 +01:00
self . assertEqual ( email_data [ " context " ] [ " ldap_username " ] , " newuser_with_email " )
2018-11-14 12:46:56 +01:00
2018-11-14 12:58:35 +01:00
def test_followup_emails_count ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
2023-03-15 20:18:09 +01:00
iago = self . example_user ( " iago " )
2018-11-14 12:58:35 +01:00
cordelia = self . example_user ( " cordelia " )
2023-03-15 20:18:09 +01:00
realm = get_realm ( " zulip " )
2018-11-14 12:58:35 +01:00
2023-03-15 20:18:09 +01:00
# Hamlet has account only in Zulip realm so day1, day2 and zulip_guide emails should be sent
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " hamlet " ) )
2023-03-15 20:18:09 +01:00
enqueue_welcome_emails ( self . example_user ( " hamlet " ) )
scheduled_emails = ScheduledEmail . objects . filter ( users = hamlet ) . order_by (
" scheduled_timestamp "
)
self . assert_length ( scheduled_emails , 3 )
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
self . assertEqual (
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day2 "
)
self . assertEqual (
orjson . loads ( scheduled_emails [ 2 ] . data ) [ " template_prefix " ] ,
" zerver/emails/onboarding_zulip_guide " ,
)
ScheduledEmail . objects . all ( ) . delete ( )
# The onboarding_zulip_guide email should not be sent to non-admin users in organizations
# that are sent the `/for/communities/` guide; see note in enqueue_welcome_emails.
realm . org_type = Realm . ORG_TYPES [ " community " ] [ " id " ]
realm . save ( )
# Hamlet is not an admin so the `/for/communities/` zulip_guide should not be sent
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " hamlet " ) )
2018-11-14 12:58:35 +01:00
enqueue_welcome_emails ( self . example_user ( " hamlet " ) )
2019-04-29 07:00:03 +02:00
scheduled_emails = ScheduledEmail . objects . filter ( users = hamlet ) . order_by (
2021-02-12 08:19:30 +01:00
" scheduled_timestamp "
)
2021-07-13 19:39:37 +02:00
self . assert_length ( scheduled_emails , 2 )
2023-03-15 20:18:09 +01:00
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
self . assertEqual (
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day2 "
)
ScheduledEmail . objects . all ( ) . delete ( )
# Iago is an admin so the `/for/communities/` zulip_guide should be sent
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " iago " ) )
2023-03-15 20:18:09 +01:00
enqueue_welcome_emails ( self . example_user ( " iago " ) )
scheduled_emails = ScheduledEmail . objects . filter ( users = iago ) . order_by ( " scheduled_timestamp " )
self . assert_length ( scheduled_emails , 3 )
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-02-12 08:20:45 +01:00
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day2 "
2021-02-12 08:19:30 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual (
orjson . loads ( scheduled_emails [ 2 ] . data ) [ " template_prefix " ] ,
" zerver/emails/onboarding_zulip_guide " ,
)
ScheduledEmail . objects . all ( ) . delete ( )
# The organization_type context for "education_nonprofit" orgs is simplified to be "education"
realm . org_type = Realm . ORG_TYPES [ " education_nonprofit " ] [ " id " ]
realm . save ( )
# Cordelia has account in more than 1 realm so day2 email should not be sent
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " cordelia " ) )
2023-03-15 20:18:09 +01:00
enqueue_welcome_emails ( self . example_user ( " cordelia " ) )
scheduled_emails = ScheduledEmail . objects . filter ( users = cordelia ) . order_by (
" scheduled_timestamp "
)
self . assert_length ( scheduled_emails , 2 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-02-12 08:20:45 +01:00
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
2021-02-12 08:19:30 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual (
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] ,
" zerver/emails/onboarding_zulip_guide " ,
)
self . assertEqual (
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " context " ] [ " organization_type " ] ,
" education " ,
)
2018-11-14 12:58:35 +01:00
ScheduledEmail . objects . all ( ) . delete ( )
2023-03-15 20:18:09 +01:00
# Only a subset of Realm.ORG_TYPES are sent the zulip_guide_followup email
realm . org_type = Realm . ORG_TYPES [ " other " ] [ " id " ]
realm . save ( )
# In this case, Cordelia should only be sent the day1 email
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " cordelia " ) )
2023-03-15 20:18:09 +01:00
enqueue_welcome_emails ( self . example_user ( " cordelia " ) )
2019-01-04 01:50:21 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = cordelia )
2021-05-17 05:41:32 +02:00
self . assert_length ( scheduled_emails , 1 )
2023-03-15 20:18:09 +01:00
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
2018-11-14 12:58:35 +01:00
2021-09-09 21:51:55 +02:00
def test_followup_emails_for_regular_realms ( self ) - > None :
cordelia = self . example_user ( " cordelia " )
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " cordelia " ) , realm_creation = True )
enqueue_welcome_emails ( self . example_user ( " cordelia " ) )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = cordelia ) . order_by (
" scheduled_timestamp "
)
assert scheduled_emails is not None
self . assert_length ( scheduled_emails , 2 )
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
2021-09-09 21:51:55 +02:00
self . assertEqual (
2023-03-15 20:18:09 +01:00
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] ,
" zerver/emails/onboarding_zulip_guide " ,
2021-09-09 21:51:55 +02:00
)
2023-03-15 20:18:09 +01:00
deliver_scheduled_emails ( scheduled_emails [ 0 ] )
2021-09-09 21:51:55 +02:00
from django . core . mail import outbox
self . assert_length ( outbox , 1 )
message = outbox [ 0 ]
2023-02-03 02:16:43 +01:00
self . assertIn ( " you have created a new Zulip organization " , message . body )
2021-09-09 21:51:55 +02:00
self . assertNotIn ( " demo org " , message . body )
def test_followup_emails_for_demo_realms ( self ) - > None :
cordelia = self . example_user ( " cordelia " )
cordelia . realm . demo_organization_scheduled_deletion_date = timezone_now ( ) + timedelta (
days = 30
)
cordelia . realm . save ( )
2023-06-30 13:27:25 +02:00
send_account_registered_email ( self . example_user ( " cordelia " ) , realm_creation = True )
enqueue_welcome_emails ( self . example_user ( " cordelia " ) )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = cordelia ) . order_by (
" scheduled_timestamp "
)
assert scheduled_emails is not None
self . assert_length ( scheduled_emails , 2 )
self . assertEqual (
orjson . loads ( scheduled_emails [ 0 ] . data ) [ " template_prefix " ] , " zerver/emails/followup_day1 "
)
2021-09-09 21:51:55 +02:00
self . assertEqual (
2023-03-15 20:18:09 +01:00
orjson . loads ( scheduled_emails [ 1 ] . data ) [ " template_prefix " ] ,
" zerver/emails/onboarding_zulip_guide " ,
2021-09-09 21:51:55 +02:00
)
2023-03-15 20:18:09 +01:00
deliver_scheduled_emails ( scheduled_emails [ 0 ] )
2021-09-09 21:51:55 +02:00
from django . core . mail import outbox
self . assert_length ( outbox , 1 )
message = outbox [ 0 ]
2023-02-03 02:16:43 +01:00
self . assertIn ( " you have created a new demo Zulip organization " , message . body )
2021-09-09 21:51:55 +02:00
2023-03-15 20:18:09 +01:00
def test_onboarding_zulip_guide_with_invalid_org_type ( self ) - > None :
cordelia = self . example_user ( " cordelia " )
realm = get_realm ( " zulip " )
invalid_org_type_id = 999
realm . org_type = invalid_org_type_id
realm . save ( )
with self . assertLogs ( level = " ERROR " ) as m :
enqueue_welcome_emails ( self . example_user ( " cordelia " ) )
scheduled_emails = ScheduledEmail . objects . filter ( users = cordelia )
2023-06-30 13:27:25 +02:00
self . assert_length ( scheduled_emails , 0 )
2023-03-15 20:18:09 +01:00
self . assertEqual (
m . output ,
[ f " ERROR:root:Unknown organization type ' { invalid_org_type_id } ' " ] ,
)
2021-02-12 08:19:30 +01:00
2017-03-08 12:28:24 +01:00
class TestMissedMessages ( ZulipTestCase ) :
2021-06-10 15:20:47 +02:00
def test_read_message ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
self . login ( " cordelia " )
result = self . client_post (
" /json/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
2022-09-13 08:39:44 +02:00
" to " : orjson . dumps ( [ hamlet . email ] ) . decode ( ) ,
2021-06-10 15:20:47 +02:00
} ,
)
self . assert_json_success ( result )
message = self . get_last_message ( )
# The message is marked as read for the sender (Cordelia) by the message send codepath.
# We obviously should not send notifications to someone for messages they sent themselves.
with mock . patch (
" zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip "
) as m :
handle_missedmessage_emails (
2023-07-12 19:05:57 +02:00
cordelia . id , { message . id : MissedMessageData ( trigger = " private_message " ) }
2021-06-10 15:20:47 +02:00
)
m . assert_not_called ( )
# If the notification is processed before Hamlet reads the message, he should get the email.
with mock . patch (
" zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip "
) as m :
handle_missedmessage_emails (
2023-07-12 19:05:57 +02:00
hamlet . id , { message . id : MissedMessageData ( trigger = " private_message " ) }
2021-06-10 15:20:47 +02:00
)
m . assert_called_once ( )
# If Hamlet reads the message before receiving the email notification, we should not sent him
# an email.
usermessage = UserMessage . objects . get (
user_profile = hamlet ,
message = message ,
)
usermessage . flags . read = True
usermessage . save ( )
with mock . patch (
" zerver.lib.email_notifications.do_send_missedmessage_events_reply_in_zulip "
) as m :
handle_missedmessage_emails (
2023-07-12 19:05:57 +02:00
hamlet . id , { message . id : MissedMessageData ( trigger = " private_message " ) }
2021-06-10 15:20:47 +02:00
)
m . assert_not_called ( )
2023-05-11 02:34:34 +02:00
def normalize_string ( self , s : Union [ str , StrPromise ] ) - > str :
2017-03-08 12:28:24 +01:00
s = s . strip ( )
2021-02-12 08:20:45 +01:00
return re . sub ( r " \ s+ " , " " , s )
2017-03-08 12:28:24 +01:00
2017-11-05 10:51:25 +01:00
def _get_tokens ( self ) - > List [ str ] :
2021-02-12 08:20:45 +01:00
return [ " mm " + str ( random . getrandbits ( 32 ) ) for _ in range ( 30 ) ]
2017-03-08 12:28:24 +01:00
2021-02-12 08:19:30 +01:00
def _test_cases (
self ,
msg_id : int ,
verify_body_include : List [ str ] ,
email_subject : str ,
send_as_user : bool ,
verify_html_body : bool = False ,
show_message_content : bool = True ,
verify_body_does_not_include : Sequence [ str ] = [ ] ,
2021-02-12 08:20:45 +01:00
trigger : str = " " ,
2020-05-21 14:51:36 +02:00
mentioned_user_group_id : Optional [ int ] = None ,
2021-02-12 08:19:30 +01:00
) - > None :
2021-02-12 08:20:45 +01:00
othello = self . example_user ( " othello " )
hamlet = self . example_user ( " hamlet " )
2019-12-19 18:26:44 +01:00
tokens = self . _get_tokens ( )
2021-02-12 08:20:45 +01:00
with patch ( " zerver.lib.email_mirror.generate_missed_message_token " , side_effect = tokens ) :
2020-05-21 14:51:36 +02:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id : MissedMessageData (
trigger = trigger , mentioned_user_group_id = mentioned_user_group_id
)
} ,
2020-05-21 14:51:36 +02:00
)
2017-03-08 04:46:49 +01:00
if settings . EMAIL_GATEWAY_PATTERN != " " :
2019-12-26 13:46:55 +01:00
reply_to_addresses = [ settings . EMAIL_GATEWAY_PATTERN % ( t , ) for t in tokens ]
2021-02-12 08:19:30 +01:00
reply_to_emails = [
str ( Address ( display_name = " Zulip " , addr_spec = address ) )
for address in reply_to_addresses
]
2017-03-08 04:46:49 +01:00
else :
2017-08-16 03:32:36 +02:00
reply_to_emails = [ " noreply@testserver " ]
2017-03-08 12:28:24 +01:00
msg = mail . outbox [ 0 ]
2022-07-01 03:05:12 +02:00
assert isinstance ( msg , EmailMultiAlternatives )
2021-04-20 22:55:31 +02:00
from_email = str ( Address ( display_name = " Zulip notifications " , addr_spec = FromAddress . NOREPLY ) )
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2017-03-08 12:28:24 +01:00
if send_as_user :
2020-06-10 06:41:04 +02:00
from_email = f ' " { othello . full_name } " < { othello . email } > '
2021-01-26 04:20:36 +01:00
self . assertEqual ( self . email_envelope_from ( msg ) , settings . NOREPLY_EMAIL_ADDRESS )
self . assertEqual ( self . email_display_from ( msg ) , from_email )
2018-11-11 16:07:05 +01:00
self . assertEqual ( msg . subject , email_subject )
2021-05-17 05:41:32 +02:00
self . assert_length ( msg . reply_to , 1 )
2017-06-26 19:43:32 +02:00
self . assertIn ( msg . reply_to [ 0 ] , reply_to_emails )
2017-07-07 22:34:22 +02:00
if verify_html_body :
2020-01-31 12:50:02 +01:00
for text in verify_body_include :
2022-07-01 03:05:12 +02:00
assert isinstance ( msg . alternatives [ 0 ] [ 0 ] , str )
2023-04-05 11:19:58 +02:00
html = self . normalize_string ( msg . alternatives [ 0 ] [ 0 ] )
self . assertIn ( text , html )
2017-07-07 22:34:22 +02:00
else :
2020-01-31 12:50:02 +01:00
for text in verify_body_include :
self . assertIn ( text , self . normalize_string ( msg . body ) )
2020-06-13 03:34:01 +02:00
for text in verify_body_does_not_include :
self . assertNotIn ( text , self . normalize_string ( msg . body ) )
2020-06-14 13:32:38 +02:00
self . assertEqual ( msg . extra_headers [ " List-Id " ] , " Zulip Dev <zulip.testserver> " )
2017-03-08 12:28:24 +01:00
2021-02-12 08:19:30 +01:00
def _realm_name_in_missed_message_email_subject (
self , realm_name_in_notifications : bool
) - > None :
2018-02-06 20:04:14 +01:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message! " ,
2018-02-06 20:04:14 +01:00
)
2021-02-12 08:20:45 +01:00
verify_body_include = [ " Extremely personal message! " ]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2018-02-06 20:04:14 +01:00
if realm_name_in_notifications :
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice [Zulip Dev] "
2020-01-31 12:50:02 +01:00
self . _test_cases ( msg_id , verify_body_include , email_subject , False )
2018-02-06 20:04:14 +01:00
2021-02-12 08:19:30 +01:00
def _extra_context_in_missed_stream_messages_mention (
self , send_as_user : bool , show_message_content : bool = True
) - > None :
2017-03-08 12:28:24 +01:00
for i in range ( 0 , 11 ) :
2023-02-07 17:43:35 +01:00
self . send_stream_message (
self . example_user ( " othello " ) ,
" Denmark " ,
content = str ( i ) ,
topic_name = " test " if i % 2 == 0 else " TEST " ,
)
2021-02-12 08:20:45 +01:00
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
2017-10-28 17:52:15 +02:00
msg_id = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , " denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
2017-11-29 13:42:39 +01:00
if show_message_content :
2020-01-31 12:50:02 +01:00
verify_body_include = [
2020-08-04 12:06:41 +02:00
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > @**King Hamlet** -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because you were personally mentioned. " ,
2020-01-31 12:50:02 +01:00
]
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
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
verify_body_does_not_include : List [ str ] = [ ]
2017-11-29 13:42:39 +01:00
else :
# Test in case if message content in missed email message are disabled.
2020-01-31 12:55:47 +01:00
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
2022-09-29 10:05:54 +02:00
" View or reply in Zulip Dev Zulip " ,
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
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
2020-01-31 12:55:47 +01:00
]
2021-04-20 23:02:19 +02:00
email_subject = " New messages "
2021-02-12 08:19:30 +01:00
verify_body_does_not_include = [
2021-02-12 08:20:45 +01:00
" Denmark > test " ,
" Othello, the Moor of Venice " ,
" 1 2 3 4 5 6 7 8 9 10 @**King Hamlet** " ,
" private " ,
" group " ,
2022-09-29 10:05:54 +02:00
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
2021-02-12 08:19:30 +01:00
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
2021-02-12 08:20:45 +01:00
trigger = " mentioned " ,
2021-02-12 08:19:30 +01:00
)
2023-06-07 19:19:33 +02:00
def _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic (
self , send_as_user : bool , show_message_content : bool = True
) - > None :
for i in range ( 1 , 6 ) :
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content = str ( i ) )
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " @**topic** " )
if show_message_content :
verify_body_include = [
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**topic** -- " ,
" You are receiving this because all topic participants were mentioned in #Denmark > test. " ,
]
email_subject = " #Denmark > test "
verify_body_does_not_include : List [ str ] = [ ]
else :
# Test in case if message content in missed email message are disabled.
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
" View or reply in Zulip Dev Zulip " ,
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
]
email_subject = " New messages "
verify_body_does_not_include = [
" Othello, the Moor of Venice " ,
" 1 2 3 4 5 @**topic** " ,
" private " ,
" group " ,
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
trigger = " topic_wildcard_mentioned_in_followed_topic " ,
)
2023-06-03 16:51:38 +02:00
def _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic (
2023-06-21 21:15:23 +02:00
self , send_as_user : bool , show_message_content : bool = True
) - > None :
for i in range ( 1 , 6 ) :
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content = str ( i ) )
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " @**all** " )
if show_message_content :
verify_body_include = [
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**all** -- " ,
" You are receiving this because you have wildcard mention notifications enabled for topics you follow. " ,
]
email_subject = " #Denmark > test "
verify_body_does_not_include : List [ str ] = [ ]
else :
# Test in case if message content in missed email message are disabled.
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
" View or reply in Zulip Dev Zulip " ,
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
]
email_subject = " New messages "
verify_body_does_not_include = [
2023-06-07 19:19:33 +02:00
" Denmark > test " ,
2023-06-21 21:15:23 +02:00
" Othello, the Moor of Venice " ,
" 1 2 3 4 5 @**all** " ,
" private " ,
" group " ,
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
2023-06-03 16:51:38 +02:00
trigger = " stream_wildcard_mentioned_in_followed_topic " ,
2023-06-21 21:15:23 +02:00
)
2023-06-07 19:19:33 +02:00
def _extra_context_in_missed_stream_messages_topic_wildcard_mention (
self , send_as_user : bool , show_message_content : bool = True
) - > None :
for i in range ( 1 , 6 ) :
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content = str ( i ) )
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " denmark " , " @**topic** " )
if show_message_content :
verify_body_include = [
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**topic** -- " ,
" You are receiving this because all topic participants were mentioned in #Denmark > test. " ,
]
email_subject = " #Denmark > test "
verify_body_does_not_include : List [ str ] = [ ]
else :
# Test in case if message content in missed email message are disabled.
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
" View or reply in Zulip Dev Zulip " ,
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
]
email_subject = " New messages "
verify_body_does_not_include = [
" Othello, the Moor of Venice " ,
" 1 2 3 4 5 @**topic** " ,
" private " ,
" group " ,
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
trigger = " topic_wildcard_mentioned " ,
)
2023-06-03 16:51:38 +02:00
def _extra_context_in_missed_stream_messages_stream_wildcard_mention (
2021-02-12 08:19:30 +01:00
self , send_as_user : bool , show_message_content : bool = True
) - > None :
2019-08-26 04:40:07 +02:00
for i in range ( 1 , 6 ) :
2021-02-12 08:20:45 +01:00
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content = str ( i ) )
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " denmark " , " @**all** " )
2019-08-26 04:40:07 +02:00
if show_message_content :
2020-01-31 12:50:02 +01:00
verify_body_include = [
2020-08-04 12:06:41 +02:00
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**all** -- " ,
2022-09-29 18:56:25 +02:00
" You are receiving this because everyone was mentioned in #Denmark. " ,
2020-01-31 12:50:02 +01:00
]
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
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
verify_body_does_not_include : List [ str ] = [ ]
2019-08-26 04:40:07 +02:00
else :
# Test in case if message content in missed email message are disabled.
2020-01-31 12:55:47 +01:00
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
2022-09-29 10:05:54 +02:00
" View or reply in Zulip Dev Zulip " ,
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
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
2020-01-31 12:55:47 +01:00
]
2021-04-20 23:02:19 +02:00
email_subject = " New messages "
2021-02-12 08:19:30 +01:00
verify_body_does_not_include = [
2021-02-12 08:20:45 +01:00
" Denmark > test " ,
" Othello, the Moor of Venice " ,
" 1 2 3 4 5 @**all** " ,
" private " ,
" group " ,
2022-09-29 10:05:54 +02:00
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
2021-02-12 08:19:30 +01:00
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
2023-06-03 16:51:38 +02:00
trigger = " stream_wildcard_mentioned " ,
2021-02-12 08:19:30 +01:00
)
2019-08-26 04:40:07 +02:00
2019-12-19 18:26:44 +01:00
def _extra_context_in_missed_stream_messages_email_notify ( self , send_as_user : bool ) - > None :
2017-11-21 06:11:13 +01:00
for i in range ( 0 , 11 ) :
2021-02-12 08:20:45 +01:00
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content = str ( i ) )
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " 11 " , topic_name = " test2 " )
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " denmark " , " 12 " )
2020-01-31 12:50:02 +01:00
verify_body_include = [
2020-08-04 12:06:41 +02:00
" Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > 12 -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because you have email notifications enabled for #Denmark. " ,
2020-01-31 12:50:02 +01:00
]
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2021-02-12 08:19:30 +01:00
self . _test_cases (
2021-02-12 08:20:45 +01:00
msg_id , verify_body_include , email_subject , send_as_user , trigger = " stream_email_notify "
2021-02-12 08:19:30 +01:00
)
2017-05-12 22:47:34 +02:00
2021-02-12 08:19:30 +01:00
def _extra_context_in_missed_stream_messages_mention_two_senders (
self , send_as_user : bool
) - > None :
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
cordelia = self . example_user ( " cordelia " )
self . subscribe ( cordelia , " Denmark " )
2017-05-12 22:47:34 +02:00
for i in range ( 0 , 3 ) :
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
self . send_stream_message ( cordelia , " Denmark " , str ( i ) )
2017-10-28 17:52:15 +02:00
msg_id = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , " Denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
2020-01-31 12:50:02 +01:00
verify_body_include = [
2020-08-04 12:06:41 +02:00
" Cordelia, Lear ' s daughter: > 0 > 1 > 2 Othello, the Moor of Venice: > @**King Hamlet** -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because you were personally mentioned. " ,
2020-01-31 12:50:02 +01:00
]
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2021-02-12 08:19:30 +01:00
self . _test_cases (
2021-02-12 08:20:45 +01:00
msg_id , verify_body_include , email_subject , send_as_user , trigger = " mentioned "
2021-02-12 08:19:30 +01:00
)
2017-05-12 22:47:34 +02:00
2022-10-31 22:13:26 +01:00
def _resolved_topic_missed_stream_messages_thread_friendly ( self , send_as_user : bool ) - > None :
topic_name = " threading and so forth "
othello_user = self . example_user ( " othello " )
msg_id = - 1
for i in range ( 0 , 3 ) :
msg_id = self . send_stream_message (
othello_user ,
" Denmark " ,
content = str ( i ) ,
topic_name = topic_name ,
)
self . assert_json_success ( self . resolve_topic_containing_message ( othello_user , msg_id ) )
verify_body_include = [
" Othello, the Moor of Venice: > 0 > 1 > 2 -- " ,
" You are receiving this because you have email notifications enabled for #Denmark. " ,
]
email_subject = " [resolved] #Denmark > threading and so forth "
self . _test_cases (
msg_id , verify_body_include , email_subject , send_as_user , trigger = " stream_email_notify "
)
2020-08-05 23:49:24 +02:00
def _extra_context_in_missed_personal_messages (
2021-02-12 08:19:30 +01:00
self ,
send_as_user : bool ,
show_message_content : bool = True ,
message_content_disabled_by_user : bool = False ,
message_content_disabled_by_realm : bool = False ,
) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message! " ,
2017-10-28 17:52:15 +02:00
)
2017-11-29 13:42:39 +01:00
if show_message_content :
2020-08-04 12:06:41 +02:00
verify_body_include = [ " > Extremely personal message! " ]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
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
verify_body_does_not_include : List [ str ] = [ ]
2017-11-29 13:42:39 +01:00
else :
2020-01-31 12:55:47 +01:00
if message_content_disabled_by_realm :
verify_body_include = [
" This email does not include message content because your organization has disabled " ,
" http://zulip.testserver/help/hide-message-content-in-emails " ,
2022-09-29 10:05:54 +02:00
" View or reply in Zulip Dev Zulip " ,
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
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
2020-01-31 12:55:47 +01:00
]
elif message_content_disabled_by_user :
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
2022-09-29 10:05:54 +02:00
" View or reply in Zulip Dev Zulip " ,
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
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
2020-01-31 12:55:47 +01:00
]
2021-04-20 23:02:19 +02:00
email_subject = " New messages "
2021-02-12 08:19:30 +01:00
verify_body_does_not_include = [
2021-02-12 08:20:45 +01:00
" Othello, the Moor of Venice " ,
" Extremely personal message! " ,
" mentioned " ,
" group " ,
2022-09-29 10:05:54 +02:00
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
2021-02-12 08:19:30 +01:00
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
)
2017-03-08 12:28:24 +01:00
2020-08-05 23:49:24 +02:00
def _reply_to_email_in_missed_personal_messages ( self , send_as_user : bool ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message! " ,
2017-10-28 17:52:15 +02:00
)
2022-09-29 10:05:54 +02:00
verify_body_include = [ " Reply to this email directly, or view it in Zulip Dev Zulip " ]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2020-01-31 12:50:02 +01:00
self . _test_cases ( msg_id , verify_body_include , email_subject , send_as_user )
2017-03-08 04:46:49 +01:00
2020-08-05 23:49:24 +02:00
def _reply_warning_in_missed_personal_messages ( self , send_as_user : bool ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message! " ,
2017-10-28 17:52:15 +02:00
)
2021-02-12 08:20:45 +01:00
verify_body_include = [ " Do not reply to this email. " ]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2020-01-31 12:50:02 +01:00
self . _test_cases ( msg_id , verify_body_include , email_subject , send_as_user )
2017-03-08 04:46:49 +01:00
2020-08-05 23:49:24 +02:00
def _extra_context_in_missed_huddle_messages_two_others (
2021-02-12 08:19:30 +01:00
self , send_as_user : bool , show_message_content : bool = True
) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_huddle_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
2017-10-28 17:52:15 +02:00
[
2021-02-12 08:20:45 +01:00
self . example_user ( " hamlet " ) ,
self . example_user ( " iago " ) ,
2017-10-28 17:52:15 +02:00
] ,
2021-02-12 08:20:45 +01:00
" Group personal message! " ,
2017-10-28 17:52:15 +02:00
)
2017-03-08 12:28:24 +01:00
2017-11-29 13:42:39 +01:00
if show_message_content :
2020-08-04 12:06:41 +02:00
verify_body_include = [
" Othello, the Moor of Venice: > Group personal message! -- Reply "
]
2023-01-24 19:47:48 +01:00
email_subject = " Group DMs with Iago and Othello, the Moor of Venice "
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
verify_body_does_not_include : List [ str ] = [ ]
2017-11-29 13:42:39 +01:00
else :
2020-01-31 12:55:47 +01:00
verify_body_include = [
" This email does not include message content because you have disabled message " ,
" http://zulip.testserver/help/pm-mention-alert-notifications " ,
2022-09-29 10:05:54 +02:00
" View or reply in Zulip Dev Zulip " ,
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
" Manage email preferences: http://zulip.testserver/#settings/notifications " ,
2020-01-31 12:55:47 +01:00
]
2021-04-20 23:02:19 +02:00
email_subject = " New messages "
2021-02-12 08:19:30 +01:00
verify_body_does_not_include = [
2021-02-12 08:20:45 +01:00
" Iago " ,
" Othello, the Moor of Venice Othello, the Moor of Venice " ,
" Group personal message! " ,
" mentioned " ,
2022-09-29 10:05:54 +02:00
" Reply to this email directly, or view it in Zulip Dev Zulip " ,
2021-02-12 08:19:30 +01:00
]
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
show_message_content = show_message_content ,
verify_body_does_not_include = verify_body_does_not_include ,
)
2020-08-05 23:49:24 +02:00
def _extra_context_in_missed_huddle_messages_three_others ( self , send_as_user : bool ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_huddle_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
2017-10-28 17:52:15 +02:00
[
2021-02-12 08:20:45 +01:00
self . example_user ( " hamlet " ) ,
self . example_user ( " iago " ) ,
self . example_user ( " cordelia " ) ,
2017-10-28 17:52:15 +02:00
] ,
2021-02-12 08:20:45 +01:00
" Group personal message! " ,
2017-10-28 17:52:15 +02:00
)
2017-05-03 09:22:58 +02:00
2020-08-04 12:06:41 +02:00
verify_body_include = [ " Othello, the Moor of Venice: > Group personal message! -- Reply " ]
2021-04-11 16:26:54 +02:00
email_subject = (
2023-01-24 19:47:48 +01:00
" Group DMs with Cordelia, Lear ' s daughter, Iago, and Othello, the Moor of Venice "
2021-04-11 16:26:54 +02:00
)
2020-01-31 12:50:02 +01:00
self . _test_cases ( msg_id , verify_body_include , email_subject , send_as_user )
2017-05-03 09:22:58 +02:00
2020-08-05 23:49:24 +02:00
def _extra_context_in_missed_huddle_messages_many_others ( self , send_as_user : bool ) - > None :
2021-02-12 08:19:30 +01:00
msg_id = self . send_huddle_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
2021-02-12 08:19:30 +01:00
[
2021-02-12 08:20:45 +01:00
self . example_user ( " hamlet " ) ,
self . example_user ( " iago " ) ,
self . example_user ( " cordelia " ) ,
self . example_user ( " prospero " ) ,
2021-02-12 08:19:30 +01:00
] ,
2021-02-12 08:20:45 +01:00
" Group personal message! " ,
2021-02-12 08:19:30 +01:00
)
2017-05-03 09:22:58 +02:00
2020-08-04 12:06:41 +02:00
verify_body_include = [ " Othello, the Moor of Venice: > Group personal message! -- Reply " ]
2023-01-24 19:47:48 +01:00
email_subject = " Group DMs with Cordelia, Lear ' s daughter, Iago, and 2 others "
2020-01-31 12:50:02 +01:00
self . _test_cases ( msg_id , verify_body_include , email_subject , send_as_user )
2017-03-14 08:38:01 +01:00
2019-12-19 18:26:44 +01:00
def _deleted_message_in_missed_stream_messages ( self , send_as_user : bool ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , " denmark " , " @**King Hamlet** to be deleted "
2021-02-12 08:19:30 +01:00
)
2017-03-14 08:38:01 +01:00
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
self . login ( " othello " )
2022-02-21 16:34:12 +01:00
result = self . client_patch ( " /json/messages/ " + str ( msg_id ) , { " content " : " " } )
2017-03-14 08:38:01 +01:00
self . assert_json_success ( result )
2023-07-12 19:05:57 +02:00
handle_missedmessage_emails ( hamlet . id , { msg_id : MissedMessageData ( trigger = " mentioned " ) } )
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 0 )
2017-03-14 08:38:01 +01:00
2020-08-05 23:49:24 +02:00
def _deleted_message_in_missed_personal_messages ( self , send_as_user : bool ) - > None :
2021-02-12 08:19:30 +01:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message! to be deleted! " ,
2021-02-12 08:19:30 +01:00
)
2017-03-14 08:38:01 +01:00
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
self . login ( " othello " )
2022-02-21 16:34:12 +01:00
result = self . client_patch ( " /json/messages/ " + str ( msg_id ) , { " content " : " " } )
2017-03-14 08:38:01 +01:00
self . assert_json_success ( result )
2023-07-12 19:05:57 +02:00
handle_missedmessage_emails (
hamlet . id , { msg_id : MissedMessageData ( trigger = " private_message " ) }
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 0 )
2017-03-14 08:38:01 +01:00
2020-08-05 23:49:24 +02:00
def _deleted_message_in_missed_huddle_messages ( self , send_as_user : bool ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_huddle_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
2017-10-28 17:52:15 +02:00
[
2021-02-12 08:20:45 +01:00
self . example_user ( " hamlet " ) ,
self . example_user ( " iago " ) ,
2017-10-28 17:52:15 +02:00
] ,
2021-02-12 08:20:45 +01:00
" Group personal message! " ,
2017-10-28 17:52:15 +02:00
)
2017-03-14 08:38:01 +01:00
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
iago = self . example_user ( " iago " )
self . login ( " othello " )
2022-02-21 16:34:12 +01:00
result = self . client_patch ( " /json/messages/ " + str ( msg_id ) , { " content " : " " } )
2017-03-14 08:38:01 +01:00
self . assert_json_success ( result )
2023-07-12 19:05:57 +02:00
handle_missedmessage_emails (
hamlet . id , { msg_id : MissedMessageData ( trigger = " private_message " ) }
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 0 )
2023-07-12 19:05:57 +02:00
handle_missedmessage_emails ( iago . id , { msg_id : MissedMessageData ( trigger = " private_message " ) } )
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 0 )
2017-03-14 08:38:01 +01:00
2020-05-21 14:51:36 +02:00
def test_smaller_user_group_mention_priority ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
cordelia = self . example_user ( " cordelia " )
2022-12-14 06:45:55 +01:00
hamlet_only = check_add_user_group (
get_realm ( " zulip " ) , " hamlet_only " , [ hamlet ] , acting_user = None
2022-11-21 03:37:11 +01:00
)
2022-12-14 06:45:55 +01:00
hamlet_and_cordelia = check_add_user_group (
get_realm ( " zulip " ) , " hamlet_and_cordelia " , [ hamlet , cordelia ] , acting_user = None
2020-05-21 14:51:36 +02:00
)
hamlet_only_message_id = self . send_stream_message ( othello , " Denmark " , " @*hamlet_only* " )
hamlet_and_cordelia_message_id = self . send_stream_message (
othello , " Denmark " , " @*hamlet_and_cordelia* "
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
hamlet_only_message_id : MissedMessageData (
trigger = " mentioned " , mentioned_user_group_id = hamlet_only . id
) ,
hamlet_and_cordelia_message_id : MissedMessageData (
trigger = " mentioned " , mentioned_user_group_id = hamlet_and_cordelia . id
) ,
} ,
2020-05-21 14:51:36 +02:00
)
expected_email_include = [
2020-08-04 12:06:41 +02:00
" Othello, the Moor of Venice: > @*hamlet_only* > @*hamlet_and_cordelia* -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because @hamlet_only was mentioned. " ,
2020-05-21 14:51:36 +02:00
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
def test_personal_over_user_group_mention_priority ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
othello = self . example_user ( " othello " )
2022-12-14 06:45:55 +01:00
hamlet_and_cordelia = check_add_user_group (
get_realm ( " zulip " ) , " hamlet_and_cordelia " , [ hamlet , cordelia ] , acting_user = None
2020-05-21 14:51:36 +02:00
)
user_group_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @*hamlet_and_cordelia* "
)
personal_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @**King Hamlet** "
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
user_group_mentioned_message_id : MissedMessageData (
trigger = " mentioned " , mentioned_user_group_id = hamlet_and_cordelia . id
) ,
personal_mentioned_message_id : MissedMessageData ( trigger = " mentioned " ) ,
} ,
2020-05-21 14:51:36 +02:00
)
expected_email_include = [
2020-08-04 12:06:41 +02:00
" Othello, the Moor of Venice: > @*hamlet_and_cordelia* > @**King Hamlet** -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because you were personally mentioned. " ,
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
2023-06-07 19:19:33 +02:00
def test_user_group_over_topic_wildcard_mention_in_followed_topic_priority ( self ) - > None :
2022-09-28 17:25:09 +02:00
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
othello = self . example_user ( " othello " )
2022-12-14 06:45:55 +01:00
hamlet_and_cordelia = check_add_user_group (
get_realm ( " zulip " ) , " hamlet_and_cordelia " , [ hamlet , cordelia ] , acting_user = None
2022-09-28 17:25:09 +02:00
)
2023-06-07 19:19:33 +02:00
topic_wildcard_mentioned_in_followed_topic_message_id = self . send_stream_message (
othello , " Denmark " , " @**topic** "
2023-06-22 06:32:32 +02:00
)
2022-09-28 17:25:09 +02:00
user_group_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @*hamlet_and_cordelia* "
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
2023-06-07 19:19:33 +02:00
topic_wildcard_mentioned_in_followed_topic_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned_in_followed_topic "
2023-07-12 19:05:57 +02:00
) ,
user_group_mentioned_message_id : MissedMessageData (
trigger = " mentioned " , mentioned_user_group_id = hamlet_and_cordelia . id
) ,
} ,
2022-09-28 17:25:09 +02:00
)
expected_email_include = [
2023-06-07 19:19:33 +02:00
" Othello, the Moor of Venice: > @**topic** > @*hamlet_and_cordelia* -- " ,
2022-09-28 17:25:09 +02:00
" You are receiving this because @hamlet_and_cordelia was mentioned. " ,
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
2023-06-07 19:19:33 +02:00
def test_topic_wildcard_in_followed_topic_over_stream_wildcard_mention_in_followed_topic_priority (
self ,
) - > None :
2022-09-28 17:25:09 +02:00
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
2023-06-07 19:19:33 +02:00
stream_wildcard_mentioned_in_followed_topic_message_id = self . send_stream_message (
othello , " Denmark " , " @**stream** "
)
topic_wildcard_mentioned_in_followed_topic_message_id = self . send_stream_message (
othello , " Denmark " , " @**topic** "
)
handle_missedmessage_emails (
hamlet . id ,
{
stream_wildcard_mentioned_in_followed_topic_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned_in_followed_topic "
) ,
topic_wildcard_mentioned_in_followed_topic_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned_in_followed_topic "
) ,
} ,
)
expected_email_include = [
" Othello, the Moor of Venice: > @**stream** > @**topic** -- " ,
" You are receiving this because all topic participants were mentioned in #Denmark > test. " ,
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
def test_stream_wildcard_in_followed_topic_over_topic_wildcard_mention_priority ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
topic_wildcard_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @**topic** "
2023-06-03 16:51:38 +02:00
)
stream_wildcard_mentioned_in_followed_topic_message_id = self . send_stream_message (
2023-06-22 06:32:32 +02:00
othello , " Denmark " , " @**all** "
)
2022-09-28 17:25:09 +02:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
2023-06-07 19:19:33 +02:00
topic_wildcard_mentioned_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned "
2023-07-12 19:05:57 +02:00
) ,
stream_wildcard_mentioned_in_followed_topic_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned_in_followed_topic "
) ,
} ,
2023-06-22 06:32:32 +02:00
)
expected_email_include = [
2023-06-07 19:19:33 +02:00
" Othello, the Moor of Venice: > @**topic** > @**all** -- " ,
2023-06-22 06:32:32 +02:00
" You are receiving this because you have wildcard mention notifications enabled for topics you follow. " ,
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
2023-06-07 19:19:33 +02:00
def test_topic_wildcard_over_stream_wildcard_mention_priority ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
stream_wildcard_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @**all** "
)
topic_wildcard_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @**topic** "
)
handle_missedmessage_emails (
hamlet . id ,
{
stream_wildcard_mentioned_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned "
) ,
topic_wildcard_mentioned_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned "
) ,
} ,
)
expected_email_include = [
" Othello, the Moor of Venice: > @**all** > @**topic** -- " ,
" You are receiving this because all topic participants were mentioned in #Denmark > test. " ,
]
for text in expected_email_include :
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
2023-06-03 16:51:38 +02:00
def test_stream_wildcard_mention_over_followed_topic_notify_priority ( self ) - > None :
2023-06-22 06:32:32 +02:00
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
followed_topic_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , " 1 " )
2023-06-03 16:51:38 +02:00
stream_wildcard_mentioned_message_id = self . send_stream_message (
othello , " Denmark " , " @**all** "
)
2023-06-22 06:32:32 +02:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
followed_topic_mentioned_message_id : MissedMessageData (
trigger = " followed_topic_email_notify "
) ,
stream_wildcard_mentioned_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned "
) ,
} ,
2022-09-28 17:25:09 +02:00
)
expected_email_include = [
" Othello, the Moor of Venice: > 1 > @**all** -- " ,
2022-09-29 18:56:25 +02:00
" You are receiving this because everyone was mentioned in #Denmark. " ,
2020-05-21 14:51:36 +02:00
]
for text in expected_email_include :
2023-06-22 06:32:32 +02:00
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
def test_followed_topic_notify_over_stream_message_notify_priority ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , " 0 " )
followed_topic_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , " 1 " )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
stream_mentioned_message_id : MissedMessageData ( trigger = " stream_email_notify " ) ,
followed_topic_mentioned_message_id : MissedMessageData (
trigger = " followed_topic_email_notify "
) ,
} ,
2023-06-22 06:32:32 +02:00
)
expected_email_include = [
" Othello, the Moor of Venice: > 0 > 1 -- " ,
" You are receiving this because you have email notifications enabled for topics you follow. " ,
]
for text in expected_email_include :
2020-05-21 14:51:36 +02:00
self . assertIn ( text , self . normalize_string ( mail . outbox [ 0 ] . body ) )
2023-01-14 20:36:37 +01:00
def test_include_realm_name_in_missedmessage_emails_subject ( self ) - > None :
user = self . example_user ( " hamlet " )
2018-02-06 20:04:14 +01:00
2023-01-14 20:36:37 +01:00
# Test with 'realm_name_in_notification_policy' set to 'Always'
do_change_user_setting (
user ,
" realm_name_in_email_notifications_policy " ,
UserProfile . REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_ALWAYS ,
acting_user = None ,
)
self . assertTrue ( include_realm_name_in_missedmessage_emails_subject ( user ) )
# Test with 'realm_name_in_notification_policy' set to 'Never'
do_change_user_setting (
user ,
" realm_name_in_email_notifications_policy " ,
UserProfile . REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_NEVER ,
acting_user = None ,
)
self . assertFalse ( include_realm_name_in_missedmessage_emails_subject ( user ) )
# Test with 'realm_name_in_notification_policy' set to 'Automatic'
do_change_user_setting (
user ,
" realm_name_in_email_notifications_policy " ,
UserProfile . REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_AUTOMATIC ,
acting_user = None ,
)
# Case 1: if user is part of a single realm, then realm_name is not present in notifications.
self . assertFalse ( include_realm_name_in_missedmessage_emails_subject ( user ) )
# Case 2: if user is part of multiple realms, then realm_name should be present in notifications.
# Create and verify a cross realm user.
cross_realm_user = do_create_user (
user . delivery_email , None , get_realm ( " lear " ) , user . full_name , acting_user = None
)
self . assertEqual ( cross_realm_user . delivery_email , user . delivery_email )
self . assertTrue ( include_realm_name_in_missedmessage_emails_subject ( cross_realm_user ) )
def test_realm_name_in_email_notifications_policy ( self ) - > None :
# Test with realm_name_in_email_notifications_policy set to Never.
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2023-01-14 20:36:37 +01:00
hamlet . realm_name_in_email_notifications_policy = (
UserProfile . REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_NEVER
)
hamlet . save ( update_fields = [ " realm_name_in_email_notifications_policy " ] )
with mock . patch (
" zerver.lib.email_notifications.include_realm_name_in_missedmessage_emails_subject " ,
return_value = False ,
) :
is_allowed = include_realm_name_in_missedmessage_emails_subject ( hamlet )
self . _realm_name_in_missed_message_email_subject ( is_allowed )
2018-02-06 20:04:14 +01:00
2023-01-14 20:36:37 +01:00
# Test with realm_name_in_email_notifications_policy set to Always.
# Note: We don't need to test separately for 'realm_name_in_email_notifications_policy'
# set to 'Automatic'.
# Here, we are concerned about the subject after the mocked function returns True/False.
# We already have separate test to check the appropriate behaviour of
# 'include_realm_name_in_missedmessage_emails_subject' for Automatic, Always, Never.
hamlet = self . example_user ( " hamlet " )
hamlet . realm_name_in_email_notifications_policy = (
UserProfile . REALM_NAME_IN_EMAIL_NOTIFICATIONS_POLICY_ALWAYS
)
hamlet . save ( update_fields = [ " realm_name_in_email_notifications_policy " ] )
with mock . patch (
" zerver.lib.email_notifications.include_realm_name_in_missedmessage_emails_subject " ,
return_value = True ,
) :
is_allowed = include_realm_name_in_missedmessage_emails_subject ( hamlet )
# Empty the test outbox
mail . outbox = [ ]
self . _realm_name_in_missed_message_email_subject ( is_allowed )
2018-02-06 20:04:14 +01:00
2017-11-29 13:42:39 +01:00
def test_message_content_disabled_in_missed_message_notifications ( self ) - > None :
# Test when user disabled message content in email notifications.
2021-09-08 15:36:08 +02:00
do_change_user_setting (
2021-04-08 11:10:34 +02:00
self . example_user ( " hamlet " ) ,
" message_content_in_email_notifications " ,
False ,
acting_user = None ,
2021-02-12 08:19:30 +01:00
)
2017-11-29 13:42:39 +01:00
self . _extra_context_in_missed_stream_messages_mention ( False , show_message_content = False )
mail . outbox = [ ]
2023-06-07 19:19:33 +02:00
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic (
False , show_message_content = False
)
mail . outbox = [ ]
2023-06-03 16:51:38 +02:00
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic (
2023-06-21 21:15:23 +02:00
False , show_message_content = False
)
mail . outbox = [ ]
2023-06-07 19:19:33 +02:00
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention (
False , show_message_content = False
)
mail . outbox = [ ]
2023-06-03 16:51:38 +02:00
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention (
2021-02-12 08:19:30 +01:00
False , show_message_content = False
)
2019-08-26 04:40:07 +02:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_personal_messages (
2021-02-12 08:19:30 +01:00
False , show_message_content = False , message_content_disabled_by_user = True
)
2017-11-29 13:42:39 +01:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_huddle_messages_two_others ( False , show_message_content = False )
2017-11-29 13:42:39 +01:00
2017-03-08 12:28:24 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2017-11-05 10:51:25 +01:00
def test_extra_context_in_missed_stream_messages_as_user ( self ) - > None :
2017-05-12 22:47:34 +02:00
self . _extra_context_in_missed_stream_messages_mention ( True )
2017-03-08 12:28:24 +01:00
2017-11-05 10:51:25 +01:00
def test_extra_context_in_missed_stream_messages ( self ) - > None :
2017-05-12 22:47:34 +02:00
self . _extra_context_in_missed_stream_messages_mention ( False )
2023-06-07 19:19:33 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
def test_extra_context_in_missed_stream_messages_as_user_topic_wildcard_in_followed_topic (
self ,
) - > None :
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic ( True )
def test_extra_context_in_missed_stream_messages_topic_wildcard_in_followed_topic (
self ,
) - > None :
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic (
False
)
2023-06-21 21:15:23 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2023-06-03 16:51:38 +02:00
def test_extra_context_in_missed_stream_messages_as_user_stream_wildcard_in_followed_topic (
2023-06-21 21:15:23 +02:00
self ,
) - > None :
2023-06-03 16:51:38 +02:00
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic (
True
)
2023-06-21 21:15:23 +02:00
2023-06-03 16:51:38 +02:00
def test_extra_context_in_missed_stream_messages_stream_wildcard_in_followed_topic (
self ,
) - > None :
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic (
False
)
2023-06-21 21:15:23 +02:00
2023-06-07 19:19:33 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
def test_extra_context_in_missed_stream_messages_as_user_topic_wildcard ( self ) - > None :
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention ( True )
def test_extra_context_in_missed_stream_messages_topic_wildcard ( self ) - > None :
self . _extra_context_in_missed_stream_messages_topic_wildcard_mention ( False )
2019-08-26 04:40:07 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2023-06-03 16:51:38 +02:00
def test_extra_context_in_missed_stream_messages_as_user_stream_wildcard ( self ) - > None :
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention ( True )
2019-08-26 04:40:07 +02:00
2023-06-03 16:51:38 +02:00
def test_extra_context_in_missed_stream_messages_stream_wildcard ( self ) - > None :
self . _extra_context_in_missed_stream_messages_stream_wildcard_mention ( False )
2019-08-26 04:40:07 +02:00
2017-05-12 22:47:34 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2017-11-05 10:51:25 +01:00
def test_extra_context_in_missed_stream_messages_as_user_two_senders ( self ) - > None :
2017-05-12 22:47:34 +02:00
self . _extra_context_in_missed_stream_messages_mention_two_senders ( True )
2017-11-05 10:51:25 +01:00
def test_extra_context_in_missed_stream_messages_two_senders ( self ) - > None :
2017-05-12 22:47:34 +02:00
self . _extra_context_in_missed_stream_messages_mention_two_senders ( False )
2017-03-08 12:28:24 +01:00
2020-08-05 23:49:24 +02:00
def test_reply_to_email_in_missed_personal_messages ( self ) - > None :
self . _reply_to_email_in_missed_personal_messages ( False )
2017-03-08 04:46:49 +01:00
2017-11-21 06:11:13 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
def test_extra_context_in_missed_stream_messages_email_notify_as_user ( self ) - > None :
self . _extra_context_in_missed_stream_messages_email_notify ( True )
def test_extra_context_in_missed_stream_messages_email_notify ( self ) - > None :
self . _extra_context_in_missed_stream_messages_email_notify ( False )
2022-10-28 00:25:31 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
def test_resolved_topic_missed_stream_messages_thread_friendly_as_user ( self ) - > None :
self . _resolved_topic_missed_stream_messages_thread_friendly ( True )
def test_resolved_topic_missed_stream_messages_thread_friendly ( self ) - > None :
self . _resolved_topic_missed_stream_messages_thread_friendly ( False )
2017-03-08 04:46:49 +01:00
@override_settings ( EMAIL_GATEWAY_PATTERN = " " )
2020-08-05 23:49:24 +02:00
def test_reply_warning_in_missed_personal_messages ( self ) - > None :
self . _reply_warning_in_missed_personal_messages ( False )
2017-03-08 04:46:49 +01:00
2017-03-08 12:28:24 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_personal_messages_as_user ( self ) - > None :
self . _extra_context_in_missed_personal_messages ( True )
2017-03-08 12:28:24 +01:00
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_personal_messages ( self ) - > None :
self . _extra_context_in_missed_personal_messages ( False )
2017-03-08 12:28:24 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_two_others_as_user ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_two_others ( True )
2017-05-03 09:22:58 +02:00
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_two_others ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_two_others ( False )
2017-05-03 09:22:58 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_three_others_as_user ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_three_others ( True )
2017-05-03 09:22:58 +02:00
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_three_others ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_three_others ( False )
2017-05-03 09:22:58 +02:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_many_others_as_user ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_many_others ( True )
2017-03-08 12:28:24 +01:00
2020-08-05 23:49:24 +02:00
def test_extra_context_in_missed_huddle_messages_many_others ( self ) - > None :
self . _extra_context_in_missed_huddle_messages_many_others ( False )
2017-03-14 08:38:01 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2017-11-05 10:51:25 +01:00
def test_deleted_message_in_missed_stream_messages_as_user ( self ) - > None :
2017-03-14 08:38:01 +01:00
self . _deleted_message_in_missed_stream_messages ( True )
2017-11-05 10:51:25 +01:00
def test_deleted_message_in_missed_stream_messages ( self ) - > None :
2017-03-14 08:38:01 +01:00
self . _deleted_message_in_missed_stream_messages ( False )
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_deleted_message_in_missed_personal_messages_as_user ( self ) - > None :
self . _deleted_message_in_missed_personal_messages ( True )
2017-03-14 08:38:01 +01:00
2020-08-05 23:49:24 +02:00
def test_deleted_message_in_missed_personal_messages ( self ) - > None :
self . _deleted_message_in_missed_personal_messages ( False )
2017-03-14 08:38:01 +01:00
@override_settings ( SEND_MISSED_MESSAGE_EMAILS_AS_USER = True )
2020-08-05 23:49:24 +02:00
def test_deleted_message_in_missed_huddle_messages_as_user ( self ) - > None :
self . _deleted_message_in_missed_huddle_messages ( True )
2017-03-14 08:38:01 +01:00
2020-08-05 23:49:24 +02:00
def test_deleted_message_in_missed_huddle_messages ( self ) - > None :
self . _deleted_message_in_missed_huddle_messages ( False )
2017-07-07 22:34:22 +02:00
2019-01-14 14:04:08 +01:00
def test_realm_message_content_allowed_in_email_notifications ( self ) - > None :
user = self . example_user ( " hamlet " )
# When message content is allowed at realm level
realm = get_realm ( " zulip " )
realm . message_content_allowed_in_email_notifications = True
2021-02-12 08:20:45 +01:00
realm . save ( update_fields = [ " message_content_allowed_in_email_notifications " ] )
2019-01-14 14:04:08 +01:00
# Emails have missed message content when message content is enabled by the user
2021-09-08 15:36:08 +02:00
do_change_user_setting (
2021-04-08 11:10:34 +02:00
user , " message_content_in_email_notifications " , True , acting_user = None
)
2019-01-14 14:04:08 +01:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_personal_messages ( False , show_message_content = True )
2019-01-14 14:04:08 +01:00
# Emails don't have missed message content when message content is disabled by the user
2021-09-08 15:36:08 +02:00
do_change_user_setting (
2021-04-08 11:10:34 +02:00
user , " message_content_in_email_notifications " , False , acting_user = None
)
2019-01-14 14:04:08 +01:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_personal_messages (
2021-02-12 08:19:30 +01:00
False , show_message_content = False , message_content_disabled_by_user = True
)
2019-01-14 14:04:08 +01:00
# When message content is not allowed at realm level
2021-04-20 23:27:25 +02:00
# Emails don't have message content irrespective of message content setting of the user
2019-01-14 14:04:08 +01:00
realm = get_realm ( " zulip " )
realm . message_content_allowed_in_email_notifications = False
2021-02-12 08:20:45 +01:00
realm . save ( update_fields = [ " message_content_allowed_in_email_notifications " ] )
2019-01-14 14:04:08 +01:00
2021-09-08 15:36:08 +02:00
do_change_user_setting (
2021-04-08 11:10:34 +02:00
user , " message_content_in_email_notifications " , True , acting_user = None
)
2019-01-14 14:04:08 +01:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_personal_messages (
2021-02-12 08:19:30 +01:00
False , show_message_content = False , message_content_disabled_by_realm = True
)
2019-01-14 14:04:08 +01:00
2021-09-08 15:36:08 +02:00
do_change_user_setting (
2021-04-08 11:10:34 +02:00
user , " message_content_in_email_notifications " , False , acting_user = None
)
2019-01-14 14:04:08 +01:00
mail . outbox = [ ]
2020-08-05 23:49:24 +02:00
self . _extra_context_in_missed_personal_messages (
2021-02-12 08:19:30 +01:00
False ,
show_message_content = False ,
message_content_disabled_by_user = True ,
message_content_disabled_by_realm = True ,
)
2019-01-14 14:04:08 +01:00
2019-12-19 18:26:44 +01:00
def test_realm_emoji_in_missed_message ( self ) - > None :
2019-03-15 18:56:56 +01:00
realm = get_realm ( " zulip " )
2017-07-07 22:34:22 +02:00
2017-10-28 17:52:15 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message with a realm emoji :green_tick:! " ,
2021-02-12 08:19:30 +01:00
)
2023-07-14 12:37:29 +02:00
realm_emoji_dict = get_name_keyed_dict_for_active_realm_emoji ( realm . id )
realm_emoji_id = realm_emoji_dict [ " green_tick " ] [ " id " ]
2021-02-12 08:19:30 +01:00
realm_emoji_url = (
f " http://zulip.testserver/user_avatars/ { realm . id } /emoji/images/ { realm_emoji_id } .png "
)
verify_body_include = [
2023-04-25 07:55:33 +02:00
f ' <img alt= " :green_tick: " src= " { realm_emoji_url } " style= " height: 20px; " title= " green tick " > '
2021-02-12 08:19:30 +01:00
]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2021-02-12 08:19:30 +01:00
self . _test_cases (
2023-04-05 11:19:58 +02:00
msg_id ,
verify_body_include ,
email_subject ,
send_as_user = False ,
verify_html_body = True ,
2021-02-12 08:19:30 +01:00
)
2017-10-28 00:55:16 +02:00
2019-12-19 18:26:44 +01:00
def test_emojiset_in_missed_message ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
hamlet . emojiset = " twitter "
hamlet . save ( update_fields = [ " emojiset " ] )
2017-10-28 00:55:16 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Extremely personal message with a hamburger :hamburger:! " ,
2021-02-12 08:19:30 +01:00
)
verify_body_include = [
2023-04-25 07:55:33 +02:00
' <img alt= " :hamburger: " src= " http://testserver/static/generated/emoji/images-twitter-64/1f354.png " style= " height: 20px; " title= " hamburger " > '
2021-02-12 08:19:30 +01:00
]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2021-02-12 08:19:30 +01:00
self . _test_cases (
2023-04-05 11:19:58 +02:00
msg_id ,
verify_body_include ,
email_subject ,
send_as_user = False ,
verify_html_body = True ,
2021-02-12 08:19:30 +01:00
)
2017-07-15 01:07:52 +02:00
2019-12-19 18:26:44 +01:00
def test_stream_link_in_missed_message ( self ) - > None :
2017-10-28 17:52:15 +02:00
msg_id = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" Come and join us in #**Verona**. " ,
2021-02-12 08:19:30 +01:00
)
2021-02-12 08:20:45 +01:00
stream_id = get_stream ( " Verona " , get_realm ( " zulip " ) ) . id
2020-06-09 00:25:09 +02:00
href = f " http://zulip.testserver/#narrow/stream/ { stream_id } -Verona "
2021-02-12 08:19:30 +01:00
verify_body_include = [
f ' <a class= " stream " data-stream-id= " { stream_id } " href= " { href } " >#Verona</a '
]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2021-02-12 08:19:30 +01:00
self . _test_cases (
2023-04-05 11:19:58 +02:00
msg_id ,
verify_body_include ,
email_subject ,
send_as_user = False ,
verify_html_body = True ,
2021-02-12 08:19:30 +01:00
)
2019-07-11 13:04:11 +02:00
2022-10-27 19:21:18 +02:00
def test_pm_link_in_missed_message_header ( self ) - > None :
cordelia = self . example_user ( " cordelia " )
msg_id = self . send_personal_message (
cordelia ,
self . example_user ( " hamlet " ) ,
2023-06-19 16:26:12 +02:00
" Let ' s test a direct message link in email notifications " ,
2022-10-27 19:21:18 +02:00
)
encoded_name = " Cordelia,-Lear ' s-daughter "
verify_body_include = [
2023-04-14 17:04:06 +02:00
f " view it in Zulip Dev Zulip: http://zulip.testserver/#narrow/dm/ { cordelia . id } - { encoded_name } "
2022-10-27 19:21:18 +02:00
]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Cordelia, Lear ' s daughter "
2022-10-27 19:21:18 +02:00
self . _test_cases ( msg_id , verify_body_include , email_subject , send_as_user = False )
2019-12-19 18:26:44 +01:00
def test_sender_name_in_missed_message ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2021-02-12 08:19:30 +01:00
msg_id_1 = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " iago " ) , " Denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
2021-02-12 08:20:45 +01:00
msg_id_2 = self . send_stream_message ( self . example_user ( " iago " ) , " Verona " , " * 1 \n *2 " )
msg_id_3 = self . send_personal_message ( self . example_user ( " iago " ) , hamlet , " Hello " )
2021-02-12 08:19:30 +01:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " mentioned " ) ,
msg_id_2 : MissedMessageData ( trigger = " stream_email_notify " ) ,
msg_id_3 : MissedMessageData ( trigger = " private_message " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2019-07-11 13:04:11 +02:00
2022-07-01 03:05:12 +02:00
assert isinstance ( mail . outbox [ 0 ] , EmailMultiAlternatives )
assert isinstance ( mail . outbox [ 0 ] . alternatives [ 0 ] [ 0 ] , str )
2020-08-04 12:06:41 +02:00
self . assertIn ( " Iago: \n > @**King Hamlet** \n \n -- \n You are " , mail . outbox [ 0 ] . body )
2019-07-11 13:04:11 +02:00
# If message content starts with <p> tag the sender name is appended inside the <p> tag.
2021-02-12 08:19:30 +01:00
self . assertIn (
2023-04-05 11:19:58 +02:00
' <p><b>Iago</b>: <span class= " user-mention " ' ,
2023-04-25 07:55:33 +02:00
mail . outbox [ 0 ] . alternatives [ 0 ] [ 0 ] ,
2021-02-12 08:19:30 +01:00
)
2019-07-11 13:04:11 +02:00
2022-07-01 03:05:12 +02:00
assert isinstance ( mail . outbox [ 1 ] , EmailMultiAlternatives )
assert isinstance ( mail . outbox [ 1 ] . alternatives [ 0 ] [ 0 ] , str )
2020-08-04 12:06:41 +02:00
self . assertIn ( " Iago: \n > * 1 \n > *2 \n \n -- \n You are receiving " , mail . outbox [ 1 ] . body )
2019-07-11 13:04:11 +02:00
# If message content does not starts with <p> tag sender name is appended before the <p> tag
2021-02-12 08:19:30 +01:00
self . assertIn (
2023-04-05 11:19:58 +02:00
" <b>Iago</b>: <div><ul> \n <li>1<br> \n *2</li> \n </ul></div> \n " ,
2023-04-25 07:55:33 +02:00
mail . outbox [ 1 ] . alternatives [ 0 ] [ 0 ] ,
2021-02-12 08:19:30 +01:00
)
2019-07-11 13:04:11 +02:00
2022-07-01 03:05:12 +02:00
assert isinstance ( mail . outbox [ 2 ] , EmailMultiAlternatives )
assert isinstance ( mail . outbox [ 2 ] . alternatives [ 0 ] [ 0 ] , str )
2020-08-04 12:06:41 +02:00
self . assertEqual ( " > Hello \n \n -- \n \n Reply " , mail . outbox [ 2 ] . body [ : 18 ] )
2023-06-19 16:26:12 +02:00
# Sender name is not appended to message for missed direct messages
2021-02-12 08:19:30 +01:00
self . assertIn (
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
" > \n \n <div><p>Hello</p></div> \n " ,
2023-04-25 07:55:33 +02:00
mail . outbox [ 2 ] . alternatives [ 0 ] [ 0 ] ,
2021-02-12 08:19:30 +01:00
)
2019-07-11 13:04:11 +02:00
2019-12-19 18:26:44 +01:00
def test_multiple_missed_personal_messages ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
2021-02-12 08:19:30 +01:00
msg_id_1 = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , hamlet , " Personal Message 1 "
2021-02-12 08:19:30 +01:00
)
msg_id_2 = self . send_personal_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " iago " ) , hamlet , " Personal Message 2 "
2021-02-12 08:19:30 +01:00
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " private_message " ) ,
msg_id_2 : MissedMessageData ( trigger = " private_message " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 2 )
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
2018-11-11 16:07:05 +01:00
self . assertEqual ( mail . outbox [ 0 ] . subject , email_subject )
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Iago "
2018-11-11 16:07:05 +01:00
self . assertEqual ( mail . outbox [ 1 ] . subject , email_subject )
2017-09-21 01:11:49 +02:00
2019-12-19 18:26:44 +01:00
def test_multiple_stream_messages ( self ) - > None :
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
msg_id_1 = self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " Message1 " )
msg_id_2 = self . send_stream_message ( self . example_user ( " iago " ) , " Denmark " , " Message2 " )
2021-02-12 08:19:30 +01:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " stream_email_notify " ) ,
msg_id_2 : MissedMessageData ( trigger = " stream_email_notify " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2018-11-11 16:07:05 +01:00
self . assertEqual ( mail . outbox [ 0 ] . subject , email_subject )
2017-11-21 06:11:13 +01:00
2019-12-19 18:26:44 +01:00
def test_multiple_stream_messages_and_mentions ( self ) - > None :
2019-07-08 08:02:17 +02:00
""" Subject should be stream name and topic as usual. """
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
msg_id_1 = self . send_stream_message ( self . example_user ( " iago " ) , " Denmark " , " Regular message " )
2021-02-12 08:19:30 +01:00
msg_id_2 = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , " Denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " stream_email_notify " ) ,
msg_id_2 : MissedMessageData ( trigger = " mentioned " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2018-11-11 16:07:05 +01:00
self . assertEqual ( mail . outbox [ 0 ] . subject , email_subject )
2017-11-21 06:11:13 +01:00
2019-12-19 18:26:44 +01:00
def test_message_access_in_emails ( self ) - > None :
2018-07-26 20:19:45 +02:00
# Messages sent to a protected history-private stream shouldn't be
# accessible/available in emails before subscribing
stream_name = " private_stream "
2021-02-12 08:19:30 +01:00
self . make_stream ( stream_name , invite_only = True , history_public_to_subscribers = False )
2021-02-12 08:20:45 +01:00
user = self . example_user ( " iago " )
2018-07-26 20:19:45 +02:00
self . subscribe ( user , stream_name )
2021-02-12 08:20:45 +01:00
late_subscribed_user = self . example_user ( " hamlet " )
2018-07-26 20:19:45 +02:00
2021-02-12 08:20:45 +01:00
self . send_stream_message ( user , stream_name , " Before subscribing " )
2018-07-26 20:19:45 +02:00
self . subscribe ( late_subscribed_user , stream_name )
2021-02-12 08:19:30 +01:00
self . send_stream_message ( user , stream_name , " After subscribing " )
2018-07-26 20:19:45 +02:00
2021-02-12 08:20:45 +01:00
mention_msg_id = self . send_stream_message ( user , stream_name , " @**King Hamlet** " )
2018-07-26 20:19:45 +02:00
2021-02-12 08:19:30 +01:00
handle_missedmessage_emails (
late_subscribed_user . id ,
2023-07-12 19:05:57 +02:00
{ mention_msg_id : MissedMessageData ( trigger = " mentioned " ) } ,
2021-02-12 08:19:30 +01:00
)
2018-07-26 20:19:45 +02:00
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2021-02-12 08:20:45 +01:00
self . assertEqual ( mail . outbox [ 0 ] . subject , " #private_stream > test " ) # email subject
2018-07-26 20:19:45 +02:00
email_text = mail . outbox [ 0 ] . message ( ) . as_string ( )
2021-02-12 08:20:45 +01:00
self . assertNotIn ( " Before subscribing " , email_text )
self . assertIn ( " After subscribing " , email_text )
self . assertIn ( " @**King Hamlet** " , email_text )
2018-07-26 20:19:45 +02:00
2019-12-19 18:26:44 +01:00
def test_stream_mentions_multiple_people ( self ) - > None :
2019-07-08 08:02:17 +02:00
""" Subject should be stream name and topic as usual. """
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
cordelia = self . example_user ( " cordelia " )
self . subscribe ( cordelia , " Denmark " )
2021-02-12 08:19:30 +01:00
msg_id_1 = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " iago " ) , " Denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
msg_id_2 = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " othello " ) , " Denmark " , " @**King Hamlet** "
2021-02-12 08:19:30 +01:00
)
tests: Ensure stream senders get a UserMessage row.
We now complain if a test author sends a stream message
that does not result in the sender getting a
UserMessage row for the message.
This is basically 100% equivalent to complaining that
the author failed to subscribe the sender to the stream
as part of the test setup, as far as I can tell, so the
AssertionError instructs the author to subscribe the
sender to the stream.
We exempt bots from this check, although it is
plausible we should only exempt the system bots like
the notification bot.
I considered auto-subscribing the sender to the stream,
but that can be a little more expensive than the
current check, and we generally want test setup to be
explicit.
If there is some legitimate way than a subscribed human
sender can't get a UserMessage, then we probably want
an explicit test for that, or we may want to change the
backend to just write a UserMessage row in that
hypothetical situation.
For most tests, including almost all the ones fixed
here, the author just wants their test setup to
realistically reflect normal operation, and often devs
may not realize that Cordelia is not subscribed to
Denmark or not realize that Hamlet is not subscribed to
Scotland.
Some of us don't remember our Shakespeare from high
school, and our stream subscriptions don't even
necessarily reflect which countries the Bard placed his
characters in.
There may also be some legitimate use case where an
author wants to simulate sending a message to an
unsubscribed stream, but for those edge cases, they can
always set allow_unsubscribed_sender to True.
2021-12-10 13:55:48 +01:00
msg_id_3 = self . send_stream_message ( cordelia , " Denmark " , " Regular message " )
2021-02-12 08:19:30 +01:00
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " mentioned " ) ,
msg_id_2 : MissedMessageData ( trigger = " mentioned " ) ,
msg_id_3 : MissedMessageData ( trigger = " stream_email_notify " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 1 )
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2018-11-11 16:07:05 +01:00
self . assertEqual ( mail . outbox [ 0 ] . subject , email_subject )
2017-11-21 06:11:13 +01:00
2019-12-19 18:26:44 +01:00
def test_multiple_stream_messages_different_topics ( self ) - > None :
2017-11-21 06:11:13 +01:00
""" Should receive separate emails for each topic within a stream. """
2021-02-12 08:20:45 +01:00
hamlet = self . example_user ( " hamlet " )
msg_id_1 = self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , " Message1 " )
2021-02-12 08:19:30 +01:00
msg_id_2 = self . send_stream_message (
2021-02-12 08:20:45 +01:00
self . example_user ( " iago " ) , " Denmark " , " Message2 " , topic_name = " test2 "
2021-02-12 08:19:30 +01:00
)
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
msg_id_1 : MissedMessageData ( trigger = " stream_email_notify " ) ,
msg_id_2 : MissedMessageData ( trigger = " stream_email_notify " ) ,
} ,
2021-02-12 08:19:30 +01:00
)
2021-05-17 05:41:32 +02:00
self . assert_length ( mail . outbox , 2 )
2018-11-11 16:07:05 +01:00
email_subjects = { mail . outbox [ 0 ] . subject , mail . outbox [ 1 ] . subject }
2021-02-12 08:20:45 +01:00
valid_email_subjects = { " #Denmark > test " , " #Denmark > test2 " }
2018-11-11 16:07:05 +01:00
self . assertEqual ( email_subjects , valid_email_subjects )
2017-11-21 06:11:13 +01:00
2017-11-05 10:51:25 +01:00
def test_relative_to_full_url ( self ) - > None :
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
def convert ( test_data : str ) - > str :
2021-09-15 01:07:21 +02:00
fragment = lxml . html . fragment_fromstring ( test_data , create_parent = True )
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
relative_to_full_url ( fragment , " http://example.com " )
return lxml . html . tostring ( fragment , encoding = " unicode " )
2019-07-24 07:47:04 +02:00
zulip_realm = get_realm ( " zulip " )
zephyr_realm = get_realm ( " zephyr " )
2017-09-21 01:11:49 +02:00
# Run `relative_to_full_url()` function over test fixtures present in
# 'markdown_test_cases.json' and check that it converts all the relative
# URLs to absolute URLs.
2020-08-07 01:09:47 +02:00
fixtures = orjson . loads ( self . fixture_data ( " markdown_test_cases.json " ) )
2017-09-21 01:11:49 +02:00
test_fixtures = { }
2021-02-12 08:20:45 +01:00
for test in fixtures [ " regular_tests " ] :
test_fixtures [ test [ " name " ] ] = test
2017-09-21 01:11:49 +02:00
for test_name in test_fixtures :
test_data = test_fixtures [ test_name ] [ " expected_output " ]
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
output_data = convert ( test_data )
2018-07-02 00:05:24 +02:00
if re . search ( r """ (?<= \ =[ ' " ])/(?=[^<]+>) """ , output_data ) is not None :
2021-02-12 08:19:30 +01:00
raise AssertionError (
" Relative URL present in email: "
+ output_data
+ " \n Failed test case ' s name is: "
+ test_name
+ " \n It is present in markdown_test_cases.json "
)
2017-09-21 01:11:49 +02:00
# Specific test cases.
2017-09-22 19:16:24 +02:00
# A path similar to our emoji path, but not in a link:
2017-09-21 01:11:49 +02:00
test_data = " <p>Check out the file at: ' /static/generated/emoji/images/emoji/ ' </p> "
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-09-15 01:07:21 +02:00
expected_output = (
" <div><p>Check out the file at: ' /static/generated/emoji/images/emoji/ ' </p></div> "
)
2017-09-21 01:11:49 +02:00
self . assertEqual ( actual_output , expected_output )
2017-09-22 19:16:24 +02:00
# An uploaded file
2019-07-24 07:47:04 +02:00
test_data = ' <a href= " /user_uploads/ {realm_id} /1f/some_random_value " >/user_uploads/ {realm_id} /1f/some_random_value</a> '
test_data = test_data . format ( realm_id = zephyr_realm . id )
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-02-12 08:19:30 +01:00
expected_output = (
2021-09-15 01:07:21 +02:00
' <div><a href= " http://example.com/user_uploads/ {realm_id} /1f/some_random_value " > '
2023-01-03 02:16:53 +01:00
" /user_uploads/ {realm_id} /1f/some_random_value</a></div> "
2021-02-12 08:19:30 +01:00
)
2019-07-24 07:47:04 +02:00
expected_output = expected_output . format ( realm_id = zephyr_realm . id )
2017-09-21 01:11:49 +02:00
self . assertEqual ( actual_output , expected_output )
2019-03-09 17:43:48 +01:00
# A profile picture like syntax, but not actually in an HTML tag
2017-09-21 01:11:49 +02:00
test_data = ' <p>Set src= " /avatar/username@example.com?s=30 " </p> '
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-09-15 01:07:21 +02:00
expected_output = ' <div><p>Set src= " /avatar/username@example.com?s=30 " </p></div> '
2017-09-21 01:11:49 +02:00
self . assertEqual ( actual_output , expected_output )
2017-09-27 19:39:42 +02:00
2017-10-11 22:41:19 +02:00
# A narrow URL which begins with a '#'.
2021-02-12 08:19:30 +01:00
test_data = (
' <p><a href= " #narrow/stream/test/topic/test.20topic/near/142 " '
2023-01-03 02:16:53 +01:00
' title= " #narrow/stream/test/topic/test.20topic/near/142 " >Conversation</a></p> '
2021-02-12 08:19:30 +01:00
)
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-02-12 08:19:30 +01:00
expected_output = (
2023-01-03 02:16:53 +01:00
' <div><p><a href= " http://example.com/#narrow/stream/test/topic/test.20topic/near/142 " '
' title= " http://example.com/#narrow/stream/test/topic/test.20topic/near/142 " >Conversation</a></p></div> '
2021-02-12 08:19:30 +01:00
)
2017-10-11 22:41:19 +02:00
self . assertEqual ( actual_output , expected_output )
2017-10-13 16:59:58 +02:00
# Scrub inline images.
2021-02-12 08:19:30 +01:00
test_data = (
2023-01-03 02:16:53 +01:00
" <p>See this <a "
' href= " /user_uploads/ {realm_id} /52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg " '
' target= " _blank " title= " avatar_103.jpeg " >avatar_103.jpeg</a>.</p> '
' <div class= " message_inline_image " ><a '
' href= " /user_uploads/ {realm_id} /52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg " '
' target= " _blank " title= " avatar_103.jpeg " ><img '
' src= " /user_uploads/ {realm_id} /52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg " ></a></div> '
2021-02-12 08:19:30 +01:00
)
2019-07-24 07:47:04 +02:00
test_data = test_data . format ( realm_id = zulip_realm . id )
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-02-12 08:19:30 +01:00
expected_output = (
2023-01-03 02:16:53 +01:00
" <div><p>See this <a "
' href= " http://example.com/user_uploads/ {realm_id} /52/fG7GM9e3afz_qsiUcSce2tl_/avatar_103.jpeg " '
' target= " _blank " title= " avatar_103.jpeg " >avatar_103.jpeg</a>.</p></div> '
2021-02-12 08:19:30 +01:00
)
2019-07-24 07:47:04 +02:00
expected_output = expected_output . format ( realm_id = zulip_realm . id )
2017-10-13 16:59:58 +02:00
self . assertEqual ( actual_output , expected_output )
2018-01-24 19:23:51 +01:00
# A message containing only an inline image URL preview, we do
# somewhat more extensive surgery.
2021-02-12 08:19:30 +01:00
test_data = (
2023-01-03 02:16:53 +01:00
' <div class= " message_inline_image " ><a '
' href= " https://www.google.com/images/srpr/logo4w.png " '
' target= " _blank " title= " https://www.google.com/images/srpr/logo4w.png " > '
' <img data-src-fullsize= " /thumbnail/https % 3A//www.google.com/images/srpr/logo4w.png?size=0x0 " '
' src= " /thumbnail/https % 3A//www.google.com/images/srpr/logo4w.png?size=0x100 " ></a></div> '
2021-02-12 08:19:30 +01:00
)
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = convert ( test_data )
2021-02-12 08:19:30 +01:00
expected_output = (
2023-01-03 02:16:53 +01:00
' <div><p><a href= " https://www.google.com/images/srpr/logo4w.png " '
' target= " _blank " title= " https://www.google.com/images/srpr/logo4w.png " > '
" https://www.google.com/images/srpr/logo4w.png</a></p></div> "
2021-02-12 08:19:30 +01:00
)
2018-01-24 19:23:51 +01:00
self . assertEqual ( actual_output , expected_output )
2020-07-15 01:21:28 +02:00
def test_spoilers_in_html_emails ( self ) - > None :
2021-02-12 08:20:45 +01:00
test_data = ' <div class= " spoiler-block " ><div class= " spoiler-header " > \n \n <p><a>header</a> text</p> \n </div><div class= " spoiler-content " aria-hidden= " true " > \n \n <p>content</p> \n </div></div> \n \n <p>outside spoiler</p> '
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
fragment = lxml . html . fromstring ( test_data )
fix_spoilers_in_html ( fragment , " en " )
actual_output = lxml . html . tostring ( fragment , encoding = " unicode " )
2021-08-04 09:17:02 +02:00
expected_output = ' <div><div class= " spoiler-block " > \n \n <p><a>header</a> text <span class= " spoiler-title " title= " Open Zulip to see the spoiler content " >(Open Zulip to see the spoiler content)</span></p> \n </div> \n \n <p>outside spoiler</p></div> '
2020-07-15 01:21:28 +02:00
self . assertEqual ( actual_output , expected_output )
# test against our markdown_test_cases so these features do not get out of sync.
2020-08-07 01:09:47 +02:00
fixtures = orjson . loads ( self . fixture_data ( " markdown_test_cases.json " ) )
2020-07-15 01:21:28 +02:00
test_fixtures = { }
2021-02-12 08:20:45 +01:00
for test in fixtures [ " regular_tests " ] :
if " spoiler " in test [ " name " ] :
test_fixtures [ test [ " name " ] ] = test
2020-07-15 01:21:28 +02:00
for test_name in test_fixtures :
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
fragment = lxml . html . fromstring ( test_fixtures [ test_name ] [ " expected_output " ] )
fix_spoilers_in_html ( fragment , " en " )
output_data = lxml . html . tostring ( fragment , encoding = " unicode " )
2021-02-12 08:20:45 +01:00
assert " spoiler-header " not in output_data
assert " spoiler-content " not in output_data
assert " spoiler-block " in output_data
assert " spoiler-title " in output_data
2020-07-15 01:21:28 +02:00
def test_spoilers_in_text_emails ( self ) - > None :
content = " @**King Hamlet** \n \n ```spoiler header text \n secret-text \n ``` "
2021-02-12 08:20:45 +01:00
msg_id = self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , content )
2021-02-12 08:19:30 +01:00
verify_body_include = [ " header text " , " Open Zulip to see the spoiler content " ]
2020-07-15 01:21:28 +02:00
verify_body_does_not_include = [ " secret-text " ]
2021-02-12 08:20:45 +01:00
email_subject = " #Denmark > test "
2020-07-15 01:21:28 +02:00
send_as_user = False
2021-02-12 08:19:30 +01:00
self . _test_cases (
msg_id ,
verify_body_include ,
email_subject ,
send_as_user ,
2021-02-12 08:20:45 +01:00
trigger = " mentioned " ,
2021-02-12 08:19:30 +01:00
verify_body_does_not_include = verify_body_does_not_include ,
)
2020-07-15 01:21:28 +02:00
2017-11-05 10:51:25 +01:00
def test_fix_emoji ( self ) - > None :
2017-09-27 19:39:42 +02:00
# An emoji.
2021-02-12 08:19:30 +01:00
test_data = (
2023-01-03 02:16:53 +01:00
' <p>See <span aria-label= " cloud with lightning and rain " class= " emoji emoji-26c8 " '
' role= " img " title= " cloud with lightning and '
' rain " >:cloud_with_lightning_and_rain:</span>.</p> '
2021-02-12 08:19:30 +01:00
)
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
fragment = lxml . html . fromstring ( test_data )
2023-02-07 22:34:16 +01:00
fix_emojis ( fragment , " google " )
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
actual_output = lxml . html . tostring ( fragment , encoding = " unicode " )
2021-02-12 08:19:30 +01:00
expected_output = (
2023-01-03 02:16:53 +01:00
' <p>See <img alt= " :cloud_with_lightning_and_rain: " '
2023-02-07 22:34:16 +01:00
' src= " http://testserver/static/generated/emoji/images-google-64/26c8.png " '
2023-01-03 02:16:53 +01:00
' title= " cloud with lightning and rain " style= " height: 20px; " >.</p> '
2021-02-12 08:19:30 +01:00
)
2017-09-27 19:39:42 +02:00
self . assertEqual ( actual_output , expected_output )
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
def test_empty_backticks_in_missed_message ( self ) - > None :
msg_id = self . send_personal_message (
self . example_user ( " othello " ) ,
self . example_user ( " hamlet " ) ,
" ``` \n ``` " ,
)
2022-09-29 10:05:54 +02:00
verify_body_include = [ " view it in Zulip Dev Zulip " ]
2023-01-24 19:47:48 +01:00
email_subject = " DMs with Othello, the Moor of Venice "
email_notifications: Handle empty rendered_messages.
The transforms called from `build_message_payload` use
`lxml.html.fromstring` to parse (and stringify, and re-parse) the HTML
generated by Markdown. However, this function fails if it is passed
an empty document. "empty" is broader than just the empty string; it
also includes any document made entirely out of control characters,
spaces, unpaired surrogates, U+FFFE, or U+FFFF, and so forth. These
documents would fail to parse, and raise a ParserError.
Using `lxml.html.fragment_fromstring` handles these cases, but does by
wrapping the contents in a <div> every time it is called. As such,
replacing each `fromstring` with `fragment_fromstring` would nest
another layer of `<div>`.
Instead of each of the helper functions re-parsing, modifying, and
stringifying the HTML, parse it once with `fragment_fromstring` and
pass around the parsed document to each helper, which modifies it
in-place. This adds one outer `<div>`, requiring minor changes to
tests and the prepend-sender functions.
The modification to add the sender is left using BeautifulSoup, as
that sort of transform is much less readable, and more fiddly, in raw
lxml.
Partial fix for #19559.
2021-08-19 20:10:35 +02:00
self . _test_cases (
msg_id , verify_body_include , email_subject , send_as_user = False , verify_html_body = True
)
2022-04-17 01:47:25 +02:00
2022-04-15 04:51:41 +02:00
def test_long_term_idle_user_missed_message ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
cordelia = self . example_user ( " cordelia " )
2022-12-14 06:45:55 +01:00
large_user_group = check_add_user_group (
get_realm ( " zulip " ) , " large_user_group " , [ hamlet , othello , cordelia ] , acting_user = None
2022-04-15 04:51:41 +02:00
)
# Do note that the event dicts for the missed messages are constructed by hand
# The part of testing the consumption of missed messages by the worker is left to
# test_queue_worker.test_missed_message_worker
# Personal mention in a stream message should soft reactivate the user
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = False ) :
mention = f " @** { hamlet . full_name } ** "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{ stream_mentioned_message_id : MissedMessageData ( trigger = " mentioned " ) } ,
2022-04-15 04:51:41 +02:00
)
2023-06-19 16:26:12 +02:00
# Direct message should soft reactivate the user
2022-04-15 04:51:41 +02:00
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = False ) :
# Soft reactivate the user by sending a personal message
personal_message_id = self . send_personal_message ( othello , hamlet , " Message " )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{ personal_message_id : MissedMessageData ( trigger = " private_message " ) } ,
2022-04-15 04:51:41 +02:00
)
2023-07-05 11:59:56 +02:00
# Hamlet FOLLOWS the topic.
# 'wildcard_mentions_notify' is disabled to verify the corner case when only
# 'enable_followed_topic_wildcard_mentions_notify' is enabled (True by default).
do_set_user_topic_visibility_policy (
hamlet ,
get_stream ( " Denmark " , hamlet . realm ) ,
" test " ,
visibility_policy = UserTopic . VisibilityPolicy . FOLLOWED ,
)
do_change_user_setting ( hamlet , " wildcard_mentions_notify " , False , acting_user = None )
2023-06-09 19:43:36 +02:00
# Topic wildcard mention in followed topic should soft reactivate the user
# hamlet should be a topic participant
self . send_stream_message ( hamlet , " Denmark " , " test message " )
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = False ) :
mention = " @**topic** "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
{
stream_mentioned_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned_in_followed_topic "
) ,
} ,
)
2023-06-03 16:51:38 +02:00
# Stream wildcard mention in followed topic should NOT soft reactivate the user
2023-06-21 21:15:23 +02:00
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = True ) :
mention = " @**all** "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
stream_mentioned_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned_in_followed_topic "
) ,
} ,
2023-06-21 21:15:23 +02:00
)
2023-07-05 11:59:56 +02:00
# Reset
do_set_user_topic_visibility_policy (
hamlet ,
get_stream ( " Denmark " , hamlet . realm ) ,
" test " ,
visibility_policy = UserTopic . VisibilityPolicy . INHERIT ,
)
do_change_user_setting ( hamlet , " wildcard_mentions_notify " , True , acting_user = None )
2023-06-09 19:43:36 +02:00
# Topic Wildcard mention should soft reactivate the user
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = False ) :
mention = " @**topic** "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
{
stream_mentioned_message_id : MissedMessageData (
trigger = " topic_wildcard_mentioned "
) ,
} ,
)
2023-06-03 16:51:38 +02:00
# Stream Wildcard mention should NOT soft reactivate the user
2022-04-15 04:51:41 +02:00
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = True ) :
mention = " @**all** "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
stream_mentioned_message_id : MissedMessageData (
trigger = " stream_wildcard_mentioned "
) ,
} ,
2022-04-15 04:51:41 +02:00
)
# Group mention should NOT soft reactivate the user
with self . soft_deactivate_and_check_long_term_idle ( hamlet , expected = True ) :
mention = " @*large_user_group* "
stream_mentioned_message_id = self . send_stream_message ( othello , " Denmark " , mention )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{
stream_mentioned_message_id : MissedMessageData (
trigger = " mentioned " , mentioned_user_group_id = large_user_group . id
) ,
} ,
2022-04-15 04:51:41 +02:00
)
2023-05-17 16:01:16 +02:00
def test_followed_topic_missed_message ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
othello = self . example_user ( " othello " )
msg_id = self . send_stream_message ( othello , " Denmark " )
handle_missedmessage_emails (
hamlet . id ,
2023-07-12 19:05:57 +02:00
{ msg_id : MissedMessageData ( trigger = " followed_topic_email_notify " ) } ,
2023-05-17 16:01:16 +02:00
)
self . assert_length ( mail . outbox , 1 )
email_subject = mail . outbox [ 0 ] . subject
email_body = mail . outbox [ 0 ] . body
self . assertEqual ( " #Denmark > test " , email_subject )
self . assertIn (
" You are receiving this because you have email notifications enabled for topics you follow. " ,
email_body ,
)
2022-04-17 01:47:25 +02:00
class TestFollowupEmailDelay ( ZulipTestCase ) :
2023-03-15 18:09:26 +01:00
def test_get_onboarding_email_schedule ( self ) - > None :
2022-04-17 01:47:25 +02:00
user_profile = self . example_user ( " hamlet " )
2023-03-15 18:09:26 +01:00
dates_joined = {
" Monday " : datetime ( 2018 , 1 , 1 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Tuesday " : datetime ( 2018 , 1 , 2 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Wednesday " : datetime ( 2018 , 1 , 3 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Thursday " : datetime ( 2018 , 1 , 4 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Friday " : datetime ( 2018 , 1 , 5 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Saturday " : datetime ( 2018 , 1 , 6 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
" Sunday " : datetime ( 2018 , 1 , 7 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc ) ,
}
2023-03-15 20:18:09 +01:00
days_delayed = {
" 2 " : timedelta ( days = 2 , hours = - 1 ) ,
" 4 " : timedelta ( days = 4 , hours = - 1 ) ,
" 6 " : timedelta ( days = 6 , hours = - 1 ) ,
}
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# joined Monday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Monday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# followup_day2 email sent on Wednesday
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 2 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Monday " ] + days_delayed [ " 2 " ] ) . isoweekday ( ) , 3 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# onboarding_zulip_guide sent on Friday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 4 " ] ,
)
self . assertEqual ( ( dates_joined [ " Monday " ] + days_delayed [ " 4 " ] ) . isoweekday ( ) , 5 )
# joined Tuesday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Tuesday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# followup_day2 email sent on Thursday
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 2 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Tuesday " ] + days_delayed [ " 2 " ] ) . isoweekday ( ) , 4 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# onboarding_zulip_guide sent on Monday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 6 " ] ,
)
self . assertEqual ( ( dates_joined [ " Tuesday " ] + days_delayed [ " 6 " ] ) . isoweekday ( ) , 1 )
# joined Wednesday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Wednesday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# followup_day2 email sent on Friday
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 2 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Wednesday " ] + days_delayed [ " 2 " ] ) . isoweekday ( ) , 5 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# onboarding_zulip_guide sent on Tuesday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 6 " ] ,
)
self . assertEqual ( ( dates_joined [ " Wednesday " ] + days_delayed [ " 6 " ] ) . isoweekday ( ) , 2 )
# joined Thursday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Thursday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
# followup_day2 email sent on Monday
2023-03-15 18:09:26 +01:00
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 4 " ] ,
)
self . assertEqual ( ( dates_joined [ " Thursday " ] + days_delayed [ " 4 " ] ) . isoweekday ( ) , 1 )
# onboarding_zulip_guide sent on Wednesday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 6 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Thursday " ] + days_delayed [ " 6 " ] ) . isoweekday ( ) , 3 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# joined Friday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Friday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
# followup_day2 email sent on Tuesday
2023-03-15 18:09:26 +01:00
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 4 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Friday " ] + days_delayed [ " 4 " ] ) . isoweekday ( ) , 2 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# onboarding_zulip_guide sent on Thursday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 6 " ] ,
)
self . assertEqual ( ( dates_joined [ " Friday " ] + days_delayed [ " 6 " ] ) . isoweekday ( ) , 4 )
# joined Saturday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Saturday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# followup_day2 email sent on Monday
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 2 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Saturday " ] + days_delayed [ " 2 " ] ) . isoweekday ( ) , 1 )
2023-03-15 18:09:26 +01:00
2023-03-15 20:18:09 +01:00
# onboarding_zulip_guide sent on Wednesday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 4 " ] ,
)
self . assertEqual ( ( dates_joined [ " Saturday " ] + days_delayed [ " 4 " ] ) . isoweekday ( ) , 3 )
# joined Sunday
2023-03-15 18:09:26 +01:00
user_profile . date_joined = dates_joined [ " Sunday " ]
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# followup_day2 email sent on Tuesday
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 2 " ] ,
2023-03-15 18:09:26 +01:00
)
2023-03-15 20:18:09 +01:00
self . assertEqual ( ( dates_joined [ " Sunday " ] + days_delayed [ " 2 " ] ) . isoweekday ( ) , 2 )
# onboarding_zulip_guide sent on Thursday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 4 " ] ,
)
self . assertEqual ( ( dates_joined [ " Sunday " ] + days_delayed [ " 4 " ] ) . isoweekday ( ) , 4 )
2023-03-15 18:09:26 +01:00
# Time offset of America/Phoenix is -07:00
user_profile . timezone = " America/Phoenix "
2023-03-15 20:18:09 +01:00
2023-03-15 18:09:26 +01:00
# Test date_joined == Friday in UTC, but Thursday in the user's time zone
2022-04-17 01:47:25 +02:00
user_profile . date_joined = datetime ( 2018 , 1 , 5 , 1 , 0 , 0 , 0 , tzinfo = timezone . utc )
2023-03-15 18:09:26 +01:00
onboarding_email_schedule = get_onboarding_email_schedule ( user_profile )
2023-03-15 20:18:09 +01:00
# followup_day2 email sent on Monday
2023-03-15 18:09:26 +01:00
self . assertEqual (
onboarding_email_schedule [ " followup_day2 " ] ,
2023-03-15 20:18:09 +01:00
days_delayed [ " 4 " ] ,
)
# onboarding_zulip_guide sent on Wednesday
self . assertEqual (
onboarding_email_schedule [ " onboarding_zulip_guide " ] ,
days_delayed [ " 6 " ] ,
2023-03-15 18:09:26 +01:00
)
2022-08-02 07:16:49 +02:00
2023-06-30 13:27:25 +02:00
class TestCustomWelcomeEmailSender ( ZulipTestCase ) :
def test_custom_welcome_email_sender ( self ) - > None :
2022-08-02 07:16:49 +02:00
name = " Nonreg Email "
email = self . nonreg_email ( " test " )
with override_settings (
WELCOME_EMAIL_SENDER = {
" name " : name ,
" email " : email ,
}
) :
hamlet = self . example_user ( " hamlet " )
enqueue_welcome_emails ( hamlet )
2023-03-15 20:18:09 +01:00
scheduled_emails = ScheduledEmail . objects . filter ( users = hamlet ) . order_by (
" scheduled_timestamp "
)
2022-08-02 07:16:49 +02:00
email_data = orjson . loads ( scheduled_emails [ 0 ] . data )
self . assertEqual ( email_data [ " from_name " ] , name )
self . assertEqual ( email_data [ " from_address " ] , email )