2020-06-11 00:54:34 +02:00
import copy
import os
import re
2022-06-13 06:02:57 +02:00
from html import escape
2020-02-14 00:09:22 +01:00
from textwrap import dedent
2021-07-16 00:45:17 +02:00
from typing import Any , Dict , List , Optional , Set , Tuple , cast
2020-06-11 00:54:34 +02:00
from unittest import mock
2020-08-07 01:09:47 +02:00
import orjson
2023-01-07 05:30:32 +01:00
from bs4 import BeautifulSoup
2014-01-31 23:13:58 +01:00
from django . conf import settings
2020-07-01 04:19:54 +02:00
from django . test import override_settings
2020-11-10 21:43:57 +01:00
from markdown import Markdown
2014-01-31 23:13:58 +01:00
2022-04-14 23:35:09 +02:00
from zerver . actions . alert_words import do_add_alert_words
2022-04-14 23:58:15 +02:00
from zerver . actions . create_realm import do_create_realm
2022-04-14 23:40:49 +02:00
from zerver . actions . realm_emoji import do_remove_realm_emoji
2022-04-14 23:57:15 +02:00
from zerver . actions . realm_settings import do_set_realm_property
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
2022-04-14 23:48:28 +02:00
from zerver . actions . users import change_user_is_active
2019-02-11 15:19:38 +01:00
from zerver . lib . alert_words import get_alert_word_automaton
2021-03-23 10:34:55 +01:00
from zerver . lib . camo import get_camo_url
2017-10-13 00:06:24 +02:00
from zerver . lib . create_user import create_user
2017-03-13 05:45:50 +01:00
from zerver . lib . emoji import get_emoji_url
2022-11-17 09:30:48 +01:00
from zerver . lib . exceptions import JsonableError , MarkdownRenderingError
2020-06-27 02:18:01 +02:00
from zerver . lib . markdown import (
2023-01-07 05:30:32 +01:00
InlineInterestingLinkProcessor ,
2020-06-27 02:18:01 +02:00
MarkdownListPreprocessor ,
2021-06-17 12:20:40 +02:00
MessageRenderingResult ,
2020-06-27 02:18:01 +02:00
clear_state_for_testing ,
content_has_emoji_syntax ,
fetch_tweet_data ,
get_tweet_id ,
image_preview_enabled ,
2020-07-04 14:34:46 +02:00
markdown_convert ,
2020-06-27 02:18:01 +02:00
maybe_update_markdown_engines ,
possible_linked_stream_names ,
topic_links ,
url_embed_preview_enabled ,
url_to_a ,
)
2020-06-27 02:13:47 +02:00
from zerver . lib . markdown . fenced_code import FencedBlockPreprocessor
2020-06-27 02:11:33 +02:00
from zerver . lib . mdiff import diff_strings
2021-06-13 00:51:30 +02:00
from zerver . lib . mention import (
2021-12-28 10:02:27 +01:00
FullNameInfo ,
2021-12-29 13:52:27 +01:00
MentionBackend ,
2021-06-13 00:51:30 +02:00
MentionData ,
2023-05-30 07:23:36 +02:00
PossibleMentions ,
2021-06-13 00:51:30 +02:00
get_possible_mentions_info ,
possible_mentions ,
possible_user_group_mentions ,
)
2016-10-04 18:32:46 +02:00
from zerver . lib . message import render_markdown
2020-06-11 00:54:34 +02:00
from zerver . lib . test_classes import ZulipTestCase
2018-03-18 20:49:28 +01:00
from zerver . lib . tex import render_tex
2014-01-31 23:13:58 +01:00
from zerver . models import (
Message ,
2018-03-11 18:55:20 +01:00
RealmEmoji ,
2014-01-31 23:13:58 +01:00
RealmFilter ,
2020-06-11 00:54:34 +02:00
UserGroup ,
2020-05-11 21:59:03 +02:00
UserMessage ,
2017-10-13 00:06:24 +02:00
UserProfile ,
2021-03-30 12:15:39 +02:00
flush_linkifiers ,
2020-06-11 00:54:34 +02:00
flush_per_request_caches ,
get_client ,
get_realm ,
get_stream ,
2021-03-30 12:15:39 +02:00
linkifiers_for_realm ,
realm_in_local_linkifiers_cache ,
2014-01-31 23:13:58 +01:00
)
2019-01-29 21:05:16 +01:00
2020-07-01 00:37:00 +02:00
class SimulatedFencedBlockPreprocessor ( FencedBlockPreprocessor ) :
# Simulate code formatting.
2021-05-13 19:42:53 +02:00
def format_code ( self , lang : Optional [ str ] , code : str ) - > str :
return ( lang or " " ) + " : " + code
2020-07-01 00:37:00 +02:00
def placeholder ( self , s : str ) - > str :
2021-02-12 08:20:45 +01:00
return " ** " + s . strip ( " \n " ) + " ** "
2020-07-01 00:37:00 +02:00
2020-07-01 04:19:54 +02:00
class FencedBlockPreprocessorTest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_simple_quoting ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = FencedBlockPreprocessor ( Markdown ( ) )
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ~~~ quote " ,
" hi " ,
" bye " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
expected = [
2021-02-12 08:20:45 +01:00
" " ,
" > hi " ,
" > bye " ,
" > " ,
" > " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
2020-06-26 12:10:42 +02:00
lines = processor . run ( markdown_input )
2014-01-31 23:13:58 +01:00
self . assertEqual ( lines , expected )
2017-11-05 10:51:25 +01:00
def test_serial_quoting ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = FencedBlockPreprocessor ( Markdown ( ) )
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ~~~ quote " ,
" hi " ,
" ~~~ " ,
" " ,
" ~~~ quote " ,
" bye " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
expected = [
2021-02-12 08:20:45 +01:00
" " ,
" > hi " ,
" " ,
" " ,
" " ,
" > bye " ,
" > " ,
" > " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
2020-06-26 12:10:42 +02:00
lines = processor . run ( markdown_input )
2014-01-31 23:13:58 +01:00
self . assertEqual ( lines , expected )
2017-11-05 10:51:25 +01:00
def test_serial_code ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = SimulatedFencedBlockPreprocessor ( Markdown ( ) )
2014-01-31 23:13:58 +01:00
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ``` .py " ,
" hello() " ,
" ``` " ,
" " ,
" ```vb.net " ,
" goodbye() " ,
" ``` " ,
" " ,
" ```c# " ,
" weirdchar() " ,
" ``` " ,
" " ,
" ``` " ,
" no-highlight() " ,
" ``` " ,
" " ,
2014-01-31 23:13:58 +01:00
]
expected = [
2021-02-12 08:20:45 +01:00
" " ,
" **py:hello()** " ,
" " ,
" " ,
" " ,
" **vb.net:goodbye()** " ,
" " ,
" " ,
" " ,
" **c#:weirdchar()** " ,
" " ,
" " ,
" " ,
" **:no-highlight()** " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
2020-06-26 12:10:42 +02:00
lines = processor . run ( markdown_input )
2014-01-31 23:13:58 +01:00
self . assertEqual ( lines , expected )
2017-11-05 10:51:25 +01:00
def test_nested_code ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = SimulatedFencedBlockPreprocessor ( Markdown ( ) )
2014-01-31 23:13:58 +01:00
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ~~~ quote " ,
" hi " ,
" ``` .py " ,
" hello() " ,
" ``` " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
expected = [
2021-02-12 08:20:45 +01:00
" " ,
" > hi " ,
" > " ,
" > **py:hello()** " ,
" > " ,
" > " ,
" > " ,
" " ,
" " ,
2014-01-31 23:13:58 +01:00
]
2020-06-26 12:10:42 +02:00
lines = processor . run ( markdown_input )
2014-01-31 23:13:58 +01:00
self . assertEqual ( lines , expected )
2021-02-12 08:19:30 +01:00
2020-07-04 14:34:46 +02:00
def markdown_convert_wrapper ( content : str ) - > str :
return markdown_convert (
2019-01-29 21:05:16 +01:00
content = content ,
2021-02-12 08:20:45 +01:00
message_realm = get_realm ( " zulip " ) ,
2021-06-17 12:20:40 +02:00
) . rendered_content
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
2020-06-27 00:35:15 +02:00
class MarkdownMiscTest ( ZulipTestCase ) :
2017-12-04 10:10:15 +01:00
def test_diffs_work_as_expected ( self ) - > None :
str1 = " <p>The quick brown fox jumps over the lazy dog. Animal stories are fun, yeah</p> "
str2 = " <p>The fast fox jumps over the lazy dogs and cats. Animal stories are fun</p> "
expected_diff = " \u001b [34m- \u001b [0m <p>The \u001b [33mquick brown \u001b [0m fox jumps over the lazy dog. Animal stories are fun \u001b [31m, yeah \u001b [0m</p> \n \u001b [34m+ \u001b [0m <p>The \u001b [33mfast \u001b [0m fox jumps over the lazy dog \u001b [32ms and cats \u001b [0m. Animal stories are fun</p> \n "
2020-06-27 02:11:33 +02:00
self . assertEqual ( diff_strings ( str1 , str2 ) , expected_diff )
2017-12-04 10:10:15 +01:00
2018-11-13 18:50:15 +01:00
def test_get_possible_mentions_info ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2017-10-13 00:06:24 +02:00
2018-05-11 01:39:38 +02:00
def make_user ( email : str , full_name : str ) - > UserProfile :
2017-10-13 00:06:24 +02:00
return create_user (
email = email ,
2021-02-12 08:20:45 +01:00
password = " whatever " ,
2017-10-13 00:06:24 +02:00
realm = realm ,
full_name = full_name ,
)
2021-02-12 08:20:45 +01:00
fred1 = make_user ( " fred1@example.com " , " Fred Flintstone " )
2021-02-14 00:03:40 +01:00
change_user_is_active ( fred1 , False )
2017-10-13 00:06:24 +02:00
2021-02-12 08:20:45 +01:00
fred2 = make_user ( " fred2@example.com " , " Fred Flintstone " )
2017-10-13 00:06:24 +02:00
2021-02-12 08:20:45 +01:00
fred3 = make_user ( " fred3@example.com " , " Fred Flintstone " )
2021-02-14 00:03:40 +01:00
change_user_is_active ( fred3 , False )
2017-10-13 00:06:24 +02:00
2021-02-12 08:20:45 +01:00
fred4 = make_user ( " fred4@example.com " , " Fred Flintstone " )
2018-08-18 23:21:47 +02:00
2021-12-29 13:52:27 +01:00
mention_backend = MentionBackend ( realm . id )
2021-02-12 08:19:30 +01:00
lst = get_possible_mentions_info (
2021-12-29 13:52:27 +01:00
mention_backend , { " Fred Flintstone " , " Cordelia, LEAR ' s daughter " , " Not A User " }
2021-02-12 08:19:30 +01:00
)
2022-10-30 02:30:03 +02:00
set_of_names = { x . full_name . lower ( ) for x in lst }
2021-04-11 16:26:54 +02:00
self . assertEqual ( set_of_names , { " fred flintstone " , " cordelia, lear ' s daughter " } )
2018-11-02 09:15:46 +01:00
2021-12-28 10:02:27 +01:00
by_id = { row . id : row for row in lst }
2021-02-12 08:19:30 +01:00
self . assertEqual (
by_id . get ( fred2 . id ) ,
2021-12-28 10:02:27 +01:00
FullNameInfo (
2021-02-12 08:20:45 +01:00
full_name = " Fred Flintstone " ,
2021-02-12 08:19:30 +01:00
id = fred2 . id ,
) ,
)
self . assertEqual (
by_id . get ( fred4 . id ) ,
2021-12-28 10:02:27 +01:00
FullNameInfo (
2021-02-12 08:20:45 +01:00
full_name = " Fred Flintstone " ,
2021-02-12 08:19:30 +01:00
id = fred4 . id ,
) ,
)
2017-10-13 00:06:24 +02:00
2017-11-05 10:51:25 +01:00
def test_mention_data ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
2021-04-11 16:26:54 +02:00
content = " @**King Hamlet** @**Cordelia, lear ' s daughter** "
2021-12-29 13:52:27 +01:00
mention_backend = MentionBackend ( realm . id )
mention_data = MentionData ( mention_backend , content )
2017-10-24 17:36:27 +02:00
self . assertEqual ( mention_data . get_user_ids ( ) , { hamlet . id , cordelia . id } )
2021-02-12 08:19:30 +01:00
self . assertEqual (
mention_data . get_user_by_id ( hamlet . id ) ,
2021-12-28 10:02:27 +01:00
FullNameInfo (
2021-02-12 08:19:30 +01:00
full_name = hamlet . full_name ,
id = hamlet . id ,
) ,
)
2017-10-24 17:36:27 +02:00
2021-02-12 08:20:45 +01:00
user = mention_data . get_user_by_name ( " king hamLET " )
2021-02-12 08:19:30 +01:00
assert user is not None
2021-12-28 10:02:27 +01:00
self . assertEqual ( user . full_name , hamlet . full_name )
2017-10-24 17:36:27 +02:00
2023-06-03 16:51:38 +02:00
self . assertFalse ( mention_data . message_has_stream_wildcards ( ) )
2021-04-11 16:26:54 +02:00
content = " @**King Hamlet** @**Cordelia, lear ' s daughter** @**all** "
2021-12-29 13:52:27 +01:00
mention_data = MentionData ( mention_backend , content )
2023-06-03 16:51:38 +02:00
self . assertTrue ( mention_data . message_has_stream_wildcards ( ) )
2019-11-22 10:38:34 +01:00
2018-03-18 20:49:28 +01:00
def test_invalid_katex_path ( self ) - > None :
2019-06-29 10:13:08 +02:00
with self . settings ( DEPLOY_ROOT = " /nonexistent " ) :
2020-07-19 14:48:33 +02:00
with self . assertLogs ( level = " ERROR " ) as m :
2018-03-18 20:49:28 +01:00
render_tex ( " random text " )
2020-07-19 14:48:33 +02:00
self . assertEqual ( m . output , [ " ERROR:root:Cannot find KaTeX for latex rendering! " ] )
2018-03-18 20:49:28 +01:00
2021-02-12 08:19:30 +01:00
2020-06-25 21:38:36 +02:00
class MarkdownListPreprocessorTest ( ZulipTestCase ) :
2020-01-02 02:22:01 +01:00
# We test that the preprocessor inserts blank lines at correct places.
# We use <> to indicate that we need to insert a blank line here.
def split_message ( self , msg : str ) - > Tuple [ List [ str ] , List [ str ] ] :
2021-02-12 08:20:45 +01:00
original = msg . replace ( " <> " , " " ) . split ( " \n " )
expected = re . split ( r " \ n|<> " , msg )
2020-01-02 02:22:01 +01:00
return original , expected
def test_basic_list ( self ) - > None :
2020-06-27 02:18:01 +02:00
preprocessor = MarkdownListPreprocessor ( )
2021-02-12 08:20:45 +01:00
original , expected = self . split_message ( " List without a gap \n <>* One \n * Two " )
2020-01-02 02:22:01 +01:00
self . assertEqual ( preprocessor . run ( original ) , expected )
def test_list_after_quotes ( self ) - > None :
2020-06-27 02:18:01 +02:00
preprocessor = MarkdownListPreprocessor ( )
2021-02-12 08:19:30 +01:00
original , expected = self . split_message (
2021-02-12 08:20:45 +01:00
" ```quote \n Something \n ``` \n \n List without a gap \n <>* One \n * Two "
2021-02-12 08:19:30 +01:00
)
2020-01-02 02:22:01 +01:00
self . assertEqual ( preprocessor . run ( original ) , expected )
def test_list_in_code ( self ) - > None :
2020-06-27 02:18:01 +02:00
preprocessor = MarkdownListPreprocessor ( )
2021-02-12 08:20:45 +01:00
original , expected = self . split_message ( " ``` \n List without a gap \n * One \n * Two \n ``` " )
2020-01-02 02:22:01 +01:00
self . assertEqual ( preprocessor . run ( original ) , expected )
def test_complex_nesting_with_different_fences ( self ) - > None :
2020-06-27 02:18:01 +02:00
preprocessor = MarkdownListPreprocessor ( )
2020-01-02 02:22:01 +01:00
msg = """ ```quote
In quote . We should convert a list here : < >
* one
* two
~ ~ ~
This is a nested code fence , do not make changes here :
* one
* two
` ` ` ` quote
Quote in code fence . Should not convert :
* one
* two
` ` ` `
~ ~ ~
Back in the quote . We should convert : < >
* one
* two
` ` `
Outside . Should convert : < >
* one
* two
"""
original , expected = self . split_message ( msg )
self . assertEqual ( preprocessor . run ( original ) , expected )
def test_complex_nesting_with_same_fence ( self ) - > None :
2020-06-27 02:18:01 +02:00
preprocessor = MarkdownListPreprocessor ( )
2020-01-02 02:22:01 +01:00
msg = """ ```quote
In quote . We should convert a list here : < >
* one
* two
` ` ` python
This is a nested code fence , do not make changes here :
* one
* two
` ` ` quote
Quote in code fence . Should not convert :
* one
* two
` ` `
` ` `
Back in the quote . We should convert : < >
* one
* two
` ` `
Outside . Should convert : < >
* one
* two
"""
original , expected = self . split_message ( msg )
self . assertEqual ( preprocessor . run ( original ) , expected )
2021-02-12 08:19:30 +01:00
2020-06-27 00:35:15 +02:00
class MarkdownTest ( ZulipTestCase ) :
2018-11-03 17:12:15 +01:00
def setUp ( self ) - > None :
2019-10-19 20:47:00 +02:00
super ( ) . setUp ( )
2020-06-27 02:18:01 +02:00
clear_state_for_testing ( )
2018-11-03 17:12:15 +01:00
2018-05-11 01:39:38 +02:00
def assertEqual ( self , first : Any , second : Any , msg : str = " " ) - > None :
if isinstance ( first , str ) and isinstance ( second , str ) :
2017-12-04 10:10:15 +01:00
if first != second :
2021-02-12 08:19:30 +01:00
raise AssertionError (
" Actual and expected outputs do not match; showing diff. \n "
+ diff_strings ( first , second )
+ msg
)
2017-12-04 10:10:15 +01:00
else :
super ( ) . assertEqual ( first , second )
2020-06-27 00:35:15 +02:00
def load_markdown_tests ( self ) - > Tuple [ Dict [ str , Any ] , List [ List [ str ] ] ] :
2014-01-31 23:13:58 +01:00
test_fixtures = { }
2021-02-12 08:19:30 +01:00
with open (
2021-02-12 08:20:45 +01:00
os . path . join ( os . path . dirname ( __file__ ) , " fixtures/markdown_test_cases.json " ) , " rb "
2021-02-12 08:19:30 +01:00
) as f :
2020-08-07 01:09:47 +02:00
data = orjson . loads ( f . read ( ) )
2021-02-12 08:20:45 +01:00
for test in data [ " regular_tests " ] :
test_fixtures [ test [ " name " ] ] = test
2014-01-31 23:13:58 +01:00
2021-02-12 08:20:45 +01:00
return test_fixtures , data [ " linkify_tests " ]
2014-01-31 23:13:58 +01:00
2020-06-27 00:35:15 +02:00
def test_markdown_no_ignores ( self ) - > None :
2018-03-28 10:40:44 +02:00
# We do not want any ignored tests to be committed and merged.
2020-06-27 00:35:15 +02:00
format_tests , linkify_tests = self . load_markdown_tests ( )
2018-03-28 10:40:44 +02:00
for name , test in format_tests . items ( ) :
2020-06-13 08:59:37 +02:00
message = f ' Test " { name } " shouldn \' t be ignored. '
2021-02-12 08:20:45 +01:00
is_ignored = test . get ( " ignore " , False )
2018-03-28 10:40:44 +02:00
self . assertFalse ( is_ignored , message )
2021-04-22 23:15:05 +02:00
def test_markdown_fixtures_unique_names ( self ) - > None :
# All markdown fixtures must have unique names.
found_names : Set [ str ] = set ( )
with open (
os . path . join ( os . path . dirname ( __file__ ) , " fixtures/markdown_test_cases.json " ) , " rb "
) as f :
data = orjson . loads ( f . read ( ) )
for test in data [ " regular_tests " ] :
test_name = test [ " name " ]
message = f ' Test name: " { test_name } " must be unique. '
is_unique = test_name not in found_names
self . assertTrue ( is_unique , message )
found_names . add ( test_name )
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True )
2020-06-27 00:35:15 +02:00
def test_markdown_fixtures ( self ) - > None :
format_tests , linkify_tests = self . load_markdown_tests ( )
2021-02-12 08:19:30 +01:00
valid_keys = {
" name " ,
" input " ,
" expected_output " ,
" backend_only_rendering " ,
" marked_expected_output " ,
" text_content " ,
" translate_emoticons " ,
" ignore " ,
}
2014-01-31 23:13:58 +01:00
2017-09-27 10:11:59 +02:00
for name , test in format_tests . items ( ) :
2019-02-13 09:00:16 +01:00
with self . subTest ( markdown_test_case = name ) :
2020-03-31 20:52:37 +02:00
# Check that there aren't any unexpected keys as those are often typos
2021-05-17 05:41:32 +02:00
self . assert_length ( set ( test . keys ( ) ) - valid_keys , 0 )
2020-03-31 20:52:37 +02:00
# Ignore tests if specified
2021-02-12 08:20:45 +01:00
if test . get ( " ignore " , False ) :
2020-03-31 20:52:37 +02:00
continue # nocoverage
2021-02-12 08:20:45 +01:00
if test . get ( " translate_emoticons " , False ) :
2020-03-31 20:52:37 +02:00
# Create a userprofile and send message with it.
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2021-09-08 13:25:50 +02:00
do_change_user_setting (
user_profile , " translate_emoticons " , True , acting_user = None
)
2020-03-31 20:52:37 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , test [ " input " ] )
converted = rendering_result . rendered_content
2020-03-31 20:52:37 +02:00
else :
2021-02-12 08:20:45 +01:00
converted = markdown_convert_wrapper ( test [ " input " ] )
2020-03-31 20:52:37 +02:00
2021-02-12 08:20:45 +01:00
self . assertEqual ( converted , test [ " expected_output " ] )
2014-01-31 23:13:58 +01:00
2021-02-12 08:20:45 +01:00
def replaced ( payload : str , url : str , phrase : str = " " ) - > str :
if url [ : 4 ] == " http " :
2014-01-31 23:13:58 +01:00
href = url
2021-02-12 08:20:45 +01:00
elif " @ " in url :
href = " mailto: " + url
2014-01-31 23:13:58 +01:00
else :
2021-02-12 08:20:45 +01:00
href = " http:// " + url
return payload % ( f ' <a href= " { href } " > { url } </a> ' , )
2014-01-31 23:13:58 +01:00
2022-04-14 21:57:20 +02:00
for inline_url , reference , url in linkify_tests :
try :
match = replaced ( reference , url , phrase = inline_url )
except TypeError :
match = reference
converted = markdown_convert_wrapper ( inline_url )
self . assertEqual ( match , converted )
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_inline_file ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " Check out this file file:///Volumes/myserver/Users/Shared/pi.py "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p>Check out this file <a href= " file:///Volumes/myserver/Users/Shared/pi.py " >file:///Volumes/myserver/Users/Shared/pi.py</a></p> ' ,
)
2016-03-10 17:17:40 +01:00
2020-06-27 02:18:01 +02:00
clear_state_for_testing ( )
2016-03-10 17:17:40 +01:00
with self . settings ( ENABLE_FILE_LINKS = False ) :
2021-03-08 13:22:43 +01:00
realm = do_create_realm ( string_id = " file_links_test " , name = " file_links_test " )
2020-06-27 02:18:01 +02:00
maybe_update_markdown_engines ( realm . id , False )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
markdown_convert ( msg , message_realm = realm ) . rendered_content ,
" <p>Check out this file file:///Volumes/myserver/Users/Shared/pi.py</p> " ,
2021-02-12 08:19:30 +01:00
)
2016-03-10 17:17:40 +01:00
2018-01-19 11:17:38 +01:00
def test_inline_bitcoin ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " To bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa or not to bitcoin "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p>To <a href= " bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa " >bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa</a> or not to bitcoin</p> ' ,
)
2018-01-19 11:17:38 +01:00
2017-11-05 10:51:25 +01:00
def test_inline_youtube ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p>Check out the debate: <a href= " http://www.youtube.com/watch?v=hx1mjT73xYE " >http://www.youtube.com/watch?v=hx1mjT73xYE</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " hx1mjT73xYE " href= " http://www.youtube.com/watch?v=hx1mjT73xYE " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2021-02-12 08:20:45 +01:00
msg = " http://www.youtube.com/watch?v=hx1mjT73xYE "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-11-27 10:03:18 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " http://www.youtube.com/watch?v=hx1mjT73xYE " >http://www.youtube.com/watch?v=hx1mjT73xYE</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " hx1mjT73xYE " href= " http://www.youtube.com/watch?v=hx1mjT73xYE " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2017-11-27 10:03:18 +01:00
2021-02-12 08:20:45 +01:00
msg = " https://youtu.be/hx1mjT73xYE "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2019-04-12 05:55:38 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " https://youtu.be/hx1mjT73xYE " >https://youtu.be/hx1mjT73xYE</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " hx1mjT73xYE " href= " https://youtu.be/hx1mjT73xYE " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2019-04-12 05:55:38 +02:00
2021-02-12 08:20:45 +01:00
msg = " https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo "
2020-07-04 14:34:46 +02:00
not_converted = markdown_convert_wrapper ( msg )
2019-05-12 11:55:25 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
not_converted ,
' <p><a href= " https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo " >https://www.youtube.com/playlist?list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo</a></p> ' ,
)
2019-05-12 11:55:25 +02:00
2023-02-10 00:45:11 +01:00
msg = " https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2019-05-12 11:43:18 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2023-02-10 00:45:11 +01:00
f """ <p><a href= " https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo " >https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " O5nskjZ_GoI " href= " https://www.youtube.com/watch?v=O5nskjZ_GoI&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/O5nskjZ_GoI/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2019-05-12 11:43:18 +02:00
2021-02-12 08:20:45 +01:00
msg = " http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2019-05-12 11:51:31 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw " >http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " nOJgD4fcZhI " href= " http://www.youtube.com/watch_videos?video_ids=nOJgD4fcZhI,i96UO8-GFvw " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/nOJgD4fcZhI/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2019-05-12 11:51:31 +02:00
2019-06-01 12:59:57 +02:00
@override_settings ( INLINE_URL_EMBED_PREVIEW = False )
2017-12-14 22:17:00 +01:00
def test_inline_vimeo ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " Check out the debate: https://vimeo.com/246979354 "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-12-14 22:17:00 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p>Check out the debate: <a href= " https://vimeo.com/246979354 " >https://vimeo.com/246979354</a></p> ' ,
)
2017-12-14 22:17:00 +01:00
2021-02-12 08:20:45 +01:00
msg = " https://vimeo.com/246979354 "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-12-14 22:17:00 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p><a href= " https://vimeo.com/246979354 " >https://vimeo.com/246979354</a></p> ' ,
)
2017-12-14 22:17:00 +01:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True , INLINE_IMAGE_PREVIEW = True )
2019-07-24 07:43:50 +02:00
def test_inline_image_thumbnail_url ( self ) - > None :
realm = get_realm ( " zephyr " )
2021-02-12 08:20:45 +01:00
msg = " [foobar](/user_uploads/ {realm_id} /50/w2G6ok9kr8AMCQCTNAUOFMln/IMG_0677.JPG) "
2019-07-24 07:43:50 +02:00
msg = msg . format ( realm_id = realm . id )
thumbnail_img = ' <img data-src-fullsize= " /thumbnail?url=user_uploads %2F {realm_id} %2F 50 %2F w2G6ok9kr8AMCQCTNAUOFMln %2F IMG_0677.JPG&size=full " src= " /thumbnail?url=user_uploads %2F {realm_id} %2F 50 %2F w2G6ok9kr8AMCQCTNAUOFMln %2F IMG_0677.JPG&size=thumbnail " >< '
thumbnail_img = thumbnail_img . format ( realm_id = realm . id )
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-03-08 09:37:09 +01:00
self . assertIn ( thumbnail_img , converted )
2021-02-12 08:20:45 +01:00
msg = " https://www.google.com/images/srpr/logo4w.png "
2018-07-30 21:26:01 +02:00
thumbnail_img = ' <img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=thumbnail " > '
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-03-08 09:37:09 +01:00
self . assertIn ( thumbnail_img , converted )
2021-02-12 08:20:45 +01:00
msg = " www.google.com/images/srpr/logo4w.png "
2018-07-30 21:26:01 +02:00
thumbnail_img = ' <img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=full " src= " /thumbnail?url=http % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=thumbnail " > '
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-03-08 09:37:09 +01:00
self . assertIn ( thumbnail_img , converted )
2021-02-12 08:20:45 +01:00
msg = " https://www.google.com/images/srpr/logo4w.png "
2021-03-23 10:34:55 +01:00
thumbnail_img = f """ <div class= " message_inline_image " ><a href= " https://www.google.com/images/srpr/logo4w.png " ><img src= " { get_camo_url ( " https://www.google.com/images/srpr/logo4w.png " ) } " ></a></div> """
2019-01-04 16:22:04 +01:00
with self . settings ( THUMBNAIL_IMAGES = False ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-03-08 09:37:09 +01:00
self . assertIn ( thumbnail_img , converted )
2020-10-23 02:43:28 +02:00
# Any URL which is not an external link and doesn't start with
2018-08-24 16:29:25 +02:00
# /user_uploads/ is not thumbnailed
2021-02-12 08:20:45 +01:00
msg = " [foobar](/static/images/cute/turtle.png) "
2020-02-29 01:37:33 +01:00
thumbnail_img = ' <div class= " message_inline_image " ><a href= " /static/images/cute/turtle.png " title= " foobar " ><img src= " /static/images/cute/turtle.png " ></a></div> '
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-08-24 16:29:25 +02:00
self . assertIn ( thumbnail_img , converted )
2021-02-12 08:20:45 +01:00
msg = " [foobar](/user_avatars/ {realm_id} /emoji/images/50.png) "
2019-07-24 07:43:50 +02:00
msg = msg . format ( realm_id = realm . id )
2020-02-29 01:37:33 +01:00
thumbnail_img = ' <div class= " message_inline_image " ><a href= " /user_avatars/ {realm_id} /emoji/images/50.png " title= " foobar " ><img src= " /user_avatars/ {realm_id} /emoji/images/50.png " ></a></div> '
2019-07-24 07:43:50 +02:00
thumbnail_img = thumbnail_img . format ( realm_id = realm . id )
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2018-08-24 16:29:25 +02:00
self . assertIn ( thumbnail_img , converted )
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True , INLINE_IMAGE_PREVIEW = True )
2019-07-24 07:43:50 +02:00
def test_inline_image_preview ( self ) - > None :
2020-05-09 03:44:56 +02:00
with_preview = ' <div class= " message_inline_image " ><a href= " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F cdn.wallpapersafari.com %2F 13 %2F 6 %2F 16eVjx.jpeg&size=full " src= " /thumbnail?url=http % 3A %2F %2F cdn.wallpapersafari.com %2F 13 %2F 6 %2F 16eVjx.jpeg&size=thumbnail " ></a></div> '
without_preview = ' <p><a href= " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg " >http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></p> '
2021-02-12 08:20:45 +01:00
content = " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg "
2017-03-13 14:42:03 +01:00
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-03-13 14:42:03 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , with_preview )
2017-03-13 14:42:03 +01:00
realm = msg . get_realm ( )
2022-10-08 06:10:17 +02:00
realm . inline_image_preview = False
2017-03-13 14:42:03 +01:00
realm . save ( )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-03-13 14:42:03 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , without_preview )
2017-03-13 14:42:03 +01:00
2023-06-12 22:52:59 +02:00
@override_settings ( EXTERNAL_URI_SCHEME = " https:// " )
2021-03-23 10:34:55 +01:00
def test_external_image_preview_use_camo ( self ) - > None :
content = " https://example.com/thing.jpeg "
thumbnail_img = f """ <div class= " message_inline_image " ><a href= " { content } " ><img src= " { get_camo_url ( content ) } " ></a></div> """
converted = markdown_convert_wrapper ( content )
self . assertIn ( converted , thumbnail_img )
2023-06-12 22:52:59 +02:00
@override_settings ( EXTERNAL_URI_SCHEME = " https:// " )
2021-03-23 10:34:55 +01:00
def test_static_image_preview_skip_camo ( self ) - > None :
content = f " { settings . STATIC_URL } /thing.jpeg "
thumbnail_img = f """ <div class= " message_inline_image " ><a href= " { content } " ><img src= " { content } " ></a></div> """
converted = markdown_convert_wrapper ( content )
self . assertIn ( converted , thumbnail_img )
2023-06-12 22:52:59 +02:00
@override_settings ( EXTERNAL_URI_SCHEME = " https:// " )
2021-03-23 10:34:55 +01:00
def test_realm_image_preview_skip_camo ( self ) - > None :
content = f " https://zulip. { settings . EXTERNAL_HOST } /thing.jpeg "
converted = markdown_convert_wrapper ( content )
self . assertNotIn ( converted , get_camo_url ( content ) )
2023-06-12 22:52:59 +02:00
@override_settings ( EXTERNAL_URI_SCHEME = " https:// " )
2021-03-23 10:34:55 +01:00
def test_cross_realm_image_preview_use_camo ( self ) - > None :
content = f " https://otherrealm. { settings . EXTERNAL_HOST } /thing.jpeg "
thumbnail_img = f """ <div class= " message_inline_image " ><a href= " { content } " ><img src= " { get_camo_url ( content ) } " ></a></div> """
converted = markdown_convert_wrapper ( content )
self . assertIn ( converted , thumbnail_img )
2023-01-07 05:30:32 +01:00
@override_settings ( INLINE_IMAGE_PREVIEW = True )
def test_max_inline_preview ( self ) - > None :
image_links = [ ]
# Add a youtube link within a spoiler to ensure other link types are counted
image_links . append (
""" ```spoiler Check out this PyCon video \n https://www.youtube.com/watch?v=0c46YHS3RY8 \n ``` """
)
# Add a link within blockquote to test that it does NOT get counted
image_links . append ( " > http://cdn.wallpapersafari.com/spoiler/dont_count.jpeg \n " )
# Using INLINE_PREVIEW_LIMIT_PER_MESSAGE - 1 because of the one link in a spoiler added already
for x in range ( InlineInterestingLinkProcessor . INLINE_PREVIEW_LIMIT_PER_MESSAGE - 1 ) :
2023-01-26 00:13:37 +01:00
image_links . append ( f " http://cdn.wallpapersafari.com/ { x } /6/16eVjx.jpeg " )
2023-01-07 05:30:32 +01:00
within_limit_content = " \n " . join ( image_links )
above_limit_content = (
within_limit_content + " \n http://cdn.wallpapersafari.com/above/0/6/16eVjx.jpeg "
)
# When the number of image links is within the preview limit, the
# output should contain the same number of inline images.
converted = markdown_convert_wrapper ( within_limit_content )
soup = BeautifulSoup ( converted , " html.parser " )
self . assert_length (
soup ( class_ = " message_inline_image " ) ,
InlineInterestingLinkProcessor . INLINE_PREVIEW_LIMIT_PER_MESSAGE ,
)
# When the number of image links is over the limit, then there should
# be zero inline images.
converted = markdown_convert_wrapper ( above_limit_content )
soup = BeautifulSoup ( converted , " html.parser " )
self . assert_length ( soup ( class_ = " message_inline_image " ) , 0 )
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True , INLINE_IMAGE_PREVIEW = True )
2019-07-11 16:26:31 +02:00
def test_inline_image_quoted_blocks ( self ) - > None :
2021-02-12 08:20:45 +01:00
content = " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg "
2020-05-09 03:44:56 +02:00
expected = ' <div class= " message_inline_image " ><a href= " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F cdn.wallpapersafari.com %2F 13 %2F 6 %2F 16eVjx.jpeg&size=full " src= " /thumbnail?url=http % 3A %2F %2F cdn.wallpapersafari.com %2F 13 %2F 6 %2F 16eVjx.jpeg&size=thumbnail " ></a></div> '
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2019-07-11 16:26:31 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2019-07-11 16:26:31 +02:00
2021-02-12 08:20:45 +01:00
content = " >http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg \n \n Awesome! "
2020-05-09 03:44:56 +02:00
expected = ' <blockquote> \n <p><a href= " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg " >http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></p> \n </blockquote> \n <p>Awesome!</p> '
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2019-07-11 16:26:31 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2019-07-11 16:26:31 +02:00
2021-02-12 08:20:45 +01:00
content = " >* http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg \n \n Awesome! "
2020-05-09 03:44:56 +02:00
expected = ' <blockquote> \n <ul> \n <li><a href= " http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg " >http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></li> \n </ul> \n </blockquote> \n <p>Awesome!</p> '
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2019-07-11 16:26:31 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2019-07-11 16:26:31 +02:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True , INLINE_IMAGE_PREVIEW = True )
2017-11-05 10:51:25 +01:00
def test_inline_image_preview_order ( self ) - > None :
2019-07-24 07:43:50 +02:00
realm = get_realm ( " zulip " )
2021-02-12 08:20:45 +01:00
content = " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg \n http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg \n http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg "
2020-05-09 03:44:56 +02:00
expected = ' <p><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg " >http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg</a><br> \n <a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg " >http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg</a><br> \n <a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg " >http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg</a></p> \n <div class= " message_inline_image " ><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_01.jpg&size=full " src= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_01.jpg&size=thumbnail " ></a></div><div class= " message_inline_image " ><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_02.jpg&size=full " src= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_02.jpg&size=thumbnail " ></a></div><div class= " message_inline_image " ><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_03.jpg&size=full " src= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_03.jpg&size=thumbnail " ></a></div> '
2017-04-15 12:53:10 +02:00
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-04-15 12:53:10 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2017-04-15 12:53:10 +02:00
2021-02-12 08:20:45 +01:00
content = " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg \n \n >http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg \n \n * http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg \n * https://www.google.com/images/srpr/logo4w.png "
2020-05-09 03:44:56 +02:00
expected = ' <div class= " message_inline_image " ><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_01.jpg&size=full " src= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_01.jpg&size=thumbnail " ></a></div><blockquote> \n <p><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg " >http://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg</a></p> \n </blockquote> \n <ul> \n <li><div class= " message_inline_image " ><a href= " http://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg " ><img data-src-fullsize= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_03.jpg&size=full " src= " /thumbnail?url=http % 3A %2F %2F imaging.nikon.com %2F lineup %2F dslr %2F df %2F img %2F sample %2F img_03.jpg&size=thumbnail " ></a></div></li> \n <li><div class= " message_inline_image " ><a href= " https://www.google.com/images/srpr/logo4w.png " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F www.google.com %2F images %2F srpr %2F logo4w.png&size=thumbnail " ></a></div></li> \n </ul> '
2017-12-25 21:35:23 +01:00
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-12-25 21:35:23 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2017-12-25 21:35:23 +01:00
2021-05-10 07:02:14 +02:00
content = " Test 1 \n [21136101110_1dde1c1a7e_o.jpg](/user_uploads/ {realm_id} /6d/F1PX6u16JA2P-nK45PyxHIYZ/21136101110_1dde1c1a7e_o.jpg) \n \n Next image \n [IMG_20161116_023910.jpg](/user_uploads/ {realm_id} /69/sh7L06e7uH7NaX6d5WFfVYQp/IMG_20161116_023910.jpg) \n \n Another screenshot \n [Screenshot-from-2016-06-01-16-22-42.png](/user_uploads/ {realm_id} /70/_aZmIEWaN1iUaxwkDjkO7bpj/Screenshot-from-2016-06-01-16-22-42.png) "
2019-07-24 07:43:50 +02:00
content = content . format ( realm_id = realm . id )
2021-05-10 07:02:14 +02:00
expected = ' <p>Test 1<br> \n <a href= " /user_uploads/ {realm_id} /6d/F1PX6u16JA2P-nK45PyxHIYZ/21136101110_1dde1c1a7e_o.jpg " >21136101110_1dde1c1a7e_o.jpg</a> </p> \n <div class= " message_inline_image " ><a href= " /user_uploads/ {realm_id} /6d/F1PX6u16JA2P-nK45PyxHIYZ/21136101110_1dde1c1a7e_o.jpg " title= " 21136101110_1dde1c1a7e_o.jpg " ><img data-src-fullsize= " /thumbnail?url=user_uploads %2F {realm_id} %2F 6d %2F F1PX6u16JA2P-nK45PyxHIYZ %2F 21136101110_1dde1c1a7e_o.jpg&size=full " src= " /thumbnail?url=user_uploads %2F {realm_id} %2F 6d %2F F1PX6u16JA2P-nK45PyxHIYZ %2F 21136101110_1dde1c1a7e_o.jpg&size=thumbnail " ></a></div><p>Next image<br> \n <a href= " /user_uploads/ {realm_id} /69/sh7L06e7uH7NaX6d5WFfVYQp/IMG_20161116_023910.jpg " >IMG_20161116_023910.jpg</a> </p> \n <div class= " message_inline_image " ><a href= " /user_uploads/ {realm_id} /69/sh7L06e7uH7NaX6d5WFfVYQp/IMG_20161116_023910.jpg " title= " IMG_20161116_023910.jpg " ><img data-src-fullsize= " /thumbnail?url=user_uploads %2F {realm_id} %2F 69 %2F sh7L06e7uH7NaX6d5WFfVYQp %2F IMG_20161116_023910.jpg&size=full " src= " /thumbnail?url=user_uploads %2F {realm_id} %2F 69 %2F sh7L06e7uH7NaX6d5WFfVYQp %2F IMG_20161116_023910.jpg&size=thumbnail " ></a></div><p>Another screenshot<br> \n <a href= " /user_uploads/ {realm_id} /70/_aZmIEWaN1iUaxwkDjkO7bpj/Screenshot-from-2016-06-01-16-22-42.png " >Screenshot-from-2016-06-01-16-22-42.png</a></p> \n <div class= " message_inline_image " ><a href= " /user_uploads/ {realm_id} /70/_aZmIEWaN1iUaxwkDjkO7bpj/Screenshot-from-2016-06-01-16-22-42.png " title= " Screenshot-from-2016-06-01-16-22-42.png " ><img data-src-fullsize= " /thumbnail?url=user_uploads %2F {realm_id} %2F 70 %2F _aZmIEWaN1iUaxwkDjkO7bpj %2F Screenshot-from-2016-06-01-16-22-42.png&size=full " src= " /thumbnail?url=user_uploads %2F {realm_id} %2F 70 %2F _aZmIEWaN1iUaxwkDjkO7bpj %2F Screenshot-from-2016-06-01-16-22-42.png&size=thumbnail " ></a></div> '
2019-07-24 07:43:50 +02:00
expected = expected . format ( realm_id = realm . id )
2017-04-15 12:53:10 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2017-04-15 12:53:10 +02:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True , INLINE_IMAGE_PREVIEW = True )
2019-02-14 17:15:30 +01:00
def test_corrected_image_source ( self ) - > None :
2020-10-23 02:43:28 +02:00
# testing only Wikipedia because linx.li URLs can be expected to expire
2021-02-12 08:20:45 +01:00
content = " https://en.wikipedia.org/wiki/File:Wright_of_Derby,_The_Orrery.jpg "
2020-05-09 03:44:56 +02:00
expected = ' <div class= " message_inline_image " ><a href= " https://en.wikipedia.org/wiki/Special:FilePath/File:Wright_of_Derby,_The_Orrery.jpg " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F en.wikipedia.org %2F wiki %2F Special % 3AFilePath %2F File % 3AWright_of_Derby % 2C_The_Orrery.jpg&size=full " src= " /thumbnail?url=https % 3A %2F %2F en.wikipedia.org %2F wiki %2F Special % 3AFilePath %2F File % 3AWright_of_Derby % 2C_The_Orrery.jpg&size=thumbnail " ></a></div> '
2019-02-14 17:15:30 +01:00
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2019-02-14 17:15:30 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2019-02-14 17:15:30 +01:00
2023-07-07 00:49:43 +02:00
content = " https://en.wikipedia.org/static/images/icons/wikipedia.png "
expected = ' <div class= " message_inline_image " ><a href= " https://en.wikipedia.org/static/images/icons/wikipedia.png " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F en.wikipedia.org %2F static %2F images %2F icons %2F wikipedia.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F en.wikipedia.org %2F static %2F images %2F icons %2F wikipedia.png&size=thumbnail " ></a></div> '
converted = render_markdown ( msg , content )
self . assertEqual ( converted . rendered_content , expected )
2017-03-13 14:42:03 +01:00
@override_settings ( INLINE_IMAGE_PREVIEW = False )
2019-03-01 01:53:18 +01:00
def test_image_preview_enabled ( self ) - > None :
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2017-03-13 14:42:03 +01:00
settings . INLINE_IMAGE_PREVIEW = True
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2018-11-07 15:24:36 +01:00
message = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm = message . get_realm ( )
2017-03-13 14:42:03 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( no_previews = True )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( message , realm )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2017-03-13 14:42:03 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( message )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2017-03-13 14:42:03 +01:00
2021-02-12 08:19:30 +01:00
ret = image_preview_enabled ( message , realm , no_previews = True )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( message , no_previews = True )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2019-03-01 01:53:18 +01:00
2017-03-13 14:42:03 +01:00
@override_settings ( INLINE_URL_EMBED_PREVIEW = False )
2019-03-01 01:53:18 +01:00
def test_url_embed_preview_enabled ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2021-02-12 08:19:30 +01:00
message = copy . deepcopy (
Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
)
2017-03-13 14:42:03 +01:00
realm = message . get_realm ( )
2019-05-31 04:45:02 +02:00
realm . inline_url_embed_preview = True # off by default
2021-02-12 08:20:45 +01:00
realm . save ( update_fields = [ " inline_url_embed_preview " ] )
2017-03-13 14:42:03 +01:00
2020-06-27 02:18:01 +02:00
ret = url_embed_preview_enabled ( )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2017-03-13 14:42:03 +01:00
settings . INLINE_URL_EMBED_PREVIEW = True
2020-06-27 02:18:01 +02:00
ret = url_embed_preview_enabled ( )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = image_preview_enabled ( no_previews = True )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = url_embed_preview_enabled ( message , realm )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2020-06-27 02:18:01 +02:00
ret = url_embed_preview_enabled ( message )
2021-05-31 20:48:48 +02:00
self . assertTrue ( ret )
2019-03-01 01:53:18 +01:00
2020-06-27 02:18:01 +02:00
ret = url_embed_preview_enabled ( message , no_previews = True )
2021-05-31 20:48:48 +02:00
self . assertFalse ( ret )
2017-03-13 14:42:03 +01:00
2017-11-05 10:51:25 +01:00
def test_inline_dropbox ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " Look at how hilarious our old office was: https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG "
2021-02-12 08:19:30 +01:00
image_info = {
2021-02-12 08:20:45 +01:00
" image " : " https://photos-4.dropbox.com/t/2/AABIre1oReJgPYuc_53iv0IHq1vUzRaDg2rrCfTpiWMccQ/12/129/jpeg/1024x1024/2/_/0/4/IMG_0923.JPG/CIEBIAEgAiAHKAIoBw/ymdijjcg67hv2ta/AABz2uuED1ox3vpWWvMpBxu6a/IMG_0923.JPG " ,
" desc " : " Shared with Dropbox " ,
" title " : " IMG_0923.JPG " ,
2021-02-12 08:19:30 +01:00
}
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.lib.markdown.fetch_open_graph_image " , return_value = image_info ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-05-25 21:53:12 +02:00
f """ <p>Look at how hilarious our old office was: <a href= " https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG " >https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG</a></p> \n <div class= " message_inline_image " ><a href= " https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG " title= " IMG_0923.JPG " ><img src= " { get_camo_url ( " https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG?raw=1 " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2021-02-12 08:20:45 +01:00
msg = " Look at my hilarious drawing folder: https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl= "
2021-02-12 08:19:30 +01:00
image_info = {
2021-02-12 08:20:45 +01:00
" image " : " https://cf.dropboxstatic.com/static/images/icons128/folder_dropbox.png " ,
" desc " : " Shared with Dropbox " ,
" title " : " Saves " ,
2021-02-12 08:19:30 +01:00
}
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.lib.markdown.fetch_open_graph_image " , return_value = image_info ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p>Look at my hilarious drawing folder: <a href= " https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl= " >https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl=</a></p> \n <div class= " message_inline_ref " ><a href= " https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl= " title= " Saves " ><img src= " { get_camo_url ( " https://cf.dropboxstatic.com/static/images/icons128/folder_dropbox.png " ) } " ></a><div><div class= " message_inline_image_title " >Saves</div><desc class= " message_inline_image_desc " ></desc></div></div> """ ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_inline_dropbox_preview ( self ) - > None :
2014-02-26 21:25:27 +01:00
# Test photo album previews
2021-02-12 08:20:45 +01:00
msg = " https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5 "
2021-02-12 08:19:30 +01:00
image_info = {
2021-02-12 08:20:45 +01:00
" image " : " https://photos-6.dropbox.com/t/2/AAAlawaeD61TyNewO5vVi-DGf2ZeuayfyHFdNTNzpGq-QA/12/271544745/jpeg/1024x1024/2/_/0/5/baby-piglet.jpg/CKnjvYEBIAIgBygCKAc/tditp9nitko60n5/AADX03VAIrQlTl28CtujDcMla/0 " ,
" desc " : " Shared with Dropbox " ,
" title " : " 1 photo " ,
2021-02-12 08:19:30 +01:00
}
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.lib.markdown.fetch_open_graph_image " , return_value = image_info ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2014-02-26 21:25:27 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5 " >https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5</a></p> \n <div class= " message_inline_image " ><a href= " https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5 " title= " 1 photo " ><img src= " { get_camo_url ( " https://photos-6.dropbox.com/t/2/AAAlawaeD61TyNewO5vVi-DGf2ZeuayfyHFdNTNzpGq-QA/12/271544745/jpeg/1024x1024/2/_/0/5/baby-piglet.jpg/CKnjvYEBIAIgBygCKAc/tditp9nitko60n5/AADX03VAIrQlTl28CtujDcMla/0 " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True )
2017-11-05 10:51:25 +01:00
def test_inline_dropbox_negative ( self ) - > None :
2014-01-31 23:13:58 +01:00
# Make sure we're not overzealous in our conversion:
2021-02-12 08:20:45 +01:00
msg = " Look at the new dropbox logo: https://www.dropbox.com/static/images/home_logo.png "
with mock . patch ( " zerver.lib.markdown.fetch_open_graph_image " , return_value = None ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p>Look at the new dropbox logo: <a href= " https://www.dropbox.com/static/images/home_logo.png " >https://www.dropbox.com/static/images/home_logo.png</a></p> \n <div class= " message_inline_image " ><a href= " https://www.dropbox.com/static/images/home_logo.png " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F www.dropbox.com %2F static %2F images %2F home_logo.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F www.dropbox.com %2F static %2F images %2F home_logo.png&size=thumbnail " ></a></div> ' ,
)
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_inline_dropbox_bad ( self ) - > None :
2014-07-17 02:41:49 +02:00
# Don't fail on bad dropbox links
2016-04-30 00:25:58 +02:00
msg = " https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM "
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.lib.markdown.fetch_open_graph_image " , return_value = None ) :
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p><a href= " https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM " >https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM</a></p> ' ,
)
2014-07-17 02:41:49 +02:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True )
2017-11-05 10:51:25 +01:00
def test_inline_github_preview ( self ) - > None :
2017-05-03 18:42:55 +02:00
# Test photo album previews
2021-09-01 00:15:31 +02:00
msg = " Test: https://github.com/zulip/zulip/blob/main/static/images/logo/zulip-icon-128x128.png "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-05-03 18:42:55 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-09-01 00:15:31 +02:00
' <p>Test: <a href= " https://github.com/zulip/zulip/blob/main/static/images/logo/zulip-icon-128x128.png " >https://github.com/zulip/zulip/blob/main/static/images/logo/zulip-icon-128x128.png</a></p> \n <div class= " message_inline_image " ><a href= " https://github.com/zulip/zulip/blob/main/static/images/logo/zulip-icon-128x128.png " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F raw.githubusercontent.com %2F zulip %2F zulip %2F main %2F static %2F images %2F logo %2F zulip-icon-128x128.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F raw.githubusercontent.com %2F zulip %2F zulip %2F main %2F static %2F images %2F logo %2F zulip-icon-128x128.png&size=thumbnail " ></a></div> ' ,
2021-02-12 08:19:30 +01:00
)
2017-05-03 18:42:55 +02:00
2021-02-12 08:20:45 +01:00
msg = " Test: https://developer.github.com/assets/images/hero-circuit-bg.png "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-05-03 18:42:55 +02:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
' <p>Test: <a href= " https://developer.github.com/assets/images/hero-circuit-bg.png " >https://developer.github.com/assets/images/hero-circuit-bg.png</a></p> \n <div class= " message_inline_image " ><a href= " https://developer.github.com/assets/images/hero-circuit-bg.png " ><img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F developer.github.com %2F assets %2F images %2F hero-circuit-bg.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F developer.github.com %2F assets %2F images %2F hero-circuit-bg.png&size=thumbnail " ></a></div> ' ,
)
2017-05-03 18:42:55 +02:00
2020-07-20 17:09:52 +02:00
def test_inline_youtube_preview ( self ) - > None :
2020-10-23 02:43:28 +02:00
# Test YouTube URLs in spoilers
msg = """ \n ```spoiler Check out this PyCon video \n https://www.youtube.com/watch?v=0c46YHS3RY8 \n ``` """
2020-07-20 17:09:52 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <div class= " spoiler-block " ><div class= " spoiler-header " > \n <p>Check out this PyCon video</p> \n </div><div class= " spoiler-content " aria-hidden= " true " > \n <p><a href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " >https://www.youtube.com/watch?v=0c46YHS3RY8</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " 0c46YHS3RY8 " href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/0c46YHS3RY8/default.jpg " ) } " ></a></div></div></div> """ ,
2021-02-12 08:19:30 +01:00
)
2020-07-20 17:09:52 +02:00
2020-10-23 02:43:28 +02:00
# Test YouTube URLs in normal messages.
2021-02-12 08:20:45 +01:00
msg = " [YouTube link](https://www.youtube.com/watch?v=0c46YHS3RY8) "
2020-07-20 17:09:52 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " >YouTube link</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " 0c46YHS3RY8 " href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/0c46YHS3RY8/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2020-07-20 17:09:52 +02:00
2021-02-12 08:20:45 +01:00
msg = " https://www.youtube.com/watch?v=0c46YHS3RY8 \n \n Sample text \n \n https://www.youtube.com/watch?v=lXFO2ULktEI "
2020-07-20 17:09:52 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-03-23 10:34:55 +01:00
f """ <p><a href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " >https://www.youtube.com/watch?v=0c46YHS3RY8</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " 0c46YHS3RY8 " href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/0c46YHS3RY8/default.jpg " ) } " ></a></div><p>Sample text</p> \n <p><a href= " https://www.youtube.com/watch?v=lXFO2ULktEI " >https://www.youtube.com/watch?v=lXFO2ULktEI</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " lXFO2ULktEI " href= " https://www.youtube.com/watch?v=lXFO2ULktEI " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/lXFO2ULktEI/default.jpg " ) } " ></a></div> """ ,
2023-07-06 16:18:08 +02:00
)
# Test order of YouTube inline previews in same paragraph.
msg = " https://www.youtube.com/watch?v=0c46YHS3RY8 \n https://www.youtube.com/watch?v=lXFO2ULktEI "
converted = markdown_convert_wrapper ( msg )
self . assertEqual (
converted ,
f """ <p><a href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " >https://www.youtube.com/watch?v=0c46YHS3RY8</a><br> \n <a href= " https://www.youtube.com/watch?v=lXFO2ULktEI " >https://www.youtube.com/watch?v=lXFO2ULktEI</a></p> \n <div class= " youtube-video message_inline_image " ><a data-id= " 0c46YHS3RY8 " href= " https://www.youtube.com/watch?v=0c46YHS3RY8 " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/0c46YHS3RY8/default.jpg " ) } " ></a></div><div class= " youtube-video message_inline_image " ><a data-id= " lXFO2ULktEI " href= " https://www.youtube.com/watch?v=lXFO2ULktEI " ><img src= " { get_camo_url ( " https://i.ytimg.com/vi/lXFO2ULktEI/default.jpg " ) } " ></a></div> """ ,
2021-02-12 08:19:30 +01:00
)
2020-07-20 17:09:52 +02:00
2017-11-05 10:51:25 +01:00
def test_twitter_id_extraction ( self ) - > None :
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " http://twitter.com/#!/VizzQuotes/status/409030735191097344 " ) ,
" 409030735191097344 " ,
2021-02-12 08:19:30 +01:00
)
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " http://twitter.com/VizzQuotes/status/409030735191097344 " ) ,
" 409030735191097344 " ,
2021-02-12 08:19:30 +01:00
)
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " http://twitter.com/VizzQuotes/statuses/409030735191097344 " ) ,
" 409030735191097344 " ,
2021-02-12 08:19:30 +01:00
)
2021-02-12 08:20:45 +01:00
self . assertEqual ( get_tweet_id ( " https://twitter.com/wdaher/status/1017581858 " ) , " 1017581858 " )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " https://twitter.com/wdaher/status/1017581858/ " ) , " 1017581858 "
2021-02-12 08:19:30 +01:00
)
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " https://twitter.com/windyoona/status/410766290349879296/photo/1 " ) ,
" 410766290349879296 " ,
2021-02-12 08:19:30 +01:00
)
self . assertEqual (
2021-02-12 08:20:45 +01:00
get_tweet_id ( " https://twitter.com/windyoona/status/410766290349879296/ " ) ,
" 410766290349879296 " ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_fetch_tweet_data_settings_validation ( self ) - > None :
2015-10-02 13:01:25 +02:00
with self . settings ( TEST_SUITE = False , TWITTER_CONSUMER_KEY = None ) :
2023-05-29 18:19:45 +02:00
with self . assertRaises ( NotImplementedError ) :
fetch_tweet_data ( " 287977969287315459 " )
2015-09-30 09:55:56 +02:00
2017-11-05 10:51:25 +01:00
def test_content_has_emoji ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . assertFalse ( content_has_emoji_syntax ( " boring " ) )
self . assertFalse ( content_has_emoji_syntax ( " hello: world " ) )
self . assertFalse ( content_has_emoji_syntax ( " :foobar " ) )
self . assertFalse ( content_has_emoji_syntax ( " ::: hello ::: " ) )
2017-09-15 03:08:15 +02:00
2021-02-12 08:20:45 +01:00
self . assertTrue ( content_has_emoji_syntax ( " foo :whatever: " ) )
self . assertTrue ( content_has_emoji_syntax ( " \n :whatever: " ) )
self . assertTrue ( content_has_emoji_syntax ( " :smile: :::::: " ) )
2017-09-15 03:08:15 +02:00
2017-11-05 10:51:25 +01:00
def test_realm_emoji ( self ) - > None :
2018-05-11 01:39:38 +02:00
def emoji_img ( name : str , file_name : str , realm_id : int ) - > str :
2020-06-13 08:59:37 +02:00
return ' <img alt= " {} " class= " emoji " src= " {} " title= " {} " > ' . format (
2021-02-12 08:19:30 +01:00
name , get_emoji_url ( file_name , realm_id ) , name [ 1 : - 1 ] . replace ( " _ " , " " )
)
2014-01-31 23:13:58 +01:00
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2014-01-31 23:13:58 +01:00
2020-08-11 01:47:49 +02:00
# Needs to mock an actual message because that's how Markdown obtains the realm
2021-02-12 08:20:45 +01:00
msg = Message ( sender = self . example_user ( " hamlet " ) )
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( " :green_tick: " , message_realm = realm , message = msg )
2021-02-12 08:19:30 +01:00
realm_emoji = RealmEmoji . objects . filter (
2021-02-12 08:20:45 +01:00
realm = realm , name = " green_tick " , deactivated = False
2021-02-12 08:19:30 +01:00
) . get ( )
2022-05-31 01:34:34 +02:00
assert realm_emoji . file_name is not None
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p> {} </p> " . format ( emoji_img ( " :green_tick: " , realm_emoji . file_name , realm . id ) ) ,
2021-02-12 08:19:30 +01:00
)
2014-01-31 23:13:58 +01:00
2017-07-18 00:22:27 +02:00
# Deactivate realm emoji.
2022-04-07 12:24:30 +02:00
do_remove_realm_emoji ( realm , " green_tick " , acting_user = None )
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( " :green_tick: " , message_realm = realm , message = msg )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , " <p>:green_tick:</p> " )
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_deactivated_realm_emoji ( self ) - > None :
2017-07-18 00:22:27 +02:00
# Deactivate realm emoji.
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-04-07 12:24:30 +02:00
do_remove_realm_emoji ( realm , " green_tick " , acting_user = None )
2017-05-23 08:41:30 +02:00
2021-02-12 08:20:45 +01:00
msg = Message ( sender = self . example_user ( " hamlet " ) )
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( " :green_tick: " , message_realm = realm , message = msg )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , " <p>:green_tick:</p> " )
2017-05-23 08:41:30 +02:00
2017-11-05 10:51:25 +01:00
def test_unicode_emoji ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " \u2615 " # ☕
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-02-12 08:20:45 +01:00
' <p><span aria-label= " coffee " class= " emoji emoji-2615 " role= " img " title= " coffee " >:coffee:</span></p> ' ,
2021-02-12 08:19:30 +01:00
)
2016-06-24 20:03:56 +02:00
2021-02-12 08:20:45 +01:00
msg = " \u2615 \u2615 " # ☕☕
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted ,
2021-02-12 08:20:45 +01:00
' <p><span aria-label= " coffee " class= " emoji emoji-2615 " role= " img " title= " coffee " >:coffee:</span><span aria-label= " coffee " class= " emoji emoji-2615 " role= " img " title= " coffee " >:coffee:</span></p> ' ,
2021-02-12 08:19:30 +01:00
)
2017-06-20 15:52:14 +02:00
2018-01-15 19:36:32 +01:00
def test_no_translate_emoticons_if_off ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2021-09-08 13:25:50 +02:00
do_change_user_setting ( user_profile , " translate_emoticons " , False , acting_user = None )
2018-01-15 19:36:32 +01:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2021-02-12 08:20:45 +01:00
content = " :) "
expected = " <p>:)</p> "
2018-01-15 19:36:32 +01:00
converted = render_markdown ( msg , content )
2021-06-17 12:20:40 +02:00
self . assertEqual ( converted . rendered_content , expected )
2018-01-15 19:36:32 +01:00
2017-11-05 10:51:25 +01:00
def test_same_markup ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " \u2615 " # ☕
2020-07-04 14:34:46 +02:00
unicode_converted = markdown_convert_wrapper ( msg )
2017-06-20 15:52:14 +02:00
2021-02-12 08:20:45 +01:00
msg = " :coffee: " # ☕☕
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2017-06-20 15:52:14 +02:00
self . assertEqual ( converted , unicode_converted )
2016-06-24 20:03:56 +02:00
2019-05-25 16:10:30 +02:00
def test_links_in_topic_name ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
msg = Message ( sender = self . example_user ( " othello " ) )
2019-05-25 16:10:30 +02:00
msg . set_topic_name ( " https://google.com/hello-world " )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-01-26 07:32:29 +01:00
self . assertEqual (
converted_topic ,
[ { " url " : " https://google.com/hello-world " , " text " : " https://google.com/hello-world " } ] ,
)
2019-05-25 16:10:30 +02:00
2019-07-19 07:46:07 +02:00
msg . set_topic_name ( " http://google.com/hello-world " )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-01-26 07:32:29 +01:00
self . assertEqual (
converted_topic ,
[ { " url " : " http://google.com/hello-world " , " text " : " http://google.com/hello-world " } ] ,
)
2019-07-19 07:46:07 +02:00
msg . set_topic_name ( " Without scheme google.com/hello-world " )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-01-26 07:32:29 +01:00
self . assertEqual (
converted_topic ,
[ { " url " : " https://google.com/hello-world " , " text " : " google.com/hello-world " } ] ,
)
2019-07-19 07:46:07 +02:00
msg . set_topic_name ( " Without scheme random.words/hello-world " )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2019-07-19 07:46:07 +02:00
self . assertEqual ( converted_topic , [ ] )
2021-02-12 08:19:30 +01:00
msg . set_topic_name (
" Try out http://ftp.debian.org, https://google.com/ and https://google.in/. "
)
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-01-26 07:32:29 +01:00
converted_topic ,
[
{ " url " : " http://ftp.debian.org " , " text " : " http://ftp.debian.org " } ,
{ " url " : " https://google.com/ " , " text " : " https://google.com/ " } ,
{ " url " : " https://google.in/ " , " text " : " https://google.in/ " } ,
] ,
)
# test order for links without scheme
msg . set_topic_name ( " google.in google.com " )
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
self . assertEqual (
converted_topic ,
[
{ " url " : " https://google.in " , " text " : " google.in " } ,
{ " url " : " https://google.com " , " text " : " google.com " } ,
] ,
2021-02-12 08:19:30 +01:00
)
2019-05-25 16:10:30 +02:00
2022-12-02 01:39:06 +01:00
def check_add_linkifiers (
2023-03-08 22:18:59 +01:00
self , linkifiers : List [ RealmFilter ] , expected_linkifier_reprs : List [ str ]
2022-12-02 01:39:06 +01:00
) - > None :
2023-03-08 22:18:59 +01:00
self . assert_length ( linkifiers , len ( expected_linkifier_reprs ) )
for linkifier , expected_linkifier_repr in zip ( linkifiers , expected_linkifier_reprs ) :
2022-12-02 01:39:06 +01:00
linkifier . clean ( )
linkifier . save ( )
2023-03-08 22:18:59 +01:00
self . assertEqual ( repr ( linkifier ) , expected_linkifier_repr )
2022-12-02 01:39:06 +01:00
2017-11-05 10:51:25 +01:00
def test_realm_patterns ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-12-02 01:39:06 +01:00
self . check_add_linkifiers (
[
RealmFilter (
realm = realm ,
pattern = r " #(?P<id>[0-9] { 2,8}) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} " ,
2022-12-02 01:39:06 +01:00
)
] ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
[ " <RealmFilter: zulip: #(?P<id>[0-9] { 2,8}) https://trac.example.com/ticket/ {id} > " ] ,
2021-02-12 08:19:30 +01:00
)
2016-09-19 23:30:41 +02:00
2021-02-12 08:20:45 +01:00
msg = Message ( sender = self . example_user ( " othello " ) )
2018-11-10 16:04:54 +01:00
msg . set_topic_name ( " #444 " )
2014-01-31 23:13:58 +01:00
2016-09-23 20:06:37 +02:00
flush_per_request_caches ( )
2022-03-22 01:11:23 +01:00
content = " We should fix #224 #336 #446 and #115, but not issue#124 or #1124z or [trac #15](https://trac.example.com/ticket/16) today. "
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( content , message_realm = realm , message = msg )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2014-01-31 23:13:58 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2022-03-22 01:11:23 +01:00
' <p>We should fix <a href= " https://trac.example.com/ticket/224 " >#224</a> <a href= " https://trac.example.com/ticket/336 " >#336</a> <a href= " https://trac.example.com/ticket/446 " >#446</a> and <a href= " https://trac.example.com/ticket/115 " >#115</a>, but not issue#124 or #1124z or <a href= " https://trac.example.com/ticket/16 " >trac #15</a> today.</p> ' ,
2021-02-12 08:19:30 +01:00
)
2021-01-26 07:32:29 +01:00
self . assertEqual (
converted_topic , [ { " url " : " https://trac.example.com/ticket/444 " , " text " : " #444 " } ]
)
2014-01-31 23:13:58 +01:00
2019-05-25 16:10:30 +02:00
msg . set_topic_name ( " #444 https://google.com " )
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-01-26 07:32:29 +01:00
converted_topic ,
[
{ " url " : " https://trac.example.com/ticket/444 " , " text " : " #444 " } ,
{ " url " : " https://google.com " , " text " : " https://google.com " } ,
] ,
2021-02-12 08:19:30 +01:00
)
2019-05-25 16:10:30 +02:00
2022-12-02 09:40:45 +01:00
msg . set_topic_name ( " #111 https://google.com #111 #222 #111 https://google.com #222 " )
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
self . assertEqual (
converted_topic ,
[
{ " url " : " https://trac.example.com/ticket/111 " , " text " : " #111 " } ,
{ " url " : " https://google.com " , " text " : " https://google.com " } ,
{ " url " : " https://trac.example.com/ticket/111 " , " text " : " #111 " } ,
{ " url " : " https://trac.example.com/ticket/222 " , " text " : " #222 " } ,
{ " url " : " https://trac.example.com/ticket/111 " , " text " : " #111 " } ,
{ " url " : " https://google.com " , " text " : " https://google.com " } ,
{ " url " : " https://trac.example.com/ticket/222 " , " text " : " #222 " } ,
] ,
)
2022-03-22 01:11:23 +01:00
msg . set_topic_name ( " #444 #555 #666 " )
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
self . assertEqual (
converted_topic ,
[
{ " url " : " https://trac.example.com/ticket/444 " , " text " : " #444 " } ,
{ " url " : " https://trac.example.com/ticket/555 " , " text " : " #555 " } ,
{ " url " : " https://trac.example.com/ticket/666 " , " text " : " #666 " } ,
] ,
)
2021-02-12 08:19:30 +01:00
RealmFilter (
realm = realm ,
2021-02-12 08:20:45 +01:00
pattern = r " #(?P<id>[a-zA-Z]+-[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} " ,
2021-02-12 08:19:30 +01:00
) . save ( )
2021-02-12 08:20:45 +01:00
msg = Message ( sender = self . example_user ( " hamlet " ) )
2016-02-13 19:17:15 +01:00
2021-02-12 08:20:45 +01:00
content = " #ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging "
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( content , message_realm = realm , message = msg )
2016-02-13 19:17:15 +01:00
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><a href= " https://trac.example.com/ticket/ZUL-123 " >#ZUL-123</a> was fixed and code was deployed to production, also <a href= " https://trac.example.com/ticket/zul-321 " >#zul-321</a> was deployed to staging</p> ' ,
)
2016-02-13 19:17:15 +01:00
2021-02-12 08:19:30 +01:00
def assert_conversion ( content : str , should_have_converted : bool = True ) - > None :
2021-06-17 12:20:40 +02:00
converted = markdown_convert ( content , message_realm = realm , message = msg ) . rendered_content
2020-06-27 02:18:01 +02:00
converted_topic = topic_links ( realm . id , content )
if should_have_converted :
2021-02-12 08:20:45 +01:00
self . assertTrue ( " https://trac.example.com " in converted )
2021-05-17 05:41:32 +02:00
self . assert_length ( converted_topic , 1 )
2021-01-26 07:32:29 +01:00
self . assertEqual (
converted_topic [ 0 ] ,
{ " url " : " https://trac.example.com/ticket/123 " , " text " : " #123 " } ,
)
2019-03-06 06:09:50 +01:00
else :
2021-02-12 08:20:45 +01:00
self . assertTrue ( " https://trac.example.com " not in converted )
2021-05-17 05:41:32 +02:00
self . assert_length ( converted_topic , 0 )
2021-02-12 08:19:30 +01:00
2021-02-12 08:20:45 +01:00
assert_conversion ( " Hello #123 World " )
assert_conversion ( " Hello #123World " , False )
assert_conversion ( " Hello#123 World " , False )
assert_conversion ( " Hello#123World " , False )
2020-08-11 01:47:49 +02:00
# Ideally, these should be converted, but Markdown doesn't
2019-03-07 17:41:54 +01:00
# handle word boundary detection in languages that don't use
# whitespace for that correctly yet.
2021-02-12 08:20:45 +01:00
assert_conversion ( " チケットは#123です " , False )
assert_conversion ( " チケットは #123です " , False )
assert_conversion ( " チケットは#123 です " , False )
assert_conversion ( " チケットは #123 です " )
assert_conversion ( " (#123) " )
assert_conversion ( " #123> " )
2019-03-06 06:09:50 +01:00
assert_conversion ( ' " #123 " ' )
2021-02-12 08:20:45 +01:00
assert_conversion ( " #123@ " )
assert_conversion ( " )#123( " , False )
assert_conversion ( " ##123 " , False )
2019-03-06 06:09:50 +01:00
# test nested realm patterns should avoid double matching
2021-02-12 08:19:30 +01:00
RealmFilter (
realm = realm ,
2021-02-12 08:20:45 +01:00
pattern = r " hello#(?P<id>[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/hello/ {id} " ,
2021-02-12 08:19:30 +01:00
) . save ( )
2021-02-12 08:20:45 +01:00
converted_topic = topic_links ( realm . id , " hello#123 #234 " )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted_topic ,
2021-01-26 07:32:29 +01:00
[
{ " url " : " https://trac.example.com/hello/123 " , " text " : " hello#123 " } ,
{ " url " : " https://trac.example.com/ticket/234 " , " text " : " #234 " } ,
] ,
)
# test correct order when realm pattern and normal links are both present.
converted_topic = topic_links ( realm . id , " #234 https://google.com " )
self . assertEqual (
converted_topic ,
[
{ " url " : " https://trac.example.com/ticket/234 " , " text " : " #234 " } ,
{ " url " : " https://google.com " , " text " : " https://google.com " } ,
] ,
2021-02-12 08:19:30 +01:00
)
2019-01-23 20:13:05 +01:00
2023-04-08 07:01:50 +02:00
# Test URL escaping
2021-10-20 04:57:53 +02:00
RealmFilter (
realm = realm ,
pattern = r " url-(?P<id>[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://example.com/A % 20Test/ %% % ba/ {id} " ,
2021-10-20 04:57:53 +02:00
) . save ( )
msg = Message ( sender = self . example_user ( " hamlet " ) )
content = " url-123 is well-escaped "
converted = markdown_convert ( content , message_realm = realm , message = msg )
self . assertEqual (
converted . rendered_content ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
' <p><a href= " https://example.com/A % 20Test/ %25% 25 % ba/123 " >url-123</a> is well-escaped</p> ' ,
2022-10-09 04:45:39 +02:00
)
converted_topic = topic_links ( realm . id , content )
self . assertEqual (
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
converted_topic ,
[ { " url " : " https://example.com/A % 20Test/ %25% 25 % ba/123 " , " text " : " url-123 " } ] ,
2021-10-20 04:57:53 +02:00
)
2020-09-22 20:48:22 +02:00
def test_multiple_matching_realm_patterns ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-12-02 01:39:06 +01:00
self . check_add_linkifiers (
[
RealmFilter (
realm = realm ,
pattern = " (?P<id>ABC-[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://trac.example.com/ticket/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = " (?P<id>[A-Z][A-Z0-9]*-[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://other-trac.example.com/ticket/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = " (?P<id>[A-Z][A-Z0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://yet-another-trac.example.com/ticket/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
] ,
[
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: (?P<id>ABC-[0-9]+) https://trac.example.com/ticket/ {id} > " ,
" <RealmFilter: zulip: (?P<id>[A-Z][A-Z0-9]*-[0-9]+) https://other-trac.example.com/ticket/ {id} > " ,
" <RealmFilter: zulip: (?P<id>[A-Z][A-Z0-9]+) https://yet-another-trac.example.com/ticket/ {id} > " ,
2022-12-02 01:39:06 +01:00
] ,
2021-02-12 08:19:30 +01:00
)
2020-09-22 20:48:22 +02:00
2021-02-12 08:20:45 +01:00
msg = Message ( sender = self . example_user ( " othello " ) )
2020-09-22 20:48:22 +02:00
msg . set_topic_name ( " ABC-123 " )
flush_per_request_caches ( )
2021-02-12 08:19:30 +01:00
content = (
" We should fix ABC-123 or [trac ABC-123](https://trac.example.com/ticket/16) today. "
)
2020-09-22 20:48:22 +02:00
converted = markdown_convert ( content , message_realm = realm , message = msg )
converted_topic = topic_links ( realm . id , msg . topic_name ( ) )
2021-03-30 12:08:03 +02:00
# The second linkifier (which was saved later) was ignored as the content was marked AtomicString after first conversion.
# There was no easy way to support parsing both linkifiers and not run into an infinite loop, hence the second linkifier is ignored.
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p>We should fix <a href= " https://trac.example.com/ticket/ABC-123 " >ABC-123</a> or <a href= " https://trac.example.com/ticket/16 " >trac ABC-123</a> today.</p> ' ,
)
2022-12-02 01:39:06 +01:00
# Only the older linkifier should be used in the topic, because the two patterns overlap.
self . assertEqual (
converted_topic ,
[
{ " url " : " https://trac.example.com/ticket/ABC-123 " , " text " : " ABC-123 " } ,
] ,
)
# linkifier 3 matches ASD, ABC and QWE, but because it has lower priority
# than linkifier 1 and linkifier 2 because it is created last, the former
# two matches will not be chosen.
# Both linkifier 1 and linkifier 2 matches ABC-123, similarly, as linkifier 2
# has a lower priority, only linkifier 1's URL will be generated.
converted_topic = topic_links ( realm . id , " ASD-123 ABC-123 QWE " )
2021-02-12 08:19:30 +01:00
self . assertEqual (
converted_topic ,
[
2022-12-02 01:39:06 +01:00
{ " url " : " https://other-trac.example.com/ticket/ASD-123 " , " text " : " ASD-123 " } ,
2021-01-26 07:32:29 +01:00
{ " url " : " https://trac.example.com/ticket/ABC-123 " , " text " : " ABC-123 " } ,
2022-12-02 01:39:06 +01:00
{ " url " : " https://yet-another-trac.example.com/ticket/QWE " , " text " : " QWE " } ,
] ,
)
def test_links_and_linkifiers_in_topic_name ( self ) - > None :
realm = get_realm ( " zulip " )
self . check_add_linkifiers (
[
RealmFilter (
realm = realm ,
pattern = " ABC-42 " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://google.com " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = r " com.+(?P<id>ABC \ -[0-9]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " https://trac.example.com/ticket/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
] ,
[
2023-03-08 22:18:59 +01:00
" <RealmFilter: zulip: ABC-42 https://google.com> " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
r " <RealmFilter: zulip: com.+(?P<id>ABC \ -[0-9]+) https://trac.example.com/ticket/ {id} > " ,
2022-12-02 01:39:06 +01:00
] ,
)
# This verifies that second linkifier has a lower priority than the first one.
# It helps us to later ensure that even with a low priority, the linkifier can take effect
# when it appears earlier than a raw URL.
converted_topic = topic_links ( realm . id , " com ABC-42 " )
self . assertEqual (
converted_topic ,
[ { " url " : " https://google.com " , " text " : " ABC-42 " } ] ,
)
# The linkifier matches "com/ABC-123", which is after where the raw URL starts
converted_topic = topic_links ( realm . id , " https://foo.com/ABC-123 " )
self . assertEqual (
converted_topic ,
[ { " url " : " https://foo.com/ABC-123 " , " text " : " https://foo.com/ABC-123 " } ] ,
)
# The linkifier matches "com https://foo.com/ABC-123", which is before where the raw URL starts
converted_topic = topic_links ( realm . id , " com https://foo.com/ABC-123 " )
self . assertEqual (
converted_topic ,
[
{
" url " : " https://trac.example.com/ticket/ABC-123 " ,
" text " : " com https://foo.com/ABC-123 " ,
}
] ,
)
def test_topic_links_ordering_by_priority ( self ) - > None :
2023-02-22 23:04:10 +01:00
# The same test case is also implemented in web/tests/markdown_parse.test.js
2022-12-02 01:39:06 +01:00
realm = get_realm ( " zulip " )
self . check_add_linkifiers (
[
RealmFilter (
realm = realm ,
pattern = " http " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " http://example.com/ " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = " b#(?P<id>[a-z]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " http://example.com/b/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = " a#(?P<aid>[a-z]+) b#(?P<bid>[a-z]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " http://example.com/a/ {aid} /b/ {bid} " ,
2022-12-02 01:39:06 +01:00
) ,
RealmFilter (
realm = realm ,
pattern = " a#(?P<id>[a-z]+) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " http://example.com/a/ {id} " ,
2022-12-02 01:39:06 +01:00
) ,
] ,
[
2023-03-08 22:18:59 +01:00
" <RealmFilter: zulip: http http://example.com/> " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: b#(?P<id>[a-z]+) http://example.com/b/ {id} > " ,
" <RealmFilter: zulip: a#(?P<aid>[a-z]+) b#(?P<bid>[a-z]+) http://example.com/a/ {aid} /b/ {bid} > " ,
" <RealmFilter: zulip: a#(?P<id>[a-z]+) http://example.com/a/ {id} > " ,
2022-12-02 01:39:06 +01:00
] ,
)
# There should be 5 link matches in the topic, if ordered from the most priortized to the least:
# 1. "http" (linkifier)
# 2. "b#bar" (linkifier)
# 3. "a#asd b#bar" (linkifier)
# 4. "a#asd" (linkifier)
# 5. "http://foo.com" (raw URL)
# When there are overlapping matches, the one that appears earlier in the list should
# have a topic link generated.
# For this test case, while "a#asd" and "a#asd b#bar" both match and they overlap,
# there is a match "b#bar" with a higher priority, preventing "a#asd b#bar" from being matched.
converted_topic = topic_links ( realm . id , " http://foo.com a#asd b#bar " )
self . assertEqual (
converted_topic ,
[
{
" text " : " http " ,
" url " : " http://example.com/ " ,
} ,
{
" text " : " a#asd " ,
" url " : " http://example.com/a/asd " ,
} ,
{
" text " : " b#bar " ,
" url " : " http://example.com/b/bar " ,
} ,
2021-02-12 08:19:30 +01:00
] ,
)
2020-09-22 20:48:22 +02:00
2021-03-30 12:08:03 +02:00
def test_flush_linkifier ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2016-09-23 21:55:21 +02:00
2017-11-05 10:51:25 +01:00
def flush ( ) - > None :
2021-02-12 08:19:30 +01:00
"""
2021-03-30 12:15:39 +02:00
flush_linkifiers is a post - save hook , so calling it
2016-09-23 21:55:21 +02:00
directly for testing is kind of awkward
2021-02-12 08:19:30 +01:00
"""
2017-11-05 11:49:43 +01:00
class Instance :
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
realm_id : Optional [ int ] = None
2021-02-12 08:19:30 +01:00
2016-09-23 21:55:21 +02:00
instance = Instance ( )
2017-01-03 21:04:55 +01:00
instance . realm_id = realm . id
2021-07-16 00:45:17 +02:00
flush_linkifiers ( sender = RealmFilter , instance = cast ( RealmFilter , instance ) )
2016-09-23 21:55:21 +02:00
2021-03-30 12:15:39 +02:00
def save_new_linkifier ( ) - > None :
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
linkifier = RealmFilter ( realm = realm , pattern = r " whatever " , url_template = " whatever " )
2021-03-30 12:15:39 +02:00
linkifier . save ( )
2016-09-23 21:55:21 +02:00
2017-01-08 20:24:05 +01:00
# start fresh for our realm
2016-09-23 21:55:21 +02:00
flush ( )
2021-03-30 12:15:39 +02:00
self . assertFalse ( realm_in_local_linkifiers_cache ( realm . id ) )
2016-09-23 21:55:21 +02:00
# call this just for side effects of populating the cache
2021-03-30 12:15:39 +02:00
linkifiers_for_realm ( realm . id )
self . assertTrue ( realm_in_local_linkifiers_cache ( realm . id ) )
2016-09-23 21:55:21 +02:00
# Saving a new RealmFilter should have the side effect of
# flushing the cache.
2021-03-30 12:15:39 +02:00
save_new_linkifier ( )
self . assertFalse ( realm_in_local_linkifiers_cache ( realm . id ) )
2016-09-23 21:55:21 +02:00
# and flush it one more time, to make sure we don't get a KeyError
flush ( )
2021-03-30 12:15:39 +02:00
self . assertFalse ( realm_in_local_linkifiers_cache ( realm . id ) )
2016-09-23 21:55:21 +02:00
2022-12-02 00:37:35 +01:00
def test_linkifier_precedence ( self ) - > None :
realm = self . example_user ( " hamlet " ) . realm
# The insertion order should not affect the fact that the linkifiers are ordered by id.
# Note that we might later switch to a different field to order the linkifiers.
sequence = ( 10 , 3 , 11 , 2 , 4 , 5 , 6 )
for cur_precedence in sequence :
linkifier = RealmFilter (
id = cur_precedence ,
realm = realm ,
pattern = f " abc { cur_precedence } " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = " http://foo.com " ,
2022-12-02 00:37:35 +01:00
)
linkifier . save ( )
linkifiers = linkifiers_for_realm ( realm . id )
for index , cur_precedence in enumerate ( sorted ( sequence ) ) :
self . assertEqual ( linkifiers [ index ] [ " id " ] , cur_precedence )
2017-11-05 10:51:25 +01:00
def test_realm_patterns_negative ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2021-02-12 08:19:30 +01:00
RealmFilter (
realm = realm ,
pattern = r " #(?P<id>[0-9] { 2,8}) " ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} " ,
2021-02-12 08:19:30 +01:00
) . save ( )
2021-02-12 08:20:45 +01:00
boring_msg = Message ( sender = self . example_user ( " othello " ) )
2018-11-10 16:04:54 +01:00
boring_msg . set_topic_name ( " no match here " )
2020-06-27 02:18:01 +02:00
converted_boring_topic = topic_links ( realm . id , boring_msg . topic_name ( ) )
2018-11-10 16:04:54 +01:00
self . assertEqual ( converted_boring_topic , [ ] )
2016-06-01 04:50:26 +02:00
2017-11-05 10:51:25 +01:00
def test_is_status_message ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2016-10-03 17:53:53 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2021-02-12 08:20:45 +01:00
content = " /me makes a list \n * one \n * two "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2016-10-03 17:53:53 +02:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p>/me makes a list</p> \n <ul> \n <li>one</li> \n <li>two</li> \n </ul> " ,
2016-10-03 17:53:53 +02:00
)
2021-06-17 12:20:40 +02:00
self . assertTrue ( Message . is_status_message ( content , rendering_result . rendered_content ) )
2016-10-03 17:53:53 +02:00
2021-02-12 08:20:45 +01:00
content = " /me takes a walk "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2016-10-03 17:53:53 +02:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p>/me takes a walk</p> " ,
2016-10-03 17:53:53 +02:00
)
2021-06-17 12:20:40 +02:00
self . assertTrue ( Message . is_status_message ( content , rendering_result . rendered_content ) )
2016-10-03 17:53:53 +02:00
2021-02-12 08:20:45 +01:00
content = " /me writes a second line \n line "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2018-01-21 19:27:36 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p>/me writes a second line<br> \n line</p> " ,
2018-01-21 19:27:36 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertTrue ( Message . is_status_message ( content , rendering_result . rendered_content ) )
2018-01-21 19:27:36 +01:00
2017-11-05 10:51:25 +01:00
def test_alert_words ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , [ " ALERTWORD " , " scaryword " ] )
2016-06-01 05:02:43 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2019-02-11 15:19:38 +01:00
realm_alert_words_automaton = get_alert_word_automaton ( user_profile . realm )
2016-09-14 18:02:24 +02:00
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2016-06-01 05:02:43 +02:00
content = " We have an ALERTWORD day today! "
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
self . assertEqual (
rendering_result . rendered_content , " <p>We have an ALERTWORD day today!</p> "
)
self . assertEqual ( rendering_result . user_ids_with_alert_words , { user_profile . id } )
2016-06-01 05:02:43 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " We have a NOTHINGWORD day today! "
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
self . assertEqual (
rendering_result . rendered_content , " <p>We have a NOTHINGWORD day today!</p> "
)
self . assertEqual ( rendering_result . user_ids_with_alert_words , set ( ) )
2016-06-01 05:02:43 +02:00
2019-02-11 15:19:38 +01:00
def test_alert_words_returns_user_ids_with_alert_words ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ " how " ] ,
" cordelia " : [ " this possible " ] ,
" iago " : [ " hello " ] ,
" prospero " : [ " hello " ] ,
" othello " : [ " how are you " ] ,
" aaron " : [ " hey " ] ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = " hello how is this possible how are you doing today "
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
expected_user_ids : Set [ int ] = {
2021-02-12 08:20:45 +01:00
user_profiles [ " hamlet " ] . id ,
user_profiles [ " cordelia " ] . id ,
user_profiles [ " iago " ] . id ,
user_profiles [ " prospero " ] . id ,
user_profiles [ " othello " ] . id ,
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
}
2019-02-11 15:19:38 +01:00
# All users except aaron have their alert word appear in the message content
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
def test_alert_words_returns_user_ids_with_alert_words_1 ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ " provisioning " , " Prod deployment " ] ,
" cordelia " : [ " test " , " Prod " ] ,
" iago " : [ " prod " ] ,
" prospero " : [ " deployment " ] ,
" othello " : [ " last " ] ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = """ Hello, everyone. Prod deployment has been completed
And this is a new line
2020-08-11 01:47:49 +02:00
to test out how Markdown convert this into something line ending split array
2019-02-11 15:19:38 +01:00
and this is a new line
last """
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
expected_user_ids : Set [ int ] = {
2021-02-12 08:20:45 +01:00
user_profiles [ " hamlet " ] . id ,
user_profiles [ " cordelia " ] . id ,
user_profiles [ " iago " ] . id ,
user_profiles [ " prospero " ] . id ,
user_profiles [ " othello " ] . id ,
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
}
2019-02-11 15:19:38 +01:00
# All users have their alert word appear in the message content
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
def test_alert_words_returns_user_ids_with_alert_words_in_french ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ " réglementaire " , " une politique " , " une merveille " ] ,
" cordelia " : [ " énormément " , " Prod " ] ,
" iago " : [ " prod " ] ,
" prospero " : [ " deployment " ] ,
" othello " : [ " last " ] ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = """ This is to test out alert words work in languages with accented characters too
bonjour est ( énormément ) ce a quoi ressemble le français
et j ' espère qu ' il n ' y n ' réglementaire a pas de mots d ' alerte dans ce texte français
"""
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
2021-02-12 08:20:45 +01:00
expected_user_ids : Set [ int ] = { user_profiles [ " hamlet " ] . id , user_profiles [ " cordelia " ] . id }
2019-02-11 15:19:38 +01:00
# Only hamlet and cordelia have their alert-words appear in the message content
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
def test_alert_words_returns_empty_user_ids_with_alert_words ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ ] ,
" cordelia " : [ ] ,
" iago " : [ ] ,
" prospero " : [ ] ,
" othello " : [ ] ,
" aaron " : [ ] ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = """ hello how is this possible how are you doing today
This is to test that the no user_ids who have alrert wourldword is participating
in sending of the message
"""
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
expected_user_ids : Set [ int ] = set ( )
2019-02-11 15:19:38 +01:00
# None of the users have their alert-words appear in the message content
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
def get_mock_alert_words ( self , num_words : int , word_length : int ) - > List [ str ] :
2021-02-12 08:20:45 +01:00
alert_words = [ " x " * word_length ] * num_words # type List[str]
2019-02-11 15:19:38 +01:00
return alert_words
def test_alert_words_with_empty_alert_words ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ ] ,
" cordelia " : [ ] ,
" iago " : [ ] ,
" othello " : [ ] ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = """ This is to test a empty alert words i.e. no user has any alert-words set """
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
expected_user_ids : Set [ int ] = set ( )
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
2022-02-08 00:13:33 +01:00
def test_alert_words_returns_user_ids_with_alert_words_with_huge_alert_words ( self ) - > None :
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
alert_words_for_users : Dict [ str , List [ str ] ] = {
2021-02-12 08:20:45 +01:00
" hamlet " : [ " issue124 " ] ,
" cordelia " : self . get_mock_alert_words ( 500 , 10 ) ,
" iago " : self . get_mock_alert_words ( 500 , 10 ) ,
" othello " : self . get_mock_alert_words ( 500 , 10 ) ,
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
}
user_profiles : Dict [ str , UserProfile ] = { }
2023-02-02 04:35:24 +01:00
for username , alert_words in alert_words_for_users . items ( ) :
2019-02-11 15:19:38 +01:00
user_profile = self . example_user ( username )
user_profiles . update ( { username : user_profile } )
2020-04-15 12:34:26 +02:00
do_add_alert_words ( user_profile , alert_words )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " polonius " )
2019-02-11 15:19:38 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
realm_alert_words_automaton = get_alert_word_automaton ( sender_user_profile . realm )
2021-06-17 12:20:40 +02:00
def render ( msg : Message , content : str ) - > MessageRenderingResult :
2021-02-12 08:19:30 +01:00
return render_markdown (
msg , content , realm_alert_words_automaton = realm_alert_words_automaton
)
2019-02-11 15:19:38 +01:00
content = """ The code above will print 10 random values of numbers between 1 and 100.
The second line , for x in range ( 10 ) , determines how many values will be printed ( when you use
range ( x ) , the number that you use in place of x will be the amount of values that you ' ll have
printed . if you want 20 values , use range ( 20 ) . use range ( 5 ) if you only want 5 values returned ,
2022-02-08 00:13:33 +01:00
etc . ) . I was talking about the issue124 on github . Then the third line : print random . randint ( 1 , 101 ) will automatically select a random integer
2019-02-11 15:19:38 +01:00
between 1 and 100 for you . The process is fairly simple
"""
2021-06-17 12:20:40 +02:00
rendering_result = render ( msg , content )
2021-02-12 08:20:45 +01:00
expected_user_ids : Set [ int ] = { user_profiles [ " hamlet " ] . id }
2019-02-11 15:19:38 +01:00
# Only hamlet has alert-word 'issue124' present in the message content
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . user_ids_with_alert_words , expected_user_ids )
2019-02-11 15:19:38 +01:00
2020-03-31 15:21:27 +02:00
def test_default_code_block_language ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2020-03-31 15:21:27 +02:00
self . assertEqual ( realm . default_code_block_language , None )
text = " ``` {} \n console.log( ' Hello World ' ); \n ``` \n "
# Render without default language
2021-02-12 08:20:45 +01:00
msg_with_js = markdown_convert_wrapper ( text . format ( " js " ) )
msg_with_python = markdown_convert_wrapper ( text . format ( " python " ) )
msg_without_language = markdown_convert_wrapper ( text . format ( " " ) )
msg_with_quote = markdown_convert_wrapper ( text . format ( " quote " ) )
msg_with_math = markdown_convert_wrapper ( text . format ( " math " ) )
msg_with_none = markdown_convert_wrapper ( text . format ( " none " ) )
2020-03-31 15:21:27 +02:00
# Render with default=javascript
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , " javascript " , acting_user = None )
2021-02-12 08:20:45 +01:00
msg_without_language_default_js = markdown_convert_wrapper ( text . format ( " " ) )
msg_with_python_default_js = markdown_convert_wrapper ( text . format ( " python " ) )
2020-03-31 15:21:27 +02:00
# Render with default=python
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , " python " , acting_user = None )
2021-02-12 08:20:45 +01:00
msg_without_language_default_py = markdown_convert_wrapper ( text . format ( " " ) )
msg_with_none_default_py = markdown_convert_wrapper ( text . format ( " none " ) )
2020-04-13 06:26:25 +02:00
# Render with default=quote
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , " quote " , acting_user = None )
2021-02-12 08:20:45 +01:00
msg_without_language_default_quote = markdown_convert_wrapper ( text . format ( " " ) )
2020-04-13 06:26:25 +02:00
# Render with default=math
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , " math " , acting_user = None )
2021-02-12 08:20:45 +01:00
msg_without_language_default_math = markdown_convert_wrapper ( text . format ( " " ) )
2020-03-31 15:21:27 +02:00
# Render without default language
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , None , acting_user = None )
2021-02-12 08:20:45 +01:00
msg_without_language_final = markdown_convert_wrapper ( text . format ( " " ) )
2020-03-31 15:21:27 +02:00
2020-04-13 06:26:25 +02:00
self . assertTrue ( msg_with_js == msg_without_language_default_js )
2021-02-12 08:19:30 +01:00
self . assertTrue (
msg_with_python == msg_with_python_default_js == msg_without_language_default_py
)
2020-04-13 06:26:25 +02:00
self . assertTrue ( msg_with_quote == msg_without_language_default_quote )
self . assertTrue ( msg_with_math == msg_without_language_default_math )
2020-09-06 08:41:37 +02:00
self . assertTrue ( msg_without_language == msg_without_language_final )
self . assertTrue ( msg_with_none == msg_with_none_default_py )
2020-04-13 06:26:25 +02:00
# Test checking inside nested quotes
2021-02-12 08:20:45 +01:00
nested_text = " ````quote \n \n {} \n \n {} ```` " . format ( text . format ( " js " ) , text . format ( " " ) )
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , " javascript " , acting_user = None )
2020-07-04 14:34:46 +02:00
rendered = markdown_convert_wrapper ( nested_text )
2021-02-12 08:20:45 +01:00
with_language , without_language = re . findall ( r " <pre>(.*?)$ " , rendered , re . MULTILINE )
2020-04-13 06:26:25 +02:00
self . assertTrue ( with_language == without_language )
2021-03-01 11:33:24 +01:00
do_set_realm_property ( realm , " default_code_block_language " , None , acting_user = None )
2020-07-04 14:34:46 +02:00
rendered = markdown_convert_wrapper ( nested_text )
2021-02-12 08:20:45 +01:00
with_language , without_language = re . findall ( r " <pre>(.*?)$ " , rendered , re . MULTILINE )
2020-04-13 06:26:25 +02:00
self . assertFalse ( with_language == without_language )
2020-03-31 15:21:27 +02:00
2017-11-05 10:51:25 +01:00
def test_mention_wildcard ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2016-06-01 05:24:01 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2018-01-24 17:18:07 +01:00
content = " @**all** test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2023-01-03 01:51:16 +01:00
' <p><span class= " user-mention " data-user-id= " * " >@all</span> test</p> ' ,
2021-02-12 08:19:30 +01:00
)
2023-06-03 16:51:38 +02:00
self . assertTrue ( rendering_result . mentions_stream_wildcard )
2016-06-01 05:24:01 +02:00
2017-11-05 10:51:25 +01:00
def test_mention_everyone ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2016-06-01 05:24:01 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2018-01-24 17:18:07 +01:00
content = " @**everyone** test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2023-01-03 01:51:16 +01:00
' <p><span class= " user-mention " data-user-id= " * " >@everyone</span> test</p> ' ,
2021-02-12 08:19:30 +01:00
)
2023-06-03 16:51:38 +02:00
self . assertTrue ( rendering_result . mentions_stream_wildcard )
2016-06-01 05:24:01 +02:00
2018-04-03 17:55:57 +02:00
def test_mention_stream ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2018-04-03 17:55:57 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " @**stream** test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2023-01-03 01:51:16 +01:00
' <p><span class= " user-mention " data-user-id= " * " >@stream</span> test</p> ' ,
2021-02-12 08:19:30 +01:00
)
2023-06-03 16:51:38 +02:00
self . assertTrue ( rendering_result . mentions_stream_wildcard )
2018-04-03 17:55:57 +02:00
2018-01-24 17:18:07 +01:00
def test_mention_at_wildcard ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2018-01-24 17:18:07 +01:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " @all test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , " <p>@all test</p> " )
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2018-01-24 17:18:07 +01:00
def test_mention_at_everyone ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2018-01-24 17:18:07 +01:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " @everyone test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , " <p>@everyone test</p> " )
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2018-01-24 17:18:07 +01:00
def test_mention_word_starting_with_at_wildcard ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2018-01-24 17:18:07 +01:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " test @alleycat.com test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , " <p>test @alleycat.com test</p> " )
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2018-01-24 17:18:07 +01:00
def test_mention_at_normal_user ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " othello " )
2017-08-16 20:18:09 +02:00
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
content = " @aaron test "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , " <p>@aaron test</p> " )
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2017-08-16 20:18:09 +02:00
2017-11-05 10:51:25 +01:00
def test_mention_single ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
2016-06-01 05:24:01 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2017-01-20 18:27:30 +01:00
user_id = user_profile . id
2016-06-01 05:24:01 +02:00
content = " @**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2023-01-03 01:51:16 +01:00
f ' <p><span class= " user-mention " data-user-id= " { user_id } " >@King Hamlet</span></p> ' ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
2019-01-08 09:30:19 +01:00
2021-03-23 20:22:04 +01:00
content = f " @**| { user_id } ** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-23 20:22:04 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2023-01-03 01:51:16 +01:00
f ' <p><span class= " user-mention " data-user-id= " { user_id } " >@King Hamlet</span></p> ' ,
2021-03-23 20:22:04 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
2021-03-23 20:22:04 +01:00
2022-06-13 06:02:57 +02:00
def test_mention_with_valid_special_characters_before ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
valid_characters_before_mention = [ " ( " , " { " , " [ " , " / " , " < " ]
for character in valid_characters_before_mention :
content = f " { character } @**King Hamlet** "
rendering_result = render_markdown ( msg , content )
self . assertEqual (
rendering_result . rendered_content ,
f ' <p> { escape ( character ) } <span class= " user-mention " '
f ' data-user-id= " { user_id } " > '
" @King Hamlet</span></p> " ,
)
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
def test_mention_with_invalid_special_characters_before ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
invalid_characters_before_mention = [ " . " , " , " , " ; " , " : " , " # " ]
for character in invalid_characters_before_mention :
content = f " { character } @**King Hamlet** "
rendering_result = render_markdown ( msg , content )
unicode_character = escape ( character )
self . assertEqual (
rendering_result . rendered_content ,
f " <p> { unicode_character } @<strong>King Hamlet</strong></p> " ,
)
2019-01-08 09:30:19 +01:00
def test_mention_silent ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
2019-01-08 09:30:19 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
2019-02-20 10:15:33 +01:00
content = " @_**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><span class= " user-mention silent " '
2021-03-16 18:19:51 +01:00
f ' data-user-id= " { user_id } " > '
" King Hamlet</span></p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2021-03-16 18:19:51 +01:00
2021-05-10 18:52:42 +02:00
def test_silent_wildcard_mention ( self ) - > None :
user_profile = self . example_user ( " othello " )
msg = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
wildcards = [ " all " , " everyone " , " stream " ]
for wildcard in wildcards :
content = f " @_** { wildcard } ** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-05-10 18:52:42 +02:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-05-10 18:52:42 +02:00
f ' <p><span class= " user-mention silent " data-user-id= " * " > { wildcard } </span></p> ' ,
)
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-05-10 18:52:42 +02:00
2021-03-16 18:19:51 +01:00
def test_mention_invalid_followed_by_valid ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
content = " @**Invalid user** and @**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-16 18:19:51 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-03-16 18:19:51 +01:00
' <p>@<strong>Invalid user</strong> and <span class= " user-mention " '
f ' data-user-id= " { user_id } " > '
" @King Hamlet</span></p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
2021-03-16 18:19:51 +01:00
2021-03-25 20:38:06 +01:00
def test_invalid_mention_not_uses_valid_mention_data ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
hamlet = self . example_user ( " hamlet " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
# Even though King Hamlet will be present in mention data as
2022-02-08 00:13:33 +01:00
# it was fetched for first mention but second mention is
2021-03-25 20:38:06 +01:00
# incorrect(as it uses hamlet's id) so it should not be able
# to use that data for creating a valid mention.
2021-07-09 20:34:19 +02:00
content = f " @**King Hamlet| { hamlet . id } ** and @**aaron| { hamlet . id } ** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-25 20:38:06 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-03-25 20:38:06 +01:00
f ' <p><span class= " user-mention " data-user-id= " { hamlet . id } " > '
f " @King Hamlet</span> and @<strong>aaron| { hamlet . id } </strong></p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { hamlet . id } )
2021-03-25 20:38:06 +01:00
2021-03-16 18:19:51 +01:00
def test_silent_mention_invalid_followed_by_valid ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
content = " @_**Invalid user** and @_**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-16 18:19:51 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-03-16 18:19:51 +01:00
' <p>@_<strong>Invalid user</strong> and <span class= " user-mention silent " '
2021-02-12 08:19:30 +01:00
f ' data-user-id= " { user_id } " > '
2021-02-12 08:20:45 +01:00
" King Hamlet</span></p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2016-06-01 05:24:01 +02:00
2021-03-23 20:22:04 +01:00
content = f " @_**|123456789** and @_**| { user_id } ** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-23 20:22:04 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-03-23 20:22:04 +01:00
" <p>@_<strong>|123456789</strong> and "
' <span class= " user-mention silent " '
f ' data-user-id= " { user_id } " > '
" King Hamlet</span></p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2021-03-23 20:22:04 +01:00
2017-11-05 10:51:25 +01:00
def test_possible_mentions ( self ) - > None :
2023-06-03 16:51:38 +02:00
def assert_mentions (
content : str , names : Set [ str ] , has_stream_wildcards : bool = False
) - > None :
2023-05-30 07:23:36 +02:00
self . assertEqual (
possible_mentions ( content ) ,
2023-06-03 16:51:38 +02:00
PossibleMentions (
mention_texts = names , message_has_stream_wildcards = has_stream_wildcards
) ,
2023-05-30 07:23:36 +02:00
)
2017-09-14 19:47:22 +02:00
2021-03-23 20:22:04 +01:00
aaron = self . example_user ( " aaron " )
2021-02-12 08:20:45 +01:00
assert_mentions ( " " , set ( ) )
assert_mentions ( " boring " , set ( ) )
assert_mentions ( " @**all** " , set ( ) , True )
assert_mentions ( " smush@**steve**smush " , set ( ) )
2017-09-14 19:47:22 +02:00
assert_mentions (
2021-04-11 16:26:54 +02:00
f " Hello @**King Hamlet**, @**| { aaron . id } ** and @**Cordelia, Lear ' s daughter** \n @**Foo van Barson|1234** @**all** " ,
{ " King Hamlet " , f " | { aaron . id } " , " Cordelia, Lear ' s daughter " , " Foo van Barson|1234 " } ,
2021-02-12 08:19:30 +01:00
True ,
2017-09-14 19:47:22 +02:00
)
2017-11-05 10:51:25 +01:00
def test_mention_multiple ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
2016-06-01 05:24:01 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2021-04-11 16:26:54 +02:00
content = " @**King Hamlet** and @**Cordelia, Lear ' s daughter**, check this out "
2017-09-14 19:47:22 +02:00
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p> "
2021-02-12 08:19:30 +01:00
' <span class= " user-mention " '
f ' data-user-id= " { hamlet . id } " >@King Hamlet</span> and '
' <span class= " user-mention " '
2021-04-11 16:26:54 +02:00
f ' data-user-id= " { cordelia . id } " >@Cordelia, Lear \' s daughter</span>, '
2021-02-12 08:20:45 +01:00
" check this out</p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { hamlet . id , cordelia . id } )
2016-06-01 05:24:01 +02:00
2019-01-08 11:30:13 +01:00
def test_mention_in_quotes ( self ) - > None :
2021-02-12 08:20:45 +01:00
othello = self . example_user ( " othello " )
hamlet = self . example_user ( " hamlet " )
cordelia = self . example_user ( " cordelia " )
2019-01-08 11:30:13 +01:00
msg = Message ( sender = othello , sending_client = get_client ( " test " ) )
2021-04-11 16:26:54 +02:00
content = " > @**King Hamlet** and @**Othello, the Moor of Venice** \n \n @**King Hamlet** and @**Cordelia, Lear ' s daughter** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <blockquote> \n <p> "
2021-02-12 08:19:30 +01:00
f ' <span class= " user-mention silent " data-user-id= " { hamlet . id } " >King Hamlet</span> '
2021-02-12 08:20:45 +01:00
" and "
2021-02-12 08:19:30 +01:00
f ' <span class= " user-mention silent " data-user-id= " { othello . id } " >Othello, the Moor of Venice</span> '
2021-02-12 08:20:45 +01:00
" </p> \n </blockquote> \n "
" <p> "
2021-02-12 08:19:30 +01:00
f ' <span class= " user-mention " data-user-id= " { hamlet . id } " >@King Hamlet</span> '
2021-02-12 08:20:45 +01:00
" and "
2021-04-11 16:26:54 +02:00
f ' <span class= " user-mention " data-user-id= " { cordelia . id } " >@Cordelia, Lear \' s daughter</span> '
2021-02-12 08:20:45 +01:00
" </p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { hamlet . id , cordelia . id } )
2019-01-08 11:30:13 +01:00
2019-01-22 14:47:10 +01:00
# Both fenced quote and > quote should be identical for both silent and regular syntax.
2021-02-12 08:19:30 +01:00
expected = (
2021-02-12 08:20:45 +01:00
" <blockquote> \n <p> "
2021-02-12 08:19:30 +01:00
f ' <span class= " user-mention silent " data-user-id= " { hamlet . id } " >King Hamlet</span> '
2021-02-12 08:20:45 +01:00
" </p> \n </blockquote> "
2021-02-12 08:19:30 +01:00
)
2019-01-08 11:30:13 +01:00
content = " ```quote \n @**King Hamlet** \n ``` "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , expected )
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2019-01-08 11:30:13 +01:00
content = " > @**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , expected )
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2019-02-20 10:15:33 +01:00
content = " ```quote \n @_**King Hamlet** \n ``` "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , expected )
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2019-02-20 10:15:33 +01:00
content = " > @_**King Hamlet** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . rendered_content , expected )
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2019-01-08 11:30:13 +01:00
2021-05-10 18:52:42 +02:00
def test_wildcard_mention_in_quotes ( self ) - > None :
user_profile = self . example_user ( " othello " )
2021-06-17 12:20:40 +02:00
message = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2021-05-10 18:52:42 +02:00
def assert_silent_mention ( content : str , wildcard : str ) - > None :
expected = (
" <blockquote> \n <p> "
f ' <span class= " user-mention silent " data-user-id= " * " > { wildcard } </span> '
" </p> \n </blockquote> "
)
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( message , content )
self . assertEqual ( rendering_result . rendered_content , expected )
2023-06-03 16:51:38 +02:00
self . assertFalse ( rendering_result . mentions_stream_wildcard )
2021-05-10 18:52:42 +02:00
wildcards = [ " all " , " everyone " , " stream " ]
for wildcard in wildcards :
assert_silent_mention ( f " > @** { wildcard } ** " , wildcard )
assert_silent_mention ( f " > @_** { wildcard } ** " , wildcard )
assert_silent_mention ( f " ```quote \n @** { wildcard } ** \n ``` " , wildcard )
assert_silent_mention ( f " ```quote \n @_** { wildcard } ** \n ``` " , wildcard )
2018-08-19 00:02:17 +02:00
def test_mention_duplicate_full_name ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2018-08-19 00:02:17 +02:00
def make_user ( email : str , full_name : str ) - > UserProfile :
return create_user (
email = email ,
2021-02-12 08:20:45 +01:00
password = " whatever " ,
2018-08-19 00:02:17 +02:00
realm = realm ,
full_name = full_name ,
)
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
twin1 = make_user ( " twin1@example.com " , " Mark Twin " )
twin2 = make_user ( " twin2@example.com " , " Mark Twin " )
cordelia = self . example_user ( " cordelia " )
2018-08-19 00:02:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2021-04-11 16:26:54 +02:00
content = f " @**Mark Twin| { twin1 . id } **, @**Mark Twin| { twin2 . id } ** and @**Cordelia, Lear ' s daughter**, hi. "
2021-02-12 08:19:30 +01:00
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p> "
2021-02-12 08:19:30 +01:00
' <span class= " user-mention " '
f ' data-user-id= " { twin1 . id } " >@Mark Twin</span>, '
' <span class= " user-mention " '
f ' data-user-id= " { twin2 . id } " >@Mark Twin</span> and '
' <span class= " user-mention " '
2021-04-11 16:26:54 +02:00
f ' data-user-id= " { cordelia . id } " >@Cordelia, Lear \' s daughter</span>, '
2021-02-12 08:20:45 +01:00
" hi.</p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { twin1 . id , twin2 . id , cordelia . id } )
2018-08-19 00:02:17 +02:00
2017-11-05 10:51:25 +01:00
def test_mention_invalid ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-06-01 05:24:01 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " Hey @**Nonexistent User** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content , " <p>Hey @<strong>Nonexistent User</strong></p> "
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2020-03-06 13:00:17 +01:00
def test_user_mention_atomic_string ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
realm = get_realm ( " zulip " )
2020-03-06 13:00:17 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
# Create a linkifier.
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} "
2021-03-30 12:08:03 +02:00
linkifier = RealmFilter (
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
realm = realm , pattern = r " #(?P<id>[0-9] { 2,8}) " , url_template = url_template
2021-02-12 08:19:30 +01:00
)
2021-03-30 12:08:03 +02:00
linkifier . save ( )
2020-03-06 13:00:17 +01:00
self . assertEqual (
2023-03-08 22:18:59 +01:00
repr ( linkifier ) ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: #(?P<id>[0-9] { 2,8}) https://trac.example.com/ticket/ {id} > " ,
2021-02-12 08:19:30 +01:00
)
2020-03-06 13:00:17 +01:00
# Create a user that potentially interferes with the pattern.
2020-07-16 14:10:43 +02:00
test_user = create_user (
2021-02-12 08:20:45 +01:00
email = " atomic@example.com " ,
password = " whatever " ,
2020-07-16 14:10:43 +02:00
realm = realm ,
2021-02-12 08:20:45 +01:00
full_name = " Atomic #123 " ,
2020-07-16 14:10:43 +02:00
)
2020-03-06 13:00:17 +01:00
content = " @**Atomic #123** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><span class= " user-mention " '
f ' data-user-id= " { test_user . id } " > '
2021-02-12 08:20:45 +01:00
" @Atomic #123</span></p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { test_user . id } )
2020-03-06 13:00:17 +01:00
content = " @_**Atomic #123** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><span class= " user-mention silent " '
f ' data-user-id= " { test_user . id } " > '
2021-02-12 08:20:45 +01:00
" Atomic #123</span></p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2016-06-01 05:24:01 +02:00
2018-05-11 01:39:38 +02:00
def create_user_group_for_test ( self , user_group_name : str ) - > UserGroup :
2021-02-12 08:20:45 +01:00
othello = self . example_user ( " othello " )
2022-12-14 06:45:55 +01:00
return check_add_user_group (
get_realm ( " zulip " ) , user_group_name , [ othello ] , acting_user = None
)
2017-09-25 09:47:15 +02:00
2017-11-05 10:51:25 +01:00
def test_user_group_mention_single ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
2017-09-25 09:47:15 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
2021-02-12 08:20:45 +01:00
user_group = self . create_user_group_for_test ( " support " )
2017-09-25 09:47:15 +02:00
content = " @**King Hamlet** @*support* "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><span class= " user-mention " '
f ' data-user-id= " { user_id } " > '
2021-02-12 08:20:45 +01:00
" @King Hamlet</span> "
2021-02-12 08:19:30 +01:00
' <span class= " user-group-mention " '
f ' data-user-group-id= " { user_group . id } " > '
2021-02-12 08:20:45 +01:00
" @support</span></p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
self . assertEqual ( rendering_result . mentions_user_group_ids , { user_group . id } )
2017-09-25 09:47:15 +02:00
2021-03-18 11:34:49 +01:00
def test_invalid_user_group_followed_by_valid_mention_single ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
user_profile = self . example_user ( " hamlet " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
user_id = user_profile . id
user_group = self . create_user_group_for_test ( " support " )
content = " @**King Hamlet** @*Invalid user group* @*support* "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-03-18 11:34:49 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-03-18 11:34:49 +01:00
' <p><span class= " user-mention " '
f ' data-user-id= " { user_id } " > '
" @King Hamlet</span> "
" @<em>Invalid user group</em> "
' <span class= " user-group-mention " '
f ' data-user-group-id= " { user_group . id } " > '
" @support</span></p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
self . assertEqual ( rendering_result . mentions_user_group_ids , { user_group . id } )
2021-03-18 11:34:49 +01:00
2020-03-06 12:48:06 +01:00
def test_user_group_mention_atomic_string ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
realm = get_realm ( " zulip " )
2020-03-06 12:48:06 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2020-03-06 12:48:06 +01:00
# Create a linkifier.
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} "
2021-03-30 12:08:03 +02:00
linkifier = RealmFilter (
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
realm = realm , pattern = r " #(?P<id>[0-9] { 2,8}) " , url_template = url_template
2021-02-12 08:19:30 +01:00
)
2021-03-30 12:08:03 +02:00
linkifier . save ( )
2020-03-06 12:48:06 +01:00
self . assertEqual (
2023-03-08 22:18:59 +01:00
repr ( linkifier ) ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: #(?P<id>[0-9] { 2,8}) https://trac.example.com/ticket/ {id} > " ,
2021-02-12 08:19:30 +01:00
)
2020-03-06 12:48:06 +01:00
# Create a user-group that potentially interferes with the pattern.
user_id = user_profile . id
2021-02-12 08:20:45 +01:00
user_group = self . create_user_group_for_test ( " support #123 " )
2020-03-06 12:48:06 +01:00
content = " @**King Hamlet** @*support #123* "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:19:30 +01:00
' <p><span class= " user-mention " '
f ' data-user-id= " { user_id } " > '
2021-02-12 08:20:45 +01:00
" @King Hamlet</span> "
2021-02-12 08:19:30 +01:00
' <span class= " user-group-mention " '
f ' data-user-group-id= " { user_group . id } " > '
2021-02-12 08:20:45 +01:00
" @support #123</span></p> " ,
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , { user_profile . id } )
self . assertEqual ( rendering_result . mentions_user_group_ids , { user_group . id } )
2020-03-06 12:48:06 +01:00
2017-11-05 10:51:25 +01:00
def test_possible_user_group_mentions ( self ) - > None :
2018-05-11 01:39:38 +02:00
def assert_mentions ( content : str , names : Set [ str ] ) - > None :
2017-09-25 09:47:15 +02:00
self . assertEqual ( possible_user_group_mentions ( content ) , names )
2021-02-12 08:20:45 +01:00
assert_mentions ( " " , set ( ) )
assert_mentions ( " boring " , set ( ) )
assert_mentions ( " @**all** " , set ( ) )
assert_mentions ( " smush@*steve*smush " , set ( ) )
2017-09-25 09:47:15 +02:00
assert_mentions (
2021-04-11 16:26:54 +02:00
" @*support* Hello @**King Hamlet** and @**Cordelia, Lear ' s daughter** \n "
2021-02-12 08:20:45 +01:00
" @**Foo van Barson** @**all** " ,
{ " support " } ,
2017-09-25 09:47:15 +02:00
)
assert_mentions (
2021-02-12 08:20:45 +01:00
" Attention @*support*, @*frontend* and @*backend* \n groups. " ,
{ " support " , " frontend " , " backend " } ,
2017-09-25 09:47:15 +02:00
)
2017-11-05 10:51:25 +01:00
def test_user_group_mention_multiple ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-09-25 09:47:15 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2021-02-12 08:20:45 +01:00
support = self . create_user_group_for_test ( " support " )
backend = self . create_user_group_for_test ( " backend " )
2017-09-25 09:47:15 +02:00
content = " @*support* and @*backend*, check this out "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p> "
2021-02-12 08:19:30 +01:00
' <span class= " user-group-mention " '
f ' data-user-group-id= " { support . id } " > '
2021-02-12 08:20:45 +01:00
" @support</span> "
" and "
2021-02-12 08:19:30 +01:00
' <span class= " user-group-mention " '
f ' data-user-group-id= " { backend . id } " > '
2021-02-12 08:20:45 +01:00
" @backend</span>, "
" check this out "
" </p> " ,
2021-02-12 08:19:30 +01:00
)
2017-09-25 09:47:15 +02:00
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_group_ids , { support . id , backend . id } )
2017-09-25 09:47:15 +02:00
2020-05-11 21:59:03 +02:00
def test_user_group_mention_edit ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " hamlet " )
user_profile = self . example_user ( " othello " )
self . create_user_group_for_test ( " support " )
self . login ( " hamlet " )
2020-05-11 21:59:03 +02:00
2021-02-12 08:19:30 +01:00
msg_id = self . send_stream_message (
2021-02-12 08:20:45 +01:00
sender_user_profile , " Denmark " , topic_name = " editing " , content = " test "
2021-02-12 08:19:30 +01:00
)
2020-05-11 21:59:03 +02:00
def update_message_and_check_flag ( content : str , mentioned : bool ) - > None :
2021-02-12 08:19:30 +01:00
result = self . client_patch (
" /json/messages/ " + str ( msg_id ) ,
{
2021-02-12 08:20:45 +01:00
" content " : content ,
2021-02-12 08:19:30 +01:00
} ,
)
2020-05-11 21:59:03 +02:00
self . assert_json_success ( result )
um = UserMessage . objects . get (
user_profile_id = user_profile . id ,
message_id = msg_id ,
)
if mentioned :
2021-02-12 08:20:45 +01:00
self . assertIn ( " mentioned " , um . flags_list ( ) )
2020-05-11 21:59:03 +02:00
else :
2021-02-12 08:20:45 +01:00
self . assertNotIn ( " mentioned " , um . flags_list ( ) )
2020-05-11 21:59:03 +02:00
update_message_and_check_flag ( " @*support* " , True )
update_message_and_check_flag ( " @*support-invalid* edited " , False )
update_message_and_check_flag ( " @*support* edited " , True )
update_message_and_check_flag ( " edited " , False )
update_message_and_check_flag ( " @*support* " , True )
2021-05-16 09:43:47 +02:00
update_message_and_check_flag ( " @_*support* " , False )
2020-05-11 21:59:03 +02:00
2017-11-05 10:51:25 +01:00
def test_user_group_mention_invalid ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-09-25 09:47:15 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " Hey @*Nonexistent group* "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
self . assertEqual (
rendering_result . rendered_content , " <p>Hey @<em>Nonexistent group</em></p> "
)
self . assertEqual ( rendering_result . mentions_user_group_ids , set ( ) )
2017-09-25 09:47:15 +02:00
2021-05-16 09:43:47 +02:00
def test_user_group_silent_mention ( self ) - > None :
sender_user_profile = self . example_user ( " othello " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
support = self . create_user_group_for_test ( " support " )
content = " We ' ll add you to @_*support* user group. "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-05-16 09:43:47 +02:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content ,
2021-05-16 09:43:47 +02:00
" <p>We ' ll add you to "
f ' <span class= " user-group-mention silent " data-user-group-id= " { support . id } " >support</span> '
" user group.</p> " ,
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_group_ids , set ( ) )
2021-05-16 09:43:47 +02:00
2021-05-16 10:33:39 +02:00
def test_user_group_mention_in_quotes ( self ) - > None :
user_profile = self . example_user ( " othello " )
2021-06-17 12:20:40 +02:00
message = Message ( sender = user_profile , sending_client = get_client ( " test " ) )
2021-05-16 10:33:39 +02:00
backend = self . create_user_group_for_test ( " backend " )
def assert_silent_mention ( content : str ) - > None :
expected = (
" <blockquote> \n <p> "
f ' <span class= " user-group-mention silent " data-user-group-id= " { backend . id } " >backend</span> '
" </p> \n </blockquote> "
)
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( message , content )
self . assertEqual ( rendering_result . rendered_content , expected )
self . assertEqual ( rendering_result . mentions_user_group_ids , set ( ) )
2021-05-16 10:33:39 +02:00
assert_silent_mention ( " > @*backend* " )
assert_silent_mention ( " > @_*backend* " )
assert_silent_mention ( " ```quote \n @*backend* \n ``` " )
assert_silent_mention ( " ```quote \n @_*backend* \n ``` " )
2021-08-21 16:25:05 +02:00
def test_system_user_group_mention ( self ) - > None :
desdemona = self . example_user ( " desdemona " )
iago = self . example_user ( " iago " )
hamlet = self . example_user ( " hamlet " )
2021-08-11 15:10:17 +02:00
moderators_group = UserGroup . objects . get (
2022-08-10 12:12:38 +02:00
realm = iago . realm , name = UserGroup . MODERATORS_GROUP_NAME , is_system_group = True
2021-08-21 16:25:05 +02:00
)
2021-08-11 15:10:17 +02:00
content = " @*role:moderators* @**King Hamlet** test message "
2021-08-21 16:25:05 +02:00
# Owner cannot mention a system user group.
msg = Message ( sender = desdemona , sending_client = get_client ( " test " ) )
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . mentions_user_ids , { hamlet . id } )
self . assertNotIn ( moderators_group , rendering_result . mentions_user_group_ids )
# Admin belonging to user group also cannot mention a system user group.
msg = Message ( sender = iago , sending_client = get_client ( " test " ) )
rendering_result = render_markdown ( msg , content )
self . assertEqual ( rendering_result . mentions_user_ids , { hamlet . id } )
self . assertNotIn ( moderators_group , rendering_result . mentions_user_group_ids )
2017-11-05 10:51:25 +01:00
def test_stream_single ( self ) - > None :
2021-02-12 08:20:45 +01:00
denmark = get_stream ( " Denmark " , get_realm ( " zulip " ) )
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**Denmark** "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2018-02-15 21:02:47 +01:00
' <p><a class= " stream " data-stream-id= " {d.id} " href= " /#narrow/stream/ {d.id} -Denmark " ># {d.name} </a></p> ' . format (
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
d = denmark ,
2021-02-12 08:19:30 +01:00
) ,
)
2016-10-26 20:56:17 +02:00
2021-03-18 11:43:52 +01:00
def test_invalid_stream_followed_by_valid_mention ( self ) - > None :
denmark = get_stream ( " Denmark " , get_realm ( " zulip " ) )
sender_user_profile = self . example_user ( " othello " )
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**Invalid** and #**Denmark** "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2021-03-18 11:43:52 +01:00
' <p>#<strong>Invalid</strong> and <a class= " stream " data-stream-id= " {d.id} " href= " /#narrow/stream/ {d.id} -Denmark " ># {d.name} </a></p> ' . format (
d = denmark ,
) ,
)
2017-11-05 10:51:25 +01:00
def test_stream_multiple ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
denmark = get_stream ( " Denmark " , realm )
scotland = get_stream ( " Scotland " , realm )
2016-10-26 20:56:17 +02:00
content = " Look to #**Denmark** and #**Scotland**, there something "
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p>Look to "
2021-02-12 08:19:30 +01:00
' <a class= " stream " '
' data-stream-id= " {denmark.id} " '
' href= " /#narrow/stream/ {denmark.id} -Denmark " ># {denmark.name} </a> and '
' <a class= " stream " '
' data-stream-id= " {scotland.id} " '
' href= " /#narrow/stream/ {scotland.id} -Scotland " ># {scotland.name} </a>, '
2021-02-12 08:20:45 +01:00
" there something</p> " . format ( denmark = denmark , scotland = scotland ) ,
2021-02-12 08:19:30 +01:00
)
2016-10-26 20:56:17 +02:00
2017-11-05 10:51:25 +01:00
def test_stream_case_sensitivity ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-07-13 20:11:29 +02:00
case_sens = self . make_stream ( stream_name = " CaseSens " , realm = realm )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**CaseSens** "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2018-02-15 21:02:47 +01:00
' <p><a class= " stream " data-stream-id= " {s.id} " href= " /#narrow/stream/ {s.id} - {s.name} " ># {s.name} </a></p> ' . format (
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
s = case_sens ,
2021-02-12 08:19:30 +01:00
) ,
)
2016-10-26 20:56:17 +02:00
2017-11-05 10:51:25 +01:00
def test_stream_case_sensitivity_nonmatching ( self ) - > None :
2016-10-26 20:56:17 +02:00
""" #StreamName requires the stream be spelled with the correct case
currently . If we change that in the future , we ' ll need to change this
test . """
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-07-13 20:11:29 +02:00
self . make_stream ( stream_name = " CaseSens " , realm = realm )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**casesens** "
2021-06-17 12:20:40 +02:00
self . assertEqual (
render_markdown ( msg , content ) . rendered_content , " <p>#<strong>casesens</strong></p> "
)
2016-10-26 20:56:17 +02:00
2019-06-21 17:31:16 +02:00
def test_topic_single ( self ) - > None :
2021-02-12 08:20:45 +01:00
denmark = get_stream ( " Denmark " , get_realm ( " zulip " ) )
sender_user_profile = self . example_user ( " othello " )
2019-06-21 17:31:16 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**Denmark>some topic** "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2019-06-21 17:31:16 +02:00
' <p><a class= " stream-topic " data-stream-id= " {d.id} " href= " /#narrow/stream/ {d.id} -Denmark/topic/some.20topic " ># {d.name} > some topic</a></p> ' . format (
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
d = denmark ,
2021-02-12 08:19:30 +01:00
) ,
)
2019-06-21 17:31:16 +02:00
2020-03-01 20:04:26 +01:00
def test_topic_atomic_string ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2020-03-01 20:04:26 +01:00
# Create a linkifier.
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} "
2021-03-30 12:08:03 +02:00
linkifier = RealmFilter (
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
realm = realm , pattern = r " #(?P<id>[0-9] { 2,8}) " , url_template = url_template
2021-02-12 08:19:30 +01:00
)
2021-03-30 12:08:03 +02:00
linkifier . save ( )
2020-03-01 20:04:26 +01:00
self . assertEqual (
2023-03-08 22:18:59 +01:00
repr ( linkifier ) ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: #(?P<id>[0-9] { 2,8}) https://trac.example.com/ticket/ {id} > " ,
2021-02-12 08:19:30 +01:00
)
2020-03-01 20:04:26 +01:00
# Create a topic link that potentially interferes with the pattern.
2021-02-12 08:20:45 +01:00
denmark = get_stream ( " Denmark " , realm )
2020-03-01 20:04:26 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " #**Denmark>#1234** "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2020-03-01 20:04:26 +01:00
' <p><a class= " stream-topic " data-stream-id= " {d.id} " href= " /#narrow/stream/ {d.id} -Denmark/topic/.231234 " ># {d.name} > #1234</a></p> ' . format (
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
d = denmark ,
2021-02-12 08:19:30 +01:00
) ,
)
2020-03-01 20:04:26 +01:00
2019-06-21 17:31:16 +02:00
def test_topic_multiple ( self ) - > None :
2021-02-12 08:20:45 +01:00
denmark = get_stream ( " Denmark " , get_realm ( " zulip " ) )
scotland = get_stream ( " Scotland " , get_realm ( " zulip " ) )
sender_user_profile = self . example_user ( " othello " )
2019-06-21 17:31:16 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " This has two links: #**Denmark>some topic** and #**Scotland>other topic**. "
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2021-02-12 08:20:45 +01:00
" <p>This has two links: "
2019-06-21 17:31:16 +02:00
' <a class= " stream-topic " data-stream-id= " {denmark.id} " '
' href= " /#narrow/stream/ {denmark.id} - {denmark.name} /topic/some.20topic " > '
2021-02-12 08:20:45 +01:00
" # {denmark.name} > some topic</a> "
" and "
2019-06-21 17:31:16 +02:00
' <a class= " stream-topic " data-stream-id= " {scotland.id} " '
' href= " /#narrow/stream/ {scotland.id} - {scotland.name} /topic/other.20topic " > '
2021-02-12 08:20:45 +01:00
" # {scotland.name} > other topic</a> "
" .</p> " . format ( denmark = denmark , scotland = scotland ) ,
2021-02-12 08:19:30 +01:00
)
2019-06-21 17:31:16 +02:00
2017-11-05 10:51:25 +01:00
def test_possible_stream_names ( self ) - > None :
2021-02-12 08:20:45 +01:00
content = """ #**test here**
2017-09-15 00:25:38 +02:00
This mentions #**Denmark** too.
#**garçon** #**천국** @**Ignore Person**
2021-02-12 08:20:45 +01:00
"""
2017-09-15 00:25:38 +02:00
self . assertEqual (
2020-06-27 02:18:01 +02:00
possible_linked_stream_names ( content ) ,
2021-02-12 08:20:45 +01:00
{ " test here " , " Denmark " , " garçon " , " 천국 " } ,
2017-09-15 00:25:38 +02:00
)
2017-11-05 10:51:25 +01:00
def test_stream_unicode ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2022-07-13 20:11:29 +02:00
uni = self . make_stream ( stream_name = " привет " , realm = realm )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2020-04-09 21:51:58 +02:00
content = " #**привет** "
2021-02-12 08:20:45 +01:00
quoted_name = " .D0.BF.D1.80.D0.B8.D0.B2.D0.B5.D1.82 "
href = f " /#narrow/stream/ { uni . id } - { quoted_name } "
2016-10-26 20:56:17 +02:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2020-04-09 21:51:58 +02:00
' <p><a class= " stream " data-stream-id= " {s.id} " href= " {href} " ># {s.name} </a></p> ' . format (
2016-10-26 20:56:17 +02:00
s = uni ,
2018-02-15 21:02:47 +01:00
href = href ,
2021-02-12 08:19:30 +01:00
) ,
)
2016-10-26 20:56:17 +02:00
2020-03-01 20:22:09 +01:00
def test_stream_atomic_string ( self ) - > None :
2021-02-12 08:20:45 +01:00
realm = get_realm ( " zulip " )
2020-03-01 20:22:09 +01:00
# Create a linkifier.
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
url_template = r " https://trac.example.com/ticket/ {id} "
2021-03-30 12:08:03 +02:00
linkifier = RealmFilter (
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
realm = realm , pattern = r " #(?P<id>[0-9] { 2,8}) " , url_template = url_template
2021-02-12 08:19:30 +01:00
)
2021-03-30 12:08:03 +02:00
linkifier . save ( )
2020-03-01 20:22:09 +01:00
self . assertEqual (
2023-03-08 22:18:59 +01:00
repr ( linkifier ) ,
linkifier: Support URL templates for linkifiers.
This swaps out url_format_string from all of our APIs and replaces it
with url_template. Note that the documentation changes in the following
commits will be squashed with this commit.
We change the "url_format" key to "url_template" for the
realm_linkifiers events in event_schema, along with updating
LinkifierDict. "url_template" is the name chosen to normalize
mixed usages of "url_format_string" and "url_format" throughout
the backend.
The markdown processor is updated to stop handling the format string
interpolation and delegate the task template expansion to the uri_template
library instead.
This change affects many test cases. We mostly just replace "%(name)s"
with "{name}", "url_format_string" with "url_template" to make sure that
they still pass. There are some test cases dedicated for testing "%"
escaping, which aren't relevant anymore and are subject to removal.
But for now we keep most of them as-is, and make sure that "%" is always
escaped since we do not use it for variable substitution any more.
Since url_format_string is not populated anymore, a migration is created
to remove this field entirely, and make url_template non-nullable since
we will always populate it. Note that it is possible to have
url_template being null after migration 0422 and before 0424, but
in practice, url_template will not be None after backfilling and the
backend now is always setting url_template.
With the removal of url_format_string, RealmFilter model will now be cleaned
with URL template checks, and the old checks for escapes are removed.
We also modified RealmFilter.clean to skip the validation when the
url_template is invalid. This avoids raising mulitple ValidationError's
when calling full_clean on a linkifier. But we might eventually want to
have a more centric approach to data validation instead of having
the same validation in both the clean method and the validator.
Fixes #23124.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-10-05 20:55:31 +02:00
" <RealmFilter: zulip: #(?P<id>[0-9] { 2,8}) https://trac.example.com/ticket/ {id} > " ,
2021-02-12 08:19:30 +01:00
)
2020-03-01 20:22:09 +01:00
# Create a stream that potentially interferes with the pattern.
2022-07-13 20:11:29 +02:00
stream = self . make_stream ( stream_name = " Stream #1234 " , realm = realm )
2020-03-01 20:22:09 +01:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2020-04-09 21:51:58 +02:00
content = " #**Stream #1234** "
2021-02-12 08:20:45 +01:00
href = f " /#narrow/stream/ { stream . id } -Stream-.231234 "
2020-03-01 20:22:09 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
render_markdown ( msg , content ) . rendered_content ,
2020-04-09 21:51:58 +02:00
' <p><a class= " stream " data-stream-id= " {s.id} " href= " {href} " ># {s.name} </a></p> ' . format (
2020-03-01 20:22:09 +01:00
s = stream ,
href = href ,
2021-02-12 08:19:30 +01:00
) ,
)
2020-03-01 20:22:09 +01:00
2017-11-05 10:51:25 +01:00
def test_stream_invalid ( self ) - > None :
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2016-10-26 20:56:17 +02:00
msg = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
content = " There #**Nonexistentstream** "
2021-06-17 12:20:40 +02:00
rendering_result = render_markdown ( msg , content )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
rendering_result . rendered_content , " <p>There #<strong>Nonexistentstream</strong></p> "
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . mentions_user_ids , set ( ) )
2016-10-26 20:56:17 +02:00
2023-06-12 22:52:59 +02:00
@override_settings ( THUMBNAIL_IMAGES = True )
2017-11-05 10:51:25 +01:00
def test_image_preview_title ( self ) - > None :
2021-02-12 08:20:45 +01:00
msg = " [My favorite image](https://example.com/testimage.png) "
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2016-09-22 22:39:24 +02:00
self . assertEqual (
converted ,
2021-02-12 08:20:45 +01:00
" <p> "
2020-05-09 03:44:56 +02:00
' <a href= " https://example.com/testimage.png " >My favorite image</a> '
2021-02-12 08:20:45 +01:00
" </p> \n "
2016-09-22 22:39:24 +02:00
' <div class= " message_inline_image " > '
2020-02-29 01:37:33 +01:00
' <a href= " https://example.com/testimage.png " title= " My favorite image " > '
2018-07-30 21:26:01 +02:00
' <img data-src-fullsize= " /thumbnail?url=https % 3A %2F %2F example.com %2F testimage.png&size=full " src= " /thumbnail?url=https % 3A %2F %2F example.com %2F testimage.png&size=thumbnail " > '
2021-02-12 08:20:45 +01:00
" </a> "
" </div> " ,
2016-09-22 22:39:24 +02:00
)
2017-11-05 10:51:25 +01:00
def test_mit_rendering ( self ) - > None :
2020-08-11 01:47:49 +02:00
""" Test the Markdown configs for the MIT Zephyr mirroring system;
2016-09-09 22:53:36 +02:00
verifies almost all inline patterns are disabled , but
inline_interesting_links is still enabled """
2014-01-31 23:13:58 +01:00
msg = " **test** "
2017-03-04 09:19:37 +01:00
realm = get_realm ( " zephyr " )
2017-01-22 06:29:11 +01:00
client = get_client ( " zephyr_mirror " )
2021-02-12 08:19:30 +01:00
message = Message ( sending_client = client , sender = self . mit_user ( " sipbtest " ) )
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( msg , message_realm = realm , message = message )
2014-01-31 23:13:58 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2014-01-31 23:13:58 +01:00
" <p>**test**</p> " ,
2017-01-24 06:34:26 +01:00
)
2014-02-04 23:37:21 +01:00
msg = " * test "
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( msg , message_realm = realm , message = message )
2014-02-04 23:37:21 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2014-02-04 23:37:21 +01:00
" <p>* test</p> " ,
2017-01-24 06:34:26 +01:00
)
2014-02-07 22:05:19 +01:00
msg = " https://lists.debian.org/debian-ctte/2014/02/msg00173.html "
2020-07-04 14:34:46 +02:00
converted = markdown_convert ( msg , message_realm = realm , message = message )
2014-02-07 22:05:19 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
converted . rendered_content ,
2020-05-09 03:44:56 +02:00
' <p><a href= " https://lists.debian.org/debian-ctte/2014/02/msg00173.html " >https://lists.debian.org/debian-ctte/2014/02/msg00173.html</a></p> ' ,
2017-01-24 06:34:26 +01:00
)
2014-01-31 23:13:58 +01:00
2017-11-05 10:51:25 +01:00
def test_url_to_a ( self ) - > None :
2021-02-12 08:20:45 +01:00
url = " javascript://example.com/invalidURL "
2020-06-27 02:18:01 +02:00
converted = url_to_a ( db_data = None , url = url , text = url )
2017-07-23 00:34:48 +02:00
self . assertEqual (
converted ,
2021-02-12 08:20:45 +01:00
" javascript://example.com/invalidURL " ,
2017-07-23 00:34:48 +02:00
)
2017-11-05 10:51:25 +01:00
def test_disabled_code_block_processor ( self ) - > None :
2021-02-12 08:19:30 +01:00
msg = (
" Hello, \n \n "
2023-01-03 02:16:53 +01:00
" I am writing this message to test something. I am writing this message to test "
" something. "
2021-02-12 08:19:30 +01:00
)
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( msg )
2021-02-12 08:19:30 +01:00
expected_output = (
2021-02-12 08:20:45 +01:00
" <p>Hello,</p> \n "
2023-01-03 02:16:53 +01:00
' <div class= " codehilite " ><pre><span></span><code>I am writing this message to test '
" something. I am writing this message to test something. \n "
" </code></pre></div> "
2021-02-12 08:19:30 +01:00
)
2017-11-03 12:13:17 +01:00
self . assertEqual ( converted , expected_output )
2021-03-08 13:22:43 +01:00
realm = do_create_realm (
string_id = " code_block_processor_test " , name = " code_block_processor_test "
)
2020-06-27 02:18:01 +02:00
maybe_update_markdown_engines ( realm . id , True )
2021-06-17 12:20:40 +02:00
rendering_result = markdown_convert ( msg , message_realm = realm , email_gateway = True )
2021-02-12 08:19:30 +01:00
expected_output = (
2021-02-12 08:20:45 +01:00
" <p>Hello,</p> \n "
2023-01-03 02:16:53 +01:00
" <p>I am writing this message to test something. I am writing this message to test "
" something.</p> "
2021-02-12 08:19:30 +01:00
)
2021-06-17 12:20:40 +02:00
self . assertEqual ( rendering_result . rendered_content , expected_output )
2017-11-03 12:13:17 +01:00
2017-11-05 10:51:25 +01:00
def test_normal_link ( self ) - > None :
2017-10-31 22:03:39 +01:00
realm = get_realm ( " zulip " )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-10-31 22:03:39 +01:00
message = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
msg = " http://example.com/#settings/ "
self . assertEqual (
2021-06-17 12:20:40 +02:00
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
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
' <p><a href= " http://example.com/#settings/ " >http://example.com/#settings/</a></p> ' ,
2017-10-31 22:03:39 +01:00
)
2017-11-05 10:51:25 +01:00
def test_relative_link ( self ) - > None :
2017-10-31 22:03:39 +01:00
realm = get_realm ( " zulip " )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-10-31 22:03:39 +01:00
message = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2022-08-17 06:37:53 +02:00
msg = " http://zulip.testserver/#narrow/stream/999-hello "
2017-10-31 22:03:39 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
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
' <p><a href= " #narrow/stream/999-hello " >http://zulip.testserver/#narrow/stream/999-hello</a></p> ' ,
2017-10-31 22:03:39 +01:00
)
2022-08-17 06:37:53 +02:00
msg = f " http://zulip.testserver/user_uploads/ { realm . id } /ff/file.txt "
self . assertEqual (
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
f ' <p><a href= " user_uploads/ { realm . id } /ff/file.txt " >http://zulip.testserver/user_uploads/ { realm . id } /ff/file.txt</a></p> ' ,
)
msg = " http://zulip.testserver/not:relative "
self . assertEqual (
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
' <p><a href= " http://zulip.testserver/not:relative " >http://zulip.testserver/not:relative</a></p> ' ,
)
2018-04-02 19:31:21 +02:00
def test_relative_link_streams_page ( self ) - > None :
realm = get_realm ( " zulip " )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2018-04-02 19:31:21 +02:00
message = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
msg = " http://zulip.testserver/#streams/all "
self . assertEqual (
2021-06-17 12:20:40 +02:00
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
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
' <p><a href= " #streams/all " >http://zulip.testserver/#streams/all</a></p> ' ,
2018-04-02 19:31:21 +02:00
)
2017-12-11 17:54:14 +01:00
def test_md_relative_link ( self ) - > None :
realm = get_realm ( " zulip " )
2021-02-12 08:20:45 +01:00
sender_user_profile = self . example_user ( " othello " )
2017-12-11 17:54:14 +01:00
message = Message ( sender = sender_user_profile , sending_client = get_client ( " test " ) )
2022-08-17 06:37:53 +02:00
msg = " [hello](http://zulip.testserver/#narrow/stream/999-hello) "
2017-12-11 17:54:14 +01:00
self . assertEqual (
2021-06-17 12:20:40 +02:00
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
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
' <p><a href= " #narrow/stream/999-hello " >hello</a></p> ' ,
2017-12-11 17:54:14 +01:00
)
2022-08-17 06:37:53 +02:00
msg = f " [hello](http://zulip.testserver/user_uploads/ { realm . id } /ff/file.txt) "
self . assertEqual (
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
f ' <p><a href= " user_uploads/ { realm . id } /ff/file.txt " >hello</a></p> ' ,
)
msg = " [hello](http://zulip.testserver/not:relative) "
self . assertEqual (
markdown_convert ( msg , message_realm = realm , message = message ) . rendered_content ,
' <p><a href= " http://zulip.testserver/not:relative " >hello</a></p> ' ,
)
2020-02-14 00:09:22 +01:00
def test_html_entity_conversion ( self ) - > None :
msg = """ \
Test raw : Hello , & copy ;
Test inline code : ` & copy ; `
Test fenced code :
` ` `
& copy ;
& copy ;
` ` `
Test quote :
~ ~ ~ quote
& copy ;
~ ~ ~
Test a list :
* & copy ;
* ` & copy ; `
* ` ` ` & copy ; ` ` `
Test an indented block :
& copy ; """
expected_output = """ \
< p > Test raw : Hello , & copy ; < br >
Test inline code : < code > & amp ; copy ; < / code > < / p >
< p > Test fenced code : < / p >
< div class = " codehilite " > < pre > < span > < / span > < code > & amp ; copy ;
& amp ; copy ;
< / code > < / pre > < / div >
< p > Test quote : < / p >
< blockquote >
< p > & copy ; < / p >
< / blockquote >
< p > Test a list : < / p >
< ul >
< li > & copy ; < / li >
< li > < code > & amp ; copy ; < / code > < / li >
< li > < code > & amp ; copy ; < / code > < / li >
< / ul >
< p > Test an indented block : < / p >
< div class = " codehilite " > < pre > < span > < / span > < code > & amp ; copy ;
< / code > < / pre > < / div > """
2020-07-04 14:34:46 +02:00
converted = markdown_convert_wrapper ( dedent ( msg ) )
2020-02-14 00:09:22 +01:00
self . assertEqual ( converted , dedent ( expected_output ) )
2021-02-12 08:19:30 +01:00
2020-06-27 00:35:15 +02:00
class MarkdownApiTests ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_render_message_api ( self ) - > None :
2021-02-12 08:20:45 +01:00
content = " That is a **bold** statement "
2017-12-14 19:02:31 +01:00
result = self . api_post (
2020-03-10 11:48:26 +01:00
self . example_user ( " othello " ) ,
2021-02-12 08:20:45 +01:00
" /api/v1/messages/render " ,
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
dict ( content = content ) ,
2016-09-19 20:44:15 +02:00
)
2022-06-07 01:37:01 +02:00
response_dict = self . assert_json_success ( result )
2021-02-12 08:19:30 +01:00
self . assertEqual (
2022-06-07 01:37:01 +02:00
response_dict [ " rendered " ] , " <p>That is a <strong>bold</strong> statement</p> "
2021-02-12 08:19:30 +01:00
)
2016-09-19 20:44:15 +02:00
2017-11-05 10:51:25 +01:00
def test_render_mention_stream_api ( self ) - > None :
2016-11-08 23:09:53 +01:00
""" Determines whether we ' re correctly passing the realm context """
2021-02-12 08:20:45 +01:00
content = " This mentions #**Denmark** and @**King Hamlet**. "
2017-12-14 19:02:31 +01:00
result = self . api_post (
2020-03-10 11:48:26 +01:00
self . example_user ( " othello " ) ,
2021-02-12 08:20:45 +01:00
" /api/v1/messages/render " ,
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
dict ( content = content ) ,
2016-11-07 20:40:40 +01:00
)
2022-06-07 01:37:01 +02:00
response_dict = self . assert_json_success ( result )
2021-02-12 08:20:45 +01:00
user_id = self . example_user ( " hamlet " ) . id
stream_id = get_stream ( " Denmark " , get_realm ( " zulip " ) ) . id
2021-02-12 08:19:30 +01:00
self . assertEqual (
2022-06-07 01:37:01 +02:00
response_dict [ " rendered " ] ,
2021-02-12 08:19:30 +01:00
f ' <p>This mentions <a class= " stream " data-stream-id= " { stream_id } " href= " /#narrow/stream/ { stream_id } -Denmark " >#Denmark</a> and <span class= " user-mention " data-user-id= " { user_id } " >@King Hamlet</span>.</p> ' ,
)
2016-11-07 20:40:40 +01:00
2020-06-27 00:35:15 +02:00
class MarkdownErrorTests ( ZulipTestCase ) :
def test_markdown_error_handling ( self ) - > None :
2016-09-15 22:05:56 +02:00
with self . simulated_markdown_failure ( ) :
2022-11-17 09:30:48 +01:00
with self . assertRaises ( MarkdownRenderingError ) :
2021-02-12 08:20:45 +01:00
markdown_convert_wrapper ( " " )
2016-09-15 22:05:56 +02:00
2017-11-05 10:51:25 +01:00
def test_send_message_errors ( self ) - > None :
2021-02-12 08:20:45 +01:00
message = " whatever "
2016-09-15 22:05:56 +02:00
with self . simulated_markdown_failure ( ) :
2016-12-16 02:11:42 +01:00
# We don't use assertRaisesRegex because it seems to not
2016-09-16 20:49:30 +02:00
# handle i18n properly here on some systems.
with self . assertRaises ( JsonableError ) :
2020-03-07 11:43:05 +01:00
self . send_stream_message ( self . example_user ( " othello " ) , " Denmark " , message )
2016-10-24 16:42:43 +02:00
2021-06-03 15:04:22 +02:00
@override_settings ( MAX_MESSAGE_LENGTH = 10 )
2018-02-09 19:49:13 +01:00
def test_ultra_long_rendering ( self ) - > None :
2021-05-28 20:04:15 +02:00
""" A rendered message with an ultra-long length (> 100 * MAX_MESSAGE_LENGTH)
2018-07-03 07:29:27 +02:00
throws an exception """
2021-05-28 20:04:15 +02:00
msg = " mock rendered message \n " * 10 * settings . MAX_MESSAGE_LENGTH
2018-02-09 19:49:13 +01:00
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.lib.markdown.timeout " , return_value = msg ) , mock . patch (
" zerver.lib.markdown.markdown_logger "
2021-02-12 08:19:30 +01:00
) :
2022-11-17 09:30:48 +01:00
with self . assertRaises ( MarkdownRenderingError ) :
2020-07-04 14:34:46 +02:00
markdown_convert_wrapper ( msg )
2018-02-09 19:49:13 +01:00
2019-05-16 22:38:53 +02:00
def test_curl_code_block_validation ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = SimulatedFencedBlockPreprocessor ( Markdown ( ) )
2019-05-16 22:38:53 +02:00
processor . run_content_validators = True
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ``` curl " ,
" curl {{ api_url }}/v1/register " ,
" -u BOT_EMAIL_ADDRESS:BOT_API_KEY " ,
2022-10-13 17:00:49 +02:00
' -d " queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8 " ' ,
2021-02-12 08:20:45 +01:00
" ``` " ,
2019-05-16 22:38:53 +02:00
]
2022-11-17 09:30:48 +01:00
with self . assertRaises ( MarkdownRenderingError ) :
2020-06-26 12:10:42 +02:00
processor . run ( markdown_input )
2019-05-16 22:38:53 +02:00
def test_curl_code_block_without_validation ( self ) - > None :
2020-11-10 21:43:57 +01:00
processor = SimulatedFencedBlockPreprocessor ( Markdown ( ) )
2019-05-16 22:38:53 +02:00
2020-06-26 12:10:42 +02:00
markdown_input = [
2021-02-12 08:20:45 +01:00
" ``` curl " ,
" curl {{ api_url }}/v1/register " ,
" -u BOT_EMAIL_ADDRESS:BOT_API_KEY " ,
2022-10-13 17:00:49 +02:00
' -d " queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8 " ' ,
2021-02-12 08:20:45 +01:00
" ``` " ,
2019-05-16 22:38:53 +02:00
]
expected = [
2021-02-12 08:20:45 +01:00
" " ,
" **curl:curl {{ api_url }}/v1/register " ,
" -u BOT_EMAIL_ADDRESS:BOT_API_KEY " ,
2022-10-13 17:00:49 +02:00
' -d " queue_id=fb67bf8a-c031-47cc-84cf-ed80accacda8 " ** ' ,
2021-02-12 08:20:45 +01:00
" " ,
" " ,
2019-05-16 22:38:53 +02:00
]
2020-06-26 12:10:42 +02:00
result = processor . run ( markdown_input )
2019-05-16 22:38:53 +02:00
self . assertEqual ( result , expected )