# -*- coding: utf-8 -*- from django.conf import settings from django.test import TestCase, override_settings from zerver.lib import bugdown from zerver.lib.actions import ( do_set_user_display_setting, do_remove_realm_emoji, do_set_alert_words, get_realm, ) from zerver.lib.alert_words import alert_words_in_realm from zerver.lib.camo import get_camo_url from zerver.lib.create_user import create_user from zerver.lib.emoji import get_emoji_url from zerver.lib.exceptions import BugdownRenderingException from zerver.lib.mention import possible_mentions, possible_user_group_mentions from zerver.lib.message import render_markdown from zerver.lib.request import ( JsonableError, ) from zerver.lib.user_groups import create_user_group from zerver.lib.test_classes import ( ZulipTestCase, ) from zerver.lib.test_runner import slow from zerver.lib import mdiff from zerver.lib.tex import render_tex from zerver.models import ( realm_in_local_realm_filters_cache, flush_per_request_caches, flush_realm_filter, get_client, get_realm, get_stream, realm_filters_for_realm, MAX_MESSAGE_LENGTH, Message, Stream, Realm, RealmEmoji, RealmFilter, Recipient, UserProfile, UserGroup, ) import copy import mock import os import ujson import urllib from zerver.lib.str_utils import NonBinaryStr from typing import Any, AnyStr, Dict, List, Optional, Set, Tuple class FencedBlockPreprocessorTest(TestCase): def test_simple_quoting(self) -> None: processor = bugdown.fenced_code.FencedBlockPreprocessor(None) markdown = [ '~~~ quote', 'hi', 'bye', '', '' ] expected = [ '', '> hi', '> bye', '', '', '' ] lines = processor.run(markdown) self.assertEqual(lines, expected) def test_serial_quoting(self) -> None: processor = bugdown.fenced_code.FencedBlockPreprocessor(None) markdown = [ '~~~ quote', 'hi', '~~~', '', '~~~ quote', 'bye', '', '' ] expected = [ '', '> hi', '', '', '', '> bye', '', '', '' ] lines = processor.run(markdown) self.assertEqual(lines, expected) def test_serial_code(self) -> None: processor = bugdown.fenced_code.FencedBlockPreprocessor(None) # Simulate code formatting. processor.format_code = lambda lang, code: lang + ':' + code # type: ignore # mypy doesn't allow monkey-patching functions processor.placeholder = lambda s: '**' + s.strip('\n') + '**' # type: ignore # https://github.com/python/mypy/issues/708 markdown = [ '``` .py', 'hello()', '```', '', '```vb.net', 'goodbye()', '```', '', '```c#', 'weirdchar()', '```', '' ] expected = [ '', '**py:hello()**', '', '', '', '**vb.net:goodbye()**', '', '', '', '**c#:weirdchar()**', '', '' ] lines = processor.run(markdown) self.assertEqual(lines, expected) def test_nested_code(self) -> None: processor = bugdown.fenced_code.FencedBlockPreprocessor(None) # Simulate code formatting. processor.format_code = lambda lang, code: lang + ':' + code # type: ignore # mypy doesn't allow monkey-patching functions processor.placeholder = lambda s: '**' + s.strip('\n') + '**' # type: ignore # https://github.com/python/mypy/issues/708 markdown = [ '~~~ quote', 'hi', '``` .py', 'hello()', '```', '', '' ] expected = [ '', '> hi', '', '> **py:hello()**', '', '', '' ] lines = processor.run(markdown) self.assertEqual(lines, expected) def bugdown_convert(text: str) -> str: return bugdown.convert(text, message_realm=get_realm('zulip')) class BugdownMiscTest(ZulipTestCase): def test_diffs_work_as_expected(self) -> None: str1 = "

The quick brown fox jumps over the lazy dog. Animal stories are fun, yeah

" str2 = "

The fast fox jumps over the lazy dogs and cats. Animal stories are fun

" expected_diff = "\u001b[34m-\u001b[0m

The \u001b[33mquick brown\u001b[0m fox jumps over the lazy dog. Animal stories are fun\u001b[31m, yeah\u001b[0m

\n\u001b[34m+\u001b[0m

The \u001b[33mfast\u001b[0m fox jumps over the lazy dog\u001b[32ms and cats\u001b[0m. Animal stories are fun

\n" self.assertEqual(mdiff.diff_strings(str1, str2), expected_diff) def test_get_full_name_info(self) -> None: realm = get_realm('zulip') def make_user(email: str, full_name: str) -> UserProfile: return create_user( email=email, password='whatever', realm=realm, full_name=full_name, short_name='whatever', ) fred1 = make_user('fred1@example.com', 'Fred Flintstone') fred1.is_active = False fred1.save() fred2 = make_user('fred2@example.com', 'Fred Flintstone') fred3 = make_user('fred3@example.com', 'Fred Flintstone') fred3.is_active = False fred3.save() dct = bugdown.get_full_name_info(realm.id, {'Fred Flintstone', 'cordelia LEAR', 'Not A User'}) self.assertEqual(set(dct.keys()), {'fred flintstone', 'cordelia lear'}) self.assertEqual(dct['fred flintstone'], dict( email='fred2@example.com', full_name='Fred Flintstone', id=fred2.id )) def test_mention_data(self) -> None: realm = get_realm('zulip') hamlet = self.example_user('hamlet') cordelia = self.example_user('cordelia') content = '@**King Hamlet** @**Cordelia lear**' mention_data = bugdown.MentionData(realm.id, content) self.assertEqual(mention_data.get_user_ids(), {hamlet.id, cordelia.id}) user = mention_data.get_user('king hamLET') assert(user is not None) self.assertEqual(user['email'], hamlet.email) def test_invalid_katex_path(self) -> None: with self.settings(STATIC_ROOT="/invalid/path"): with mock.patch('logging.error') as mock_logger: render_tex("random text") mock_logger.assert_called_with("Cannot find KaTeX for latex rendering!") class BugdownTest(ZulipTestCase): def assertEqual(self, first: Any, second: Any, msg: str = "") -> None: if isinstance(first, str) and isinstance(second, str): if first != second: raise AssertionError("Actual and expected outputs do not match; showing diff.\n" + mdiff.diff_strings(first, second) + msg) else: super().assertEqual(first, second) def load_bugdown_tests(self) -> Tuple[Dict[str, Any], List[List[str]]]: test_fixtures = {} data_file = open(os.path.join(os.path.dirname(__file__), 'fixtures/markdown_test_cases.json'), 'r') data = ujson.loads('\n'.join(data_file.readlines())) for test in data['regular_tests']: test_fixtures[test['name']] = test return test_fixtures, data['linkify_tests'] def test_bugdown_no_ignores(self) -> None: # We do not want any ignored tests to be committed and merged. format_tests, linkify_tests = self.load_bugdown_tests() for name, test in format_tests.items(): message = 'Test "%s" shouldn\'t be ignored.' % (name,) is_ignored = test.get('ignore', False) self.assertFalse(is_ignored, message) @slow("Aggregate of runs dozens of individual markdown tests") def test_bugdown_fixtures(self) -> None: format_tests, linkify_tests = self.load_bugdown_tests() valid_keys = set(["name", "input", "expected_output", "backend_only_rendering", "marked_expected_output", "text_content", "translate_emoticons", "ignore"]) for name, test in format_tests.items(): # Check that there aren't any unexpected keys as those are often typos self.assertEqual(len(set(test.keys()) - valid_keys), 0) # Ignore tests if specified if test.get('ignore', False): continue # nocoverage if test.get('translate_emoticons', False): # Create a userprofile and send message with it. user_profile = self.example_user('othello') do_set_user_display_setting(user_profile, 'translate_emoticons', True) msg = Message(sender=user_profile, sending_client=get_client("test")) converted = render_markdown(msg, test['input']) else: converted = bugdown_convert(test['input']) print("Running Bugdown test %s" % (name,)) self.assertEqual(converted, test['expected_output']) def replaced(payload: str, url: str, phrase: str='') -> str: target = " target=\"_blank\"" if url[:4] == 'http': href = url elif '@' in url: href = 'mailto:' + url target = "" else: href = 'http://' + url return payload % ("%s" % (href, target, href, url),) print("Running Bugdown Linkify tests") with mock.patch('zerver.lib.url_preview.preview.link_embed_data_from_cache', return_value=None): for inline_url, reference, url in linkify_tests: try: match = replaced(reference, url, phrase=inline_url) except TypeError: match = reference converted = bugdown_convert(inline_url) self.assertEqual(match, converted) def test_inline_file(self) -> None: msg = 'Check out this file file:///Volumes/myserver/Users/Shared/pi.py' converted = bugdown_convert(msg) self.assertEqual(converted, '

Check out this file file:///Volumes/myserver/Users/Shared/pi.py

') with self.settings(ENABLE_FILE_LINKS=False): realm = Realm.objects.create(string_id='file_links_test') bugdown.maybe_update_markdown_engines(realm.id, False) converted = bugdown.convert(msg, message_realm=realm) self.assertEqual(converted, '

Check out this file file:///Volumes/myserver/Users/Shared/pi.py

') def test_inline_bitcoin(self) -> None: msg = 'To bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa or not to bitcoin' converted = bugdown_convert(msg) self.assertEqual(converted, '

To bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa or not to bitcoin

') def test_inline_youtube(self) -> None: msg = 'Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE' converted = bugdown_convert(msg) self.assertEqual(converted, '

Check out the debate: http://www.youtube.com/watch?v=hx1mjT73xYE

\n
') msg = 'http://www.youtube.com/watch?v=hx1mjT73xYE' converted = bugdown_convert(msg) self.assertEqual(converted, '

http://www.youtube.com/watch?v=hx1mjT73xYE

\n
') def test_inline_vimeo(self) -> None: msg = 'Check out the debate: https://vimeo.com/246979354' converted = bugdown_convert(msg) self.assertEqual(converted, '

Check out the debate: https://vimeo.com/246979354

') msg = 'https://vimeo.com/246979354' converted = bugdown_convert(msg) self.assertEqual(converted, '

https://vimeo.com/246979354

') @override_settings(INLINE_IMAGE_PREVIEW=True) def test_inline_image_preview(self) -> None: with_preview = '

Test: http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg

\n
' without_preview = '

Test: http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg

' content = 'Test: http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg' sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) converted = render_markdown(msg, content) self.assertEqual(converted, with_preview) realm = msg.get_realm() setattr(realm, 'inline_image_preview', False) realm.save() sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) converted = render_markdown(msg, content) self.assertEqual(converted, without_preview) @override_settings(INLINE_IMAGE_PREVIEW=True) def test_inline_image_preview_order(self) -> None: content = 'http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg\nhttp://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg\nhttp://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg' expected = '

http://imaging.nikon.com/lineup/dslr/df/img/sample/img_01.jpg
\nhttp://imaging.nikon.com/lineup/dslr/df/img/sample/img_02.jpg
\nhttp://imaging.nikon.com/lineup/dslr/df/img/sample/img_03.jpg

\n
' sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) converted = render_markdown(msg, content) self.assertEqual(converted, expected) 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' expected = '
\n
\n' sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) converted = render_markdown(msg, content) self.assertEqual(converted, expected) content = 'Test 1\n[21136101110_1dde1c1a7e_o.jpg](/user_uploads/1/6d/F1PX6u16JA2P-nK45PyxHIYZ/21136101110_1dde1c1a7e_o.jpg) \n\nNext Image\n[IMG_20161116_023910.jpg](/user_uploads/1/69/sh7L06e7uH7NaX6d5WFfVYQp/IMG_20161116_023910.jpg) \n\nAnother Screenshot\n[Screenshot-from-2016-06-01-16-22-42.png](/user_uploads/1/70/_aZmIEWaN1iUaxwkDjkO7bpj/Screenshot-from-2016-06-01-16-22-42.png)' expected = '

Test 1
\n21136101110_1dde1c1a7e_o.jpg

\n

Next Image
\nIMG_20161116_023910.jpg

\n

Another Screenshot
\nScreenshot-from-2016-06-01-16-22-42.png

\n
' msg = Message(sender=sender_user_profile, sending_client=get_client("test")) converted = render_markdown(msg, content) self.assertEqual(converted, expected) @override_settings(INLINE_IMAGE_PREVIEW=False) def test_image_preview_enabled_for_realm(self) -> None: ret = bugdown.image_preview_enabled_for_realm() self.assertEqual(ret, False) settings.INLINE_IMAGE_PREVIEW = True sender_user_profile = self.example_user('othello') bugdown.current_message = copy.deepcopy(Message(sender=sender_user_profile, sending_client=get_client("test"))) realm = bugdown.current_message.get_realm() ret = bugdown.image_preview_enabled_for_realm() self.assertEqual(ret, realm.inline_image_preview) bugdown.current_message = None ret = bugdown.image_preview_enabled_for_realm() self.assertEqual(ret, True) @override_settings(INLINE_URL_EMBED_PREVIEW=False) def test_url_embed_preview_enabled_for_realm(self) -> None: sender_user_profile = self.example_user('othello') message = copy.deepcopy(Message(sender=sender_user_profile, sending_client=get_client("test"))) realm = message.get_realm() ret = bugdown.url_embed_preview_enabled_for_realm(message) self.assertEqual(ret, False) settings.INLINE_URL_EMBED_PREVIEW = True ret = bugdown.url_embed_preview_enabled_for_realm(message) self.assertEqual(ret, realm.inline_image_preview) ret = bugdown.url_embed_preview_enabled_for_realm(None) self.assertEqual(ret, True) def test_inline_dropbox(self) -> None: msg = 'Look at how hilarious our old office was: https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG' image_info = {'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'} with mock.patch('zerver.lib.bugdown.fetch_open_graph_image', return_value=image_info): converted = bugdown_convert(msg) self.assertEqual(converted, '

Look at how hilarious our old office was: https://www.dropbox.com/s/ymdijjcg67hv2ta/IMG_0923.JPG

\n
') msg = 'Look at my hilarious drawing folder: https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl=' image_info = {'image': 'https://cf.dropboxstatic.com/static/images/icons128/folder_dropbox.png', 'desc': 'Shared with Dropbox', 'title': 'Saves'} with mock.patch('zerver.lib.bugdown.fetch_open_graph_image', return_value=image_info): converted = bugdown_convert(msg) self.assertEqual(converted, '

Look at my hilarious drawing folder: https://www.dropbox.com/sh/cm39k9e04z7fhim/AAAII5NK-9daee3FcF41anEua?dl=

\n
Saves
') def test_inline_dropbox_preview(self) -> None: # Test photo album previews msg = 'https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5' image_info = {'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'} with mock.patch('zerver.lib.bugdown.fetch_open_graph_image', return_value=image_info): converted = bugdown_convert(msg) self.assertEqual(converted, '

https://www.dropbox.com/sc/tditp9nitko60n5/03rEiZldy5

\n
') def test_inline_dropbox_negative(self) -> None: # Make sure we're not overzealous in our conversion: msg = 'Look at the new dropbox logo: https://www.dropbox.com/static/images/home_logo.png' with mock.patch('zerver.lib.bugdown.fetch_open_graph_image', return_value=None): converted = bugdown_convert(msg) self.assertEqual(converted, '

Look at the new dropbox logo: https://www.dropbox.com/static/images/home_logo.png

\n
') def test_inline_dropbox_bad(self) -> None: # Don't fail on bad dropbox links msg = "https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM" with mock.patch('zerver.lib.bugdown.fetch_open_graph_image', return_value=None): converted = bugdown_convert(msg) self.assertEqual(converted, '

https://zulip-test.dropbox.com/photos/cl/ROmr9K1XYtmpneM

') def test_inline_github_preview(self) -> None: # Test photo album previews msg = 'Test: https://github.com/zulip/zulip/blob/master/static/images/logo/zulip-icon-128x128.png' converted = bugdown_convert(msg) self.assertEqual(converted, '

Test: https://github.com/zulip/zulip/blob/master/static/images/logo/zulip-icon-128x128.png

\n
') msg = 'Test: https://developer.github.com/assets/images/hero-circuit-bg.png' converted = bugdown_convert(msg) self.assertEqual(converted, '

Test: https://developer.github.com/assets/images/hero-circuit-bg.png

\n
') def test_twitter_id_extraction(self) -> None: self.assertEqual(bugdown.get_tweet_id('http://twitter.com/#!/VizzQuotes/status/409030735191097344'), '409030735191097344') self.assertEqual(bugdown.get_tweet_id('http://twitter.com/VizzQuotes/status/409030735191097344'), '409030735191097344') self.assertEqual(bugdown.get_tweet_id('http://twitter.com/VizzQuotes/statuses/409030735191097344'), '409030735191097344') self.assertEqual(bugdown.get_tweet_id('https://twitter.com/wdaher/status/1017581858'), '1017581858') self.assertEqual(bugdown.get_tweet_id('https://twitter.com/wdaher/status/1017581858/'), '1017581858') self.assertEqual(bugdown.get_tweet_id('https://twitter.com/windyoona/status/410766290349879296/photo/1'), '410766290349879296') self.assertEqual(bugdown.get_tweet_id('https://twitter.com/windyoona/status/410766290349879296/'), '410766290349879296') def test_inline_interesting_links(self) -> None: def make_link(url: str) -> str: return '%s' % (url, url, url) normal_tweet_html = ('@Twitter ' 'meets @seepicturely at #tcdisrupt cc.' '@boscomonkey ' '@episod ' 'http://instagr.am/p/MuW67/') mention_in_link_tweet_html = """http://foo.com""" media_tweet_html = ('' 'http://twitter.com/NEVNBoston/status/421654515616849920/photo/1') emoji_in_tweet_html = """Zulip is :hundred_points:% open-source!""" def make_inline_twitter_preview(url: str, tweet_html: str, image_html: str='') -> str: ## As of right now, all previews are mocked to be the exact same tweet return ('
' '
' '' '' '' '

%s

' '- Eoin McMillan (@imeoin)' '%s' '
' '
') % (url, tweet_html, image_html) msg = 'http://www.twitter.com' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

' % make_link('http://www.twitter.com')) msg = 'http://www.twitter.com/wdaher/' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

' % make_link('http://www.twitter.com/wdaher/')) msg = 'http://www.twitter.com/wdaher/status/3' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

' % make_link('http://www.twitter.com/wdaher/status/3')) # id too long msg = 'http://www.twitter.com/wdaher/status/2879779692873154569' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

' % make_link('http://www.twitter.com/wdaher/status/2879779692873154569')) # id too large (i.e. tweet doesn't exist) msg = 'http://www.twitter.com/wdaher/status/999999999999999999' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

' % make_link('http://www.twitter.com/wdaher/status/999999999999999999')) msg = 'http://www.twitter.com/wdaher/status/287977969287315456' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('http://www.twitter.com/wdaher/status/287977969287315456'), make_inline_twitter_preview('http://www.twitter.com/wdaher/status/287977969287315456', normal_tweet_html))) msg = 'https://www.twitter.com/wdaher/status/287977969287315456' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('https://www.twitter.com/wdaher/status/287977969287315456'), make_inline_twitter_preview('https://www.twitter.com/wdaher/status/287977969287315456', normal_tweet_html))) msg = 'http://twitter.com/wdaher/status/287977969287315456' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('http://twitter.com/wdaher/status/287977969287315456'), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315456', normal_tweet_html))) # A max of 3 will be converted msg = ('http://twitter.com/wdaher/status/287977969287315456 ' 'http://twitter.com/wdaher/status/287977969287315457 ' 'http://twitter.com/wdaher/status/287977969287315457 ' 'http://twitter.com/wdaher/status/287977969287315457') converted = bugdown_convert(msg) self.assertEqual(converted, '

%s %s %s %s

\n%s%s%s' % ( make_link('http://twitter.com/wdaher/status/287977969287315456'), make_link('http://twitter.com/wdaher/status/287977969287315457'), make_link('http://twitter.com/wdaher/status/287977969287315457'), make_link('http://twitter.com/wdaher/status/287977969287315457'), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315456', normal_tweet_html), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315457', normal_tweet_html), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315457', normal_tweet_html))) # Tweet has a mention in a URL, only the URL is linked msg = 'http://twitter.com/wdaher/status/287977969287315458' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('http://twitter.com/wdaher/status/287977969287315458'), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315458', mention_in_link_tweet_html))) # Tweet with an image msg = 'http://twitter.com/wdaher/status/287977969287315459' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('http://twitter.com/wdaher/status/287977969287315459'), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315459', media_tweet_html, ('
' '' '' '' '
')))) msg = 'http://twitter.com/wdaher/status/287977969287315460' converted = bugdown_convert(msg) self.assertEqual(converted, '

%s

\n%s' % ( make_link('http://twitter.com/wdaher/status/287977969287315460'), make_inline_twitter_preview('http://twitter.com/wdaher/status/287977969287315460', emoji_in_tweet_html))) def test_fetch_tweet_data_settings_validation(self) -> None: with self.settings(TEST_SUITE=False, TWITTER_CONSUMER_KEY=None): self.assertIs(None, bugdown.fetch_tweet_data('287977969287315459')) def test_content_has_emoji(self) -> None: self.assertFalse(bugdown.content_has_emoji_syntax('boring')) self.assertFalse(bugdown.content_has_emoji_syntax('hello: world')) self.assertFalse(bugdown.content_has_emoji_syntax(':foobar')) self.assertFalse(bugdown.content_has_emoji_syntax('::: hello :::')) self.assertTrue(bugdown.content_has_emoji_syntax('foo :whatever:')) self.assertTrue(bugdown.content_has_emoji_syntax('\n:whatever:')) self.assertTrue(bugdown.content_has_emoji_syntax(':smile: ::::::')) def test_realm_emoji(self) -> None: def emoji_img(name: str, file_name: str, realm_id: int) -> str: return '%s' % ( name, get_emoji_url(file_name, realm_id), name[1:-1].replace("_", " ")) realm = get_realm('zulip') # Needs to mock an actual message because that's how bugdown obtains the realm msg = Message(sender=self.example_user('hamlet')) converted = bugdown.convert(":green_tick:", message_realm=realm, message=msg) realm_emoji = RealmEmoji.objects.filter(realm=realm, name='green_tick', deactivated=False).get() self.assertEqual(converted, '

%s

' % (emoji_img(':green_tick:', realm_emoji.file_name, realm.id))) # Deactivate realm emoji. do_remove_realm_emoji(realm, 'green_tick') converted = bugdown.convert(":green_tick:", message_realm=realm, message=msg) self.assertEqual(converted, '

:green_tick:

') def test_deactivated_realm_emoji(self) -> None: # Deactivate realm emoji. realm = get_realm('zulip') do_remove_realm_emoji(realm, 'green_tick') msg = Message(sender=self.example_user('hamlet')) converted = bugdown.convert(":green_tick:", message_realm=realm, message=msg) self.assertEqual(converted, '

:green_tick:

') def test_unicode_emoji(self) -> None: msg = u'\u2615' # ☕ converted = bugdown_convert(msg) self.assertEqual(converted, u'

:coffee:

') msg = u'\u2615\u2615' # ☕☕ converted = bugdown_convert(msg) self.assertEqual(converted, u'

:coffee::coffee:

') def test_no_translate_emoticons_if_off(self) -> None: user_profile = self.example_user('othello') do_set_user_display_setting(user_profile, 'translate_emoticons', False) msg = Message(sender=user_profile, sending_client=get_client("test")) content = u':)' expected = u'

:)

' converted = render_markdown(msg, content) self.assertEqual(converted, expected) def test_same_markup(self) -> None: msg = u'\u2615' # ☕ unicode_converted = bugdown_convert(msg) msg = u':coffee:' # ☕☕ converted = bugdown_convert(msg) self.assertEqual(converted, unicode_converted) def test_realm_patterns(self) -> None: realm = get_realm('zulip') url_format_string = r"https://trac.zulip.net/ticket/%(id)s" realm_filter = RealmFilter(realm=realm, pattern=r"#(?P[0-9]{2,8})", url_format_string=url_format_string) realm_filter.save() self.assertEqual( realm_filter.__str__(), '[0-9]{2,8})' ' https://trac.zulip.net/ticket/%(id)s>') msg = Message(sender=self.example_user('othello'), subject="#444") flush_per_request_caches() content = "We should fix #224 and #115, but not issue#124 or #1124z or [trac #15](https://trac.zulip.net/ticket/16) today." converted = bugdown.convert(content, message_realm=realm, message=msg) converted_subject = bugdown.subject_links(realm.id, msg.subject) self.assertEqual(converted, '

We should fix #224 and #115, but not issue#124 or #1124z or trac #15 today.

') self.assertEqual(converted_subject, [u'https://trac.zulip.net/ticket/444']) RealmFilter(realm=realm, pattern=r'#(?P[a-zA-Z]+-[0-9]+)', url_format_string=r'https://trac.zulip.net/ticket/%(id)s').save() msg = Message(sender=self.example_user('hamlet')) content = '#ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging' converted = bugdown.convert(content, message_realm=realm, message=msg) self.assertEqual(converted, '

#ZUL-123 was fixed and code was deployed to production, also #zul-321 was deployed to staging

') def test_maybe_update_markdown_engines(self) -> None: realm = get_realm('zulip') url_format_string = r"https://trac.zulip.net/ticket/%(id)s" realm_filter = RealmFilter(realm=realm, pattern=r"#(?P[0-9]{2,8})", url_format_string=url_format_string) realm_filter.save() bugdown.realm_filter_data = {} bugdown.maybe_update_markdown_engines(None, False) all_filters = bugdown.realm_filter_data zulip_filters = all_filters[realm.id] self.assertEqual(len(zulip_filters), 1) self.assertEqual(zulip_filters[0], (u'#(?P[0-9]{2,8})', u'https://trac.zulip.net/ticket/%(id)s', realm_filter.id)) def test_flush_realm_filter(self) -> None: realm = get_realm('zulip') def flush() -> None: ''' flush_realm_filter is a post-save hook, so calling it directly for testing is kind of awkward ''' class Instance: realm_id = None # type: Optional[int] instance = Instance() instance.realm_id = realm.id flush_realm_filter(sender=None, instance=instance) def save_new_realm_filter() -> None: realm_filter = RealmFilter(realm=realm, pattern=r"whatever", url_format_string='whatever') realm_filter.save() # start fresh for our realm flush() self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) # call this just for side effects of populating the cache realm_filters_for_realm(realm.id) self.assertTrue(realm_in_local_realm_filters_cache(realm.id)) # Saving a new RealmFilter should have the side effect of # flushing the cache. save_new_realm_filter() self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) # and flush it one more time, to make sure we don't get a KeyError flush() self.assertFalse(realm_in_local_realm_filters_cache(realm.id)) def test_realm_patterns_negative(self) -> None: realm = get_realm('zulip') RealmFilter(realm=realm, pattern=r"#(?P[0-9]{2,8})", url_format_string=r"https://trac.zulip.net/ticket/%(id)s").save() boring_msg = Message(sender=self.example_user('othello'), subject=u"no match here") converted_boring_subject = bugdown.subject_links(realm.id, boring_msg.subject) self.assertEqual(converted_boring_subject, []) def test_is_status_message(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = '/me makes a list\n* one\n* two' rendered_content = render_markdown(msg, content) self.assertEqual( rendered_content, '

/me makes a list

\n
    \n
  • one
  • \n
  • two
  • \n
' ) self.assertFalse(Message.is_status_message(content, rendered_content)) content = '/me takes a walk' rendered_content = render_markdown(msg, content) self.assertEqual( rendered_content, '

/me takes a walk

' ) self.assertTrue(Message.is_status_message(content, rendered_content)) content = '/me writes a second line\nline' rendered_content = render_markdown(msg, content) self.assertEqual( rendered_content, '

/me writes a second line
\nline

' ) self.assertFalse(Message.is_status_message(content, rendered_content)) def test_alert_words(self) -> None: user_profile = self.example_user('othello') do_set_alert_words(user_profile, ["ALERTWORD", "scaryword"]) msg = Message(sender=user_profile, sending_client=get_client("test")) realm_alert_words = alert_words_in_realm(user_profile.realm) def render(msg: Message, content: str) -> str: return render_markdown(msg, content, realm_alert_words=realm_alert_words, user_ids={user_profile.id}) content = "We have an ALERTWORD day today!" self.assertEqual(render(msg, content), "

We have an ALERTWORD day today!

") self.assertEqual(msg.user_ids_with_alert_words, set([user_profile.id])) msg = Message(sender=user_profile, sending_client=get_client("test")) content = "We have a NOTHINGWORD day today!" self.assertEqual(render(msg, content), "

We have a NOTHINGWORD day today!

") self.assertEqual(msg.user_ids_with_alert_words, set()) def test_mention_wildcard(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@**all** test" self.assertEqual(render_markdown(msg, content), '

' '@all' ' test

') self.assertTrue(msg.mentions_wildcard) def test_mention_everyone(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@**everyone** test" self.assertEqual(render_markdown(msg, content), '

' '@everyone' ' test

') self.assertTrue(msg.mentions_wildcard) def test_mention_stream(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@**stream** test" self.assertEqual(render_markdown(msg, content), '

' '@stream' ' test

') self.assertTrue(msg.mentions_wildcard) def test_mention_at_wildcard(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@all test" self.assertEqual(render_markdown(msg, content), '

@all test

') self.assertFalse(msg.mentions_wildcard) self.assertEqual(msg.mentions_user_ids, set([])) def test_mention_at_everyone(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@everyone test" self.assertEqual(render_markdown(msg, content), '

@everyone test

') self.assertFalse(msg.mentions_wildcard) self.assertEqual(msg.mentions_user_ids, set([])) def test_mention_word_starting_with_at_wildcard(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "test @alleycat.com test" self.assertEqual(render_markdown(msg, content), '

test @alleycat.com test

') self.assertFalse(msg.mentions_wildcard) self.assertEqual(msg.mentions_user_ids, set([])) def test_mention_at_normal_user(self) -> None: user_profile = self.example_user('othello') msg = Message(sender=user_profile, sending_client=get_client("test")) content = "@aaron test" self.assertEqual(render_markdown(msg, content), '

@aaron test

') self.assertFalse(msg.mentions_wildcard) self.assertEqual(msg.mentions_user_ids, set([])) def test_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 content = "@**King Hamlet**" self.assertEqual(render_markdown(msg, content), '

' '@King Hamlet

' % (user_id)) self.assertEqual(msg.mentions_user_ids, set([user_profile.id])) def test_possible_mentions(self) -> None: def assert_mentions(content: str, names: Set[str]) -> None: self.assertEqual(possible_mentions(content), names) assert_mentions('', set()) assert_mentions('boring', set()) assert_mentions('@**all**', set()) assert_mentions('smush@**steve**smush', set()) assert_mentions( 'Hello @**King Hamlet** and @**Cordelia Lear**\n@**Foo van Barson** @**all**', {'King Hamlet', 'Cordelia Lear', 'Foo van Barson'} ) def test_mention_multiple(self) -> None: sender_user_profile = self.example_user('othello') hamlet = self.example_user('hamlet') cordelia = self.example_user('cordelia') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "@**King Hamlet** and @**Cordelia Lear**, check this out" self.assertEqual(render_markdown(msg, content), '

' '@King Hamlet and ' '@Cordelia Lear, ' 'check this out

' % (hamlet.id, cordelia.id)) self.assertEqual(msg.mentions_user_ids, set([hamlet.id, cordelia.id])) def test_mention_invalid(self) -> None: sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "Hey @**Nonexistent User**" self.assertEqual(render_markdown(msg, content), '

Hey @Nonexistent User

') self.assertEqual(msg.mentions_user_ids, set()) def create_user_group_for_test(self, user_group_name: str) -> UserGroup: othello = self.example_user('othello') return create_user_group(user_group_name, [othello], get_realm('zulip')) def test_user_group_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** @*support*" self.assertEqual(render_markdown(msg, content), '

' '@King Hamlet ' '' '@support

' % (user_id, user_group.id)) self.assertEqual(msg.mentions_user_ids, set([user_profile.id])) self.assertEqual(msg.mentions_user_group_ids, set([user_group.id])) def test_possible_user_group_mentions(self) -> None: def assert_mentions(content: str, names: Set[str]) -> None: self.assertEqual(possible_user_group_mentions(content), names) assert_mentions('', set()) assert_mentions('boring', set()) assert_mentions('@**all**', set()) assert_mentions('smush@*steve*smush', set()) assert_mentions( '@*support* Hello @**King Hamlet** and @**Cordelia Lear**\n' '@**Foo van Barson** @**all**', {'support'} ) assert_mentions( 'Attention @*support*, @*frontend* and @*backend*\ngroups.', {'support', 'frontend', 'backend'} ) def test_user_group_mention_multiple(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') backend = self.create_user_group_for_test('backend') content = "@*support* and @*backend*, check this out" self.assertEqual(render_markdown(msg, content), '

' '' '@support ' 'and ' '' '@backend, ' 'check this out' '

' % (support.id, backend.id)) self.assertEqual(msg.mentions_user_group_ids, set([support.id, backend.id])) def test_user_group_mention_invalid(self) -> None: sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "Hey @*Nonexistent group*" self.assertEqual(render_markdown(msg, content), '

Hey @Nonexistent group

') self.assertEqual(msg.mentions_user_group_ids, set()) def test_stream_single(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 = "#**Denmark**" self.assertEqual( render_markdown(msg, content), '

#{d.name}

'.format( d=denmark )) def test_stream_multiple(self) -> None: sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) realm = get_realm('zulip') denmark = get_stream('Denmark', realm) scotland = get_stream('Scotland', realm) content = "Look to #**Denmark** and #**Scotland**, there something" self.assertEqual(render_markdown(msg, content), '

Look to ' '#{denmark.name} and ' '#{scotland.name}, ' 'there something

'.format(denmark=denmark, scotland=scotland)) def test_stream_case_sensitivity(self) -> None: realm = get_realm('zulip') case_sens = Stream.objects.create(name='CaseSens', realm=realm) sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "#**CaseSens**" self.assertEqual( render_markdown(msg, content), '

#{s.name}

'.format( s=case_sens )) def test_stream_case_sensitivity_nonmatching(self) -> None: """#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.""" realm = get_realm('zulip') Stream.objects.create(name='CaseSens', realm=realm) sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "#**casesens**" self.assertEqual( render_markdown(msg, content), '

#casesens

') def test_possible_stream_names(self) -> None: content = '''#**test here** This mentions #**Denmark** too. #**garçon** #**천국** @**Ignore Person** ''' self.assertEqual( bugdown.possible_linked_stream_names(content), {'test here', 'Denmark', 'garçon', '천국'} ) def test_stream_unicode(self) -> None: realm = get_realm('zulip') uni = Stream.objects.create(name=u'привет', realm=realm) sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = u"#**привет**" quoted_name = '.D0.BF.D1.80.D0.B8.D0.B2.D0.B5.D1.82' href = '/#narrow/stream/{stream_id}-{quoted_name}'.format( stream_id=uni.id, quoted_name=quoted_name) self.assertEqual( render_markdown(msg, content), u'

#{s.name}

'.format( s=uni, href=href, )) def test_stream_invalid(self) -> None: sender_user_profile = self.example_user('othello') msg = Message(sender=sender_user_profile, sending_client=get_client("test")) content = "There #**Nonexistentstream**" self.assertEqual(render_markdown(msg, content), '

There #Nonexistentstream

') self.assertEqual(msg.mentions_user_ids, set()) def test_in_app_modal_link(self) -> None: msg = '!modal_link(#settings, Settings page)' converted = bugdown_convert(msg) self.assertEqual( converted, '

' 'Settings page' '

' ) def test_image_preview_title(self) -> None: msg = '[My favorite image](https://example.com/testimage.png)' converted = bugdown_convert(msg) self.assertEqual( converted, '

' 'My favorite image' '

\n' '
' '' '' '' '
' ) def test_mit_rendering(self) -> None: """Test the markdown configs for the MIT Zephyr mirroring system; verifies almost all inline patterns are disabled, but inline_interesting_links is still enabled""" msg = "**test**" realm = get_realm("zephyr") client = get_client("zephyr_mirror") message = Message(sending_client=client, sender=self.mit_user("sipbtest")) converted = bugdown.convert(msg, message_realm=realm, message=message) self.assertEqual( converted, "

**test**

", ) msg = "* test" converted = bugdown.convert(msg, message_realm=realm, message=message) self.assertEqual( converted, "

* test

", ) msg = "https://lists.debian.org/debian-ctte/2014/02/msg00173.html" converted = bugdown.convert(msg, message_realm=realm, message=message) self.assertEqual( converted, '

https://lists.debian.org/debian-ctte/2014/02/msg00173.html

', ) def test_url_to_a(self) -> None: url = 'javascript://example.com/invalidURL' converted = bugdown.url_to_a(url, url) self.assertEqual( converted, 'javascript://example.com/invalidURL', ) def test_disabled_code_block_processor(self) -> None: msg = "Hello,\n\n" + \ " I am writing this message to test something. I am writing this message to test something." converted = bugdown_convert(msg) expected_output = '

Hello,

\n' + \ '
I am writing this message to test something. I am writing this message to test something.\n' +     \
                          '
' self.assertEqual(converted, expected_output) realm = Realm.objects.create(string_id='code_block_processor_test') bugdown.maybe_update_markdown_engines(realm.id, True) converted = bugdown.convert(msg, message_realm=realm, email_gateway=True) expected_output = '

Hello,

\n' + \ '

I am writing this message to test something. I am writing this message to test something.

' self.assertEqual(converted, expected_output) def test_normal_link(self) -> None: realm = get_realm("zulip") sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) msg = "http://example.com/#settings/" self.assertEqual( bugdown.convert(msg, message_realm=realm, message=message), '

http://example.com/#settings/

' ) def test_relative_link(self) -> None: realm = get_realm("zulip") sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) msg = "http://zulip.testserver/#narrow/stream/999-hello" self.assertEqual( bugdown.convert(msg, message_realm=realm, message=message), '

http://zulip.testserver/#narrow/stream/999-hello

' ) def test_relative_link_streams_page(self) -> None: realm = get_realm("zulip") sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) msg = "http://zulip.testserver/#streams/all" self.assertEqual( bugdown.convert(msg, message_realm=realm, message=message), '

http://zulip.testserver/#streams/all

' ) def test_md_relative_link(self) -> None: realm = get_realm("zulip") sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) msg = "[hello](http://zulip.testserver/#narrow/stream/999-hello)" self.assertEqual( bugdown.convert(msg, message_realm=realm, message=message), '

hello

' ) class BugdownApiTests(ZulipTestCase): def test_render_message_api(self) -> None: content = 'That is a **bold** statement' result = self.api_post( self.example_email("othello"), '/api/v1/messages/render', dict(content=content) ) self.assert_json_success(result) self.assertEqual(result.json()['rendered'], u'

That is a bold statement

') def test_render_mention_stream_api(self) -> None: """Determines whether we're correctly passing the realm context""" content = 'This mentions #**Denmark** and @**King Hamlet**.' result = self.api_post( self.example_email("othello"), '/api/v1/messages/render', dict(content=content) ) self.assert_json_success(result) user_id = self.example_user('hamlet').id stream_id = get_stream('Denmark', get_realm('zulip')).id self.assertEqual(result.json()['rendered'], u'

This mentions #Denmark and @King Hamlet.

' % (stream_id, stream_id, user_id)) class BugdownErrorTests(ZulipTestCase): def test_bugdown_error_handling(self) -> None: with self.simulated_markdown_failure(): with self.assertRaises(BugdownRenderingException): bugdown_convert('') def test_send_message_errors(self) -> None: message = 'whatever' with self.simulated_markdown_failure(): # We don't use assertRaisesRegex because it seems to not # handle i18n properly here on some systems. with self.assertRaises(JsonableError): self.send_stream_message(self.example_email("othello"), "Denmark", message) def test_ultra_long_rendering(self) -> None: """A message with an ultra-long rendering throws an exception""" msg = u'* a\n' * MAX_MESSAGE_LENGTH # Rendering is >100K characters with self.simulated_markdown_failure(): with self.assertRaises(BugdownRenderingException): bugdown_convert(msg) class BugdownAvatarTestCase(ZulipTestCase): def test_possible_avatar_emails(self) -> None: content = ''' hello !avatar(foo@example.com) my email is ignore@ignore.com !gravatar(bar@yo.tv) smushing!avatar(hamlet@example.org) is allowed ''' self.assertEqual( bugdown.possible_avatar_emails(content), {'foo@example.com', 'bar@yo.tv', 'hamlet@example.org'}, ) def test_avatar_with_id(self) -> None: sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) user_profile = self.example_user('hamlet') msg = '!avatar({0})'.format(user_profile.email) converted = bugdown.convert(msg, message=message) values = {'email': user_profile.email, 'id': user_profile.id} self.assertEqual( converted, '

{email}

'.format(**values)) def test_avatar_of_unregistered_user(self) -> None: sender_user_profile = self.example_user('othello') message = Message(sender=sender_user_profile, sending_client=get_client("test")) email = 'fakeuser@example.com' msg = '!avatar({0})'.format(email) converted = bugdown.convert(msg, message=message) self.assertEqual( converted, '

{0}

'.format(email))