Ejemplo n.º 1
0
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))
Ejemplo n.º 2
0
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))
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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")
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
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()
Ejemplo n.º 8
0
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))