From 1f17d0ed894ee6a4c6a9c703d03598d734ffeac2 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:20:17 +0100 Subject: Add unit test case for burst antispam rule --- tests/bot/rules/test_burst.py | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/bot/rules/test_burst.py (limited to 'tests') diff --git a/tests/bot/rules/test_burst.py b/tests/bot/rules/test_burst.py new file mode 100644 index 000000000..4da0cc78e --- /dev/null +++ b/tests/bot/rules/test_burst.py @@ -0,0 +1,69 @@ +import unittest + +from bot.rules import burst +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str) -> MockMessage: + """Init a MockMessage instance with author set to `author`. + + This serves as a shorthand / alias to keep the test cases visually clean. + """ + return MockMessage(author=author) + + +class BurstRuleTests(unittest.TestCase): + """Tests the `burst` antispam rule.""" + + def setUp(self): + self.config = {"max": 2, "interval": 10} + + @async_test + async def test_allows_messages_within_limit(self): + """Cases which do not violate the rule.""" + cases = ( + [make_msg("bob"), make_msg("bob")], + [make_msg("bob"), make_msg("alice"), make_msg("bob")], + ) + + 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 burst.apply(last_message, recent_messages, self.config)) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases where the amount of messages exceeds the limit, triggering the rule.""" + cases = ( + ( + [make_msg("bob"), make_msg("bob"), make_msg("bob")], + "bob", + 3, + ), + ( + [make_msg("bob"), make_msg("bob"), make_msg("alice"), make_msg("bob")], + "bob", + 3, + ), + ) + + for recent_messages, culprit, total_msgs in cases: + last_message = recent_messages[0] + relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) + expected_output = ( + f"sent {total_msgs} messages in {self.config['interval']}s", + (culprit,), + relevant_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + config=self.config, + expected_output=expected_output, + ): + self.assertTupleEqual( + await burst.apply(last_message, recent_messages, self.config), + expected_output, + ) -- cgit v1.2.3 From b0713be662fae40f58104923534b563b19e79f1d Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:20:47 +0100 Subject: Add unit test case for burst shared antispam rule --- tests/bot/rules/test_burst_shared.py | 65 ++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/bot/rules/test_burst_shared.py (limited to 'tests') diff --git a/tests/bot/rules/test_burst_shared.py b/tests/bot/rules/test_burst_shared.py new file mode 100644 index 000000000..1f5662eff --- /dev/null +++ b/tests/bot/rules/test_burst_shared.py @@ -0,0 +1,65 @@ +import unittest + +from bot.rules import burst_shared +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str) -> MockMessage: + """Init a MockMessage instance with the passed arg. + + This serves as a shorthand / alias to keep the test cases visually clean. + """ + return MockMessage(author=author) + + +class BurstSharedRuleTests(unittest.TestCase): + """Tests the `burst_shared` antispam rule.""" + + def setUp(self): + self.config = {"max": 2, "interval": 10} + + @async_test + async def test_allows_messages_within_limit(self): + """Cases that do not violate the rule. + + There really isn't more to test here than a single case. + """ + recent_messages = [make_msg("spongebob"), make_msg("patrick")] + last_message = recent_messages[0] + + self.assertIsNone(await burst_shared.apply(last_message, recent_messages, self.config)) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases where the amount of messages exceeds the limit, triggering the rule.""" + cases = ( + ( + [make_msg("bob"), make_msg("bob"), make_msg("bob")], + {"bob"}, + 3, + ), + ( + [make_msg("bob"), make_msg("bob"), make_msg("alice"), make_msg("bob")], + {"bob", "alice"}, + 4, + ), + ) + + for recent_messages, culprits, total_msgs in cases: + last_message = recent_messages[0] + expected_output = ( + f"sent {total_msgs} messages in {self.config['interval']}s", + culprits, + recent_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + config=self.config, + expected_output=expected_output, + ): + self.assertTupleEqual( + await burst_shared.apply(last_message, recent_messages, self.config), + expected_output, + ) -- cgit v1.2.3 From f15f3184d6e0c2ad4db303528a1954d589d93900 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:21:08 +0100 Subject: Add unit test case for chars antispam rule --- tests/bot/rules/test_chars.py | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/bot/rules/test_chars.py (limited to 'tests') diff --git a/tests/bot/rules/test_chars.py b/tests/bot/rules/test_chars.py new file mode 100644 index 000000000..f466a898e --- /dev/null +++ b/tests/bot/rules/test_chars.py @@ -0,0 +1,75 @@ +import unittest + +from bot.rules import chars +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str, n_chars: int) -> MockMessage: + """Build a message with arbitrary content of `n_chars` length.""" + return MockMessage(author=author, content="A" * n_chars) + + +class CharsRuleTests(unittest.TestCase): + """Tests the `chars` antispam rule.""" + + def setUp(self): + self.config = { + "max": 20, # Max allowed sum of chars per user + "interval": 10, + } + + @async_test + async def test_allows_messages_within_limit(self): + """Cases with a total amount of chars within limit.""" + cases = ( + [make_msg("bob", 0)], + [make_msg("bob", 20)], + [make_msg("bob", 15), make_msg("alice", 15)], + ) + + 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 chars.apply(last_message, recent_messages, self.config)) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases where the total amount of chars exceeds the limit, triggering the rule.""" + cases = ( + ( + [make_msg("bob", 21)], + "bob", + 21, + ), + ( + [make_msg("bob", 15), make_msg("bob", 15)], + "bob", + 30, + ), + ( + [make_msg("alice", 15), make_msg("bob", 20), make_msg("alice", 15)], + "alice", + 30, + ), + ) + + for recent_messages, culprit, total_chars in cases: + last_message = recent_messages[0] + relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) + expected_output = ( + f"sent {total_chars} characters in {self.config['interval']}s", + (culprit,), + relevant_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + config=self.config, + expected_output=expected_output, + ): + self.assertTupleEqual( + await chars.apply(last_message, recent_messages, self.config), + expected_output, + ) -- cgit v1.2.3 From 4bfe30def137921a4320208b66472dc97c5bd298 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:21:28 +0100 Subject: Add unit test case for discord emojis antispam rule --- tests/bot/rules/test_discord_emojis.py | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/bot/rules/test_discord_emojis.py (limited to 'tests') diff --git a/tests/bot/rules/test_discord_emojis.py b/tests/bot/rules/test_discord_emojis.py new file mode 100644 index 000000000..1c56c9563 --- /dev/null +++ b/tests/bot/rules/test_discord_emojis.py @@ -0,0 +1,68 @@ +import unittest + +from bot.rules import discord_emojis +from tests.helpers import MockMessage, async_test + +discord_emoji = "<:abcd:1234>" # Discord emojis follow the format <:name:id> + + +def make_msg(author: str, n_emojis: int) -> MockMessage: + """Build a MockMessage instance with content containing `n_emojis` arbitrary emojis.""" + return MockMessage(author=author, content=discord_emoji * n_emojis) + + +class DiscordEmojisRuleTests(unittest.TestCase): + """Tests for the `discord_emojis` antispam rule.""" + + def setUp(self): + self.config = {"max": 2, "interval": 10} + + @async_test + async def test_allows_messages_within_limit(self): + """Cases with a total amount of discord emojis within limit.""" + cases = ( + [make_msg("bob", 2)], + [make_msg("alice", 1), make_msg("bob", 2), make_msg("alice", 1)], + ) + + 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 discord_emojis.apply(last_message, recent_messages, self.config)) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases with more than the allowed amount of discord emojis.""" + cases = ( + ( + [make_msg("bob", 3)], + "bob", + 3, + ), + ( + [make_msg("alice", 2), make_msg("bob", 2), make_msg("alice", 2)], + "alice", + 4, + ), + ) + + for recent_messages, culprit, total_emojis in cases: + last_message = recent_messages[0] + relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) + expected_output = ( + f"sent {total_emojis} emojis in {self.config['interval']}s", + (culprit,), + relevant_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + config=self.config, + expected_output=expected_output, + ): + self.assertTupleEqual( + await discord_emojis.apply(last_message, recent_messages, self.config), + expected_output, + ) -- cgit v1.2.3 From f32ffaffd92b0521adec42432121aefb3d596b0e Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:21:42 +0100 Subject: Add unit test case for role mentions antispam rule --- tests/bot/rules/test_role_mentions.py | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/bot/rules/test_role_mentions.py (limited to 'tests') diff --git a/tests/bot/rules/test_role_mentions.py b/tests/bot/rules/test_role_mentions.py new file mode 100644 index 000000000..6377ffbc8 --- /dev/null +++ b/tests/bot/rules/test_role_mentions.py @@ -0,0 +1,66 @@ +import unittest + +from bot.rules import role_mentions +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str, n_mentions: int) -> MockMessage: + """Build a MockMessage instance with `n_mentions` role mentions.""" + return MockMessage(author=author, role_mentions=[None] * n_mentions) + + +class RoleMentionsRuleTests(unittest.TestCase): + """Tests for the `role_mentions` antispam rule.""" + + def setUp(self): + self.config = {"max": 2, "interval": 10} + + @async_test + async def test_allows_messages_within_limit(self): + """Cases with a total amount of role mentions within limit.""" + cases = ( + [make_msg("bob", 2)], + [make_msg("bob", 1), make_msg("alice", 1), make_msg("bob", 1)], + ) + + 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 role_mentions.apply(last_message, recent_messages, self.config)) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases with more than the allowed amount of role mentions.""" + cases = ( + ( + [make_msg("bob", 3)], + "bob", + 3, + ), + ( + [make_msg("alice", 2), make_msg("bob", 2), make_msg("alice", 2)], + "alice", + 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 == culprit) + expected_output = ( + f"sent {total_mentions} role mentions in {self.config['interval']}s", + (culprit,), + relevant_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + config=self.config, + expected_output=expected_output, + ): + self.assertTupleEqual( + await role_mentions.apply(last_message, recent_messages, self.config), + expected_output, + ) -- cgit v1.2.3 From 4199b12da2dedd8720cf521a75a821811af59cfd Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:30:12 +0100 Subject: Fix incorrect config key in attachments antispam rule The rule was incorrectly printing out the maximum amount of allowed attachments instead of the configured interval. This commit also adjusts the rule's unit test case. --- bot/rules/attachments.py | 2 +- tests/bot/rules/test_attachments.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/bot/rules/attachments.py b/bot/rules/attachments.py index 00bb2a949..8903c385c 100644 --- a/bot/rules/attachments.py +++ b/bot/rules/attachments.py @@ -19,7 +19,7 @@ async def apply( if total_recent_attachments > config['max']: return ( - f"sent {total_recent_attachments} attachments in {config['max']}s", + f"sent {total_recent_attachments} attachments in {config['interval']}s", (last_message.author,), relevant_messages ) diff --git a/tests/bot/rules/test_attachments.py b/tests/bot/rules/test_attachments.py index d7187f315..0af5ff0dc 100644 --- a/tests/bot/rules/test_attachments.py +++ b/tests/bot/rules/test_attachments.py @@ -20,7 +20,7 @@ class AttachmentRuleTests(unittest.TestCase): """Tests applying the `attachments` antispam rule.""" def setUp(self): - self.config = {"max": 5} + self.config = {"max": 5, "interval": 10} @async_test async def test_allows_messages_without_too_many_attachments(self): @@ -88,7 +88,7 @@ class AttachmentRuleTests(unittest.TestCase): config=self.config ): desired_output = ( - f"sent {total_attachments} attachments in {self.config['max']}s", + f"sent {total_attachments} attachments in {self.config['interval']}s", culprit, relevant_messages ) -- cgit v1.2.3 From aedbd6f697d6c66b3dbefa894795c86c6d0a2628 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 26 Jan 2020 22:34:52 +0100 Subject: Refactor msg helper function name to make_msg The name msg is less descriptive and creates a needless name conflict in local gen exp. --- tests/bot/rules/test_attachments.py | 16 ++++++++-------- tests/bot/rules/test_links.py | 18 +++++++++--------- tests/bot/rules/test_mentions.py | 16 ++++++++-------- 3 files changed, 25 insertions(+), 25 deletions(-) (limited to 'tests') diff --git a/tests/bot/rules/test_attachments.py b/tests/bot/rules/test_attachments.py index 0af5ff0dc..419336417 100644 --- a/tests/bot/rules/test_attachments.py +++ b/tests/bot/rules/test_attachments.py @@ -11,7 +11,7 @@ class Case(NamedTuple): total_attachments: int -def msg(author: str, total_attachments: int) -> MockMessage: +def make_msg(author: str, total_attachments: int) -> MockMessage: """Builds a message with `total_attachments` attachments.""" return MockMessage(author=author, attachments=list(range(total_attachments))) @@ -26,9 +26,9 @@ class AttachmentRuleTests(unittest.TestCase): async def test_allows_messages_without_too_many_attachments(self): """Messages without too many attachments are allowed as-is.""" cases = ( - [msg("bob", 0), msg("bob", 0), msg("bob", 0)], - [msg("bob", 2), msg("bob", 2)], - [msg("bob", 2), msg("alice", 2), msg("bob", 2)], + [make_msg("bob", 0), make_msg("bob", 0), make_msg("bob", 0)], + [make_msg("bob", 2), make_msg("bob", 2)], + [make_msg("bob", 2), make_msg("alice", 2), make_msg("bob", 2)], ) for recent_messages in cases: @@ -48,22 +48,22 @@ class AttachmentRuleTests(unittest.TestCase): """Messages with too many attachments trigger the rule.""" cases = ( Case( - [msg("bob", 4), msg("bob", 0), msg("bob", 6)], + [make_msg("bob", 4), make_msg("bob", 0), make_msg("bob", 6)], ("bob",), 10 ), Case( - [msg("bob", 4), msg("alice", 6), msg("bob", 2)], + [make_msg("bob", 4), make_msg("alice", 6), make_msg("bob", 2)], ("bob",), 6 ), Case( - [msg("alice", 6)], + [make_msg("alice", 6)], ("alice",), 6 ), ( - [msg("alice", 1) for _ in range(6)], + [make_msg("alice", 1) for _ in range(6)], ("alice",), 6 ), diff --git a/tests/bot/rules/test_links.py b/tests/bot/rules/test_links.py index 02a5d5501..b77e01c84 100644 --- a/tests/bot/rules/test_links.py +++ b/tests/bot/rules/test_links.py @@ -11,7 +11,7 @@ class Case(NamedTuple): total_links: int -def msg(author: str, total_links: int) -> MockMessage: +def make_msg(author: str, total_links: int) -> MockMessage: """Makes a message with `total_links` links.""" content = " ".join(["https://pydis.com"] * total_links) return MockMessage(author=author, content=content) @@ -30,11 +30,11 @@ class LinksTests(unittest.TestCase): async def test_links_within_limit(self): """Messages with an allowed amount of links.""" cases = ( - [msg("bob", 0)], - [msg("bob", 2)], - [msg("bob", 3)], # Filter only applies if len(messages_with_links) > 1 - [msg("bob", 1), msg("bob", 1)], - [msg("bob", 2), msg("alice", 2)] # Only messages from latest author count + [make_msg("bob", 0)], + [make_msg("bob", 2)], + [make_msg("bob", 3)], # Filter only applies if len(messages_with_links) > 1 + [make_msg("bob", 1), make_msg("bob", 1)], + [make_msg("bob", 2), make_msg("alice", 2)] # Only messages from latest author count ) for recent_messages in cases: @@ -54,17 +54,17 @@ class LinksTests(unittest.TestCase): """Messages with a a higher than allowed amount of links.""" cases = ( Case( - [msg("bob", 1), msg("bob", 2)], + [make_msg("bob", 1), make_msg("bob", 2)], ("bob",), 3 ), Case( - [msg("alice", 1), msg("alice", 1), msg("alice", 1)], + [make_msg("alice", 1), make_msg("alice", 1), make_msg("alice", 1)], ("alice",), 3 ), Case( - [msg("alice", 2), msg("bob", 3), msg("alice", 1)], + [make_msg("alice", 2), make_msg("bob", 3), make_msg("alice", 1)], ("alice",), 3 ) diff --git a/tests/bot/rules/test_mentions.py b/tests/bot/rules/test_mentions.py index ad49ead32..43211f097 100644 --- a/tests/bot/rules/test_mentions.py +++ b/tests/bot/rules/test_mentions.py @@ -11,7 +11,7 @@ class Case(NamedTuple): total_mentions: int -def msg(author: str, total_mentions: int) -> MockMessage: +def make_msg(author: str, total_mentions: int) -> MockMessage: """Makes a message with `total_mentions` mentions.""" return MockMessage(author=author, mentions=list(range(total_mentions))) @@ -29,10 +29,10 @@ class TestMentions(unittest.TestCase): 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)] + [make_msg("bob", 0)], + [make_msg("bob", 2)], + [make_msg("bob", 1), make_msg("bob", 1)], + [make_msg("bob", 1), make_msg("alice", 2)] ) for recent_messages in cases: @@ -52,17 +52,17 @@ class TestMentions(unittest.TestCase): """Messages with a higher than allowed amount of mentions.""" cases = ( Case( - [msg("bob", 3)], + [make_msg("bob", 3)], ("bob",), 3 ), Case( - [msg("alice", 2), msg("alice", 0), msg("alice", 1)], + [make_msg("alice", 2), make_msg("alice", 0), make_msg("alice", 1)], ("alice",), 3 ), Case( - [msg("bob", 2), msg("alice", 3), msg("bob", 2)], + [make_msg("bob", 2), make_msg("alice", 3), make_msg("bob", 2)], ("bob",), 4 ) -- cgit v1.2.3 From 8456331140bdd6bdc1d2c8bf395b69febef45ad8 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Fri, 31 Jan 2020 20:14:43 +0100 Subject: Adjust multi-line docstrings to prevailing style --- tests/bot/rules/test_burst.py | 3 ++- tests/bot/rules/test_burst_shared.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/bot/rules/test_burst.py b/tests/bot/rules/test_burst.py index 4da0cc78e..afcc5554d 100644 --- a/tests/bot/rules/test_burst.py +++ b/tests/bot/rules/test_burst.py @@ -5,7 +5,8 @@ from tests.helpers import MockMessage, async_test def make_msg(author: str) -> MockMessage: - """Init a MockMessage instance with author set to `author`. + """ + Init a MockMessage instance with author set to `author`. This serves as a shorthand / alias to keep the test cases visually clean. """ diff --git a/tests/bot/rules/test_burst_shared.py b/tests/bot/rules/test_burst_shared.py index 1f5662eff..401e0b666 100644 --- a/tests/bot/rules/test_burst_shared.py +++ b/tests/bot/rules/test_burst_shared.py @@ -5,7 +5,8 @@ from tests.helpers import MockMessage, async_test def make_msg(author: str) -> MockMessage: - """Init a MockMessage instance with the passed arg. + """ + Init a MockMessage instance with the passed arg. This serves as a shorthand / alias to keep the test cases visually clean. """ @@ -20,7 +21,8 @@ class BurstSharedRuleTests(unittest.TestCase): @async_test async def test_allows_messages_within_limit(self): - """Cases that do not violate the rule. + """ + Cases that do not violate the rule. There really isn't more to test here than a single case. """ -- cgit v1.2.3 From 93d19f378e206135286e376377e7fc8f5bdcc7a2 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 2 Feb 2020 18:25:07 +0100 Subject: Implement RuleTest ABC This will serve as an ABC for tests for individual rules. The base class provides runners for allowed and disallowed cases, and the children classes then only provide the cases and implementations of helper methods specific to each rule. --- tests/bot/rules/__init__.py | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'tests') diff --git a/tests/bot/rules/__init__.py b/tests/bot/rules/__init__.py index e69de29bb..d7cd7b66b 100644 --- a/tests/bot/rules/__init__.py +++ b/tests/bot/rules/__init__.py @@ -0,0 +1,76 @@ +import unittest +from abc import abstractmethod +from typing import Callable, Dict, Iterable, List, NamedTuple, Tuple + +from tests.helpers import MockMessage + + +class DisallowedCase(NamedTuple): + """Encapsulation for test cases expected to fail.""" + recent_messages: List[MockMessage] + culprits: Iterable[str] + n_violations: int + + +class RuleTest(unittest.TestCase): + """ + Abstract class for antispam rule test cases. + + Tests for specific rules should inherit from `RuleTest` and implement + `relevant_messages` and `get_report`. Each instance should also set the + `apply` and `config` attributes as necessary. + + The execution of test cases can then be delegated to the `run_allowed` + and `run_disallowed` methods. + """ + + apply: Callable # The tested rule's apply function + config: Dict[str, int] + + async def run_allowed(self, cases: Tuple[List[MockMessage], ...]) -> None: + """Run all `cases` against `self.apply` expecting them to pass.""" + 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 self.apply(last_message, recent_messages, self.config) + ) + + async def run_disallowed(self, cases: Tuple[DisallowedCase, ...]) -> None: + """Run all `cases` against `self.apply` expecting them to fail.""" + for case in cases: + recent_messages, culprits, n_violations = case + last_message = recent_messages[0] + relevant_messages = self.relevant_messages(case) + desired_output = ( + self.get_report(case), + culprits, + relevant_messages, + ) + + with self.subTest( + last_message=last_message, + recent_messages=recent_messages, + relevant_messages=relevant_messages, + n_violations=n_violations, + config=self.config, + ): + self.assertTupleEqual( + await self.apply(last_message, recent_messages, self.config), + desired_output, + ) + + @abstractmethod + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + """Give expected relevant messages for `case`.""" + raise NotImplementedError + + @abstractmethod + def get_report(self, case: DisallowedCase) -> str: + """Give expected error report for `case`.""" + raise NotImplementedError -- cgit v1.2.3 From b89f9c55329aa44448d55963e22ce4f7a6ec0ff6 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 2 Feb 2020 18:31:50 +0100 Subject: Adjust existing tests to inherit from RuleTest ABC --- tests/bot/rules/test_attachments.py | 79 +++++++++++----------------------- tests/bot/rules/test_burst.py | 44 +++++++------------ tests/bot/rules/test_burst_shared.py | 40 +++++++---------- tests/bot/rules/test_chars.py | 53 ++++++++++------------- tests/bot/rules/test_discord_emojis.py | 44 +++++++------------ tests/bot/rules/test_links.py | 66 ++++++++-------------------- tests/bot/rules/test_mentions.py | 76 +++++++++++--------------------- tests/bot/rules/test_role_mentions.py | 49 +++++++++------------ 8 files changed, 157 insertions(+), 294 deletions(-) (limited to 'tests') diff --git a/tests/bot/rules/test_attachments.py b/tests/bot/rules/test_attachments.py index 419336417..e54b4b5b8 100644 --- a/tests/bot/rules/test_attachments.py +++ b/tests/bot/rules/test_attachments.py @@ -1,25 +1,20 @@ -import unittest -from typing import List, NamedTuple, Tuple +from typing import Iterable from bot.rules import attachments +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test -class Case(NamedTuple): - recent_messages: List[MockMessage] - culprit: Tuple[str] - total_attachments: int - - def make_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): +class AttachmentRuleTests(RuleTest): """Tests applying the `attachments` antispam rule.""" def setUp(self): + self.apply = attachments.apply self.config = {"max": 5, "interval": 10} @async_test @@ -31,68 +26,46 @@ class AttachmentRuleTests(unittest.TestCase): [make_msg("bob", 2), make_msg("alice", 2), make_msg("bob", 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 attachments.apply(last_message, recent_messages, self.config) - ) + await self.run_allowed(cases) @async_test async def test_disallows_messages_with_too_many_attachments(self): """Messages with too many attachments trigger the rule.""" cases = ( - Case( + DisallowedCase( [make_msg("bob", 4), make_msg("bob", 0), make_msg("bob", 6)], ("bob",), - 10 + 10, ), - Case( + DisallowedCase( [make_msg("bob", 4), make_msg("alice", 6), make_msg("bob", 2)], ("bob",), - 6 + 6, ), - Case( + DisallowedCase( [make_msg("alice", 6)], ("alice",), - 6 + 6, ), - ( + DisallowedCase( [make_msg("alice", 1) for _ in range(6)], ("alice",), - 6 + 6, ), ) - 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 - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.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['interval']}s", - culprit, - relevant_messages - ) - self.assertTupleEqual( - await attachments.apply(last_message, recent_messages, self.config), - desired_output - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} attachments in {self.config['interval']}s" diff --git a/tests/bot/rules/test_burst.py b/tests/bot/rules/test_burst.py index afcc5554d..72f0be0c7 100644 --- a/tests/bot/rules/test_burst.py +++ b/tests/bot/rules/test_burst.py @@ -1,6 +1,7 @@ -import unittest +from typing import Iterable from bot.rules import burst +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test @@ -13,10 +14,11 @@ def make_msg(author: str) -> MockMessage: return MockMessage(author=author) -class BurstRuleTests(unittest.TestCase): +class BurstRuleTests(RuleTest): """Tests the `burst` antispam rule.""" def setUp(self): + self.apply = burst.apply self.config = {"max": 2, "interval": 10} @async_test @@ -27,44 +29,28 @@ class BurstRuleTests(unittest.TestCase): [make_msg("bob"), make_msg("alice"), make_msg("bob")], ) - 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 burst.apply(last_message, recent_messages, self.config)) + await self.run_allowed(cases) @async_test async def test_disallows_messages_beyond_limit(self): """Cases where the amount of messages exceeds the limit, triggering the rule.""" cases = ( - ( + DisallowedCase( [make_msg("bob"), make_msg("bob"), make_msg("bob")], - "bob", + ("bob",), 3, ), - ( + DisallowedCase( [make_msg("bob"), make_msg("bob"), make_msg("alice"), make_msg("bob")], - "bob", + ("bob",), 3, ), ) - for recent_messages, culprit, total_msgs in cases: - last_message = recent_messages[0] - relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) - expected_output = ( - f"sent {total_msgs} messages in {self.config['interval']}s", - (culprit,), - relevant_messages, - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + return tuple(msg for msg in case.recent_messages if msg.author in case.culprits) - with self.subTest( - last_message=last_message, - recent_messages=recent_messages, - config=self.config, - expected_output=expected_output, - ): - self.assertTupleEqual( - await burst.apply(last_message, recent_messages, self.config), - expected_output, - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} messages in {self.config['interval']}s" diff --git a/tests/bot/rules/test_burst_shared.py b/tests/bot/rules/test_burst_shared.py index 401e0b666..47367a5f8 100644 --- a/tests/bot/rules/test_burst_shared.py +++ b/tests/bot/rules/test_burst_shared.py @@ -1,6 +1,7 @@ -import unittest +from typing import Iterable from bot.rules import burst_shared +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test @@ -13,10 +14,11 @@ def make_msg(author: str) -> MockMessage: return MockMessage(author=author) -class BurstSharedRuleTests(unittest.TestCase): +class BurstSharedRuleTests(RuleTest): """Tests the `burst_shared` antispam rule.""" def setUp(self): + self.apply = burst_shared.apply self.config = {"max": 2, "interval": 10} @async_test @@ -26,42 +28,32 @@ class BurstSharedRuleTests(unittest.TestCase): There really isn't more to test here than a single case. """ - recent_messages = [make_msg("spongebob"), make_msg("patrick")] - last_message = recent_messages[0] + cases = ( + [make_msg("spongebob"), make_msg("patrick")], + ) - self.assertIsNone(await burst_shared.apply(last_message, recent_messages, self.config)) + await self.run_allowed(cases) @async_test async def test_disallows_messages_beyond_limit(self): """Cases where the amount of messages exceeds the limit, triggering the rule.""" cases = ( - ( + DisallowedCase( [make_msg("bob"), make_msg("bob"), make_msg("bob")], {"bob"}, 3, ), - ( + DisallowedCase( [make_msg("bob"), make_msg("bob"), make_msg("alice"), make_msg("bob")], {"bob", "alice"}, 4, ), ) - for recent_messages, culprits, total_msgs in cases: - last_message = recent_messages[0] - expected_output = ( - f"sent {total_msgs} messages in {self.config['interval']}s", - culprits, - recent_messages, - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + return case.recent_messages - with self.subTest( - last_message=last_message, - recent_messages=recent_messages, - config=self.config, - expected_output=expected_output, - ): - self.assertTupleEqual( - await burst_shared.apply(last_message, recent_messages, self.config), - expected_output, - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} messages in {self.config['interval']}s" diff --git a/tests/bot/rules/test_chars.py b/tests/bot/rules/test_chars.py index f466a898e..7cc36f49e 100644 --- a/tests/bot/rules/test_chars.py +++ b/tests/bot/rules/test_chars.py @@ -1,6 +1,7 @@ -import unittest +from typing import Iterable from bot.rules import chars +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test @@ -9,10 +10,11 @@ def make_msg(author: str, n_chars: int) -> MockMessage: return MockMessage(author=author, content="A" * n_chars) -class CharsRuleTests(unittest.TestCase): +class CharsRuleTests(RuleTest): """Tests the `chars` antispam rule.""" def setUp(self): + self.apply = chars.apply self.config = { "max": 20, # Max allowed sum of chars per user "interval": 10, @@ -27,49 +29,38 @@ class CharsRuleTests(unittest.TestCase): [make_msg("bob", 15), make_msg("alice", 15)], ) - 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 chars.apply(last_message, recent_messages, self.config)) + await self.run_allowed(cases) @async_test async def test_disallows_messages_beyond_limit(self): """Cases where the total amount of chars exceeds the limit, triggering the rule.""" cases = ( - ( + DisallowedCase( [make_msg("bob", 21)], - "bob", + ("bob",), 21, ), - ( + DisallowedCase( [make_msg("bob", 15), make_msg("bob", 15)], - "bob", + ("bob",), 30, ), - ( + DisallowedCase( [make_msg("alice", 15), make_msg("bob", 20), make_msg("alice", 15)], - "alice", + ("alice",), 30, ), ) - for recent_messages, culprit, total_chars in cases: - last_message = recent_messages[0] - relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) - expected_output = ( - f"sent {total_chars} characters in {self.config['interval']}s", - (culprit,), - relevant_messages, - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.recent_messages + if msg.author == last_message.author + ) - with self.subTest( - last_message=last_message, - recent_messages=recent_messages, - config=self.config, - expected_output=expected_output, - ): - self.assertTupleEqual( - await chars.apply(last_message, recent_messages, self.config), - expected_output, - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} characters in {self.config['interval']}s" diff --git a/tests/bot/rules/test_discord_emojis.py b/tests/bot/rules/test_discord_emojis.py index 1c56c9563..0239b0b00 100644 --- a/tests/bot/rules/test_discord_emojis.py +++ b/tests/bot/rules/test_discord_emojis.py @@ -1,6 +1,7 @@ -import unittest +from typing import Iterable from bot.rules import discord_emojis +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test discord_emoji = "<:abcd:1234>" # Discord emojis follow the format <:name:id> @@ -11,10 +12,11 @@ def make_msg(author: str, n_emojis: int) -> MockMessage: return MockMessage(author=author, content=discord_emoji * n_emojis) -class DiscordEmojisRuleTests(unittest.TestCase): +class DiscordEmojisRuleTests(RuleTest): """Tests for the `discord_emojis` antispam rule.""" def setUp(self): + self.apply = discord_emojis.apply self.config = {"max": 2, "interval": 10} @async_test @@ -25,44 +27,28 @@ class DiscordEmojisRuleTests(unittest.TestCase): [make_msg("alice", 1), make_msg("bob", 2), make_msg("alice", 1)], ) - 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 discord_emojis.apply(last_message, recent_messages, self.config)) + await self.run_allowed(cases) @async_test async def test_disallows_messages_beyond_limit(self): """Cases with more than the allowed amount of discord emojis.""" cases = ( - ( + DisallowedCase( [make_msg("bob", 3)], - "bob", + ("bob",), 3, ), - ( + DisallowedCase( [make_msg("alice", 2), make_msg("bob", 2), make_msg("alice", 2)], - "alice", + ("alice",), 4, ), ) - for recent_messages, culprit, total_emojis in cases: - last_message = recent_messages[0] - relevant_messages = tuple(msg for msg in recent_messages if msg.author == culprit) - expected_output = ( - f"sent {total_emojis} emojis in {self.config['interval']}s", - (culprit,), - relevant_messages, - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + return tuple(msg for msg in case.recent_messages if msg.author in case.culprits) - with self.subTest( - last_message=last_message, - recent_messages=recent_messages, - config=self.config, - expected_output=expected_output, - ): - self.assertTupleEqual( - await discord_emojis.apply(last_message, recent_messages, self.config), - expected_output, - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} emojis in {self.config['interval']}s" diff --git a/tests/bot/rules/test_links.py b/tests/bot/rules/test_links.py index b77e01c84..3c3f90e5f 100644 --- a/tests/bot/rules/test_links.py +++ b/tests/bot/rules/test_links.py @@ -1,26 +1,21 @@ -import unittest -from typing import List, NamedTuple, Tuple +from typing import Iterable from bot.rules import links +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test -class Case(NamedTuple): - recent_messages: List[MockMessage] - culprit: Tuple[str] - total_links: int - - def make_msg(author: str, total_links: int) -> MockMessage: """Makes a message with `total_links` links.""" content = " ".join(["https://pydis.com"] * total_links) return MockMessage(author=author, content=content) -class LinksTests(unittest.TestCase): +class LinksTests(RuleTest): """Tests applying the `links` rule.""" def setUp(self): + self.apply = links.apply self.config = { "max": 2, "interval": 10 @@ -37,61 +32,38 @@ class LinksTests(unittest.TestCase): [make_msg("bob", 2), make_msg("alice", 2)] # Only messages from latest author count ) - 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 links.apply(last_message, recent_messages, self.config) - ) + await self.run_allowed(cases) @async_test async def test_links_exceeding_limit(self): """Messages with a a higher than allowed amount of links.""" cases = ( - Case( + DisallowedCase( [make_msg("bob", 1), make_msg("bob", 2)], ("bob",), 3 ), - Case( + DisallowedCase( [make_msg("alice", 1), make_msg("alice", 1), make_msg("alice", 1)], ("alice",), 3 ), - Case( + DisallowedCase( [make_msg("alice", 2), make_msg("bob", 3), make_msg("alice", 1)], ("alice",), 3 ) ) - 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 - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.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_links=total_links, - config=self.config - ): - desired_output = ( - f"sent {total_links} links in {self.config['interval']}s", - culprit, - relevant_messages - ) - self.assertTupleEqual( - await links.apply(last_message, recent_messages, self.config), - desired_output - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} links in {self.config['interval']}s" diff --git a/tests/bot/rules/test_mentions.py b/tests/bot/rules/test_mentions.py index 43211f097..ebcdabac6 100644 --- a/tests/bot/rules/test_mentions.py +++ b/tests/bot/rules/test_mentions.py @@ -1,28 +1,23 @@ -import unittest -from typing import List, NamedTuple, Tuple +from typing import Iterable from bot.rules import mentions +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test -class Case(NamedTuple): - recent_messages: List[MockMessage] - culprit: Tuple[str] - total_mentions: int - - def make_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): +class TestMentions(RuleTest): """Tests applying the `mentions` antispam rule.""" def setUp(self): + self.apply = mentions.apply self.config = { "max": 2, - "interval": 10 + "interval": 10, } @async_test @@ -32,64 +27,41 @@ class TestMentions(unittest.TestCase): [make_msg("bob", 0)], [make_msg("bob", 2)], [make_msg("bob", 1), make_msg("bob", 1)], - [make_msg("bob", 1), make_msg("alice", 2)] + [make_msg("bob", 1), make_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) - ) + await self.run_allowed(cases) @async_test async def test_mentions_exceeding_limit(self): """Messages with a higher than allowed amount of mentions.""" cases = ( - Case( + DisallowedCase( [make_msg("bob", 3)], ("bob",), - 3 + 3, ), - Case( + DisallowedCase( [make_msg("alice", 2), make_msg("alice", 0), make_msg("alice", 1)], ("alice",), - 3 + 3, ), - Case( + DisallowedCase( [make_msg("bob", 2), make_msg("alice", 3), make_msg("bob", 2)], ("bob",), - 4 + 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 - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.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 - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} mentions in {self.config['interval']}s" diff --git a/tests/bot/rules/test_role_mentions.py b/tests/bot/rules/test_role_mentions.py index 6377ffbc8..b339cccf7 100644 --- a/tests/bot/rules/test_role_mentions.py +++ b/tests/bot/rules/test_role_mentions.py @@ -1,6 +1,7 @@ -import unittest +from typing import Iterable from bot.rules import role_mentions +from tests.bot.rules import DisallowedCase, RuleTest from tests.helpers import MockMessage, async_test @@ -9,10 +10,11 @@ def make_msg(author: str, n_mentions: int) -> MockMessage: return MockMessage(author=author, role_mentions=[None] * n_mentions) -class RoleMentionsRuleTests(unittest.TestCase): +class RoleMentionsRuleTests(RuleTest): """Tests for the `role_mentions` antispam rule.""" def setUp(self): + self.apply = role_mentions.apply self.config = {"max": 2, "interval": 10} @async_test @@ -23,44 +25,33 @@ class RoleMentionsRuleTests(unittest.TestCase): [make_msg("bob", 1), make_msg("alice", 1), make_msg("bob", 1)], ) - 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 role_mentions.apply(last_message, recent_messages, self.config)) + await self.run_allowed(cases) @async_test async def test_disallows_messages_beyond_limit(self): """Cases with more than the allowed amount of role mentions.""" cases = ( - ( + DisallowedCase( [make_msg("bob", 3)], - "bob", + ("bob",), 3, ), - ( + DisallowedCase( [make_msg("alice", 2), make_msg("bob", 2), make_msg("alice", 2)], - "alice", + ("alice",), 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 == culprit) - expected_output = ( - f"sent {total_mentions} role mentions in {self.config['interval']}s", - (culprit,), - relevant_messages, - ) + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.recent_messages + if msg.author == last_message.author + ) - with self.subTest( - last_message=last_message, - recent_messages=recent_messages, - config=self.config, - expected_output=expected_output, - ): - self.assertTupleEqual( - await role_mentions.apply(last_message, recent_messages, self.config), - expected_output, - ) + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} role mentions in {self.config['interval']}s" -- cgit v1.2.3 From 9758e32beba0f88cdb1ee80c2e4bc5539013740e Mon Sep 17 00:00:00 2001 From: kwzrd Date: Sun, 2 Feb 2020 18:36:36 +0100 Subject: Make RuleTest use ABCMeta This will prevent child classes to be instantiated unless they implement all abstract methods, leading to a more descriptive error message. --- tests/bot/rules/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/bot/rules/__init__.py b/tests/bot/rules/__init__.py index d7cd7b66b..36c986fe1 100644 --- a/tests/bot/rules/__init__.py +++ b/tests/bot/rules/__init__.py @@ -1,5 +1,5 @@ import unittest -from abc import abstractmethod +from abc import ABCMeta, abstractmethod from typing import Callable, Dict, Iterable, List, NamedTuple, Tuple from tests.helpers import MockMessage @@ -12,7 +12,7 @@ class DisallowedCase(NamedTuple): n_violations: int -class RuleTest(unittest.TestCase): +class RuleTest(unittest.TestCase, metaclass=ABCMeta): """ Abstract class for antispam rule test cases. -- cgit v1.2.3 From 54e8c98f6ceb4572df5c73719649624613adeda6 Mon Sep 17 00:00:00 2001 From: kwzrd Date: Tue, 4 Feb 2020 21:49:46 +0100 Subject: Add unit test for duplicates antispam rule --- tests/bot/rules/test_duplicates.py | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/bot/rules/test_duplicates.py (limited to 'tests') diff --git a/tests/bot/rules/test_duplicates.py b/tests/bot/rules/test_duplicates.py new file mode 100644 index 000000000..59e0fb6ef --- /dev/null +++ b/tests/bot/rules/test_duplicates.py @@ -0,0 +1,66 @@ +from typing import Iterable + +from bot.rules import duplicates +from tests.bot.rules import DisallowedCase, RuleTest +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str, content: str) -> MockMessage: + """Give a MockMessage instance with `author` and `content` attrs.""" + return MockMessage(author=author, content=content) + + +class DuplicatesRuleTests(RuleTest): + """Tests the `duplicates` antispam rule.""" + + def setUp(self): + self.apply = duplicates.apply + self.config = {"max": 2, "interval": 10} + + @async_test + async def test_allows_messages_within_limit(self): + """Cases which do not violate the rule.""" + cases = ( + [make_msg("alice", "A"), make_msg("alice", "A")], + [make_msg("alice", "A"), make_msg("alice", "B"), make_msg("alice", "C")], # Non-duplicate + [make_msg("alice", "A"), make_msg("bob", "A"), make_msg("alice", "A")], # Different author + ) + + await self.run_allowed(cases) + + @async_test + async def test_disallows_messages_beyond_limit(self): + """Cases with too many duplicate messages from the same author.""" + cases = ( + DisallowedCase( + [make_msg("alice", "A"), make_msg("alice", "A"), make_msg("alice", "A")], + ("alice",), + 3, + ), + DisallowedCase( + [make_msg("bob", "A"), make_msg("alice", "A"), make_msg("bob", "A"), make_msg("bob", "A")], + ("bob",), + 3, # 4 duplicate messages, but only 3 from bob + ), + DisallowedCase( + [make_msg("bob", "A"), make_msg("bob", "B"), make_msg("bob", "A"), make_msg("bob", "A")], + ("bob",), + 3, # 4 message from bob, but only 3 duplicates + ), + ) + + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_message = case.recent_messages[0] + return tuple( + msg + for msg in case.recent_messages + if ( + msg.author == last_message.author + and msg.content == last_message.content + ) + ) + + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} duplicated messages in {self.config['interval']}s" -- cgit v1.2.3 From cae7f25a735516d92af34353715ed23604b1d71d Mon Sep 17 00:00:00 2001 From: kwzrd Date: Tue, 4 Feb 2020 21:51:25 +0100 Subject: Add unit test for newlines antispam rule --- tests/bot/rules/test_newlines.py | 105 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/bot/rules/test_newlines.py (limited to 'tests') diff --git a/tests/bot/rules/test_newlines.py b/tests/bot/rules/test_newlines.py new file mode 100644 index 000000000..d61c4609d --- /dev/null +++ b/tests/bot/rules/test_newlines.py @@ -0,0 +1,105 @@ +from typing import Iterable, List + +from bot.rules import newlines +from tests.bot.rules import DisallowedCase, RuleTest +from tests.helpers import MockMessage, async_test + + +def make_msg(author: str, newline_groups: List[int]) -> MockMessage: + """Init a MockMessage instance with `author` and content configured by `newline_groups". + + Configure content by passing a list of ints, where each int `n` will generate + a separate group of `n` newlines. + + Example: + newline_groups=[3, 1, 2] -> content="\n\n\n \n \n\n" + """ + content = " ".join("\n" * n for n in newline_groups) + return MockMessage(author=author, content=content) + + +class TotalNewlinesRuleTests(RuleTest): + """Tests the `newlines` antispam rule against allowed cases and total newline count violations.""" + + def setUp(self): + self.apply = newlines.apply + self.config = { + "max": 5, # Max sum of newlines in relevant messages + "max_consecutive": 3, # Max newlines in one group, in one message + "interval": 10, + } + + @async_test + async def test_allows_messages_within_limit(self): + """Cases which do not violate the rule.""" + cases = ( + [make_msg("alice", [])], # Single message with no newlines + [make_msg("alice", [1, 2]), make_msg("alice", [1, 1])], # 5 newlines in 2 messages + [make_msg("alice", [2, 2, 1]), make_msg("bob", [2, 3])], # 5 newlines from each author + [make_msg("bob", [1]), make_msg("alice", [5])], # Alice breaks the rule, but only bob is relevant + ) + + await self.run_allowed(cases) + + @async_test + async def test_disallows_messages_total(self): + """Cases which violate the rule by having too many newlines in total.""" + cases = ( + DisallowedCase( # Alice sends a total of 6 newlines (disallowed) + [make_msg("alice", [2, 2]), make_msg("alice", [2])], + ("alice",), + 6, + ), + DisallowedCase( # Here we test that only alice's newlines count in the sum + [make_msg("alice", [2, 2]), make_msg("bob", [3]), make_msg("alice", [3])], + ("alice",), + 7, + ), + ) + + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_author = case.recent_messages[0].author + return tuple(msg for msg in case.recent_messages if msg.author == last_author) + + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} newlines in {self.config['interval']}s" + + +class GroupNewlinesRuleTests(RuleTest): + """ + Tests the `newlines` antispam rule against max consecutive newline violations. + + As these violations yield a different error report, they require a different + `get_report` implementation. + """ + + def setUp(self): + self.apply = newlines.apply + self.config = {"max": 5, "max_consecutive": 3, "interval": 10} + + @async_test + async def test_disallows_messages_consecutive(self): + """Cases which violate the rule due to having too many consecutive newlines.""" + cases = ( + DisallowedCase( # Bob sends a group of newlines too large + [make_msg("bob", [4])], + ("bob",), + 4, + ), + DisallowedCase( # Alice sends 5 in total (allowed), but 4 in one group (disallowed) + [make_msg("alice", [1]), make_msg("alice", [4])], + ("alice",), + 4, + ), + ) + + await self.run_disallowed(cases) + + def relevant_messages(self, case: DisallowedCase) -> Iterable[MockMessage]: + last_author = case.recent_messages[0].author + return tuple(msg for msg in case.recent_messages if msg.author == last_author) + + def get_report(self, case: DisallowedCase) -> str: + return f"sent {case.n_violations} consecutive newlines in {self.config['interval']}s" -- cgit v1.2.3 From aa12d72c7ad6ad54f82f113da1e916cee1903eaf Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Fri, 21 Feb 2020 17:22:23 +0000 Subject: Remove tests for custom bot log --- tests/bot/test_api.py | 64 ++------------------------------------------------- 1 file changed, 2 insertions(+), 62 deletions(-) (limited to 'tests') diff --git a/tests/bot/test_api.py b/tests/bot/test_api.py index 5a88adc5c..bdfcc73e4 100644 --- a/tests/bot/test_api.py +++ b/tests/bot/test_api.py @@ -1,9 +1,7 @@ -import logging import unittest -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from bot import api -from tests.base import LoggingTestCase from tests.helpers import async_test @@ -34,7 +32,7 @@ class APIClientTests(unittest.TestCase): self.assertEqual(error.response_text, "") self.assertIs(error.response, self.error_api_response) - def test_responde_code_error_string_representation_default_initialization(self): + def test_response_code_error_string_representation_default_initialization(self): """Test the string representation of `ResponseCodeError` initialized without text or json.""" error = api.ResponseCodeError(response=self.error_api_response) self.assertEqual(str(error), f"Status: {self.error_api_response.status} Response: ") @@ -76,61 +74,3 @@ class APIClientTests(unittest.TestCase): response_text=text_data ) self.assertEqual(str(error), f"Status: {self.error_api_response.status} Response: {text_data}") - - -class LoggingHandlerTests(LoggingTestCase): - """Tests the bot's API Log Handler.""" - - @classmethod - def setUpClass(cls): - cls.debug_log_record = logging.LogRecord( - name='my.logger', level=logging.DEBUG, - pathname='my/logger.py', lineno=666, - msg="Lemon wins", args=(), - exc_info=None - ) - - cls.trace_log_record = logging.LogRecord( - name='my.logger', level=logging.TRACE, - pathname='my/logger.py', lineno=666, - msg="This will not be logged", args=(), - exc_info=None - ) - - def setUp(self): - self.log_handler = api.APILoggingHandler(None) - - def test_emit_appends_to_queue_with_stopped_event_loop(self): - """Test if `APILoggingHandler.emit` appends to queue when the event loop is not running.""" - with patch("bot.api.APILoggingHandler.ship_off") as ship_off: - # Patch `ship_off` to ease testing against the return value of this coroutine. - ship_off.return_value = 42 - self.log_handler.emit(self.debug_log_record) - - self.assertListEqual(self.log_handler.queue, [42]) - - def test_emit_ignores_less_than_debug(self): - """`APILoggingHandler.emit` should not queue logs with a log level lower than DEBUG.""" - self.log_handler.emit(self.trace_log_record) - self.assertListEqual(self.log_handler.queue, []) - - def test_schedule_queued_tasks_for_empty_queue(self): - """`APILoggingHandler` should not schedule anything when the queue is empty.""" - with self.assertNotLogs(level=logging.DEBUG): - self.log_handler.schedule_queued_tasks() - - def test_schedule_queued_tasks_for_nonempty_queue(self): - """`APILoggingHandler` should schedule logs when the queue is not empty.""" - log = logging.getLogger("bot.api") - - with self.assertLogs(logger=log, level=logging.DEBUG) as logs, patch('asyncio.create_task') as create_task: - self.log_handler.queue = [555] - self.log_handler.schedule_queued_tasks() - self.assertListEqual(self.log_handler.queue, []) - create_task.assert_called_once_with(555) - - [record] = logs.records - self.assertEqual(record.message, "Scheduled 1 pending logging tasks.") - self.assertEqual(record.levelno, logging.DEBUG) - self.assertEqual(record.name, 'bot.api') - self.assertIn('via_handler', record.__dict__) -- cgit v1.2.3