diff options
| author | 2020-02-03 14:28:36 +0100 | |
|---|---|---|
| committer | 2020-02-03 14:28:36 +0100 | |
| commit | 90763fd761a70168e8752fa78729af40ad98144b (patch) | |
| tree | 53f242431a2ceb37295e4e90846bd1bfb66c5fd4 /tests/bot | |
| parent | Replace constant attachment-repost by the actual ID of #attachment-log (diff) | |
| parent | Update CODEOWNERS (diff) | |
Merge branch 'master' into #549-show-attachments-staff
Diffstat (limited to 'tests/bot')
| -rw-r--r-- | tests/bot/rules/test_attachments.py | 110 | ||||
| -rw-r--r-- | tests/bot/rules/test_links.py | 26 | ||||
| -rw-r--r-- | tests/bot/rules/test_mentions.py | 95 | ||||
| -rw-r--r-- | tests/bot/utils/test_time.py | 162 | 
4 files changed, 346 insertions, 47 deletions
| diff --git a/tests/bot/rules/test_attachments.py b/tests/bot/rules/test_attachments.py index 4bb0acf7c..d7187f315 100644 --- a/tests/bot/rules/test_attachments.py +++ b/tests/bot/rules/test_attachments.py @@ -1,52 +1,98 @@ -import asyncio  import unittest -from dataclasses import dataclass -from typing import Any, List +from typing import List, NamedTuple, Tuple  from bot.rules import attachments +from tests.helpers import MockMessage, async_test -# Using `MagicMock` sadly doesn't work for this usecase -# since it's __eq__ compares the MagicMock's ID. We just -# want to compare the actual attributes we set. -@dataclass -class FakeMessage: -    author: str -    attachments: List[Any] +class Case(NamedTuple): +    recent_messages: List[MockMessage] +    culprit: Tuple[str] +    total_attachments: int -def msg(total_attachments: int) -> FakeMessage: -    return FakeMessage(author='lemon', attachments=list(range(total_attachments))) +def msg(author: str, total_attachments: int) -> MockMessage: +    """Builds a message with `total_attachments` attachments.""" +    return MockMessage(author=author, attachments=list(range(total_attachments)))  class AttachmentRuleTests(unittest.TestCase): -    """Tests applying the `attachment` antispam rule.""" +    """Tests applying the `attachments` antispam rule.""" -    def test_allows_messages_without_too_many_attachments(self): +    def setUp(self): +        self.config = {"max": 5} + +    @async_test +    async def test_allows_messages_without_too_many_attachments(self):          """Messages without too many attachments are allowed as-is."""          cases = ( -            (msg(0), msg(0), msg(0)), -            (msg(2), msg(2)), -            (msg(0),), +            [msg("bob", 0), msg("bob", 0), msg("bob", 0)], +            [msg("bob", 2), msg("bob", 2)], +            [msg("bob", 2), msg("alice", 2), msg("bob", 2)],          ) -        for last_message, *recent_messages in cases: -            with self.subTest(last_message=last_message, recent_messages=recent_messages): -                coro = attachments.apply(last_message, recent_messages, {'max': 5}) -                self.assertIsNone(asyncio.run(coro)) +        for recent_messages in cases: +            last_message = recent_messages[0] + +            with self.subTest( +                last_message=last_message, +                recent_messages=recent_messages, +                config=self.config +            ): +                self.assertIsNone( +                    await attachments.apply(last_message, recent_messages, self.config) +                ) -    def test_disallows_messages_with_too_many_attachments(self): +    @async_test +    async def test_disallows_messages_with_too_many_attachments(self):          """Messages with too many attachments trigger the rule."""          cases = ( -            ((msg(4), msg(0), msg(6)), [msg(4), msg(6)], 10), -            ((msg(6),), [msg(6)], 6), -            ((msg(1),) * 6, [msg(1)] * 6, 6), +            Case( +                [msg("bob", 4), msg("bob", 0), msg("bob", 6)], +                ("bob",), +                10 +            ), +            Case( +                [msg("bob", 4), msg("alice", 6), msg("bob", 2)], +                ("bob",), +                6 +            ), +            Case( +                [msg("alice", 6)], +                ("alice",), +                6 +            ), +            ( +                [msg("alice", 1) for _ in range(6)], +                ("alice",), +                6 +            ),          ) -        for messages, relevant_messages, total in cases: -            with self.subTest(messages=messages, relevant_messages=relevant_messages, total=total): -                last_message, *recent_messages = messages -                coro = attachments.apply(last_message, recent_messages, {'max': 5}) -                self.assertEqual( -                    asyncio.run(coro), -                    (f"sent {total} attachments in 5s", ('lemon',), relevant_messages) + +        for recent_messages, culprit, total_attachments in cases: +            last_message = recent_messages[0] +            relevant_messages = tuple( +                msg +                for msg in recent_messages +                if ( +                    msg.author == last_message.author +                    and len(msg.attachments) > 0 +                ) +            ) + +            with self.subTest( +                last_message=last_message, +                recent_messages=recent_messages, +                relevant_messages=relevant_messages, +                total_attachments=total_attachments, +                config=self.config +            ): +                desired_output = ( +                    f"sent {total_attachments} attachments in {self.config['max']}s", +                    culprit, +                    relevant_messages +                ) +                self.assertTupleEqual( +                    await attachments.apply(last_message, recent_messages, self.config), +                    desired_output                  ) diff --git a/tests/bot/rules/test_links.py b/tests/bot/rules/test_links.py index be832843b..02a5d5501 100644 --- a/tests/bot/rules/test_links.py +++ b/tests/bot/rules/test_links.py @@ -2,25 +2,19 @@ import unittest  from typing import List, NamedTuple, Tuple  from bot.rules import links -from tests.helpers import async_test - - -class FakeMessage(NamedTuple): -    author: str -    content: str +from tests.helpers import MockMessage, async_test  class Case(NamedTuple): -    recent_messages: List[FakeMessage] -    relevant_messages: Tuple[FakeMessage] +    recent_messages: List[MockMessage]      culprit: Tuple[str]      total_links: int -def msg(author: str, total_links: int) -> FakeMessage: -    """Makes a message with *total_links* links.""" +def msg(author: str, total_links: int) -> MockMessage: +    """Makes a message with `total_links` links."""      content = " ".join(["https://pydis.com"] * total_links) -    return FakeMessage(author=author, content=content) +    return MockMessage(author=author, content=content)  class LinksTests(unittest.TestCase): @@ -61,26 +55,28 @@ class LinksTests(unittest.TestCase):          cases = (              Case(                  [msg("bob", 1), msg("bob", 2)], -                (msg("bob", 1), msg("bob", 2)),                  ("bob",),                  3              ),              Case(                  [msg("alice", 1), msg("alice", 1), msg("alice", 1)], -                (msg("alice", 1), msg("alice", 1), msg("alice", 1)),                  ("alice",),                  3              ),              Case(                  [msg("alice", 2), msg("bob", 3), msg("alice", 1)], -                (msg("alice", 2), msg("alice", 1)),                  ("alice",),                  3              )          ) -        for recent_messages, relevant_messages, culprit, total_links in cases: +        for recent_messages, culprit, total_links in cases:              last_message = recent_messages[0] +            relevant_messages = tuple( +                msg +                for msg in recent_messages +                if msg.author == last_message.author +            )              with self.subTest(                  last_message=last_message, diff --git a/tests/bot/rules/test_mentions.py b/tests/bot/rules/test_mentions.py new file mode 100644 index 000000000..ad49ead32 --- /dev/null +++ b/tests/bot/rules/test_mentions.py @@ -0,0 +1,95 @@ +import unittest +from typing import List, NamedTuple, Tuple + +from bot.rules import mentions +from tests.helpers import MockMessage, async_test + + +class Case(NamedTuple): +    recent_messages: List[MockMessage] +    culprit: Tuple[str] +    total_mentions: int + + +def msg(author: str, total_mentions: int) -> MockMessage: +    """Makes a message with `total_mentions` mentions.""" +    return MockMessage(author=author, mentions=list(range(total_mentions))) + + +class TestMentions(unittest.TestCase): +    """Tests applying the `mentions` antispam rule.""" + +    def setUp(self): +        self.config = { +            "max": 2, +            "interval": 10 +        } + +    @async_test +    async def test_mentions_within_limit(self): +        """Messages with an allowed amount of mentions.""" +        cases = ( +            [msg("bob", 0)], +            [msg("bob", 2)], +            [msg("bob", 1), msg("bob", 1)], +            [msg("bob", 1), msg("alice", 2)] +        ) + +        for recent_messages in cases: +            last_message = recent_messages[0] + +            with self.subTest( +                last_message=last_message, +                recent_messages=recent_messages, +                config=self.config +            ): +                self.assertIsNone( +                    await mentions.apply(last_message, recent_messages, self.config) +                ) + +    @async_test +    async def test_mentions_exceeding_limit(self): +        """Messages with a higher than allowed amount of mentions.""" +        cases = ( +            Case( +                [msg("bob", 3)], +                ("bob",), +                3 +            ), +            Case( +                [msg("alice", 2), msg("alice", 0), msg("alice", 1)], +                ("alice",), +                3 +            ), +            Case( +                [msg("bob", 2), msg("alice", 3), msg("bob", 2)], +                ("bob",), +                4 +            ) +        ) + +        for recent_messages, culprit, total_mentions in cases: +            last_message = recent_messages[0] +            relevant_messages = tuple( +                msg +                for msg in recent_messages +                if msg.author == last_message.author +            ) + +            with self.subTest( +                last_message=last_message, +                recent_messages=recent_messages, +                relevant_messages=relevant_messages, +                culprit=culprit, +                total_mentions=total_mentions, +                cofig=self.config +            ): +                desired_output = ( +                    f"sent {total_mentions} mentions in {self.config['interval']}s", +                    culprit, +                    relevant_messages +                ) +                self.assertTupleEqual( +                    await mentions.apply(last_message, recent_messages, self.config), +                    desired_output +                ) diff --git a/tests/bot/utils/test_time.py b/tests/bot/utils/test_time.py new file mode 100644 index 000000000..69f35f2f5 --- /dev/null +++ b/tests/bot/utils/test_time.py @@ -0,0 +1,162 @@ +import asyncio +import unittest +from datetime import datetime, timezone +from unittest.mock import patch + +from dateutil.relativedelta import relativedelta + +from bot.utils import time +from tests.helpers import AsyncMock + + +class TimeTests(unittest.TestCase): +    """Test helper functions in bot.utils.time.""" + +    def test_humanize_delta_handle_unknown_units(self): +        """humanize_delta should be able to handle unknown units, and will not abort.""" +        # Does not abort for unknown units, as the unit name is checked +        # against the attribute of the relativedelta instance. +        self.assertEqual(time.humanize_delta(relativedelta(days=2, hours=2), 'elephants', 2), '2 days and 2 hours') + +    def test_humanize_delta_handle_high_units(self): +        """humanize_delta should be able to handle very high units.""" +        # Very high maximum units, but it only ever iterates over +        # each value the relativedelta might have. +        self.assertEqual(time.humanize_delta(relativedelta(days=2, hours=2), 'hours', 20), '2 days and 2 hours') + +    def test_humanize_delta_should_normal_usage(self): +        """Testing humanize delta.""" +        test_cases = ( +            (relativedelta(days=2), 'seconds', 1, '2 days'), +            (relativedelta(days=2, hours=2), 'seconds', 2, '2 days and 2 hours'), +            (relativedelta(days=2, hours=2), 'seconds', 1, '2 days'), +            (relativedelta(days=2, hours=2), 'days', 2, '2 days'), +        ) + +        for delta, precision, max_units, expected in test_cases: +            with self.subTest(delta=delta, precision=precision, max_units=max_units, expected=expected): +                self.assertEqual(time.humanize_delta(delta, precision, max_units), expected) + +    def test_humanize_delta_raises_for_invalid_max_units(self): +        """humanize_delta should raises ValueError('max_units must be positive') for invalid max_units.""" +        test_cases = (-1, 0) + +        for max_units in test_cases: +            with self.subTest(max_units=max_units), self.assertRaises(ValueError) as error: +                time.humanize_delta(relativedelta(days=2, hours=2), 'hours', max_units) +                self.assertEqual(str(error), 'max_units must be positive') + +    def test_parse_rfc1123(self): +        """Testing parse_rfc1123.""" +        self.assertEqual( +            time.parse_rfc1123('Sun, 15 Sep 2019 12:00:00 GMT'), +            datetime(2019, 9, 15, 12, 0, 0, tzinfo=timezone.utc) +        ) + +    def test_format_infraction(self): +        """Testing format_infraction.""" +        self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'), '2019-12-12 00:01') + +    @patch('asyncio.sleep', new_callable=AsyncMock) +    def test_wait_until(self, mock): +        """Testing wait_until.""" +        start = datetime(2019, 1, 1, 0, 0) +        then = datetime(2019, 1, 1, 0, 10) + +        # No return value +        self.assertIs(asyncio.run(time.wait_until(then, start)), None) + +        mock.assert_called_once_with(10 * 60) + +    def test_format_infraction_with_duration_none_expiry(self): +        """format_infraction_with_duration should work for None expiry.""" +        test_cases = ( +            (None, None, None, None), + +            # To make sure that date_from and max_units are not touched +            (None, 'Why hello there!', None, None), +            (None, None, float('inf'), None), +            (None, 'Why hello there!', float('inf'), None), +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_format_infraction_with_duration_custom_units(self): +        """format_infraction_with_duration should work for custom max_units.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6, +             '2019-12-12 00:01 (11 hours, 55 minutes and 55 seconds)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20, +             '2019-11-23 20:09 (6 months, 28 days, 23 hours and 54 minutes)') +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_format_infraction_with_duration_normal_usage(self): +        """format_infraction_with_duration should work for normal usage, across various durations.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '2019-12-12 00:01 (12 hours and 55 seconds)'), +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '2019-12-12 00:01 (12 hours)'), +            ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '2019-12-12 00:00 (1 minute)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '2019-11-23 20:09 (7 days and 23 hours)'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '2019-11-23 20:09 (6 months and 28 days)'), +            ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '2019-11-23 20:58 (5 minutes)'), +            ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '2019-11-24 00:00 (1 minute)'), +            ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2019-11-23 23:59 (2 years and 4 months)'), +            ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2, +             '2019-11-23 23:59 (9 minutes and 55 seconds)'), +            (None, datetime(2019, 11, 23, 23, 49, 5), 2, None), +        ) + +        for expiry, date_from, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, date_from=date_from, max_units=max_units, expected=expected): +                self.assertEqual(time.format_infraction_with_duration(expiry, date_from, max_units), expected) + +    def test_until_expiration_with_duration_none_expiry(self): +        """until_expiration should work for None expiry.""" +        test_cases = ( +            (None, None, None, None), + +            # To make sure that now and max_units are not touched +            (None, 'Why hello there!', None, None), +            (None, None, float('inf'), None), +            (None, 'Why hello there!', float('inf'), None), +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) + +    def test_until_expiration_with_duration_custom_units(self): +        """until_expiration should work for custom max_units.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6, '11 hours, 55 minutes and 55 seconds'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20, '6 months, 28 days, 23 hours and 54 minutes') +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) + +    def test_until_expiration_normal_usage(self): +        """until_expiration should work for normal usage, across various durations.""" +        test_cases = ( +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '12 hours and 55 seconds'), +            ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '12 hours'), +            ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '1 minute'), +            ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '7 days and 23 hours'), +            ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '6 months and 28 days'), +            ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '5 minutes'), +            ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '1 minute'), +            ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2 years and 4 months'), +            ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2, '9 minutes and 55 seconds'), +            (None, datetime(2019, 11, 23, 23, 49, 5), 2, None), +        ) + +        for expiry, now, max_units, expected in test_cases: +            with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected): +                self.assertEqual(time.until_expiration(expiry, now, max_units), expected) | 
