aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/bot/cogs/test_duck_pond.py200
1 files changed, 128 insertions, 72 deletions
diff --git a/tests/bot/cogs/test_duck_pond.py b/tests/bot/cogs/test_duck_pond.py
index 8f0c4f068..b801e86f1 100644
--- a/tests/bot/cogs/test_duck_pond.py
+++ b/tests/bot/cogs/test_duck_pond.py
@@ -72,8 +72,7 @@ class DuckPondTests(base.LoggingTestCase):
self.assertEqual(len(log_watcher.records), 1)
- [record] = log_watcher.records
- self.assertEqual(record.message, f"Failed to fetch webhook with id `{self.cog.webhook_id}`")
+ record = log_watcher.records[0]
self.assertEqual(record.levelno, logging.ERROR)
def test_is_staff_returns_correct_values_based_on_instance_passed(self):
@@ -99,15 +98,15 @@ class DuckPondTests(base.LoggingTestCase):
(
"No green check mark reactions",
helpers.MockMessage(reactions=[
- helpers.MockReaction(emoji=self.unicode_duck_emoji),
- helpers.MockReaction(emoji=self.thumbs_up_emoji)
+ helpers.MockReaction(emoji=self.unicode_duck_emoji, users=[self.bot.user]),
+ helpers.MockReaction(emoji=self.thumbs_up_emoji, users=[self.bot.user])
]),
False
),
(
"Green check mark reaction, but not from our bot",
helpers.MockMessage(reactions=[
- helpers.MockReaction(emoji=self.unicode_duck_emoji),
+ helpers.MockReaction(emoji=self.unicode_duck_emoji, users=[self.bot.user]),
helpers.MockReaction(emoji=self.checkmark_emoji, users=[self.staff_member])
]),
False
@@ -115,7 +114,7 @@ class DuckPondTests(base.LoggingTestCase):
(
"Green check mark reaction, with one from the bot",
helpers.MockMessage(reactions=[
- helpers.MockReaction(emoji=self.unicode_duck_emoji),
+ helpers.MockReaction(emoji=self.unicode_duck_emoji, users=[self.bot.user]),
helpers.MockReaction(emoji=self.checkmark_emoji, users=[self.staff_member, self.bot.user])
]),
True
@@ -160,8 +159,7 @@ class DuckPondTests(base.LoggingTestCase):
self.assertEqual(len(log_watcher.records), 1)
- [record] = log_watcher.records
- self.assertEqual(record.message, "Failed to send a message to the Duck Pool webhook")
+ record = log_watcher.records[0]
self.assertEqual(record.levelno, logging.ERROR)
def _get_reaction(
@@ -250,10 +248,12 @@ class DuckPondTests(base.LoggingTestCase):
# A staffer with multiple duck reactions only counts once
(
"Two different duck reactions from the same staffer",
- helpers.MockMessage(reactions=[
- helpers.MockReaction(emoji=self.duck_pond_emoji, users=[self.staff_member]),
- helpers.MockReaction(emoji=self.unicode_duck_emoji, users=[self.staff_member]),
- ]),
+ helpers.MockMessage(
+ reactions=[
+ helpers.MockReaction(emoji=self.duck_pond_emoji, users=[self.staff_member]),
+ helpers.MockReaction(emoji=self.unicode_duck_emoji, users=[self.staff_member]),
+ ]
+ ),
1
),
# A non-string emoji does not count (to test the `isinstance(reaction.emoji, str)` elif)
@@ -265,10 +265,12 @@ class DuckPondTests(base.LoggingTestCase):
# We correctly sum when multiple reactions are provided.
(
"Duckpond Duck Reaction from 3 staffers + 2 non-staffers",
- helpers.MockMessage(reactions=[
- self._get_reaction(emoji=self.duck_pond_emoji, staff=3, nonstaff=2),
- self._get_reaction(emoji=self.unicode_duck_emoji, staff=4, nonstaff=9),
- ]),
+ helpers.MockMessage(
+ reactions=[
+ self._get_reaction(emoji=self.duck_pond_emoji, staff=3, nonstaff=2),
+ self._get_reaction(emoji=self.unicode_duck_emoji, staff=4, nonstaff=9),
+ ]
+ ),
3 + 4
),
)
@@ -279,8 +281,8 @@ class DuckPondTests(base.LoggingTestCase):
self.assertEqual(expected_count, actual_count)
@helpers.async_test
- async def test_relay_message_to_duck_pond_correctly_relays_content_and_attachments(self):
- """The `relay_message_to_duck_pond` method should correctly relay message content and attachments."""
+ async def test_relay_message_correctly_relays_content_and_attachments(self):
+ """The `relay_message` method should correctly relay message content and attachments."""
send_webhook_path = f"{MODULE_PATH}.DuckPond.send_webhook"
send_attachments_path = f"{MODULE_PATH}.send_attachments"
@@ -297,41 +299,47 @@ class DuckPondTests(base.LoggingTestCase):
with patch(send_webhook_path, new_callable=helpers.AsyncMock) as send_webhook:
with patch(send_attachments_path, new_callable=helpers.AsyncMock) as send_attachments:
with self.subTest(clean_content=message.clean_content, attachments=message.attachments):
- await self.cog.relay_message_to_duck_pond(message)
+ await self.cog.relay_message(message)
self.assertEqual(expect_webhook_call, send_webhook.called)
self.assertEqual(expect_attachment_call, send_attachments.called)
message.add_reaction.assert_called_once_with(self.checkmark_emoji)
- message.reset_mock()
- @patch(f"{MODULE_PATH}.DuckPond.send_webhook", new_callable=helpers.AsyncMock)
@patch(f"{MODULE_PATH}.send_attachments", new_callable=helpers.AsyncMock)
@helpers.async_test
- async def test_relay_message_to_duck_pond_handles_send_attachments_exceptions(self, send_attachments, send_webhook):
- """The `relay_message_to_duck_pond` method should handle exceptions when calling `send_attachment`."""
+ async def test_relay_message_handles_irretrievable_attachment_exceptions(self, send_attachments):
+ """The `relay_message` method should handle irretrievable attachments."""
message = helpers.MockMessage(clean_content="message", attachments=["attachment"])
side_effects = (discord.errors.Forbidden(MagicMock(), ""), discord.errors.NotFound(MagicMock(), ""))
self.cog.webhook = helpers.MockAsyncWebhook()
log = logging.getLogger("bot.cogs.duck_pond")
- # Subtests for the first `except` block
for side_effect in side_effects:
send_attachments.side_effect = side_effect
- with self.subTest(side_effect=type(side_effect).__name__):
- with self.assertNotLogs(logger=log, level=logging.ERROR):
- await self.cog.relay_message_to_duck_pond(message)
+ with patch(f"{MODULE_PATH}.DuckPond.send_webhook", new_callable=helpers.AsyncMock) as send_webhook:
+ with self.subTest(side_effect=type(side_effect).__name__):
+ with self.assertNotLogs(logger=log, level=logging.ERROR):
+ await self.cog.relay_message(message)
+
+ self.assertEqual(send_webhook.call_count, 2)
+
+ @patch(f"{MODULE_PATH}.DuckPond.send_webhook", new_callable=helpers.AsyncMock)
+ @patch(f"{MODULE_PATH}.send_attachments", new_callable=helpers.AsyncMock)
+ @helpers.async_test
+ async def test_relay_message_handles_attachment_http_error(self, send_attachments, send_webhook):
+ """The `relay_message` method should handle irretrievable attachments."""
+ message = helpers.MockMessage(clean_content="message", attachments=["attachment"])
- self.assertEqual(send_webhook.call_count, 2)
- send_webhook.reset_mock()
+ self.cog.webhook = helpers.MockAsyncWebhook()
+ log = logging.getLogger("bot.cogs.duck_pond")
- # Subtests for the second `except` block
side_effect = discord.HTTPException(MagicMock(), "")
send_attachments.side_effect = side_effect
with self.subTest(side_effect=type(side_effect).__name__):
with self.assertLogs(logger=log, level=logging.ERROR) as log_watcher:
- await self.cog.relay_message_to_duck_pond(message)
+ await self.cog.relay_message(message)
send_webhook.assert_called_once_with(
content=message.clean_content,
@@ -341,10 +349,75 @@ class DuckPondTests(base.LoggingTestCase):
self.assertEqual(len(log_watcher.records), 1)
- [record] = log_watcher.records
- self.assertEqual(record.message, "Failed to send an attachment to the webhook")
+ record = log_watcher.records[0]
self.assertEqual(record.levelno, logging.ERROR)
+ def _mock_payload(self, label: str, is_custom_emoji: bool, id_: int, emoji_name: str):
+ """Creates a mock `on_raw_reaction_add` payload with the specified emoji data."""
+ payload = MagicMock(name=label)
+ payload.emoji.is_custom_emoji.return_value = is_custom_emoji
+ payload.emoji.id = id_
+ payload.emoji.name = emoji_name
+ return payload
+
+ @helpers.async_test
+ async def test_payload_has_duckpond_emoji_correctly_detects_relevant_emojis(self):
+ """The `on_raw_reaction_add` event handler should ignore irrelevant emojis."""
+ test_values = (
+ # Custom Emojis
+ (
+ self._mock_payload(
+ label="Custom Duckpond Emoji",
+ is_custom_emoji=True,
+ id_=constants.DuckPond.custom_emojis[0],
+ emoji_name=""
+ ),
+ True
+ ),
+ (
+ self._mock_payload(
+ label="Custom Non-Duckpond Emoji",
+ is_custom_emoji=True,
+ id_=123,
+ emoji_name=""
+ ),
+ False
+ ),
+ # Unicode Emojis
+ (
+ self._mock_payload(
+ label="Unicode Duck Emoji",
+ is_custom_emoji=False,
+ id_=1,
+ emoji_name=self.unicode_duck_emoji
+ ),
+ True
+ ),
+ (
+ self._mock_payload(
+ label="Unicode Non-Duck Emoji",
+ is_custom_emoji=False,
+ id_=1,
+ emoji_name=self.thumbs_up_emoji
+ ),
+ False
+ ),
+ )
+
+ for payload, expected_return in test_values:
+ actual_return = self.cog._payload_has_duckpond_emoji(payload)
+ with self.subTest(case=payload._mock_name, expected_return=expected_return, actual_return=actual_return):
+ self.assertEqual(expected_return, actual_return)
+
+ @patch(f"{MODULE_PATH}.discord.utils.get")
+ @patch(f"{MODULE_PATH}.DuckPond._payload_has_duckpond_emoji", new=MagicMock(return_value=False))
+ def test_on_raw_reaction_add_returns_early_with_payload_without_duck_emoji(self, utils_get):
+ """The `on_raw_reaction_add` method should return early if the payload does not contain a duck emoji."""
+ self.assertIsNone(asyncio.run(self.cog.on_raw_reaction_add(payload=MagicMock())))
+
+ # Ensure we've returned before making an unnecessary API call in the lines of code after the emoji check
+ utils_get.assert_not_called()
+
def _raw_reaction_mocks(self, channel_id, message_id, user_id):
"""Sets up mocks for tests of the `on_raw_reaction_add` event listener."""
channel = helpers.MockTextChannel(id=channel_id)
@@ -362,22 +435,6 @@ class DuckPondTests(base.LoggingTestCase):
return channel, message, member, payload
@helpers.async_test
- async def test_on_raw_reaction_add_returns_for_non_relevant_emojis(self):
- """The `on_raw_reaction_add` event handler should ignore irrelevant emojis."""
- payload_custom_emoji = MagicMock(label="Non-Duck Custom Emoji")
- payload_custom_emoji.emoji.is_custom_emoji.return_value = True
- payload_custom_emoji.emoji.id = 12345
-
- payload_unicode_emoji = MagicMock(label="Non-Duck Unicode Emoji")
- payload_unicode_emoji.emoji.is_custom_emoji.return_value = False
- payload_unicode_emoji.emoji.name = self.thumbs_up_emoji
-
- for payload in (payload_custom_emoji, payload_unicode_emoji):
- with self.subTest(case=payload.label), patch(f"{MODULE_PATH}.discord.utils.get") as discord_utils_get:
- self.assertIsNone(await self.cog.on_raw_reaction_add(payload))
- discord_utils_get.assert_not_called()
-
- @helpers.async_test
async def test_on_raw_reaction_add_returns_for_bot_and_non_staff_members(self):
"""The `on_raw_reaction_add` event handler should return for bot users or non-staff members."""
channel_id = 1234
@@ -428,10 +485,8 @@ class DuckPondTests(base.LoggingTestCase):
# Assert that we've made it past `self.is_staff`
is_staff.assert_called_once()
- @patch(f"{MODULE_PATH}.DuckPond.relay_message_to_duck_pond", new_callable=helpers.AsyncMock)
- @patch(f"{MODULE_PATH}.DuckPond.count_ducks", new_callable=helpers.AsyncMock)
@helpers.async_test
- async def test_on_raw_reaction_add_does_not_relay_below_duck_threshold(self, count_ducks, message_relay):
+ async def test_on_raw_reaction_add_does_not_relay_below_duck_threshold(self):
"""The `on_raw_reaction_add` listener should not relay messages or attachments below the duck threshold."""
test_cases = (
(constants.DuckPond.threshold - 1, False),
@@ -444,21 +499,21 @@ class DuckPondTests(base.LoggingTestCase):
payload.emoji = self.duck_pond_emoji
for duck_count, should_relay in test_cases:
- count_ducks.return_value = duck_count
- with self.subTest(duck_count=duck_count, should_relay=should_relay):
- await self.cog.on_raw_reaction_add(payload)
+ with patch(f"{MODULE_PATH}.DuckPond.relay_message", new_callable=helpers.AsyncMock) as relay_message:
+ with patch(f"{MODULE_PATH}.DuckPond.count_ducks", new_callable=helpers.AsyncMock) as count_ducks:
+ count_ducks.return_value = duck_count
+ with self.subTest(duck_count=duck_count, should_relay=should_relay):
+ await self.cog.on_raw_reaction_add(payload)
- # Confirm that we've made it past counting
- count_ducks.assert_called_once()
- count_ducks.reset_mock()
+ # Confirm that we've made it past counting
+ count_ducks.assert_called_once()
- # Did we relay a message?
- has_relayed = message_relay.called
- self.assertEqual(has_relayed, should_relay)
+ # Did we relay a message?
+ has_relayed = relay_message.called
+ self.assertEqual(has_relayed, should_relay)
- if should_relay:
- message_relay.assert_called_once_with(message)
- message_relay.reset_mock()
+ if should_relay:
+ relay_message.assert_called_once_with(message)
@helpers.async_test
async def test_on_raw_reaction_remove_prevents_removal_of_green_checkmark_depending_on_the_duck_count(self):
@@ -479,10 +534,10 @@ class DuckPondTests(base.LoggingTestCase):
(constants.DuckPond.threshold, True),
(constants.DuckPond.threshold + 1, True),
)
- for duck_count, should_readd_checkmark in test_cases:
+ for duck_count, should_re_add_checkmark in test_cases:
with patch(f"{MODULE_PATH}.DuckPond.count_ducks", new_callable=helpers.AsyncMock) as count_ducks:
count_ducks.return_value = duck_count
- with self.subTest(duck_count=duck_count, should_readd_checkmark=should_readd_checkmark):
+ with self.subTest(duck_count=duck_count, should_re_add_checkmark=should_re_add_checkmark):
await self.cog.on_raw_reaction_remove(payload)
# Check if we fetched the message
@@ -491,16 +546,15 @@ class DuckPondTests(base.LoggingTestCase):
# Check if we actually counted the number of ducks
count_ducks.assert_called_once_with(message)
- has_readded_checkmark = message.add_reaction.called
- self.assertEqual(should_readd_checkmark, has_readded_checkmark)
+ has_re_added_checkmark = message.add_reaction.called
+ self.assertEqual(should_re_add_checkmark, has_re_added_checkmark)
- if should_readd_checkmark:
+ if should_re_add_checkmark:
message.add_reaction.assert_called_once_with(self.checkmark_emoji)
message.add_reaction.reset_mock()
# reset mocks
channel.fetch_message.reset_mock()
- count_ducks.reset_mock()
message.reset_mock()
def test_on_raw_reaction_remove_ignores_removal_of_non_checkmark_reactions(self):
@@ -530,7 +584,9 @@ class DuckPondSetupTests(unittest.TestCase):
with self.assertLogs(logger=log, level=logging.INFO) as log_watcher:
duck_pond.setup(bot)
- line = log_watcher.output[0]
+
+ self.assertEqual(len(log_watcher.records), 1)
+ record = log_watcher.records[0]
+ self.assertEqual(record.levelno, logging.INFO)
bot.add_cog.assert_called_once()
- self.assertIn("Cog loaded: DuckPond", line)