From e66237395ab1470002f5dd61de9eeb19ca4600eb Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 14 Oct 2019 22:28:51 +0200 Subject: Make test_token_remover use our discord Mocks This commit replaces the standard MagicMocks by our specialized mocks for discord.py objects. It also adds the missing `channel` attribute to the `tests.helpers.MockMessage` mock and moves the file to the correct folder. --- tests/bot/cogs/test_token_remover.py | 135 ++++++++++++++++++++++++++++++++++ tests/cogs/test_token_remover.py | 139 ----------------------------------- tests/helpers.py | 1 + 3 files changed, 136 insertions(+), 139 deletions(-) create mode 100644 tests/bot/cogs/test_token_remover.py delete mode 100644 tests/cogs/test_token_remover.py (limited to 'tests') diff --git a/tests/bot/cogs/test_token_remover.py b/tests/bot/cogs/test_token_remover.py new file mode 100644 index 000000000..dfb1bafc9 --- /dev/null +++ b/tests/bot/cogs/test_token_remover.py @@ -0,0 +1,135 @@ +import asyncio +import logging +import unittest +from unittest.mock import MagicMock + +from discord import Colour + +from bot.cogs.token_remover import ( + DELETION_MESSAGE_TEMPLATE, + TokenRemover, + setup as setup_cog, +) +from bot.constants import Channels, Colours, Event, Icons +from tests.helpers import AsyncMock, MockBot, MockMessage + + +class TokenRemoverTests(unittest.TestCase): + """Tests the `TokenRemover` cog.""" + + def setUp(self): + """Adds the cog, a bot, and a message to the instance for usage in tests.""" + self.bot = MockBot() + self.bot.get_cog.return_value = MagicMock() + self.bot.get_cog.return_value.send_log_message = AsyncMock() + self.cog = TokenRemover(bot=self.bot) + + self.msg = MockMessage(message_id=555, content='') + self.msg.author.__str__ = MagicMock() + self.msg.author.__str__.return_value = 'lemon' + self.msg.author.bot = False + self.msg.author.avatar_url_as.return_value = 'picture-lemon.png' + self.msg.author.id = 42 + self.msg.author.mention = '@lemon' + self.msg.channel.mention = "#lemonade-stand" + + def test_is_valid_user_id_is_true_for_numeric_content(self): + """A string decoding to numeric characters is a valid user ID.""" + # MTIz = base64(123) + self.assertTrue(TokenRemover.is_valid_user_id('MTIz')) + + def test_is_valid_user_id_is_false_for_alphabetic_content(self): + """A string decoding to alphabetic characters is not a valid user ID.""" + # YWJj = base64(abc) + self.assertFalse(TokenRemover.is_valid_user_id('YWJj')) + + def test_is_valid_timestamp_is_true_for_valid_timestamps(self): + """A string decoding to a valid timestamp should be recognized as such.""" + self.assertTrue(TokenRemover.is_valid_timestamp('DN9r_A')) + + def test_is_valid_timestamp_is_false_for_invalid_values(self): + """A string not decoding to a valid timestamp should not be recognized as such.""" + # MTIz = base64(123) + self.assertFalse(TokenRemover.is_valid_timestamp('MTIz')) + + def test_mod_log_property(self): + """The `mod_log` property should ask the bot to return the `ModLog` cog.""" + self.bot.get_cog.return_value = 'lemon' + self.assertEqual(self.cog.mod_log, self.bot.get_cog.return_value) + self.bot.get_cog.assert_called_once_with('ModLog') + + def test_ignores_bot_messages(self): + """When the message event handler is called with a bot message, nothing is done.""" + self.msg.author.bot = True + coroutine = self.cog.on_message(self.msg) + self.assertIsNone(asyncio.run(coroutine)) + + def test_ignores_messages_without_tokens(self): + """Messages without anything looking like a token are ignored.""" + for content in ('', 'lemon wins'): + with self.subTest(content=content): + self.msg.content = content + coroutine = self.cog.on_message(self.msg) + self.assertIsNone(asyncio.run(coroutine)) + + def test_ignores_messages_with_invalid_tokens(self): + """Messages with values that are invalid tokens are ignored.""" + for content in ('foo.bar.baz', 'x.y.'): + with self.subTest(content=content): + self.msg.content = content + coroutine = self.cog.on_message(self.msg) + self.assertIsNone(asyncio.run(coroutine)) + + def test_censors_valid_tokens(self): + """Valid tokens are censored.""" + cases = ( + # (content, censored_token) + ('MTIz.DN9R_A.xyz', 'MTIz.DN9R_A.xxx'), + ) + + for content, censored_token in cases: + with self.subTest(content=content, censored_token=censored_token): + self.msg.content = content + coroutine = self.cog.on_message(self.msg) + with self.assertLogs(logger='bot.cogs.token_remover', level=logging.DEBUG) as cm: + self.assertIsNone(asyncio.run(coroutine)) # no return value + + [line] = cm.output + log_message = ( + "Censored a seemingly valid token sent by " + "lemon (`42`) in #lemonade-stand, " + f"token was `{censored_token}`" + ) + self.assertIn(log_message, line) + + self.msg.delete.assert_called_once_with() + self.msg.channel.send.assert_called_once_with( + DELETION_MESSAGE_TEMPLATE.format(mention='@lemon') + ) + self.bot.get_cog.assert_called_with('ModLog') + self.msg.author.avatar_url_as.assert_called_once_with(static_format='png') + + mod_log = self.bot.get_cog.return_value + mod_log.ignore.assert_called_once_with(Event.message_delete, self.msg.id) + mod_log.send_log_message.assert_called_once_with( + icon_url=Icons.token_removed, + colour=Colour(Colours.soft_red), + title="Token removed!", + text=log_message, + thumbnail='picture-lemon.png', + channel_id=Channels.mod_alerts + ) + + +class TokenRemoverSetupTests(unittest.TestCase): + """Tests setup of the `TokenRemover` cog.""" + + def test_setup(self): + """Setup of the cog should log a message at `INFO` level.""" + bot = MockBot() + with self.assertLogs(logger='bot.cogs.token_remover', level=logging.INFO) as cm: + setup_cog(bot) + + [line] = cm.output + bot.add_cog.assert_called_once() + self.assertIn("Cog loaded: TokenRemover", line) diff --git a/tests/cogs/test_token_remover.py b/tests/cogs/test_token_remover.py deleted file mode 100644 index 3738b6d1b..000000000 --- a/tests/cogs/test_token_remover.py +++ /dev/null @@ -1,139 +0,0 @@ -import asyncio -import logging -import unittest -from unittest.mock import MagicMock - -from discord import Colour - -from bot.cogs.token_remover import ( - DELETION_MESSAGE_TEMPLATE, - TokenRemover, - setup as setup_cog, -) -from bot.constants import Channels, Colours, Event, Icons -from tests.helpers import AsyncMock, MockBot - - -class TokenRemoverTests(unittest.TestCase): - """Tests the `TokenRemover` cog.""" - - def setUp(self): - """Adds the cog, a bot, and a message to the instance for usage in tests.""" - self.bot = MockBot() - self.bot.get_cog.return_value = MagicMock() - self.bot.get_cog.return_value.send_log_message = AsyncMock() - self.cog = TokenRemover(bot=self.bot) - - self.msg = MagicMock() - self.msg.author = MagicMock() - self.msg.author.__str__.return_value = 'lemon' - self.msg.author.bot = False - self.msg.author.avatar_url_as.return_value = 'picture-lemon.png' - self.msg.author.id = 42 - self.msg.author.mention = '@lemon' - self.msg.channel.send = AsyncMock() - self.msg.channel.mention = '#lemonade-stand' - self.msg.content = '' - self.msg.delete = AsyncMock() - self.msg.id = 555 - - def test_is_valid_user_id_is_true_for_numeric_content(self): - """A string decoding to numeric characters is a valid user ID.""" - # MTIz = base64(123) - self.assertTrue(TokenRemover.is_valid_user_id('MTIz')) - - def test_is_valid_user_id_is_false_for_alphabetic_content(self): - """A string decoding to alphabetic characters is not a valid user ID.""" - # YWJj = base64(abc) - self.assertFalse(TokenRemover.is_valid_user_id('YWJj')) - - def test_is_valid_timestamp_is_true_for_valid_timestamps(self): - """A string decoding to a valid timestamp should be recognized as such.""" - self.assertTrue(TokenRemover.is_valid_timestamp('DN9r_A')) - - def test_is_valid_timestamp_is_false_for_invalid_values(self): - """A string not decoding to a valid timestamp should not be recognized as such.""" - # MTIz = base64(123) - self.assertFalse(TokenRemover.is_valid_timestamp('MTIz')) - - def test_mod_log_property(self): - """The `mod_log` property should ask the bot to return the `ModLog` cog.""" - self.bot.get_cog.return_value = 'lemon' - self.assertEqual(self.cog.mod_log, self.bot.get_cog.return_value) - self.bot.get_cog.assert_called_once_with('ModLog') - - def test_ignores_bot_messages(self): - """When the message event handler is called with a bot message, nothing is done.""" - self.msg.author.bot = True - coroutine = self.cog.on_message(self.msg) - self.assertIsNone(asyncio.run(coroutine)) - - def test_ignores_messages_without_tokens(self): - """Messages without anything looking like a token are ignored.""" - for content in ('', 'lemon wins'): - with self.subTest(content=content): - self.msg.content = content - coroutine = self.cog.on_message(self.msg) - self.assertIsNone(asyncio.run(coroutine)) - - def test_ignores_messages_with_invalid_tokens(self): - """Messages with values that are invalid tokens are ignored.""" - for content in ('foo.bar.baz', 'x.y.'): - with self.subTest(content=content): - self.msg.content = content - coroutine = self.cog.on_message(self.msg) - self.assertIsNone(asyncio.run(coroutine)) - - def test_censors_valid_tokens(self): - """Valid tokens are censored.""" - cases = ( - # (content, censored_token) - ('MTIz.DN9R_A.xyz', 'MTIz.DN9R_A.xxx'), - ) - - for content, censored_token in cases: - with self.subTest(content=content, censored_token=censored_token): - self.msg.content = content - coroutine = self.cog.on_message(self.msg) - with self.assertLogs(logger='bot.cogs.token_remover', level=logging.DEBUG) as cm: - self.assertIsNone(asyncio.run(coroutine)) # no return value - - [line] = cm.output - log_message = ( - "Censored a seemingly valid token sent by " - "lemon (`42`) in #lemonade-stand, " - f"token was `{censored_token}`" - ) - self.assertIn(log_message, line) - - self.msg.delete.assert_called_once_with() - self.msg.channel.send.assert_called_once_with( - DELETION_MESSAGE_TEMPLATE.format(mention='@lemon') - ) - self.bot.get_cog.assert_called_with('ModLog') - self.msg.author.avatar_url_as.assert_called_once_with(static_format='png') - - mod_log = self.bot.get_cog.return_value - mod_log.ignore.assert_called_once_with(Event.message_delete, self.msg.id) - mod_log.send_log_message.assert_called_once_with( - icon_url=Icons.token_removed, - colour=Colour(Colours.soft_red), - title="Token removed!", - text=log_message, - thumbnail='picture-lemon.png', - channel_id=Channels.mod_alerts - ) - - -class TokenRemoverSetupTests(unittest.TestCase): - """Tests setup of the `TokenRemover` cog.""" - - def test_setup(self): - """Setup of the cog should log a message at `INFO` level.""" - bot = MockBot() - with self.assertLogs(logger='bot.cogs.token_remover', level=logging.INFO) as cm: - setup_cog(bot) - - [line] = cm.output - bot.add_cog.assert_called_once() - self.assertIn("Cog loaded: TokenRemover", line) diff --git a/tests/helpers.py b/tests/helpers.py index f8e8357f1..892d42e6c 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -415,6 +415,7 @@ class MockMessage(AttributeMock, unittest.mock.MagicMock): def __init__(self, **kwargs) -> None: super().__init__(spec=message_instance, **kwargs) self.author = MockMember() + self.channel = MockTextChannel() # `discord.Message` coroutines self.ack = AsyncMock() -- cgit v1.2.3