class SlowmodeTests(unittest.IsolatedAsyncioTestCase): def setUp(self) -> None: self.bot = MockBot() self.cog = Slowmode(self.bot) self.ctx = MockContext() async def test_get_slowmode_no_channel(self) -> None: """Get slowmode without a given channel.""" self.ctx.channel = MockTextChannel(name='python-general', slowmode_delay=5) await self.cog.get_slowmode(self.cog, self.ctx, None) self.ctx.send.assert_called_once_with("The slowmode delay for #python-general is 5 seconds.") async def test_get_slowmode_with_channel(self) -> None: """Get slowmode with a given channel.""" text_channel = MockTextChannel(name='python-language', slowmode_delay=2) await self.cog.get_slowmode(self.cog, self.ctx, text_channel) self.ctx.send.assert_called_once_with('The slowmode delay for #python-language is 2 seconds.') async def test_set_slowmode_no_channel(self) -> None: """Set slowmode without a given channel.""" test_cases = ( ('helpers', 23, True, f'{Emojis.check_mark} The slowmode delay for #helpers is now 23 seconds.'), ('mods', 76526, False, f'{Emojis.cross_mark} The slowmode delay must be between 0 and 6 hours.'), ('admins', 97, True, f'{Emojis.check_mark} The slowmode delay for #admins is now 1 minute and 37 seconds.') ) for channel_name, seconds, edited, result_msg in test_cases: with self.subTest( channel_mention=channel_name, seconds=seconds, edited=edited, result_msg=result_msg ): self.ctx.channel = MockTextChannel(name=channel_name) await self.cog.set_slowmode(self.cog, self.ctx, None, relativedelta(seconds=seconds)) if edited: self.ctx.channel.edit.assert_awaited_once_with(slowmode_delay=float(seconds)) else: self.ctx.channel.edit.assert_not_called() self.ctx.send.assert_called_once_with(result_msg) self.ctx.reset_mock() async def test_set_slowmode_with_channel(self) -> None: """Set slowmode with a given channel.""" test_cases = ( ('bot-commands', 12, True, f'{Emojis.check_mark} The slowmode delay for #bot-commands is now 12 seconds.'), ('mod-spam', 21, True, f'{Emojis.check_mark} The slowmode delay for #mod-spam is now 21 seconds.'), ('admin-spam', 4323598, False, f'{Emojis.cross_mark} The slowmode delay must be between 0 and 6 hours.') ) for channel_name, seconds, edited, result_msg in test_cases: with self.subTest( channel_mention=channel_name, seconds=seconds, edited=edited, result_msg=result_msg ): text_channel = MockTextChannel(name=channel_name) await self.cog.set_slowmode(self.cog, self.ctx, text_channel, relativedelta(seconds=seconds)) if edited: text_channel.edit.assert_awaited_once_with(slowmode_delay=float(seconds)) else: text_channel.edit.assert_not_called() self.ctx.send.assert_called_once_with(result_msg) self.ctx.reset_mock() async def test_reset_slowmode_no_channel(self) -> None: """Reset slowmode without a given channel.""" self.ctx.channel = MockTextChannel(name='careers', slowmode_delay=6) await self.cog.reset_slowmode(self.cog, self.ctx, None) self.ctx.send.assert_called_once_with( f'{Emojis.check_mark} The slowmode delay for #careers has been reset to 0 seconds.' ) async def test_reset_slowmode_with_channel(self) -> None: """Reset slowmode with a given channel.""" text_channel = MockTextChannel(name='meta', slowmode_delay=1) await self.cog.reset_slowmode(self.cog, self.ctx, text_channel) self.ctx.send.assert_called_once_with( f'{Emojis.check_mark} The slowmode delay for #meta has been reset to 0 seconds.' ) @mock.patch("bot.cogs.moderation.slowmode.with_role_check") @mock.patch("bot.cogs.moderation.slowmode.MODERATION_ROLES", new=(1, 2, 3)) def test_cog_check(self, role_check): """Role check is called with `MODERATION_ROLES`""" self.cog.cog_check(self.ctx) role_check.assert_called_once_with(self.ctx, *(1, 2, 3))
class SilenceTests(unittest.IsolatedAsyncioTestCase): def setUp(self) -> None: self.bot = MockBot() self.cog = Silence(self.bot) self.ctx = MockContext() self.cog._verified_role = None # Set event so command callbacks can continue. self.cog._get_instance_vars_event.set() async def test_instance_vars_got_guild(self): """Bot got guild after it became available.""" await self.cog._get_instance_vars() self.bot.wait_until_guild_available.assert_called_once() self.bot.get_guild.assert_called_once_with(Guild.id) async def test_instance_vars_got_role(self): """Got `Roles.verified` role from guild.""" await self.cog._get_instance_vars() guild = self.bot.get_guild() guild.get_role.assert_called_once_with(Roles.verified) async def test_instance_vars_got_channels(self): """Got channels from bot.""" await self.cog._get_instance_vars() self.bot.get_channel.called_once_with(Channels.mod_alerts) self.bot.get_channel.called_once_with(Channels.mod_log) @mock.patch("bot.cogs.moderation.silence.SilenceNotifier") async def test_instance_vars_got_notifier(self, notifier): """Notifier was started with channel.""" mod_log = MockTextChannel() self.bot.get_channel.side_effect = (None, mod_log) await self.cog._get_instance_vars() notifier.assert_called_once_with(mod_log) self.bot.get_channel.side_effect = None async def test_silence_sent_correct_discord_message(self): """Check if proper message was sent when called with duration in channel with previous state.""" test_cases = ( ( 0.0001, f"{Emojis.check_mark} silenced current channel for 0.0001 minute(s).", True, ), ( None, f"{Emojis.check_mark} silenced current channel indefinitely.", True, ), ( 5, f"{Emojis.cross_mark} current channel is already silenced.", False, ), ) for duration, result_message, _silence_patch_return in test_cases: with self.subTest(silence_duration=duration, result_message=result_message, starting_unsilenced_state=_silence_patch_return): with mock.patch.object(self.cog, "_silence", return_value=_silence_patch_return): await self.cog.silence.callback(self.cog, self.ctx, duration) self.ctx.send.assert_called_once_with(result_message) self.ctx.reset_mock() async def test_unsilence_sent_correct_discord_message(self): """Proper reply after a successful unsilence.""" with mock.patch.object(self.cog, "_unsilence", return_value=True): await self.cog.unsilence.callback(self.cog, self.ctx) self.ctx.send.assert_called_once_with( f"{Emojis.check_mark} unsilenced current channel.") async def test_silence_private_for_false(self): """Permissions are not set and `False` is returned in an already silenced channel.""" perm_overwrite = Mock(send_messages=False) channel = Mock(overwrites_for=Mock(return_value=perm_overwrite)) self.assertFalse(await self.cog._silence(channel, True, None)) channel.set_permissions.assert_not_called() async def test_silence_private_silenced_channel(self): """Channel had `send_message` permissions revoked.""" channel = MockTextChannel() self.assertTrue(await self.cog._silence(channel, False, None)) channel.set_permissions.assert_called_once() self.assertFalse( channel.set_permissions.call_args.kwargs['send_messages']) async def test_silence_private_preserves_permissions(self): """Previous permissions were preserved when channel was silenced.""" channel = MockTextChannel() # Set up mock channel permission state. mock_permissions = PermissionOverwrite() mock_permissions_dict = dict(mock_permissions) channel.overwrites_for.return_value = mock_permissions await self.cog._silence(channel, False, None) new_permissions = channel.set_permissions.call_args.kwargs # Remove 'send_messages' key because it got changed in the method. del new_permissions['send_messages'] del mock_permissions_dict['send_messages'] self.assertDictEqual(mock_permissions_dict, new_permissions) async def test_silence_private_notifier(self): """Channel should be added to notifier with `persistent` set to `True`, and the other way around.""" channel = MockTextChannel() with mock.patch.object(self.cog, "notifier", create=True): with self.subTest(persistent=True): await self.cog._silence(channel, True, None) self.cog.notifier.add_channel.assert_called_once() with mock.patch.object(self.cog, "notifier", create=True): with self.subTest(persistent=False): await self.cog._silence(channel, False, None) self.cog.notifier.add_channel.assert_not_called() async def test_silence_private_added_muted_channel(self): """Channel was added to `muted_channels` on silence.""" channel = MockTextChannel() with mock.patch.object(self.cog, "muted_channels") as muted_channels: await self.cog._silence(channel, False, None) muted_channels.add.assert_called_once_with(channel) async def test_unsilence_private_for_false(self): """Permissions are not set and `False` is returned in an unsilenced channel.""" channel = Mock() self.assertFalse(await self.cog._unsilence(channel)) channel.set_permissions.assert_not_called() @mock.patch.object(Silence, "notifier", create=True) async def test_unsilence_private_unsilenced_channel(self, _): """Channel had `send_message` permissions restored""" perm_overwrite = MagicMock(send_messages=False) channel = MockTextChannel(overwrites_for=Mock( return_value=perm_overwrite)) self.assertTrue(await self.cog._unsilence(channel)) channel.set_permissions.assert_called_once() self.assertIsNone( channel.set_permissions.call_args.kwargs['send_messages']) @mock.patch.object(Silence, "notifier", create=True) async def test_unsilence_private_removed_notifier(self, notifier): """Channel was removed from `notifier` on unsilence.""" perm_overwrite = MagicMock(send_messages=False) channel = MockTextChannel(overwrites_for=Mock( return_value=perm_overwrite)) await self.cog._unsilence(channel) notifier.remove_channel.assert_called_once_with(channel) @mock.patch.object(Silence, "notifier", create=True) async def test_unsilence_private_removed_muted_channel(self, _): """Channel was removed from `muted_channels` on unsilence.""" perm_overwrite = MagicMock(send_messages=False) channel = MockTextChannel(overwrites_for=Mock( return_value=perm_overwrite)) with mock.patch.object(self.cog, "muted_channels") as muted_channels: await self.cog._unsilence(channel) muted_channels.discard.assert_called_once_with(channel) @mock.patch.object(Silence, "notifier", create=True) async def test_unsilence_private_preserves_permissions(self, _): """Previous permissions were preserved when channel was unsilenced.""" channel = MockTextChannel() # Set up mock channel permission state. mock_permissions = PermissionOverwrite(send_messages=False) mock_permissions_dict = dict(mock_permissions) channel.overwrites_for.return_value = mock_permissions await self.cog._unsilence(channel) new_permissions = channel.set_permissions.call_args.kwargs # Remove 'send_messages' key because it got changed in the method. del new_permissions['send_messages'] del mock_permissions_dict['send_messages'] self.assertDictEqual(mock_permissions_dict, new_permissions) @mock.patch("bot.cogs.moderation.silence.asyncio") @mock.patch.object(Silence, "_mod_alerts_channel", create=True) def test_cog_unload_starts_task(self, alert_channel, asyncio_mock): """Task for sending an alert was created with present `muted_channels`.""" with mock.patch.object(self.cog, "muted_channels"): self.cog.cog_unload() alert_channel.send.assert_called_once_with( f"<@&{Roles.moderators}> channels left silenced on cog unload: " ) asyncio_mock.create_task.assert_called_once_with( alert_channel.send()) @mock.patch("bot.cogs.moderation.silence.asyncio") def test_cog_unload_skips_task_start(self, asyncio_mock): """No task created with no channels.""" self.cog.cog_unload() asyncio_mock.create_task.assert_not_called() @mock.patch("bot.cogs.moderation.silence.with_role_check") @mock.patch("bot.cogs.moderation.silence.MODERATION_ROLES", new=(1, 2, 3)) def test_cog_check(self, role_check): """Role check is called with `MODERATION_ROLES`""" self.cog.cog_check(self.ctx) role_check.assert_called_once_with(self.ctx, *(1, 2, 3))
class JamCreateTeamTests(unittest.IsolatedAsyncioTestCase): """Tests for `createteam` command.""" def setUp(self): self.bot = MockBot() self.admin_role = MockRole(name="Admins", id=Roles.admins) self.command_user = MockMember([self.admin_role]) self.guild = MockGuild([self.admin_role]) self.ctx = MockContext(bot=self.bot, author=self.command_user, guild=self.guild) self.cog = jams.CodeJams(self.bot) async def test_too_small_amount_of_team_members_passed(self): """Should `ctx.send` and exit early when too small amount of members.""" for case in (1, 2): with self.subTest(amount_of_members=case): self.cog.create_channels = AsyncMock() self.cog.add_roles = AsyncMock() self.ctx.reset_mock() members = (MockMember() for _ in range(case)) await self.cog.createteam(self.cog, self.ctx, "foo", members) self.ctx.send.assert_awaited_once() self.cog.create_channels.assert_not_awaited() self.cog.add_roles.assert_not_awaited() async def test_duplicate_members_provided(self): """Should `ctx.send` and exit early because duplicate members provided and total there is only 1 member.""" self.cog.create_channels = AsyncMock() self.cog.add_roles = AsyncMock() member = MockMember() await self.cog.createteam(self.cog, self.ctx, "foo", (member for _ in range(5))) self.ctx.send.assert_awaited_once() self.cog.create_channels.assert_not_awaited() self.cog.add_roles.assert_not_awaited() async def test_result_sending(self): """Should call `ctx.send` when everything goes right.""" self.cog.create_channels = AsyncMock() self.cog.add_roles = AsyncMock() members = [MockMember() for _ in range(5)] await self.cog.createteam(self.cog, self.ctx, "foo", members) self.cog.create_channels.assert_awaited_once() self.cog.add_roles.assert_awaited_once() self.ctx.send.assert_awaited_once() async def test_category_doesnt_exist(self): """Should create a new code jam category.""" subtests = ( [], [get_mock_category(jams.MAX_CHANNELS - 1, jams.CATEGORY_NAME)], [get_mock_category(jams.MAX_CHANNELS - 2, "other")], ) for categories in subtests: self.guild.reset_mock() self.guild.categories = categories with self.subTest(categories=categories): actual_category = await self.cog.get_category(self.guild) self.guild.create_category_channel.assert_awaited_once() category_overwrites = self.guild.create_category_channel.call_args[1]["overwrites"] self.assertFalse(category_overwrites[self.guild.default_role].read_messages) self.assertTrue(category_overwrites[self.guild.me].read_messages) self.assertEqual(self.guild.create_category_channel.return_value, actual_category) async def test_category_channel_exist(self): """Should not try to create category channel.""" expected_category = get_mock_category(jams.MAX_CHANNELS - 2, jams.CATEGORY_NAME) self.guild.categories = [ get_mock_category(jams.MAX_CHANNELS - 2, "other"), expected_category, get_mock_category(0, jams.CATEGORY_NAME), ] actual_category = await self.cog.get_category(self.guild) self.assertEqual(expected_category, actual_category) async def test_channel_overwrites(self): """Should have correct permission overwrites for users and roles.""" leader = MockMember() members = [leader] + [MockMember() for _ in range(4)] overwrites = self.cog.get_overwrites(members, self.guild) # Leader permission overwrites self.assertTrue(overwrites[leader].manage_messages) self.assertTrue(overwrites[leader].read_messages) self.assertTrue(overwrites[leader].manage_webhooks) self.assertTrue(overwrites[leader].connect) # Other members permission overwrites for member in members[1:]: self.assertTrue(overwrites[member].read_messages) self.assertTrue(overwrites[member].connect) # Everyone and verified role overwrite self.assertFalse(overwrites[self.guild.default_role].read_messages) self.assertFalse(overwrites[self.guild.default_role].connect) self.assertFalse(overwrites[self.guild.get_role(Roles.verified)].read_messages) self.assertFalse(overwrites[self.guild.get_role(Roles.verified)].connect) async def test_team_channels_creation(self): """Should create new voice and text channel for team.""" members = [MockMember() for _ in range(5)] self.cog.get_overwrites = MagicMock() self.cog.get_category = AsyncMock() self.ctx.guild.create_text_channel.return_value = MockTextChannel(mention="foobar-channel") actual = await self.cog.create_channels(self.guild, "my-team", members) self.assertEqual("foobar-channel", actual) self.cog.get_overwrites.assert_called_once_with(members, self.guild) self.cog.get_category.assert_awaited_once_with(self.guild) self.guild.create_text_channel.assert_awaited_once_with( "my-team", overwrites=self.cog.get_overwrites.return_value, category=self.cog.get_category.return_value ) self.guild.create_voice_channel.assert_awaited_once_with( "My Team", overwrites=self.cog.get_overwrites.return_value, category=self.cog.get_category.return_value ) async def test_jam_roles_adding(self): """Should add team leader role to leader and jam role to every team member.""" leader_role = MockRole(name="Team Leader") jam_role = MockRole(name="Jammer") self.guild.get_role.side_effect = [leader_role, jam_role] leader = MockMember() members = [leader] + [MockMember() for _ in range(4)] await self.cog.add_roles(self.guild, members) leader.add_roles.assert_any_await(leader_role) for member in members: member.add_roles.assert_any_await(jam_role)
class IndividualErrorHandlerTests(unittest.IsolatedAsyncioTestCase): """Individual error categories handler tests.""" def setUp(self): self.bot = MockBot() self.ctx = MockContext(bot=self.bot) self.cog = ErrorHandler(self.bot) async def test_handle_input_error_handler_errors(self): """Should handle each error probably.""" test_cases = ({ "error": errors.MissingRequiredArgument(MagicMock()), "call_prepared": True }, { "error": errors.TooManyArguments(), "call_prepared": True }, { "error": errors.BadArgument(), "call_prepared": True }, { "error": errors.BadUnionArgument(MagicMock(), MagicMock(), MagicMock()), "call_prepared": True }, { "error": errors.ArgumentParsingError(), "call_prepared": False }, { "error": errors.UserInputError(), "call_prepared": True }) for case in test_cases: with self.subTest(error=case["error"], call_prepared=case["call_prepared"]): self.ctx.reset_mock() self.assertIsNone(await self.cog.handle_user_input_error( self.ctx, case["error"])) self.ctx.send.assert_awaited_once() if case["call_prepared"]: self.ctx.send_help.assert_awaited_once() else: self.ctx.send_help.assert_not_awaited() async def test_handle_check_failure_errors(self): """Should await `ctx.send` when error is check failure.""" test_cases = ({ "error": errors.BotMissingPermissions(MagicMock()), "call_ctx_send": True }, { "error": errors.BotMissingRole(MagicMock()), "call_ctx_send": True }, { "error": errors.BotMissingAnyRole(MagicMock()), "call_ctx_send": True }, { "error": errors.NoPrivateMessage(), "call_ctx_send": True }, { "error": InWhitelistCheckFailure(1234), "call_ctx_send": True }, { "error": ResponseCodeError(MagicMock()), "call_ctx_send": False }) for case in test_cases: with self.subTest(error=case["error"], call_ctx_send=case["call_ctx_send"]): self.ctx.reset_mock() await self.cog.handle_check_failure(self.ctx, case["error"]) if case["call_ctx_send"]: self.ctx.send.assert_awaited_once() else: self.ctx.send.assert_not_awaited() @patch("bot.exts.backend.error_handler.log") async def test_handle_api_error(self, log_mock): """Should `ctx.send` on HTTP error codes, `log.debug|warning` depends on code.""" test_cases = ({ "error": ResponseCodeError(AsyncMock(status=400)), "log_level": "debug" }, { "error": ResponseCodeError(AsyncMock(status=404)), "log_level": "debug" }, { "error": ResponseCodeError(AsyncMock(status=550)), "log_level": "warning" }, { "error": ResponseCodeError(AsyncMock(status=1000)), "log_level": "warning" }) for case in test_cases: with self.subTest(error=case["error"], log_level=case["log_level"]): self.ctx.reset_mock() log_mock.reset_mock() await self.cog.handle_api_error(self.ctx, case["error"]) self.ctx.send.assert_awaited_once() if case["log_level"] == "warning": log_mock.warning.assert_called_once() else: log_mock.debug.assert_called_once() @patch("bot.exts.backend.error_handler.push_scope") @patch("bot.exts.backend.error_handler.log") async def test_handle_unexpected_error(self, log_mock, push_scope_mock): """Should `ctx.send` this error, error log this and sent to Sentry.""" for case in (None, MockGuild()): with self.subTest(guild=case): self.ctx.reset_mock() log_mock.reset_mock() push_scope_mock.reset_mock() self.ctx.guild = case await self.cog.handle_unexpected_error(self.ctx, errors.CommandError()) self.ctx.send.assert_awaited_once() log_mock.error.assert_called_once() push_scope_mock.assert_called_once() set_tag_calls = [ call("command", self.ctx.command.qualified_name), call("message_id", self.ctx.message.id), call("channel_id", self.ctx.channel.id), ] set_extra_calls = [ call("full_message", self.ctx.message.content) ] if case: url = ( f"https://discordapp.com/channels/" f"{self.ctx.guild.id}/{self.ctx.channel.id}/{self.ctx.message.id}" ) set_extra_calls.append(call("jump_to", url)) push_scope_mock.set_tag.has_calls(set_tag_calls) push_scope_mock.set_extra.has_calls(set_extra_calls)
class TryGetTagTests(unittest.IsolatedAsyncioTestCase): """Tests for `try_get_tag` function.""" def setUp(self): self.bot = MockBot() self.ctx = MockContext() self.tag = Tags(self.bot) self.cog = ErrorHandler(self.bot) self.bot.get_command.return_value = self.tag.get_command async def test_try_get_tag_get_command(self): """Should call `Bot.get_command` with `tags get` argument.""" self.bot.get_command.reset_mock() self.ctx.invoked_with = "foo" await self.cog.try_get_tag(self.ctx) self.bot.get_command.assert_called_once_with("tags get") async def test_try_get_tag_invoked_from_error_handler(self): """`self.ctx` should have `invoked_from_error_handler` `True`.""" self.ctx.invoked_from_error_handler = False self.ctx.invoked_with = "foo" await self.cog.try_get_tag(self.ctx) self.assertTrue(self.ctx.invoked_from_error_handler) async def test_try_get_tag_no_permissions(self): """Test how to handle checks failing.""" self.tag.get_command.can_run = AsyncMock(return_value=False) self.ctx.invoked_with = "foo" self.assertIsNone(await self.cog.try_get_tag(self.ctx)) async def test_try_get_tag_command_error(self): """Should call `on_command_error` when `CommandError` raised.""" err = errors.CommandError() self.tag.get_command.can_run = AsyncMock(side_effect=err) self.cog.on_command_error = AsyncMock() self.ctx.invoked_with = "foo" self.assertIsNone(await self.cog.try_get_tag(self.ctx)) self.cog.on_command_error.assert_awaited_once_with(self.ctx, err) @patch("bot.exts.backend.error_handler.TagNameConverter") async def test_try_get_tag_convert_success(self, tag_converter): """Converting tag should successful.""" self.ctx.invoked_with = "foo" tag_converter.convert = AsyncMock(return_value="foo") self.assertIsNone(await self.cog.try_get_tag(self.ctx)) tag_converter.convert.assert_awaited_once_with(self.ctx, "foo") self.ctx.invoke.assert_awaited_once() @patch("bot.exts.backend.error_handler.TagNameConverter") async def test_try_get_tag_convert_fail(self, tag_converter): """Converting tag should raise `BadArgument`.""" self.ctx.reset_mock() self.ctx.invoked_with = "bar" tag_converter.convert = AsyncMock(side_effect=errors.BadArgument()) self.assertIsNone(await self.cog.try_get_tag(self.ctx)) self.ctx.invoke.assert_not_awaited() async def test_try_get_tag_ctx_invoke(self): """Should call `ctx.invoke` with proper args/kwargs.""" self.ctx.reset_mock() self.ctx.invoked_with = "foo" self.assertIsNone(await self.cog.try_get_tag(self.ctx)) self.ctx.invoke.assert_awaited_once_with(self.tag.get_command, tag_name="foo") async def test_dont_call_suggestion_tag_sent(self): """Should never call command suggestion if tag is already sent.""" self.ctx.invoked_with = "foo" self.ctx.invoke = AsyncMock(return_value=True) self.cog.send_command_suggestion = AsyncMock() await self.cog.try_get_tag(self.ctx) self.cog.send_command_suggestion.assert_not_awaited() @patch("bot.exts.backend.error_handler.MODERATION_ROLES", new=[1234]) async def test_dont_call_suggestion_if_user_mod(self): """Should not call command suggestion if user is a mod.""" self.ctx.invoked_with = "foo" self.ctx.invoke = AsyncMock(return_value=False) self.ctx.author.roles = [MockRole(id=1234)] self.cog.send_command_suggestion = AsyncMock() await self.cog.try_get_tag(self.ctx) self.cog.send_command_suggestion.assert_not_awaited() async def test_call_suggestion(self): """Should call command suggestion if user is not a mod.""" self.ctx.invoked_with = "foo" self.ctx.invoke = AsyncMock(return_value=False) self.cog.send_command_suggestion = AsyncMock() await self.cog.try_get_tag(self.ctx) self.cog.send_command_suggestion.assert_awaited_once_with( self.ctx, "foo")
class TrySilenceTests(unittest.IsolatedAsyncioTestCase): """Test for helper functions that handle `CommandNotFound` error.""" def setUp(self): self.bot = MockBot() self.silence = Silence(self.bot) self.bot.get_command.return_value = self.silence.silence self.ctx = MockContext(bot=self.bot) self.cog = ErrorHandler(self.bot) async def test_try_silence_context_invoked_from_error_handler(self): """Should set `Context.invoked_from_error_handler` to `True`.""" self.ctx.invoked_with = "foo" await self.cog.try_silence(self.ctx) self.assertTrue(hasattr(self.ctx, "invoked_from_error_handler")) self.assertTrue(self.ctx.invoked_from_error_handler) async def test_try_silence_get_command(self): """Should call `get_command` with `silence`.""" self.ctx.invoked_with = "foo" await self.cog.try_silence(self.ctx) self.bot.get_command.assert_called_once_with("silence") async def test_try_silence_no_permissions_to_run(self): """Should return `False` because missing permissions.""" self.ctx.invoked_with = "foo" self.bot.get_command.return_value.can_run = AsyncMock( return_value=False) self.assertFalse(await self.cog.try_silence(self.ctx)) async def test_try_silence_no_permissions_to_run_command_error(self): """Should return `False` because `CommandError` raised (no permissions).""" self.ctx.invoked_with = "foo" self.bot.get_command.return_value.can_run = AsyncMock( side_effect=errors.CommandError()) self.assertFalse(await self.cog.try_silence(self.ctx)) async def test_try_silence_silence_duration(self): """Should run silence command with correct duration argument.""" self.bot.get_command.return_value.can_run = AsyncMock( return_value=True) test_cases = ("shh", "shhh", "shhhhhh", "shhhhhhhhhhhhhhhhhhh") for case in test_cases: with self.subTest(message=case): self.ctx.reset_mock() self.ctx.invoked_with = case self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with( self.bot.get_command.return_value, duration_or_channel=None, duration=min(case.count("h") * 2, 15), kick=False) async def test_try_silence_silence_arguments(self): """Should run silence with the correct channel, duration, and kick arguments.""" self.bot.get_command.return_value.can_run = AsyncMock( return_value=True) test_cases = ( (MockTextChannel(), None), # None represents the case when no argument is passed (MockTextChannel(), False), (MockTextChannel(), True)) for channel, kick in test_cases: with self.subTest(kick=kick, channel=channel): self.ctx.reset_mock() self.ctx.invoked_with = "shh" self.ctx.message.content = f"!shh {channel.name} {kick if kick is not None else ''}" self.ctx.guild.text_channels = [channel] self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with( self.bot.get_command.return_value, duration_or_channel=channel, duration=4, kick=(kick if kick is not None else False)) async def test_try_silence_silence_message(self): """If the words after the command could not be converted to a channel, None should be passed as channel.""" self.bot.get_command.return_value.can_run = AsyncMock( return_value=True) self.ctx.invoked_with = "shh" self.ctx.message.content = "!shh not_a_channel true" self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with( self.bot.get_command.return_value, duration_or_channel=None, duration=4, kick=False) async def test_try_silence_unsilence(self): """Should call unsilence command with correct duration and channel arguments.""" self.silence.silence.can_run = AsyncMock(return_value=True) test_cases = (("unshh", None), ("unshhhhh", None), ("unshhhhhhhhh", None), ("unshh", MockTextChannel())) for invoke, channel in test_cases: with self.subTest(message=invoke, channel=channel): self.bot.get_command.side_effect = (self.silence.silence, self.silence.unsilence) self.ctx.reset_mock() self.ctx.invoked_with = invoke self.ctx.message.content = f"!{invoke}" if channel is not None: self.ctx.message.content += f" {channel.name}" self.ctx.guild.text_channels = [channel] self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with( self.silence.unsilence, channel=channel) async def test_try_silence_unsilence_message(self): """If the words after the command could not be converted to a channel, None should be passed as channel.""" self.silence.silence.can_run = AsyncMock(return_value=True) self.bot.get_command.side_effect = (self.silence.silence, self.silence.unsilence) self.ctx.invoked_with = "unshh" self.ctx.message.content = "!unshh not_a_channel" self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with(self.silence.unsilence, channel=None) async def test_try_silence_no_match(self): """Should return `False` when message don't match.""" self.ctx.invoked_with = "foo" self.assertFalse(await self.cog.try_silence(self.ctx))
class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase): """Tests for error handler functionality.""" def setUp(self): self.bot = MockBot() self.ctx = MockContext(bot=self.bot) async def test_error_handler_already_handled(self): """Should not do anything when error is already handled by local error handler.""" self.ctx.reset_mock() cog = ErrorHandler(self.bot) error = errors.CommandError() error.handled = "foo" self.assertIsNone(await cog.on_command_error(self.ctx, error)) self.ctx.send.assert_not_awaited() async def test_error_handler_command_not_found_error_not_invoked_by_handler( self): """Should try first (un)silence channel, when fail, try to get tag.""" error = errors.CommandNotFound() test_cases = ({ "try_silence_return": True, "called_try_get_tag": False }, { "try_silence_return": False, "called_try_get_tag": False }, { "try_silence_return": False, "called_try_get_tag": True }) cog = ErrorHandler(self.bot) cog.try_silence = AsyncMock() cog.try_get_tag = AsyncMock() for case in test_cases: with self.subTest(try_silence_return=case["try_silence_return"], try_get_tag=case["called_try_get_tag"]): self.ctx.reset_mock() cog.try_silence.reset_mock(return_value=True) cog.try_get_tag.reset_mock() cog.try_silence.return_value = case["try_silence_return"] self.ctx.channel.id = 1234 self.assertIsNone(await cog.on_command_error(self.ctx, error)) if case["try_silence_return"]: cog.try_get_tag.assert_not_awaited() cog.try_silence.assert_awaited_once() else: cog.try_silence.assert_awaited_once() cog.try_get_tag.assert_awaited_once() self.ctx.send.assert_not_awaited() async def test_error_handler_command_not_found_error_invoked_by_handler( self): """Should do nothing when error is `CommandNotFound` and have attribute `invoked_from_error_handler`.""" ctx = MockContext(bot=self.bot, invoked_from_error_handler=True) cog = ErrorHandler(self.bot) cog.try_silence = AsyncMock() cog.try_get_tag = AsyncMock() error = errors.CommandNotFound() self.assertIsNone(await cog.on_command_error(ctx, error)) cog.try_silence.assert_not_awaited() cog.try_get_tag.assert_not_awaited() self.ctx.send.assert_not_awaited() async def test_error_handler_user_input_error(self): """Should await `ErrorHandler.handle_user_input_error` when error is `UserInputError`.""" self.ctx.reset_mock() cog = ErrorHandler(self.bot) cog.handle_user_input_error = AsyncMock() error = errors.UserInputError() self.assertIsNone(await cog.on_command_error(self.ctx, error)) cog.handle_user_input_error.assert_awaited_once_with(self.ctx, error) async def test_error_handler_check_failure(self): """Should await `ErrorHandler.handle_check_failure` when error is `CheckFailure`.""" self.ctx.reset_mock() cog = ErrorHandler(self.bot) cog.handle_check_failure = AsyncMock() error = errors.CheckFailure() self.assertIsNone(await cog.on_command_error(self.ctx, error)) cog.handle_check_failure.assert_awaited_once_with(self.ctx, error) async def test_error_handler_command_on_cooldown(self): """Should send error with `ctx.send` when error is `CommandOnCooldown`.""" self.ctx.reset_mock() cog = ErrorHandler(self.bot) error = errors.CommandOnCooldown(10, 9) self.assertIsNone(await cog.on_command_error(self.ctx, error)) self.ctx.send.assert_awaited_once_with(error) async def test_error_handler_command_invoke_error(self): """Should call `handle_api_error` or `handle_unexpected_error` depending on original error.""" cog = ErrorHandler(self.bot) cog.handle_api_error = AsyncMock() cog.handle_unexpected_error = AsyncMock() test_cases = ({ "args": (self.ctx, errors.CommandInvokeError(ResponseCodeError(AsyncMock()))), "expect_mock_call": cog.handle_api_error }, { "args": (self.ctx, errors.CommandInvokeError(TypeError)), "expect_mock_call": cog.handle_unexpected_error }, { "args": (self.ctx, errors.CommandInvokeError(LockedResourceError("abc", "test"))), "expect_mock_call": "send" }, { "args": (self.ctx, errors.CommandInvokeError( InvalidInfractedUserError(self.ctx.author))), "expect_mock_call": "send" }) for case in test_cases: with self.subTest(args=case["args"], expect_mock_call=case["expect_mock_call"]): self.ctx.send.reset_mock() self.assertIsNone(await cog.on_command_error(*case["args"])) if case["expect_mock_call"] == "send": self.ctx.send.assert_awaited_once() else: case["expect_mock_call"].assert_awaited_once_with( self.ctx, case["args"][1].original) async def test_error_handler_conversion_error(self): """Should call `handle_api_error` or `handle_unexpected_error` depending on original error.""" cog = ErrorHandler(self.bot) cog.handle_api_error = AsyncMock() cog.handle_unexpected_error = AsyncMock() cases = ({ "error": errors.ConversionError(AsyncMock(), ResponseCodeError(AsyncMock())), "mock_function_to_call": cog.handle_api_error }, { "error": errors.ConversionError(AsyncMock(), TypeError), "mock_function_to_call": cog.handle_unexpected_error }) for case in cases: with self.subTest(**case): self.assertIsNone(await cog.on_command_error(self.ctx, case["error"])) case["mock_function_to_call"].assert_awaited_once_with( self.ctx, case["error"].original) async def test_error_handler_two_other_errors(self): """Should call `handle_unexpected_error` if error is `MaxConcurrencyReached` or `ExtensionError`.""" cog = ErrorHandler(self.bot) cog.handle_unexpected_error = AsyncMock() errs = (errors.MaxConcurrencyReached(1, MagicMock()), errors.ExtensionError(name="foo")) for err in errs: with self.subTest(error=err): cog.handle_unexpected_error.reset_mock() self.assertIsNone(await cog.on_command_error(self.ctx, err)) cog.handle_unexpected_error.assert_awaited_once_with( self.ctx, err) @patch("bot.exts.backend.error_handler.log") async def test_error_handler_other_errors(self, log_mock): """Should `log.debug` other errors.""" cog = ErrorHandler(self.bot) error = errors.DisabledCommand() # Use this just as a other error self.assertIsNone(await cog.on_command_error(self.ctx, error)) log_mock.debug.assert_called_once()
class TrySilenceTests(unittest.IsolatedAsyncioTestCase): """Test for helper functions that handle `CommandNotFound` error.""" def setUp(self): self.bot = MockBot() self.silence = Silence(self.bot) self.bot.get_command.return_value = self.silence.silence self.ctx = MockContext(bot=self.bot) self.cog = ErrorHandler(self.bot) async def test_try_silence_context_invoked_from_error_handler(self): """Should set `Context.invoked_from_error_handler` to `True`.""" self.ctx.invoked_with = "foo" await self.cog.try_silence(self.ctx) self.assertTrue(hasattr(self.ctx, "invoked_from_error_handler")) self.assertTrue(self.ctx.invoked_from_error_handler) async def test_try_silence_get_command(self): """Should call `get_command` with `silence`.""" self.ctx.invoked_with = "foo" await self.cog.try_silence(self.ctx) self.bot.get_command.assert_called_once_with("silence") async def test_try_silence_no_permissions_to_run(self): """Should return `False` because missing permissions.""" self.ctx.invoked_with = "foo" self.bot.get_command.return_value.can_run = AsyncMock(return_value=False) self.assertFalse(await self.cog.try_silence(self.ctx)) async def test_try_silence_no_permissions_to_run_command_error(self): """Should return `False` because `CommandError` raised (no permissions).""" self.ctx.invoked_with = "foo" self.bot.get_command.return_value.can_run = AsyncMock(side_effect=errors.CommandError()) self.assertFalse(await self.cog.try_silence(self.ctx)) async def test_try_silence_silencing(self): """Should run silence command with correct arguments.""" self.bot.get_command.return_value.can_run = AsyncMock(return_value=True) test_cases = ("shh", "shhh", "shhhhhh", "shhhhhhhhhhhhhhhhhhh") for case in test_cases: with self.subTest(message=case): self.ctx.reset_mock() self.ctx.invoked_with = case self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with( self.bot.get_command.return_value, duration=min(case.count("h")*2, 15) ) async def test_try_silence_unsilence(self): """Should call unsilence command.""" self.silence.silence.can_run = AsyncMock(return_value=True) test_cases = ("unshh", "unshhhhh", "unshhhhhhhhh") for case in test_cases: with self.subTest(message=case): self.bot.get_command.side_effect = (self.silence.silence, self.silence.unsilence) self.ctx.reset_mock() self.ctx.invoked_with = case self.assertTrue(await self.cog.try_silence(self.ctx)) self.ctx.invoke.assert_awaited_once_with(self.silence.unsilence) async def test_try_silence_no_match(self): """Should return `False` when message don't match.""" self.ctx.invoked_with = "foo" self.assertFalse(await self.cog.try_silence(self.ctx))