async def test_continue_eval_does_not_continue(self): ctx = MockContext(message=MockMessage(clear_reactions=AsyncMock())) self.bot.wait_for.side_effect = asyncio.TimeoutError actual = await self.cog.continue_eval(ctx, MockMessage()) self.assertEqual(actual, None) ctx.message.clear_reactions.assert_called_once()
async def test_continue_eval_does_continue(self, partial_mock): """Test that the continue_eval function does continue if required conditions are met.""" ctx = MockContext(message=MockMessage(add_reaction=AsyncMock(), clear_reactions=AsyncMock())) response = MockMessage(delete=AsyncMock()) new_msg = MockMessage() self.bot.wait_for.side_effect = ((None, new_msg), None) expected = "NewCode" self.cog.get_code = create_autospec(self.cog.get_code, spec_set=True, return_value=expected) actual = await self.cog.continue_eval(ctx, response) self.cog.get_code.assert_awaited_once_with(new_msg) self.assertEqual(actual, expected) self.bot.wait_for.assert_has_awaits( (call('message_edit', check=partial_mock(snekbox.predicate_eval_message_edit, ctx), timeout=10), call('reaction_add', check=partial_mock(snekbox.predicate_eval_emoji_reaction, ctx), timeout=10))) ctx.message.add_reaction.assert_called_once_with(snekbox.REEVAL_EMOJI) ctx.message.clear_reactions.assert_called_once() response.delete.assert_called_once()
def test_predicate_eval_emoji_reaction(self): """Test the predicate_eval_emoji_reaction function.""" valid_reaction = MockReaction(message=MockMessage(id=1)) valid_reaction.__str__.return_value = snekbox.REEVAL_EMOJI valid_ctx = MockContext(message=MockMessage(id=1), author=MockUser(id=2)) valid_user = MockUser(id=2) invalid_reaction_id = MockReaction(message=MockMessage(id=42)) invalid_reaction_id.__str__.return_value = snekbox.REEVAL_EMOJI invalid_user_id = MockUser(id=42) invalid_reaction_str = MockReaction(message=MockMessage(id=1)) invalid_reaction_str.__str__.return_value = ':longbeard:' cases = ((invalid_reaction_id, valid_user, False, 'invalid reaction ID'), (valid_reaction, invalid_user_id, False, 'invalid user ID'), (invalid_reaction_str, valid_user, False, 'invalid reaction __str__'), (valid_reaction, valid_user, True, 'matching attributes')) for reaction, user, expected, testname in cases: with self.subTest( msg=f'Test with {testname} and expected return {expected}' ): actual = snekbox.predicate_eval_emoji_reaction( valid_ctx, reaction, user) self.assertEqual(actual, expected)
async def test_on_message_ignores_dms_bots(self, find_token_in_message): """Shouldn't parse a message if it is a DM or authored by a bot.""" cog = TokenRemover(self.bot) dm_msg = MockMessage(guild=None) bot_msg = MockMessage(author=MagicMock(bot=True)) for msg in (dm_msg, bot_msg): await cog.on_message(msg) find_token_in_message.assert_not_called()
def test_update_replaces_old_element(self): """Test if an update replaced the old message with the same ID.""" cache = MessageCache(maxlen=5) message = MockMessage(id=1234) cache.append(message) message = MockMessage(id=1234) cache.update(message) self.assertIs(cache.get_message(1234), message) self.assertEqual(len(cache), 1)
def test_append_adds_in_the_right_order(self): """Test if two appends are added in the same order if newest_first is False, or in reverse order otherwise.""" messages = [MockMessage(), MockMessage()] cache = MessageCache(maxlen=10, newest_first=False) for msg in messages: cache.append(msg) self.assertListEqual(messages, list(cache)) cache = MessageCache(maxlen=10, newest_first=True) for msg in messages: cache.append(msg) self.assertListEqual(messages[::-1], list(cache))
async def test_on_message_non_incident(self): """Messages not qualifying as incidents are ignored.""" with patch("bot.exts.moderation.incidents.add_signals", AsyncMock()) as mock_add_signals: await self.cog_instance.on_message(MockMessage()) mock_add_signals.assert_not_called()
async def test_on_raw_reaction_add_valid_event_is_processed(self): """ If the reaction event is valid, it is passed to `process_event`. This is the case when everything goes right: * The reaction was placed in #incidents, and not by a bot * The message was found successfully * The message qualifies as an incident Additionally, we check that all arguments were passed as expected. """ incident = MockMessage(id=1) self.cog_instance.process_event = AsyncMock() self.cog_instance.resolve_message = AsyncMock(return_value=incident) with patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)): await self.cog_instance.on_raw_reaction_add(self.payload) self.cog_instance.process_event.assert_called_with( "reaction", # Defined in `self.payload` incident, self.payload.member, )
async def test_process_event_bad_role(self): """The reaction is removed when the author lacks all allowed roles.""" incident = MockMessage() member = MockMember(roles=[MockRole(id=0)]) # Must have role 1 or 2 await self.cog_instance.process_event("reaction", incident, member) incident.remove_reaction.assert_called_once_with("reaction", member)
def test_make_confirmation_task_check(self): """ The internal check will recognize the passed incident. This is a little tricky - we first pass a message with a specific `id` in, and then retrieve the built check from the `call_args` of the `wait_for` method. This relies on the check being passed as a kwarg. Once the check is retrieved, we assert that it gives True for our incident's `id`, and False for any other. If this function begins to fail, first check that `created_check` is being retrieved correctly. It should be the function that is built locally in the tested method. """ self.cog_instance.make_confirmation_task(MockMessage(id=123)) self.cog_instance.bot.wait_for.assert_called_once() created_check = self.cog_instance.bot.wait_for.call_args.kwargs[ "check"] # The `message_id` matches the `id` of our incident self.assertTrue(created_check(payload=MagicMock(message_id=123))) # This `message_id` does not match self.assertFalse(created_check(payload=MagicMock(message_id=0)))
async def test_archive_relays_incident(self): """ If webhook is found, method relays `incident` properly. This test will assert that the fetched webhook's `send` method is fed the correct arguments, and that the `archive` method returns True. """ webhook = MockAsyncWebhook() self.cog_instance.bot.fetch_webhook = AsyncMock( return_value=webhook) # Patch in our webhook # Define our own `incident` to be archived incident = MockMessage( content="this is an incident", author=MockUser(name="author_name", display_avatar=Mock(url="author_avatar")), id=123, ) built_embed = MagicMock(discord.Embed, id=123) # We patch `make_embed` to return this with patch("bot.exts.moderation.incidents.make_embed", AsyncMock(return_value=(built_embed, None))): archive_return = await self.cog_instance.archive( incident, MagicMock(value="A"), MockMember()) # Now we check that the webhook was given the correct args, and that `archive` returned True webhook.send.assert_called_once_with( embed=built_embed, username="******", avatar_url="author_avatar", file=None, ) self.assertTrue(archive_return)
def make_msg(author: str, total_user_mentions: int, total_bot_mentions: int = 0) -> MockMessage: """Makes a message with `total_mentions` mentions.""" user_mentions = [MockMember() for _ in range(total_user_mentions)] bot_mentions = [MockMember(bot=True) for _ in range(total_bot_mentions)] return MockMessage(author=author, mentions=user_mentions + bot_mentions)
def test_predicate_eval_message_edit(self): """Test the predicate_eval_message_edit function.""" msg0 = MockMessage(id=1, content='abc') msg1 = MockMessage(id=2, content='abcdef') msg2 = MockMessage(id=1, content='abcdef') cases = ( (msg0, msg0, False, 'same ID, same content'), (msg0, msg1, False, 'different ID, different content'), (msg0, msg2, True, 'same ID, different content') ) for ctx_msg, new_msg, expected, testname in cases: with self.subTest(msg=f'Messages with {testname} return {expected}'): ctx = MockContext(message=ctx_msg) actual = snekbox.predicate_eval_message_edit(ctx, ctx_msg, new_msg) self.assertEqual(actual, expected)
async def test_get_code(self): """Should return 1st arg (or None) if eval cmd in message, otherwise return full content.""" prefix = constants.Bot.prefix subtests = ( (self.cog.eval_command, f"{prefix}{self.cog.eval_command.name} print(1)", "print(1)"), (self.cog.eval_command, f"{prefix}{self.cog.eval_command.name}", None), (MagicMock(spec=commands.Command), f"{prefix}tags get foo"), (None, "print(123)") ) for command, content, *expected_code in subtests: if not expected_code: expected_code = content else: [expected_code] = expected_code with self.subTest(content=content, expected_code=expected_code): self.bot.get_context.reset_mock() self.bot.get_context.return_value = MockContext(command=command) message = MockMessage(content=content) actual_code = await self.cog.get_code(message) self.bot.get_context.assert_awaited_once_with(message) self.assertEqual(actual_code, expected_code)
async def test_on_message_edit_uses_on_message(self): """The edit listener should delegate handling of the message to the normal listener.""" self.cog.on_message = mock.create_autospec(self.cog.on_message, spec_set=True) await self.cog.on_message_edit(MockMessage(), self.msg) self.cog.on_message.assert_awaited_once_with(self.msg)
async def test_send_eval_with_non_zero_eval(self): """Test the send_eval function with a code returning a non-zero code.""" ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() ctx.author.mention = '@LemonLemonishBeard#0042' self.cog.post_eval = AsyncMock(return_value={ 'stdout': 'ERROR', 'returncode': 127 }) self.cog.get_results_message = MagicMock( return_value=('Return code 127', 'Beard got stuck in the eval')) self.cog.get_status_emoji = MagicMock(return_value=':nope!:') self.cog.format_output = AsyncMock() # This function isn't called await self.cog.send_eval(ctx, 'MyAwesomeCode') ctx.send.assert_called_once_with( '@LemonLemonishBeard#0042 :nope!: Return code 127.\n\n```py\nBeard got stuck in the eval\n```' ) self.cog.post_eval.assert_called_once_with('MyAwesomeCode') self.cog.get_status_emoji.assert_called_once_with({ 'stdout': 'ERROR', 'returncode': 127 }) self.cog.get_results_message.assert_called_once_with({ 'stdout': 'ERROR', 'returncode': 127 }) self.cog.format_output.assert_not_called()
async def test_send_eval_with_paste_link(self): """Test the send_eval function with a too long output that generate a paste link.""" ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() ctx.author.mention = '@LemonLemonishBeard#0042' self.cog.post_eval = AsyncMock(return_value={'stdout': 'Way too long beard', 'returncode': 0}) self.cog.get_results_message = MagicMock(return_value=('Return code 0', '')) self.cog.get_status_emoji = MagicMock(return_value=':yay!:') self.cog.format_output = AsyncMock(return_value=('Way too long beard', 'lookatmybeard.com')) mocked_filter_cog = MagicMock() mocked_filter_cog.filter_eval = AsyncMock(return_value=False) self.bot.get_cog.return_value = mocked_filter_cog await self.cog.send_eval(ctx, 'MyAwesomeCode') ctx.send.assert_called_once_with( '@LemonLemonishBeard#0042 :yay!: Return code 0.' '\n\n```\nWay too long beard\n```\nFull output: lookatmybeard.com' ) self.cog.post_eval.assert_called_once_with('MyAwesomeCode') self.cog.get_status_emoji.assert_called_once_with({'stdout': 'Way too long beard', 'returncode': 0}) self.cog.get_results_message.assert_called_once_with({'stdout': 'Way too long beard', 'returncode': 0}) self.cog.format_output.assert_called_once_with('Way too long beard')
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)
async def test_send_eval(self): """Test the send_eval function.""" ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() ctx.author.mention = '@LemonLemonishBeard#0042' self.cog.post_eval = AsyncMock(return_value={ 'stdout': '', 'returncode': 0 }) self.cog.get_results_message = MagicMock(return_value=('Return code 0', '')) self.cog.get_status_emoji = MagicMock(return_value=':yay!:') self.cog.format_output = AsyncMock(return_value=('[No output]', None)) await self.cog.send_eval(ctx, 'MyAwesomeCode') ctx.send.assert_called_once_with( '@LemonLemonishBeard#0042 :yay!: Return code 0.\n\n```py\n[No output]\n```' ) self.cog.post_eval.assert_called_once_with('MyAwesomeCode') self.cog.get_status_emoji.assert_called_once_with({ 'stdout': '', 'returncode': 0 }) self.cog.get_results_message.assert_called_once_with({ 'stdout': '', 'returncode': 0 }) self.cog.format_output.assert_called_once_with('')
def test_first_append_sets_the_first_value(self): """Test if the first append adds the message to the first cell.""" cache = MessageCache(maxlen=10) message = MockMessage() cache.append(message) self.assertEqual(cache[0], message)
def test_get_message_returns_none(self): """Test if get_message returns None for an ID of a non-cached message.""" cache = MessageCache(maxlen=5) message = MockMessage(id=1234) cache.append(message) self.assertIsNone(cache.get_message(4321))
def test_length(self): """Test if len returns the correct number of items in the cache.""" cache = MessageCache(maxlen=5) for current_loop in range(10): with self.subTest(current_loop=current_loop): self.assertEqual(len(cache), min(current_loop, 5)) cache.append(MockMessage())
def test_get_message_returns_the_message(self): """Test if get_message returns the cached message.""" cache = MessageCache(maxlen=5) message = MockMessage(id=1234) cache.append(message) self.assertEqual(cache.get_message(1234), message)
def test_has_signals_false(self): """False when `own_reactions` does not return all emoji in `ALL_SIGNALS`.""" message = MockMessage() own_reactions = MagicMock(return_value={"A", "C"}) with patch("bot.exts.moderation.incidents.own_reactions", own_reactions): self.assertFalse(incidents.has_signals(message))
def test_contains_returns_false_for_non_cached_message(self): """Test if contains returns False for an ID of a non-cached message.""" cache = MessageCache(maxlen=5) message = MockMessage(id=1234) cache.append(message) self.assertNotIn(4321, cache)
def setUp(self) -> None: """Prepare a mock message which should qualify as an incident.""" self.incident = MockMessage( channel=MockTextChannel(id=123), content="this is an incident", author=MockUser(bot=False), pinned=False, )
def setUp(self): """For each test, ensure `bot.get_channel` returns a channel with 1 arbitrary message.""" super().setUp() # First ensure we get `cog_instance` from parent incidents_history = MagicMock( return_value=MockAsyncIterable([MockMessage()])) self.cog_instance.bot.get_channel = MagicMock( return_value=MockTextChannel(history=incidents_history))
async def test_make_embed_actioned(self): """Embed is coloured green and footer contains 'Actioned' when `outcome=Signal.ACTIONED`.""" embed, file = await incidents.make_embed(MockMessage(), incidents.Signal.ACTIONED, MockMember()) self.assertEqual(embed.colour.value, Colours.soft_green) self.assertIn("Actioned", embed.footer.text)
def test_contains_returns_true_for_cached_message(self): """Test if contains returns True for an ID of a cached message.""" cache = MessageCache(maxlen=5) message = MockMessage(id=1234) cache.append(message) self.assertIn(1234, cache)
async def test_make_embed_content(self): """Incident content appears as embed description.""" incident = MockMessage(content="this is an incident") embed, file = await incidents.make_embed(incident, incidents.Signal.ACTIONED, MockMember()) self.assertEqual(incident.content, embed.description)