<commit_before>from zerver.lib.alert_words import (
    add_user_alert_words,
    alert_words_in_realm,
    remove_user_alert_words,
    user_alert_words,
)

from zerver.lib.test_helpers import (
    most_recent_message,
    most_recent_usermessage,
)

from zerver.lib.test_classes import (
    ZulipTestCase,
)

from zerver.models import (
    UserProfile,
)

import ujson

class AlertWordTests(ZulipTestCase):
    interesting_alert_word_list = ['alert', 'multi-word word', '☃']

    def test_internal_endpoint(self) -> None:
        user_name = "cordelia"
        self.login(user_name)

        params = {
            'alert_words': ujson.dumps(['milk', 'cookies'])
        }
        result = self.client_post('/json/users/me/alert_words', params)
        self.assert_json_success(result)
        user = self.example_user(user_name)
        words = user_alert_words(user)
        self.assertEqual(words, ['milk', 'cookies'])

    def test_default_no_words(self) -> None:
        """
        Users start out with no alert words.
        """
        user = self.example_user('cordelia')
        words = user_alert_words(user)
        self.assertEqual(words, [])

    def test_add_word(self) -> None:
        """
        add_user_alert_words can add multiple alert words at once.
        """
        user = self.example_user('cordelia')

        # Add several words, including multi-word and non-ascii words.
        add_user_alert_words(user, self.interesting_alert_word_list)

        words = user_alert_words(user)
        self.assertEqual(words, self.interesting_alert_word_list)

    def test_remove_word(self) -> None:
        """
        Removing alert words works via remove_user_alert_words, even
        for multi-word and non-ascii words.
        """
        user = self.example_user('cordelia')

        add_user_alert_words(user, self.interesting_alert_word_list)

        theoretical_remaining_alerts = self.interesting_alert_word_list[:]

        for alert_word in self.interesting_alert_word_list:
            remove_user_alert_words(user, alert_word)
            theoretical_remaining_alerts.remove(alert_word)
            actual_remaining_alerts = user_alert_words(user)
            self.assertEqual(actual_remaining_alerts,
                             theoretical_remaining_alerts)

    def test_realm_words(self) -> None:
        """
        We can gather alert words for an entire realm via
        alert_words_in_realm. Alerts added for one user do not impact other
        users.
        """
        user1 = self.example_user('cordelia')

        add_user_alert_words(user1, self.interesting_alert_word_list)

        user2 = self.example_user('othello')
        add_user_alert_words(user2, ['another'])

        realm_words = alert_words_in_realm(user2.realm)
        self.assertEqual(len(realm_words), 2)
        self.assertEqual(set(realm_words.keys()), {user1.id, user2.id})
        self.assertEqual(realm_words[user1.id],
                         self.interesting_alert_word_list)
        self.assertEqual(realm_words[user2.id], ['another'])

    def test_json_list_default(self) -> None:
        self.login('hamlet')

        result = self.client_get('/json/users/me/alert_words')
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], [])

    def test_json_list_nonempty(self) -> None:
        hamlet = self.example_user('hamlet')
        add_user_alert_words(hamlet, ['one', 'two', 'three'])

        self.login('hamlet')
        result = self.client_get('/json/users/me/alert_words')
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])

    def test_json_list_add(self) -> None:
        self.login('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one ', '\n two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])

    def test_json_list_remove(self) -> None:
        self.login('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])

        result = self.client_delete('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one'])})
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], ['two', 'three'])

    def message_does_alert(self, user_profile: UserProfile, message: str) -> bool:
        """Send a bunch of messages as othello, so Hamlet is notified"""
        self.send_stream_message(self.example_user("othello"), "Denmark", message)
        user_message = most_recent_usermessage(user_profile)
        return 'has_alert_word' in user_message.flags_list()

    def test_alert_flags(self) -> None:
        self.login('hamlet')
        user_profile_hamlet = self.example_user('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])

        # Alerts in the middle of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Normal alert one time"))
        # Alerts at the end of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Normal alert one"))
        # Alerts at the beginning of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "two normal alerts"))
        # Alerts with surrounding punctuation work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "This one? should alert"))
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Definitely time for three."))
        # Multiple alerts in a message work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "One two three o'clock"))
        # Alerts are case-insensitive.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "One o'clock"))
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Case of ONE, won't stop me"))

        # We don't cause alerts for matches in URLs.
        self.assertFalse(self.message_does_alert(user_profile_hamlet, "Don't alert on http://t.co/one/ urls"))
        self.assertFalse(self.message_does_alert(user_profile_hamlet, "Don't alert on http://t.co/one urls"))

    def test_update_alert_words(self) -> None:
        user_profile = self.example_user('hamlet')
<commit_msg>test_alert_words: Use set comparisons for alert word sets.<commit_after>from zerver.lib.alert_words import (
    add_user_alert_words,
    alert_words_in_realm,
    remove_user_alert_words,
    user_alert_words,
)

from zerver.lib.test_helpers import (
    most_recent_message,
    most_recent_usermessage,
)

from zerver.lib.test_classes import (
    ZulipTestCase,
)

from zerver.models import (
    UserProfile,
)

import ujson

class AlertWordTests(ZulipTestCase):
    interesting_alert_word_list = ['alert', 'multi-word word', '☃']

    def test_internal_endpoint(self) -> None:
        user_name = "cordelia"
        self.login(user_name)

        params = {
            'alert_words': ujson.dumps(['milk', 'cookies'])
        }
        result = self.client_post('/json/users/me/alert_words', params)
        self.assert_json_success(result)
        user = self.example_user(user_name)
        words = user_alert_words(user)
        self.assertEqual(set(words), {'milk', 'cookies'})

    def test_default_no_words(self) -> None:
        """
        Users start out with no alert words.
        """
        user = self.example_user('cordelia')
        words = user_alert_words(user)
        self.assertEqual(words, [])

    def test_add_word(self) -> None:
        """
        add_user_alert_words can add multiple alert words at once.
        """
        user = self.example_user('cordelia')

        # Add several words, including multi-word and non-ascii words.
        add_user_alert_words(user, self.interesting_alert_word_list)

        words = user_alert_words(user)
        self.assertEqual(set(words), set(self.interesting_alert_word_list))

    def test_remove_word(self) -> None:
        """
        Removing alert words works via remove_user_alert_words, even
        for multi-word and non-ascii words.
        """
        user = self.example_user('cordelia')

        add_user_alert_words(user, self.interesting_alert_word_list)

        theoretical_remaining_alerts = self.interesting_alert_word_list[:]

        for alert_word in self.interesting_alert_word_list:
            remove_user_alert_words(user, alert_word)
            theoretical_remaining_alerts.remove(alert_word)
            actual_remaining_alerts = user_alert_words(user)
            self.assertEqual(set(actual_remaining_alerts),
                             set(theoretical_remaining_alerts))

    def test_realm_words(self) -> None:
        """
        We can gather alert words for an entire realm via
        alert_words_in_realm. Alerts added for one user do not impact other
        users.
        """
        user1 = self.example_user('cordelia')

        add_user_alert_words(user1, self.interesting_alert_word_list)

        user2 = self.example_user('othello')
        add_user_alert_words(user2, ['another'])

        realm_words = alert_words_in_realm(user2.realm)
        self.assertEqual(len(realm_words), 2)
        self.assertEqual(set(realm_words.keys()), {user1.id, user2.id})
        self.assertEqual(set(realm_words[user1.id]),
                         set(self.interesting_alert_word_list))
        self.assertEqual(set(realm_words[user2.id]), {'another'})

    def test_json_list_default(self) -> None:
        self.login('hamlet')

        result = self.client_get('/json/users/me/alert_words')
        self.assert_json_success(result)
        self.assertEqual(result.json()['alert_words'], [])

    def test_json_list_nonempty(self) -> None:
        hamlet = self.example_user('hamlet')
        add_user_alert_words(hamlet, ['one', 'two', 'three'])

        self.login('hamlet')
        result = self.client_get('/json/users/me/alert_words')
        self.assert_json_success(result)
        self.assertEqual(set(result.json()['alert_words']), {'one', 'two', 'three'})

    def test_json_list_add(self) -> None:
        self.login('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one ', '\n two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(set(result.json()['alert_words']), {'one', 'two', 'three'})

    def test_json_list_remove(self) -> None:
        self.login('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(set(result.json()['alert_words']), {'one', 'two', 'three'})

        result = self.client_delete('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one'])})
        self.assert_json_success(result)
        self.assertEqual(set(result.json()['alert_words']), {'two', 'three'})

    def message_does_alert(self, user_profile: UserProfile, message: str) -> bool:
        """Send a bunch of messages as othello, so Hamlet is notified"""
        self.send_stream_message(self.example_user("othello"), "Denmark", message)
        user_message = most_recent_usermessage(user_profile)
        return 'has_alert_word' in user_message.flags_list()

    def test_alert_flags(self) -> None:
        self.login('hamlet')
        user_profile_hamlet = self.example_user('hamlet')

        result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
        self.assert_json_success(result)
        self.assertEqual(set(result.json()['alert_words']), {'one', 'two', 'three'})

        # Alerts in the middle of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Normal alert one time"))
        # Alerts at the end of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Normal alert one"))
        # Alerts at the beginning of messages work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "two normal alerts"))
        # Alerts with surrounding punctuation work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "This one? should alert"))
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Definitely time for three."))
        # Multiple alerts in a message work.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "One two three o'clock"))
        # Alerts are case-insensitive.
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "One o'clock"))
        self.assertTrue(self.message_does_alert(user_profile_hamlet, "Case of ONE, won't stop me"))

        # We don't cause alerts for matches in URLs.
        self.assertFalse(self.message_does_alert(user_profile_hamlet, "Don't alert on http://t.co/one/ urls"))
        self.assertFalse(self.message_does_alert(user_profile_hamlet, "Don't alert on http://t.co/one urls"))

    def test_update_alert_words(self) -> None:
        user_profile = self.example_user('hamlet')

{'fraction_non_alphanumeric': 0.12047477744807121, 'fraction_numerical': 0.001483679525222552, 'mean_word_length': 3.2433112999685236, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7369850', 'n_tokens_mistral': 4218, 'n_tokens_neox': 4120, 'n_words': 753}