diff options
Diffstat (limited to '')
| -rw-r--r-- | tests/bot/exts/filtering/test_discord_token_filter.py | 276 | ||||
| -rw-r--r-- | tests/bot/exts/filtering/test_extension_filter.py | 139 | ||||
| -rw-r--r-- | tests/bot/exts/filtering/test_settings_entries.py | 66 | ||||
| -rw-r--r-- | tests/bot/exts/filtering/test_token_filter.py (renamed from tests/bot/exts/filtering/test_filters.py) | 14 | 
4 files changed, 484 insertions, 11 deletions
diff --git a/tests/bot/exts/filtering/test_discord_token_filter.py b/tests/bot/exts/filtering/test_discord_token_filter.py new file mode 100644 index 000000000..ef124e6ff --- /dev/null +++ b/tests/bot/exts/filtering/test_discord_token_filter.py @@ -0,0 +1,276 @@ +import unittest +from re import Match +from unittest import mock +from unittest.mock import MagicMock, patch + +import arrow + +from bot.exts.filtering._filter_context import Event, FilterContext +from bot.exts.filtering._filters.unique import discord_token +from bot.exts.filtering._filters.unique.discord_token import DiscordTokenFilter, Token +from tests.helpers import MockBot, MockMember, MockMessage, MockTextChannel, autospec + + +class DiscordTokenFilterTests(unittest.IsolatedAsyncioTestCase): +    """Tests the DiscordTokenFilter class.""" + +    def setUp(self): +        """Adds the filter, a bot, and a message to the instance for usage in tests.""" +        now = arrow.utcnow().timestamp() +        self.filter = DiscordTokenFilter({ +            "id": 1, +            "content": "discord_token", +            "description": None, +            "settings": {}, +            "additional_field": "{}",  # noqa: P103 +            "created_at": now, +            "updated_at": now +        }) + +        self.msg = MockMessage(id=555, content="hello world") +        self.msg.author.__str__ = MagicMock(return_value=self.msg.author.name) + +        member = MockMember(id=123) +        channel = MockTextChannel(id=345) +        self.ctx = FilterContext(Event.MESSAGE, member, channel, "", self.msg) + +    def test_extract_user_id_valid(self): +        """Should consider user IDs valid if they decode into an integer ID.""" +        id_pairs = ( +            ("NDcyMjY1OTQzMDYyNDEzMzMy", 472265943062413332), +            ("NDc1MDczNjI5Mzk5NTQ3OTA0", 475073629399547904), +            ("NDY3MjIzMjMwNjUwNzc3NjQx", 467223230650777641), +        ) + +        for token_id, user_id in id_pairs: +            with self.subTest(token_id=token_id): +                result = DiscordTokenFilter.extract_user_id(token_id) +                self.assertEqual(result, user_id) + +    def test_extract_user_id_invalid(self): +        """Should consider non-digit and non-ASCII IDs invalid.""" +        ids = ( +            ("SGVsbG8gd29ybGQ", "non-digit ASCII"), +            ("0J_RgNC40LLQtdGCINC80LjRgA", "cyrillic text"), +            ("4pO14p6L4p6C4pG34p264pGl8J-EiOKSj-KCieKBsA", "Unicode digits"), +            ("4oaA4oaB4oWh4oWi4Lyz4Lyq4Lyr4LG9", "Unicode numerals"), +            ("8J2fjvCdn5nwnZ-k8J2fr_Cdn7rgravvvJngr6c", "Unicode decimals"), +            ("{hello}[world]&(bye!)", "ASCII invalid Base64"), +            ("Þíß-ï§-ňøẗ-våłìÐ", "Unicode invalid Base64"), +        ) + +        for user_id, msg in ids: +            with self.subTest(msg=msg): +                result = DiscordTokenFilter.extract_user_id(user_id) +                self.assertIsNone(result) + +    def test_is_valid_timestamp_valid(self): +        """Should consider timestamps valid if they're greater than the Discord epoch.""" +        timestamps = ( +            "XsyRkw", +            "Xrim9Q", +            "XsyR-w", +            "XsySD_", +            "Dn9r_A", +        ) + +        for timestamp in timestamps: +            with self.subTest(timestamp=timestamp): +                result = DiscordTokenFilter.is_valid_timestamp(timestamp) +                self.assertTrue(result) + +    def test_is_valid_timestamp_invalid(self): +        """Should consider timestamps invalid if they're before Discord epoch or can't be parsed.""" +        timestamps = ( +            ("B4Yffw", "DISCORD_EPOCH - TOKEN_EPOCH - 1"), +            ("ew", "123"), +            ("AoIKgA", "42076800"), +            ("{hello}[world]&(bye!)", "ASCII invalid Base64"), +            ("Þíß-ï§-ňøẗ-våłìÐ", "Unicode invalid Base64"), +        ) + +        for timestamp, msg in timestamps: +            with self.subTest(msg=msg): +                result = DiscordTokenFilter.is_valid_timestamp(timestamp) +                self.assertFalse(result) + +    def test_is_valid_hmac_valid(self): +        """Should consider an HMAC valid if it has at least 3 unique characters.""" +        valid_hmacs = ( +            "VXmErH7j511turNpfURmb0rVNm8", +            "Ysnu2wacjaKs7qnoo46S8Dm2us8", +            "sJf6omBPORBPju3WJEIAcwW9Zds", +            "s45jqDV_Iisn-symw0yDRrk_jf4", +        ) + +        for hmac in valid_hmacs: +            with self.subTest(msg=hmac): +                result = DiscordTokenFilter.is_maybe_valid_hmac(hmac) +                self.assertTrue(result) + +    def test_is_invalid_hmac_invalid(self): +        """Should consider an HMAC invalid if has fewer than 3 unique characters.""" +        invalid_hmacs = ( +            ("xxxxxxxxxxxxxxxxxx", "Single character"), +            ("XxXxXxXxXxXxXxXxXx", "Single character alternating case"), +            ("ASFasfASFasfASFASsf", "Three characters alternating-case"), +            ("asdasdasdasdasdasdasd", "Three characters one case"), +        ) + +        for hmac, msg in invalid_hmacs: +            with self.subTest(msg=msg): +                result = DiscordTokenFilter.is_maybe_valid_hmac(hmac) +                self.assertFalse(result) + +    async def test_no_trigger_when_no_token(self): +        """False should be returned if the message doesn't contain a Discord token.""" +        return_value = await self.filter.triggered_on(self.ctx) + +        self.assertFalse(return_value) + +    @autospec(DiscordTokenFilter, "extract_user_id", "is_valid_timestamp", "is_maybe_valid_hmac") +    @autospec("bot.exts.filtering._filters.unique.discord_token", "Token") +    @autospec("bot.exts.filtering._filters.unique.discord_token", "TOKEN_RE") +    def test_find_token_valid_match( +        self, +        token_re, +        token_cls, +        extract_user_id, +        is_valid_timestamp, +        is_maybe_valid_hmac, +    ): +        """The first match with a valid user ID, timestamp, and HMAC should be returned as a `Token`.""" +        matches = [ +            mock.create_autospec(Match, spec_set=True, instance=True), +            mock.create_autospec(Match, spec_set=True, instance=True), +        ] +        tokens = [ +            mock.create_autospec(Token, spec_set=True, instance=True), +            mock.create_autospec(Token, spec_set=True, instance=True), +        ] + +        token_re.finditer.return_value = matches +        token_cls.side_effect = tokens +        extract_user_id.side_effect = (None, True)  # The 1st match will be invalid, 2nd one valid. +        is_valid_timestamp.return_value = True +        is_maybe_valid_hmac.return_value = True + +        return_value = DiscordTokenFilter.find_token_in_message(self.msg) + +        self.assertEqual(tokens[1], return_value) + +    @autospec(DiscordTokenFilter, "extract_user_id", "is_valid_timestamp", "is_maybe_valid_hmac") +    @autospec("bot.exts.filtering._filters.unique.discord_token", "Token") +    @autospec("bot.exts.filtering._filters.unique.discord_token", "TOKEN_RE") +    def test_find_token_invalid_matches( +        self, +        token_re, +        token_cls, +        extract_user_id, +        is_valid_timestamp, +        is_maybe_valid_hmac, +    ): +        """None should be returned if no matches have valid user IDs, HMACs, and timestamps.""" +        token_re.finditer.return_value = [mock.create_autospec(Match, spec_set=True, instance=True)] +        token_cls.return_value = mock.create_autospec(Token, spec_set=True, instance=True) +        extract_user_id.return_value = None +        is_valid_timestamp.return_value = False +        is_maybe_valid_hmac.return_value = False + +        return_value = DiscordTokenFilter.find_token_in_message(self.msg) + +        self.assertIsNone(return_value) + +    def test_regex_invalid_tokens(self): +        """Messages without anything looking like a token are not matched.""" +        tokens = ( +            "", +            "lemon wins", +            "..", +            "x.y", +            "x.y.", +            ".y.z", +            ".y.", +            "..z", +            "x..z", +            " . . ", +            "\n.\n.\n", +            "hellö.world.bye", +            "base64.nötbåse64.morebase64", +            "19jd3J.dfkm3d.€víł§tüff", +        ) + +        for token in tokens: +            with self.subTest(token=token): +                results = discord_token.TOKEN_RE.findall(token) +                self.assertEqual(len(results), 0) + +    def test_regex_valid_tokens(self): +        """Messages that look like tokens should be matched.""" +        # Don't worry, these tokens have been invalidated. +        tokens = ( +            "NDcyMjY1OTQzMDYy_DEzMz-y.XsyRkw.VXmErH7j511turNpfURmb0rVNm8", +            "NDcyMjY1OTQzMDYyNDEzMzMy.Xrim9Q.Ysnu2wacjaKs7qnoo46S8Dm2us8", +            "NDc1MDczNjI5Mzk5NTQ3OTA0.XsyR-w.sJf6omBPORBPju3WJEIAcwW9Zds", +            "NDY3MjIzMjMwNjUwNzc3NjQx.XsySD_.s45jqDV_Iisn-symw0yDRrk_jf4", +        ) + +        for token in tokens: +            with self.subTest(token=token): +                results = discord_token.TOKEN_RE.fullmatch(token) +                self.assertIsNotNone(results, f"{token} was not matched by the regex") + +    def test_regex_matches_multiple_valid(self): +        """Should support multiple matches in the middle of a string.""" +        token_1 = "NDY3MjIzMjMwNjUwNzc3NjQx.XsyWGg.uFNEQPCc4ePwGh7egG8UicQssz8" +        token_2 = "NDcyMjY1OTQzMDYyNDEzMzMy.XsyWMw.l8XPnDqb0lp-EiQ2g_0xVFT1pyc" +        message = f"garbage {token_1} hello {token_2} world" + +        results = discord_token.TOKEN_RE.finditer(message) +        results = [match[0] for match in results] +        self.assertCountEqual((token_1, token_2), results) + +    @autospec("bot.exts.filtering._filters.unique.discord_token", "LOG_MESSAGE") +    def test_format_log_message(self, log_message): +        """Should correctly format the log message with info from the message and token.""" +        token = Token("NDcyMjY1OTQzMDYyNDEzMzMy", "XsySD_", "s45jqDV_Iisn-symw0yDRrk_jf4") +        log_message.format.return_value = "Howdy" + +        return_value = DiscordTokenFilter.format_log_message(self.msg.author, self.msg.channel, token) + +        self.assertEqual(return_value, log_message.format.return_value) + +    @patch("bot.instance", MockBot()) +    @autospec("bot.exts.filtering._filters.unique.discord_token", "UNKNOWN_USER_LOG_MESSAGE") +    @autospec("bot.exts.filtering._filters.unique.discord_token", "get_or_fetch_member") +    async def test_format_userid_log_message_unknown(self, get_or_fetch_member, unknown_user_log_message): +        """Should correctly format the user ID portion when the actual user it belongs to is unknown.""" +        token = Token("NDcyMjY1OTQzMDYyNDEzMzMy", "XsySD_", "s45jqDV_Iisn-symw0yDRrk_jf4") +        unknown_user_log_message.format.return_value = " Partner" +        get_or_fetch_member.return_value = None + +        return_value = await DiscordTokenFilter.format_userid_log_message(token) + +        self.assertEqual(return_value, (unknown_user_log_message.format.return_value, False)) + +    @patch("bot.instance", MockBot()) +    @autospec("bot.exts.filtering._filters.unique.discord_token", "KNOWN_USER_LOG_MESSAGE") +    async def test_format_userid_log_message_bot(self, known_user_log_message): +        """Should correctly format the user ID portion when the ID belongs to a known bot.""" +        token = Token("NDcyMjY1OTQzMDYyNDEzMzMy", "XsySD_", "s45jqDV_Iisn-symw0yDRrk_jf4") +        known_user_log_message.format.return_value = " Partner" + +        return_value = await DiscordTokenFilter.format_userid_log_message(token) + +        self.assertEqual(return_value, (known_user_log_message.format.return_value, True)) + +    @patch("bot.instance", MockBot()) +    @autospec("bot.exts.filtering._filters.unique.discord_token", "KNOWN_USER_LOG_MESSAGE") +    async def test_format_log_message_user_token_user(self, user_token_message): +        """Should correctly format the user ID portion when the ID belongs to a known user.""" +        token = Token("NDY3MjIzMjMwNjUwNzc3NjQx", "XsySD_", "s45jqDV_Iisn-symw0yDRrk_jf4") +        user_token_message.format.return_value = "Partner" + +        return_value = await DiscordTokenFilter.format_userid_log_message(token) + +        self.assertEqual(return_value, (user_token_message.format.return_value, True)) diff --git a/tests/bot/exts/filtering/test_extension_filter.py b/tests/bot/exts/filtering/test_extension_filter.py new file mode 100644 index 000000000..0ad41116d --- /dev/null +++ b/tests/bot/exts/filtering/test_extension_filter.py @@ -0,0 +1,139 @@ +import unittest +from unittest.mock import MagicMock, patch + +import arrow + +from bot.constants import Channels +from bot.exts.filtering._filter_context import Event, FilterContext +from bot.exts.filtering._filter_lists import extension +from bot.exts.filtering._filter_lists.extension import ExtensionsList +from bot.exts.filtering._filter_lists.filter_list import ListType +from tests.helpers import MockAttachment, MockBot, MockMember, MockMessage, MockTextChannel + +BOT = MockBot() + + +class ExtensionsListTests(unittest.IsolatedAsyncioTestCase): +    """Test the ExtensionsList class.""" + +    def setUp(self): +        """Sets up fresh objects for each test.""" +        self.filter_list = ExtensionsList(MagicMock()) +        now = arrow.utcnow().timestamp() +        filters = [] +        self.whitelist = [".first", ".second", ".third"] +        for i, filter_content in enumerate(self.whitelist, start=1): +            filters.append({ +                "id": i, "content": filter_content, "description": None, "settings": {}, +                "additional_field": "{}", "created_at": now, "updated_at": now  # noqa: P103 +            }) +        self.filter_list.add_list({ +            "id": 1, +            "list_type": 1, +            "created_at": now, +            "updated_at": now, +            "settings": {}, +            "filters": filters +        }) + +        self.message = MockMessage() +        member = MockMember(id=123) +        channel = MockTextChannel(id=345) +        self.ctx = FilterContext(Event.MESSAGE, member, channel, "", self.message) + +    @patch("bot.instance", BOT) +    async def test_message_with_allowed_attachment(self): +        """Messages with allowed extensions should trigger the whitelist and result in no actions or messages.""" +        attachment = MockAttachment(filename="python.first") +        self.message.attachments = [attachment] + +        result = await self.filter_list.actions_for(self.ctx) + +        self.assertEqual(result, (None, [], {ListType.ALLOW: [self.filter_list[ListType.ALLOW].filters[1]]})) + +    @patch("bot.instance", BOT) +    async def test_message_without_attachment(self): +        """Messages without attachments should return no triggers, messages, or actions.""" +        result = await self.filter_list.actions_for(self.ctx) + +        self.assertEqual(result, (None, [], {})) + +    @patch("bot.instance", BOT) +    async def test_message_with_illegal_extension(self): +        """A message with an illegal extension shouldn't trigger the whitelist, and return some action and message.""" +        attachment = MockAttachment(filename="python.disallowed") +        self.message.attachments = [attachment] + +        result = await self.filter_list.actions_for(self.ctx) + +        self.assertEqual(result, ({}, ["`.disallowed`"], {ListType.ALLOW: []})) + +    @patch("bot.instance", BOT) +    async def test_python_file_redirect_embed_description(self): +        """A message containing a .py file should result in an embed redirecting the user to our paste site.""" +        attachment = MockAttachment(filename="python.py") +        self.message.attachments = [attachment] + +        await self.filter_list.actions_for(self.ctx) + +        self.assertEqual(self.ctx.dm_embed, extension.PY_EMBED_DESCRIPTION) + +    @patch("bot.instance", BOT) +    async def test_txt_file_redirect_embed_description(self): +        """A message containing a .txt/.json/.csv file should result in the correct embed.""" +        test_values = ( +            ("text", ".txt"), +            ("json", ".json"), +            ("csv", ".csv"), +        ) + +        for file_name, disallowed_extension in test_values: +            with self.subTest(file_name=file_name, disallowed_extension=disallowed_extension): + +                attachment = MockAttachment(filename=f"{file_name}{disallowed_extension}") +                self.message.attachments = [attachment] + +                await self.filter_list.actions_for(self.ctx) + +                self.assertEqual( +                    self.ctx.dm_embed, +                    extension.TXT_EMBED_DESCRIPTION.format( +                        blocked_extension=disallowed_extension, +                    ) +                ) + +    @patch("bot.instance", BOT) +    async def test_other_disallowed_extension_embed_description(self): +        """Test the description for a non .py/.txt/.json/.csv disallowed extension.""" +        attachment = MockAttachment(filename="python.disallowed") +        self.message.attachments = [attachment] + +        await self.filter_list.actions_for(self.ctx) +        meta_channel = BOT.get_channel(Channels.meta) + +        self.assertEqual( +            self.ctx.dm_embed, +            extension.DISALLOWED_EMBED_DESCRIPTION.format( +                joined_whitelist=", ".join(self.whitelist), +                blocked_extensions_str=".disallowed", +                meta_channel_mention=meta_channel.mention +            ) +        ) + +    @patch("bot.instance", BOT) +    async def test_get_disallowed_extensions(self): +        """The return value should include all non-whitelisted extensions.""" +        test_values = ( +            ([], []), +            (self.whitelist, []), +            ([".first"], []), +            ([".first", ".disallowed"], ["`.disallowed`"]), +            ([".disallowed"], ["`.disallowed`"]), +            ([".disallowed", ".illegal"], ["`.disallowed`", "`.illegal`"]), +        ) + +        for extensions, expected_disallowed_extensions in test_values: +            with self.subTest(extensions=extensions, expected_disallowed_extensions=expected_disallowed_extensions): +                self.message.attachments = [MockAttachment(filename=f"filename{ext}") for ext in extensions] +                result = await self.filter_list.actions_for(self.ctx) +                self.assertCountEqual(result[1], expected_disallowed_extensions) diff --git a/tests/bot/exts/filtering/test_settings_entries.py b/tests/bot/exts/filtering/test_settings_entries.py index 34b155d6b..5a1eb6fe6 100644 --- a/tests/bot/exts/filtering/test_settings_entries.py +++ b/tests/bot/exts/filtering/test_settings_entries.py @@ -50,7 +50,9 @@ class FilterTests(unittest.TestCase):      def test_context_doesnt_trigger_for_empty_channel_scope(self):          """A filter is enabled for all channels by default."""          channel = MockTextChannel() -        scope = ChannelScope(disabled_channels=None, disabled_categories=None, enabled_channels=None) +        scope = ChannelScope( +            disabled_channels=None, disabled_categories=None, enabled_channels=None, enabled_categories=None +        )          self.ctx.channel = channel          result = scope.triggers_on(self.ctx) @@ -60,7 +62,9 @@ class FilterTests(unittest.TestCase):      def test_context_doesnt_trigger_for_disabled_channel(self):          """A filter shouldn't trigger if it's been disabled in the channel."""          channel = MockTextChannel(id=123) -        scope = ChannelScope(disabled_channels=["123"], disabled_categories=None, enabled_channels=None) +        scope = ChannelScope( +            disabled_channels=["123"], disabled_categories=None, enabled_channels=None, enabled_categories=None +        )          self.ctx.channel = channel          result = scope.triggers_on(self.ctx) @@ -70,7 +74,9 @@ class FilterTests(unittest.TestCase):      def test_context_doesnt_trigger_in_disabled_category(self):          """A filter shouldn't trigger if it's been disabled in the category."""          channel = MockTextChannel(category=MockCategoryChannel(id=456)) -        scope = ChannelScope(disabled_channels=None, disabled_categories=["456"], enabled_channels=None) +        scope = ChannelScope( +            disabled_channels=None, disabled_categories=["456"], enabled_channels=None, enabled_categories=None +        )          self.ctx.channel = channel          result = scope.triggers_on(self.ctx) @@ -80,13 +86,51 @@ class FilterTests(unittest.TestCase):      def test_context_triggers_in_enabled_channel_in_disabled_category(self):          """A filter should trigger in an enabled channel even if it's been disabled in the category."""          channel = MockTextChannel(id=123, category=MockCategoryChannel(id=234)) -        scope = ChannelScope(disabled_channels=None, disabled_categories=["234"], enabled_channels=["123"]) +        scope = ChannelScope( +            disabled_channels=None, disabled_categories=["234"], enabled_channels=["123"], enabled_categories=None +        ) +        self.ctx.channel = channel + +        result = scope.triggers_on(self.ctx) + +        self.assertTrue(result) + +    def test_context_triggers_inside_enabled_category(self): +        """A filter shouldn't trigger outside enabled categories, if there are any.""" +        channel = MockTextChannel(id=123, category=MockCategoryChannel(id=234)) +        scope = ChannelScope( +            disabled_channels=None, disabled_categories=None, enabled_channels=None, enabled_categories=["234"] +        )          self.ctx.channel = channel          result = scope.triggers_on(self.ctx)          self.assertTrue(result) +    def test_context_doesnt_trigger_outside_enabled_category(self): +        """A filter shouldn't trigger outside enabled categories, if there are any.""" +        channel = MockTextChannel(id=123, category=MockCategoryChannel(id=234)) +        scope = ChannelScope( +            disabled_channels=None, disabled_categories=None, enabled_channels=None, enabled_categories=["789"] +        ) +        self.ctx.channel = channel + +        result = scope.triggers_on(self.ctx) + +        self.assertFalse(result) + +    def test_context_doesnt_trigger_inside_disabled_channel_in_enabled_category(self): +        """A filter shouldn't trigger outside enabled categories, if there are any.""" +        channel = MockTextChannel(id=123, category=MockCategoryChannel(id=234)) +        scope = ChannelScope( +            disabled_channels=["123"], disabled_categories=None, enabled_channels=None, enabled_categories=["234"] +        ) +        self.ctx.channel = channel + +        result = scope.triggers_on(self.ctx) + +        self.assertFalse(result) +      def test_filtering_dms_when_necessary(self):          """A filter correctly ignores or triggers in a channel depending on the value of FilterDM."""          cases = ( @@ -112,14 +156,16 @@ class FilterTests(unittest.TestCase):              infraction_reason="hi",              infraction_duration=10,              dm_content="how", -            dm_embed="what is" +            dm_embed="what is", +            infraction_channel=0          )          infraction2 = InfractionAndNotification(              infraction_type="MUTE",              infraction_reason="there",              infraction_duration=20,              dm_content="are you", -            dm_embed="your name" +            dm_embed="your name", +            infraction_channel=0          )          result = infraction1 | infraction2 @@ -132,6 +178,7 @@ class FilterTests(unittest.TestCase):                  "infraction_duration": 20.0,                  "dm_content": "are you",                  "dm_embed": "your name", +                "infraction_channel": 0              }          ) @@ -142,14 +189,16 @@ class FilterTests(unittest.TestCase):              infraction_reason="hi",              infraction_duration=20,              dm_content="", -            dm_embed="" +            dm_embed="", +            infraction_channel=0          )          infraction2 = InfractionAndNotification(              infraction_type="BAN",              infraction_reason="",              infraction_duration=10,              dm_content="there", -            dm_embed="" +            dm_embed="", +            infraction_channel=0          )          result = infraction1 | infraction2 @@ -162,5 +211,6 @@ class FilterTests(unittest.TestCase):                  "infraction_duration": 10.0,                  "dm_content": "there",                  "dm_embed": "", +                "infraction_channel": 0              }          ) diff --git a/tests/bot/exts/filtering/test_filters.py b/tests/bot/exts/filtering/test_token_filter.py index 29b50188a..0dfc8ae9f 100644 --- a/tests/bot/exts/filtering/test_filters.py +++ b/tests/bot/exts/filtering/test_token_filter.py @@ -1,11 +1,13 @@  import unittest +import arrow +  from bot.exts.filtering._filter_context import Event, FilterContext  from bot.exts.filtering._filters.token import TokenFilter  from tests.helpers import MockMember, MockMessage, MockTextChannel -class FilterTests(unittest.IsolatedAsyncioTestCase): +class TokenFilterTests(unittest.IsolatedAsyncioTestCase):      """Test functionality of the token filter."""      def setUp(self) -> None: @@ -20,8 +22,12 @@ class FilterTests(unittest.IsolatedAsyncioTestCase):              (r"hi", "oh hi there", True),              (r"hi", "goodbye", False),              (r"bla\d{2,4}", "bla18", True), -            (r"bla\d{2,4}", "bla1", False) +            (r"bla\d{2,4}", "bla1", False), +            # See advisory https://github.com/python-discord/bot/security/advisories/GHSA-j8c3-8x46-8pp6 +            (r"TOKEN", "https://google.com TOKEN", True), +            (r"TOKEN", "https://google.com something else", False)          ) +        now = arrow.utcnow().timestamp()          for pattern, content, expected in test_cases:              with self.subTest( @@ -34,7 +40,9 @@ class FilterTests(unittest.IsolatedAsyncioTestCase):                      "content": pattern,                      "description": None,                      "settings": {}, -                    "additional_field": "{}"  # noqa: P103 +                    "additional_field": "{}",  # noqa: P103 +                    "created_at": now, +                    "updated_at": now                  })                  self.ctx.content = content                  result = await filter_.triggered_on(self.ctx)  |