diff options
author | 2023-04-09 21:15:18 +0100 | |
---|---|---|
committer | 2023-04-11 16:48:14 +0100 | |
commit | 8dca42846d2956122d45795763095559a6a51b64 (patch) | |
tree | 480cd7d3c1a6d6bc87710e2d3c19f223a92f7c5d /tests | |
parent | Replace CI flake8 config with ruff (diff) |
Migrate code style to ruff
Co-authored-by: Boris Muratov <[email protected]>
Co-authored-by: wookie184 <[email protected]>
Diffstat (limited to 'tests')
24 files changed, 446 insertions, 440 deletions
diff --git a/tests/_autospec.py b/tests/_autospec.py index ecff6bcbe..6f990a580 100644 --- a/tests/_autospec.py +++ b/tests/_autospec.py @@ -2,7 +2,7 @@ import contextlib import functools import pkgutil import unittest.mock -from typing import Callable +from collections.abc import Callable @functools.wraps(unittest.mock._patch.decoration_helper) diff --git a/tests/base.py b/tests/base.py index 4863a1821..cad187b6a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,7 +1,6 @@ import logging import unittest from contextlib import contextmanager -from typing import Dict import discord from async_rediscache import RedisSession @@ -86,7 +85,7 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase): async def assertHasPermissionsCheck( # noqa: N802 self, cmd: commands.Command, - permissions: Dict[str, bool], + permissions: dict[str, bool], ) -> None: """ Test that `cmd` raises a `MissingPermissions` exception if author lacks `permissions`. diff --git a/tests/bot/exts/filtering/test_discord_token_filter.py b/tests/bot/exts/filtering/test_discord_token_filter.py index a5cddf8d9..1cb9e16fa 100644 --- a/tests/bot/exts/filtering/test_discord_token_filter.py +++ b/tests/bot/exts/filtering/test_discord_token_filter.py @@ -222,8 +222,8 @@ class DiscordTokenFilterTests(unittest.IsolatedAsyncioTestCase): def test_regex_matches_multiple_valid(self): """Should support multiple matches in the middle of a string.""" - token_1 = "NDY3MjIzMjMwNjUwNzc3NjQx.XsyWGg.uFNEQPCc4ePwGh7egG8UicQssz8" - token_2 = "NDcyMjY1OTQzMDYyNDEzMzMy.XsyWMw.l8XPnDqb0lp-EiQ2g_0xVFT1pyc" + token_1 = "NDY3MjIzMjMwNjUwNzc3NjQx.XsyWGg.uFNEQPCc4ePwGh7egG8UicQssz8" # noqa: S105 + token_2 = "NDcyMjY1OTQzMDYyNDEzMzMy.XsyWMw.l8XPnDqb0lp-EiQ2g_0xVFT1pyc" # noqa: S105 message = f"garbage {token_1} hello {token_2} world" results = discord_token.TOKEN_RE.finditer(message) diff --git a/tests/bot/exts/filtering/test_extension_filter.py b/tests/bot/exts/filtering/test_extension_filter.py index 827d267d2..f71de1e1b 100644 --- a/tests/bot/exts/filtering/test_extension_filter.py +++ b/tests/bot/exts/filtering/test_extension_filter.py @@ -25,7 +25,7 @@ class ExtensionsListTests(unittest.IsolatedAsyncioTestCase): for i, filter_content in enumerate(self.whitelist, start=1): filters.append({ "id": i, "content": filter_content, "description": None, "settings": {}, - "additional_settings": {}, "created_at": now, "updated_at": now # noqa: P103 + "additional_settings": {}, "created_at": now, "updated_at": now }) self.filter_list.add_list({ "id": 1, diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py index 65595e959..e90291f62 100644 --- a/tests/bot/exts/info/test_information.py +++ b/tests/bot/exts/info/test_information.py @@ -1,7 +1,7 @@ import textwrap import unittest import unittest.mock -from datetime import datetime +from datetime import UTC, datetime from textwrap import shorten import discord @@ -41,7 +41,7 @@ class InformationCogTests(unittest.IsolatedAsyncioTestCase): self.ctx.send.assert_called_once() _, kwargs = self.ctx.send.call_args - embed = kwargs.pop('embed') + embed = kwargs.pop("embed") self.assertEqual(embed.title, "Role information (Total 1 role)") self.assertEqual(embed.colour, discord.Colour.og_blurple()) @@ -110,15 +110,15 @@ class UserInfractionHelperMethodTests(unittest.IsolatedAsyncioTestCase): test_values = ( { "helper_method": self.cog.basic_user_infraction_counts, - "expected_args": ("bot/infractions", {'hidden': 'False', 'user__id': str(self.member.id)}), + "expected_args": ("bot/infractions", {"hidden": "False", "user__id": str(self.member.id)}), }, { "helper_method": self.cog.expanded_user_infraction_counts, - "expected_args": ("bot/infractions", {'user__id': str(self.member.id)}), + "expected_args": ("bot/infractions", {"user__id": str(self.member.id)}), }, { "helper_method": self.cog.user_nomination_counts, - "expected_args": ("bot/nominations", {'user__id': str(self.member.id)}), + "expected_args": ("bot/nominations", {"user__id": str(self.member.id)}), }, ) @@ -241,19 +241,19 @@ class UserInfractionHelperMethodTests(unittest.IsolatedAsyncioTestCase): "expected_lines": ["No nominations"], }, { - "api response": [{'active': True}], + "api response": [{"active": True}], "expected_lines": ["This user is **currently** nominated", "(1 nomination in total)"], }, { - "api response": [{'active': True}, {'active': False}], + "api response": [{"active": True}, {"active": False}], "expected_lines": ["This user is **currently** nominated", "(2 nominations in total)"], }, { - "api response": [{'active': False}], + "api response": [{"active": False}], "expected_lines": ["This user has 1 historical nomination, but is currently not nominated."], }, { - "api response": [{'active': False}, {'active': False}], + "api response": [{"active": False}, {"active": False}], "expected_lines": ["This user has 2 historical nominations, but is currently not nominated."], }, @@ -290,7 +290,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): user.nick = None user.__str__ = unittest.mock.Mock(return_value="Mr. Hemlock") user.colour = 0 - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) embed = await self.cog.create_user_embed(ctx, user, False) @@ -312,7 +312,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): user.nick = "Cat lover" user.__str__ = unittest.mock.Mock(return_value="Mr. Hemlock") user.colour = 0 - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) embed = await self.cog.create_user_embed(ctx, user, False) @@ -329,11 +329,11 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): async def test_create_user_embed_ignores_everyone_role(self): """Created `!user` embeds should not contain mention of the @everyone-role.""" ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=1)) - admins_role = helpers.MockRole(name='Admins') + admins_role = helpers.MockRole(name="Admins") # A `MockMember` has the @Everyone role by default; we add the Admins to that. user = helpers.MockMember(roles=[admins_role], colour=100) - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) embed = await self.cog.create_user_embed(ctx, user, False) @@ -354,13 +354,13 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): """The embed should contain expanded infractions and nomination info in mod channels.""" ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=50)) - moderators_role = helpers.MockRole(name='Moderators') + moderators_role = helpers.MockRole(name="Moderators") infraction_counts.return_value = ("Infractions", "expanded infractions info") nomination_counts.return_value = ("Nominations", "nomination info") user = helpers.MockMember(id=314, roles=[moderators_role], colour=100) - user.created_at = user.joined_at = datetime.utcfromtimestamp(1) + user.created_at = user.joined_at = datetime.fromtimestamp(1, tz=UTC) embed = await self.cog.create_user_embed(ctx, user, False) infraction_counts.assert_called_once_with(user) @@ -394,13 +394,13 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): """The embed should contain only basic infraction data outside of mod channels.""" ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=100)) - moderators_role = helpers.MockRole(name='Moderators') + moderators_role = helpers.MockRole(name="Moderators") infraction_counts.return_value = ("Infractions", "basic infractions info") user_messages.return_value = ("Messages", "user message counts") user = helpers.MockMember(id=314, roles=[moderators_role], colour=100) - user.created_at = user.joined_at = datetime.utcfromtimestamp(1) + user.created_at = user.joined_at = datetime.fromtimestamp(1, tz=UTC) embed = await self.cog.create_user_embed(ctx, user, False) infraction_counts.assert_called_once_with(user) @@ -444,10 +444,10 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): """The embed should be created with the colour of the top role, if a top role is available.""" ctx = helpers.MockContext() - moderators_role = helpers.MockRole(name='Moderators') + moderators_role = helpers.MockRole(name="Moderators") user = helpers.MockMember(id=314, roles=[moderators_role], colour=100) - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) embed = await self.cog.create_user_embed(ctx, user, False) self.assertEqual(embed.colour, discord.Colour(100)) @@ -465,7 +465,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): ctx = helpers.MockContext() user = helpers.MockMember(id=217, colour=discord.Colour.default()) - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) embed = await self.cog.create_user_embed(ctx, user, False) self.assertEqual(embed.colour, discord.Colour.og_blurple()) @@ -483,7 +483,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase): ctx = helpers.MockContext() user = helpers.MockMember(id=217, colour=0) - user.created_at = user.joined_at = datetime.utcnow() + user.created_at = user.joined_at = datetime.now(UTC) user.display_avatar.url = "avatar url" embed = await self.cog.create_user_embed(ctx, user, False) @@ -644,6 +644,6 @@ class RuleCommandTests(unittest.IsolatedAsyncioTestCase): for raw_user_input, expected_matched_rule_numbers in test_cases: with self.subTest(identifier=raw_user_input): final_rule_numbers = await self.cog.rules(self.cog, self.ctx, args=raw_user_input) - embed = self.ctx.send.call_args.kwargs['embed'] + embed = self.ctx.send.call_args.kwargs["embed"] self.assertEqual(information.DEFAULT_RULES_DESCRIPTION, embed.description) self.assertEqual(expected_matched_rule_numbers, final_rule_numbers) diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py index b78328137..26ba770dc 100644 --- a/tests/bot/exts/moderation/infraction/test_infractions.py +++ b/tests/bot/exts/moderation/infraction/test_infractions.py @@ -270,10 +270,9 @@ class CleanBanTests(unittest.IsolatedAsyncioTestCase): def inner(name): if name == "ModManagement": return self.management_cog if enable_manage else None - elif name == "Clean": + if name == "Clean": return self.clean_cog if enable_clean else None - else: - return DEFAULT + return DEFAULT return inner async def test_cleanban_falls_back_to_native_purge_without_clean_cog(self): diff --git a/tests/bot/exts/moderation/infraction/test_utils.py b/tests/bot/exts/moderation/infraction/test_utils.py index 122935e37..25337673e 100644 --- a/tests/bot/exts/moderation/infraction/test_utils.py +++ b/tests/bot/exts/moderation/infraction/test_utils.py @@ -1,6 +1,6 @@ import unittest from collections import namedtuple -from datetime import datetime +from datetime import UTC, datetime from unittest.mock import AsyncMock, MagicMock, patch from discord import Embed, Forbidden, HTTPException, NotFound @@ -136,7 +136,10 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase): """ test_cases = [ { - "args": (dict(id=0, type="ban", reason=None, expires_at=datetime(2020, 2, 26, 9, 20)), self.user), + "args": ( + dict(id=0, type="ban", reason=None, expires_at=datetime(2020, 2, 26, 9, 20, tzinfo=UTC)), + self.user, + ), "expected_output": Embed( title=utils.INFRACTION_TITLE, description=utils.INFRACTION_DESCRIPTION_TEMPLATE.format( @@ -192,7 +195,10 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase): "send_result": False }, { - "args": (dict(id=0, type="mute", reason="Test", expires_at=datetime(2020, 2, 26, 9, 20)), self.user), + "args": ( + dict(id=0, type="mute", reason="Test", expires_at=datetime(2020, 2, 26, 9, 20, tzinfo=UTC)), + self.user, + ), "expected_output": Embed( title=utils.INFRACTION_TITLE, description=utils.INFRACTION_DESCRIPTION_TEMPLATE.format( @@ -309,7 +315,7 @@ class TestPostInfraction(unittest.IsolatedAsyncioTestCase): async def test_normal_post_infraction(self): """Should return response from POST request if there are no errors.""" - now = datetime.utcnow() + now = datetime.now(UTC) expected = { "actor": self.ctx.author.id, "hidden": True, diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 53d98360c..1a02339d4 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -20,7 +20,7 @@ from tests.helpers import ( MockUser ) -CURRENT_TIME = datetime.datetime(2022, 1, 1, tzinfo=datetime.timezone.utc) +CURRENT_TIME = datetime.datetime(2022, 1, 1, tzinfo=datetime.UTC) class MockAsyncIterable: @@ -799,7 +799,7 @@ class TestMessageLinkEmbeds(TestIncidents): "\n".join("Lets make a new line test".split()): "Lets\nmake\na...", - 'Hello, World!' * 300: ( + "Hello, World!" * 300: ( "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" diff --git a/tests/bot/exts/moderation/test_silence.py b/tests/bot/exts/moderation/test_silence.py index 2622f46a7..ec0b3bf43 100644 --- a/tests/bot/exts/moderation/test_silence.py +++ b/tests/bot/exts/moderation/test_silence.py @@ -1,7 +1,6 @@ import itertools import unittest -from datetime import datetime, timezone -from typing import List, Tuple +from datetime import UTC, datetime from unittest import mock from unittest.mock import AsyncMock, Mock @@ -97,10 +96,12 @@ class SilenceNotifierTests(SilenceTest): """Alert is skipped on first loop or not an increment of 900.""" test_cases = (0, 15, 5000) for current_loop in test_cases: - with self.subTest(current_loop=current_loop): - with mock.patch.object(self.notifier, "_current_loop", new=current_loop): - await self.notifier._notifier() - self.alert_channel.send.assert_not_called() + with ( + self.subTest(current_loop=current_loop), + mock.patch.object(self.notifier, "_current_loop", new=current_loop), + ): + await self.notifier._notifier() + self.alert_channel.send.assert_not_called() @autospec(silence.Silence, "previous_overwrites", "unsilence_timestamps", pass_mocks=False) @@ -203,7 +204,7 @@ class SilenceCogTests(SilenceTest): self.assertEqual((None,), member.move_to.call_args_list[0].args) @staticmethod - def create_erroneous_members() -> Tuple[List[MockMember], List[MockMember]]: + def create_erroneous_members() -> tuple[list[MockMember], list[MockMember]]: """ Helper method to generate a list of members that error out on move_to call. @@ -363,7 +364,7 @@ class RescheduleTests(RedisTestCase): channels = [MockTextChannel(id=123), MockTextChannel(id=456)] self.bot.get_channel.side_effect = channels self.cog.unsilence_timestamps.items.return_value = [(123, 2000), (456, 3000)] - silence.datetime.now.return_value = datetime.fromtimestamp(1000, tz=timezone.utc) + silence.datetime.now.return_value = datetime.fromtimestamp(1000, tz=UTC) self.cog._unsilence_wrapper = mock.MagicMock() unsilence_return = self.cog._unsilence_wrapper.return_value @@ -426,17 +427,19 @@ class SilenceTests(SilenceTest): targets = (MockTextChannel(), MockVoiceChannel(), None) for (duration, message, was_silenced), target in itertools.product(test_cases, targets): - with mock.patch.object(self.cog, "_set_silence_overwrites", return_value=was_silenced): - with self.subTest(was_silenced=was_silenced, target=target, message=message): - with mock.patch.object(self.cog, "send_message") as send_message: - ctx = MockContext() - await self.cog.silence.callback(self.cog, ctx, target, duration) - send_message.assert_called_once_with( - message, - ctx.channel, - target or ctx.channel, - alert_target=was_silenced - ) + with ( + mock.patch.object(self.cog, "_set_silence_overwrites", return_value=was_silenced), + self.subTest(was_silenced=was_silenced, target=target, message=message), + mock.patch.object(self.cog, "send_message") as send_message + ): + ctx = MockContext() + await self.cog.silence.callback(self.cog, ctx, target, duration) + send_message.assert_called_once_with( + message, + ctx.channel, + target or ctx.channel, + alert_target=was_silenced + ) @voice_sync_helper async def test_sync_called(self, ctx, sync, kick): @@ -577,10 +580,10 @@ class SilenceTests(SilenceTest): new_overwrite_dict = dict(self.voice_overwrite) # Remove 'connect' & 'speak' keys because they were changed by the method. - del prev_overwrite_dict['connect'] - del prev_overwrite_dict['speak'] - del new_overwrite_dict['connect'] - del new_overwrite_dict['speak'] + del prev_overwrite_dict["connect"] + del prev_overwrite_dict["speak"] + del new_overwrite_dict["connect"] + del new_overwrite_dict["speak"] self.assertDictEqual(prev_overwrite_dict, new_overwrite_dict) @@ -617,13 +620,13 @@ class SilenceTests(SilenceTest): now_timestamp = 100 duration = 15 timestamp = now_timestamp + duration * 60 - datetime_mock.now.return_value = datetime.fromtimestamp(now_timestamp, tz=timezone.utc) + datetime_mock.now.return_value = datetime.fromtimestamp(now_timestamp, tz=UTC) ctx = MockContext(channel=self.text_channel) await self.cog.silence.callback(self.cog, ctx, duration) self.cog.unsilence_timestamps.set.assert_awaited_once_with(ctx.channel.id, timestamp) - datetime_mock.now.assert_called_once_with(tz=timezone.utc) # Ensure it's using an aware dt. + datetime_mock.now.assert_called_once_with(tz=UTC) # Ensure it's using an aware dt. async def test_cached_indefinite_time(self): """A value of -1 was cached for a permanent silence.""" @@ -697,13 +700,15 @@ class UnsilenceTests(SilenceTest): if target: target.overwrites_for.return_value = overwrite - with mock.patch.object(self.cog, "_unsilence", return_value=was_unsilenced): - with mock.patch.object(self.cog, "send_message") as send_message: - with self.subTest(was_unsilenced=was_unsilenced, overwrite=overwrite, target=target): - await self.cog.unsilence.callback(self.cog, ctx, channel=target) + with ( + mock.patch.object(self.cog, "_unsilence", return_value=was_unsilenced), + mock.patch.object(self.cog, "send_message") as send_message, + self.subTest(was_unsilenced=was_unsilenced, overwrite=overwrite, target=target), + ): + await self.cog.unsilence.callback(self.cog, ctx, channel=target) - call_args = (message, ctx.channel, target or ctx.channel) - send_message.assert_awaited_once_with(*call_args, alert_target=was_unsilenced) + call_args = (message, ctx.channel, target or ctx.channel) + send_message.assert_awaited_once_with(*call_args, alert_target=was_unsilenced) async def test_skipped_already_unsilenced(self): """Permissions were not set and `False` was returned for an already unsilenced channel.""" @@ -808,10 +813,10 @@ class UnsilenceTests(SilenceTest): new_overwrite_dict = dict(self.text_overwrite) # Remove these keys because they were modified by the unsilence. - del prev_overwrite_dict['send_messages'] - del prev_overwrite_dict['add_reactions'] - del new_overwrite_dict['send_messages'] - del new_overwrite_dict['add_reactions'] + del prev_overwrite_dict["send_messages"] + del prev_overwrite_dict["add_reactions"] + del new_overwrite_dict["send_messages"] + del new_overwrite_dict["add_reactions"] self.assertDictEqual(prev_overwrite_dict, new_overwrite_dict) @@ -826,10 +831,10 @@ class UnsilenceTests(SilenceTest): new_overwrite_dict = dict(self.voice_overwrite) # Remove these keys because they were modified by the unsilence. - del prev_overwrite_dict['connect'] - del prev_overwrite_dict['speak'] - del new_overwrite_dict['connect'] - del new_overwrite_dict['speak'] + del prev_overwrite_dict["connect"] + del prev_overwrite_dict["speak"] + del new_overwrite_dict["connect"] + del new_overwrite_dict["speak"] self.assertDictEqual(prev_overwrite_dict, new_overwrite_dict) diff --git a/tests/bot/exts/moderation/test_slowmode.py b/tests/bot/exts/moderation/test_slowmode.py index 5483b7a64..cf5101e16 100644 --- a/tests/bot/exts/moderation/test_slowmode.py +++ b/tests/bot/exts/moderation/test_slowmode.py @@ -17,24 +17,24 @@ class SlowmodeTests(unittest.IsolatedAsyncioTestCase): 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) + 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) + 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.') + 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.') + ("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: @@ -60,9 +60,9 @@ class SlowmodeTests(unittest.IsolatedAsyncioTestCase): 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.') + ("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: @@ -87,7 +87,7 @@ class SlowmodeTests(unittest.IsolatedAsyncioTestCase): async def test_reset_slowmode_sets_delay_to_zero(self) -> None: """Reset slowmode with a given channel.""" - text_channel = MockTextChannel(name='meta', slowmode_delay=1) + text_channel = MockTextChannel(name="meta", slowmode_delay=1) self.cog.set_slowmode = mock.AsyncMock() await self.cog.reset_slowmode(self.cog, self.ctx, text_channel) diff --git a/tests/bot/exts/recruitment/talentpool/test_review.py b/tests/bot/exts/recruitment/talentpool/test_review.py index f726fccc7..25622e91f 100644 --- a/tests/bot/exts/recruitment/talentpool/test_review.py +++ b/tests/bot/exts/recruitment/talentpool/test_review.py @@ -1,5 +1,5 @@ import unittest -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from unittest.mock import AsyncMock, Mock, patch from bot.exts.recruitment.talentpool import _review @@ -61,8 +61,8 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): @patch("bot.exts.recruitment.talentpool._review.MIN_REVIEW_INTERVAL", timedelta(days=1)) async def test_is_ready_for_review(self): """Tests for the `is_ready_for_review` function.""" - too_recent = datetime.now(timezone.utc) - timedelta(hours=1) - not_too_recent = datetime.now(timezone.utc) - timedelta(days=7) + too_recent = datetime.now(UTC) - timedelta(hours=1) + not_too_recent = datetime.now(UTC) - timedelta(days=7) cases = ( # Only one review, and not too recent, so ready. ( @@ -126,7 +126,7 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): @patch("bot.exts.recruitment.talentpool._review.MIN_NOMINATION_TIME", timedelta(days=7)) async def test_get_nomination_to_review(self): """Test get_nomination_to_review function.""" - now = datetime.now(timezone.utc) + now = datetime.now(UTC) # Each case contains a list of nominations, followed by the index in that list # of the one that should be selected, or None if None should be returned @@ -184,7 +184,7 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): @patch("bot.exts.recruitment.talentpool._review.MIN_NOMINATION_TIME", timedelta(days=0)) async def test_get_nomination_to_review_order(self): - now = datetime.now(timezone.utc) + now = datetime.now(UTC) # Each case in cases is a list of nominations in the order they should be chosen from first to last cases = [ diff --git a/tests/bot/exts/test_cogs.py b/tests/bot/exts/test_cogs.py index f8e120262..99bc87120 100644 --- a/tests/bot/exts/test_cogs.py +++ b/tests/bot/exts/test_cogs.py @@ -50,7 +50,7 @@ class CommandNameTests(unittest.TestCase): yield obj @staticmethod - def get_qualified_names(command: commands.Command) -> t.List[str]: + def get_qualified_names(command: commands.Command) -> list[str]: """Return a list of all qualified names, including aliases, for the `command`.""" names = [f"{command.full_parent_name} {alias}".strip() for alias in command.aliases] names.append(command.qualified_name) diff --git a/tests/bot/exts/utils/snekbox/test_io.py b/tests/bot/exts/utils/snekbox/test_io.py index bcf1162b8..4f7f49a5e 100644 --- a/tests/bot/exts/utils/snekbox/test_io.py +++ b/tests/bot/exts/utils/snekbox/test_io.py @@ -19,7 +19,7 @@ class SnekboxIOTests(TestCase): (r"A\0\tB", "A__B"), # Any other disallowed chars -> underscore (r"\\.txt", "_.txt"), - (r"A!@#$%^&*B, C()[]{}+=D.txt", "A_B_C_D.txt"), # noqa: P103 + (r"A!@#$%^&*B, C()[]{}+=D.txt", "A_B_C_D.txt"), (" ", "_"), # Normal file names should be unchanged ("legal_file-name.txt", "legal_file-name.txt"), diff --git a/tests/bot/exts/utils/snekbox/test_snekbox.py b/tests/bot/exts/utils/snekbox/test_snekbox.py index 79ac8ea2c..fa28aade8 100644 --- a/tests/bot/exts/utils/snekbox/test_snekbox.py +++ b/tests/bot/exts/utils/snekbox/test_snekbox.py @@ -43,7 +43,7 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): "files": [ { "path": "main.py", - "content": b64encode("import random".encode()).decode() + "content": b64encode(b"import random").decode() } ] } @@ -72,45 +72,45 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): async def test_codeblock_converter(self): ctx = MockContext() cases = ( - ('print("Hello world!")', 'print("Hello world!")', 'non-formatted'), - ('`print("Hello world!")`', 'print("Hello world!")', 'one line code block'), - ('```\nprint("Hello world!")```', 'print("Hello world!")', 'multiline code block'), - ('```py\nprint("Hello world!")```', 'print("Hello world!")', 'multiline python code block'), - ('text```print("Hello world!")```text', 'print("Hello world!")', 'code block surrounded by text'), + ('print("Hello world!")', 'print("Hello world!")', "non-formatted"), + ('`print("Hello world!")`', 'print("Hello world!")', "one line code block"), + ('```\nprint("Hello world!")```', 'print("Hello world!")', "multiline code block"), + ('```py\nprint("Hello world!")```', 'print("Hello world!")', "multiline python code block"), + ('text```print("Hello world!")```text', 'print("Hello world!")', "code block surrounded by text"), ('```print("Hello world!")```\ntext\n```py\nprint("Hello world!")```', - 'print("Hello world!")\nprint("Hello world!")', 'two code blocks with text in-between'), + 'print("Hello world!")\nprint("Hello world!")', "two code blocks with text in-between"), ('`print("Hello world!")`\ntext\n```print("How\'s it going?")```', - 'print("How\'s it going?")', 'code block preceded by inline code'), + 'print("How\'s it going?")', "code block preceded by inline code"), ('`print("Hello world!")`\ntext\n`print("Hello world!")`', - 'print("Hello world!")', 'one inline code block of two') + 'print("Hello world!")', "one inline code block of two") ) for case, expected, testname in cases: - with self.subTest(msg=f'Extract code from {testname}.'): + with self.subTest(msg=f"Extract code from {testname}."): self.assertEqual( - '\n'.join(await snekbox.CodeblockConverter.convert(ctx, case)), expected + "\n".join(await snekbox.CodeblockConverter.convert(ctx, case)), expected ) def test_prepare_timeit_input(self): """Test the prepare_timeit_input codeblock detection.""" - base_args = ('-m', 'timeit', '-s') + base_args = ("-m", "timeit", "-s") cases = ( - (['print("Hello World")'], '', 'single block of code'), - (['x = 1', 'print(x)'], 'x = 1', 'two blocks of code'), - (['x = 1', 'print(x)', 'print("Some other code.")'], 'x = 1', 'three blocks of code') + (['print("Hello World")'], "", "single block of code"), + (["x = 1", "print(x)"], "x = 1", "two blocks of code"), + (["x = 1", "print(x)", 'print("Some other code.")'], "x = 1", "three blocks of code") ) for case, setup_code, test_name in cases: setup = snekbox._cog.TIMEIT_SETUP_WRAPPER.format(setup=setup_code) - expected = [*base_args, setup, '\n'.join(case[1:] if setup_code else case)] - with self.subTest(msg=f'Test with {test_name} and expected return {expected}'): + expected = [*base_args, setup, "\n".join(case[1:] if setup_code else case)] + with self.subTest(msg=f"Test with {test_name} and expected return {expected}"): self.assertEqual(self.cog.prepare_timeit_input(case), expected) def test_eval_result_message(self): """EvalResult.get_message(), should return message.""" cases = ( - ('ERROR', None, ('Your 3.11 eval job has failed', 'ERROR', '')), - ('', 128 + snekbox._eval.SIGKILL, ('Your 3.11 eval job timed out or ran out of memory', '', '')), - ('', 255, ('Your 3.11 eval job has failed', 'A fatal NsJail error occurred', '')) + ("ERROR", None, ("Your 3.11 eval job has failed", "ERROR", "")), + ("", 128 + snekbox._eval.SIGKILL, ("Your 3.11 eval job timed out or ran out of memory", "", "")), + ("", 255, ("Your 3.11 eval job has failed", "A fatal NsJail error occurred", "")) ) for stdout, returncode, expected in cases: exp_msg, exp_err, exp_files_err = expected @@ -167,7 +167,7 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): msg = result.get_failed_files_str(char_max=10) self.assertEqual(msg, expected) - @patch('bot.exts.utils.snekbox._eval.Signals', side_effect=ValueError) + @patch("bot.exts.utils.snekbox._eval.Signals", side_effect=ValueError) def test_eval_result_message_invalid_signal(self, _mock_signals: Mock): result = EvalResult(stdout="", returncode=127) self.assertEqual( @@ -177,7 +177,7 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): self.assertEqual(result.error_message, "") self.assertEqual(result.files_error_message, "") - @patch('bot.exts.utils.snekbox._eval.Signals') + @patch("bot.exts.utils.snekbox._eval.Signals") def test_eval_result_message_valid_signal(self, mock_signals: Mock): mock_signals.return_value.name = "SIGTEST" result = EvalResult(stdout="", returncode=127) @@ -189,9 +189,9 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): def test_eval_result_status_emoji(self): """Return emoji according to the eval result.""" cases = ( - (' ', -1, ':warning:'), - ('Hello world!', 0, ':white_check_mark:'), - ('Invalid beard size', -1, ':x:') + (" ", -1, ":warning:"), + ("Hello world!", 0, ":white_check_mark:"), + ("Invalid beard size", -1, ":x:") ) for stdout, returncode, expected in cases: with self.subTest(stdout=stdout, returncode=returncode, expected=expected): @@ -200,48 +200,48 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): async def test_format_output(self): """Test output formatting.""" - self.cog.upload_output = AsyncMock(return_value='https://testificate.com/') + self.cog.upload_output = AsyncMock(return_value="https://testificate.com/") too_many_lines = ( - '001 | v\n002 | e\n003 | r\n004 | y\n005 | l\n006 | o\n' - '007 | n\n008 | g\n009 | b\n010 | e\n011 | a\n... (truncated - too many lines)' + "001 | v\n002 | e\n003 | r\n004 | y\n005 | l\n006 | o\n" + "007 | n\n008 | g\n009 | b\n010 | e\n011 | a\n... (truncated - too many lines)" ) too_long_too_many_lines = ( "\n".join( - f"{i:03d} | {line}" for i, line in enumerate(['verylongbeard' * 10] * 15, 1) + f"{i:03d} | {line}" for i, line in enumerate(["verylongbeard" * 10] * 15, 1) )[:1000] + "\n... (truncated - too long, too many lines)" ) cases = ( - ('', ('[No output]', None), 'No output'), - ('My awesome output', ('My awesome output', None), 'One line output'), - ('<@', ("<@\u200B", None), r'Convert <@ to <@\u200B'), - ('<!@', ("<!@\u200B", None), r'Convert <!@ to <!@\u200B'), + ("", ("[No output]", None), "No output"), + ("My awesome output", ("My awesome output", None), "One line output"), + ("<@", ("<@\u200B", None), r"Convert <@ to <@\u200B"), + ("<!@", ("<!@\u200B", None), r"Convert <!@ to <!@\u200B"), ( - '\u202E\u202E\u202E', - ('Code block escape attempt detected; will not output result', 'https://testificate.com/'), - 'Detect RIGHT-TO-LEFT OVERRIDE' + "\u202E\u202E\u202E", + ("Code block escape attempt detected; will not output result", "https://testificate.com/"), + "Detect RIGHT-TO-LEFT OVERRIDE" ), ( - '\u200B\u200B\u200B', - ('Code block escape attempt detected; will not output result', 'https://testificate.com/'), - 'Detect ZERO WIDTH SPACE' + "\u200B\u200B\u200B", + ("Code block escape attempt detected; will not output result", "https://testificate.com/"), + "Detect ZERO WIDTH SPACE" ), - ('long\nbeard', ('001 | long\n002 | beard', None), 'Two line output'), + ("long\nbeard", ("001 | long\n002 | beard", None), "Two line output"), ( - 'v\ne\nr\ny\nl\no\nn\ng\nb\ne\na\nr\nd', - (too_many_lines, 'https://testificate.com/'), - '12 lines output' + "v\ne\nr\ny\nl\no\nn\ng\nb\ne\na\nr\nd", + (too_many_lines, "https://testificate.com/"), + "12 lines output" ), ( - 'verylongbeard' * 100, - ('verylongbeard' * 76 + 'verylongbear\n... (truncated - too long)', 'https://testificate.com/'), - '1300 characters output' + "verylongbeard" * 100, + ("verylongbeard" * 76 + "verylongbear\n... (truncated - too long)", "https://testificate.com/"), + "1300 characters output" ), ( - ('verylongbeard' * 10 + '\n') * 15, - (too_long_too_many_lines, 'https://testificate.com/'), - '15 lines, 1965 characters output' + ("verylongbeard" * 10 + "\n") * 15, + (too_long_too_many_lines, "https://testificate.com/"), + "15 lines, 1965 characters output" ), ) for case, expected, testname in cases: @@ -257,10 +257,10 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): self.cog.send_job = AsyncMock(return_value=response) self.cog.continue_job = AsyncMock(return_value=None) - await self.cog.eval_command(self.cog, ctx=ctx, python_version='3.11', code=['MyAwesomeCode']) + await self.cog.eval_command(self.cog, ctx=ctx, python_version="3.11", code=["MyAwesomeCode"]) job = EvalJob.from_code("MyAwesomeCode") self.cog.send_job.assert_called_once_with(ctx, job) - self.cog.continue_job.assert_called_once_with(ctx, response, 'eval') + self.cog.continue_job.assert_called_once_with(ctx, response, "eval") async def test_eval_command_evaluate_twice(self): """Test the eval and re-eval command procedure.""" @@ -269,9 +269,9 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): ctx.command = MagicMock() self.cog.send_job = AsyncMock(return_value=response) self.cog.continue_job = AsyncMock() - self.cog.continue_job.side_effect = (EvalJob.from_code('MyAwesomeFormattedCode'), None) + self.cog.continue_job.side_effect = (EvalJob.from_code("MyAwesomeFormattedCode"), None) - await self.cog.eval_command(self.cog, ctx=ctx, python_version='3.11', code=['MyAwesomeCode']) + await self.cog.eval_command(self.cog, ctx=ctx, python_version="3.11", code=["MyAwesomeCode"]) expected_job = EvalJob.from_code("MyAwesomeFormattedCode") self.cog.send_job.assert_called_with(ctx, expected_job) @@ -285,7 +285,7 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): async def delay_with_side_effect(*args, **kwargs) -> dict: """Delay the post_job call to ensure the job runs long enough to conflict.""" await asyncio.sleep(1) - return {'stdout': '', 'returncode': 0} + return {"stdout": "", "returncode": 0} self.cog.post_job = AsyncMock(side_effect=delay_with_side_effect) with self.assertRaises(LockedResourceError): @@ -299,32 +299,32 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() - ctx.author = MockUser(mention='@LemonLemonishBeard#0042') + ctx.author = MockUser(mention="@LemonLemonishBeard#0042") eval_result = EvalResult("", 0) self.cog.post_job = AsyncMock(return_value=eval_result) - self.cog.format_output = AsyncMock(return_value=('[No output]', None)) + self.cog.format_output = AsyncMock(return_value=("[No output]", None)) self.cog.upload_output = AsyncMock() # Should not be called mocked_filter_cog = MagicMock() mocked_filter_cog.filter_snekbox_output = AsyncMock(return_value=(False, [])) self.bot.get_cog.return_value = mocked_filter_cog - job = EvalJob.from_code('MyAwesomeCode') + job = EvalJob.from_code("MyAwesomeCode") await self.cog.send_job(ctx, job), ctx.send.assert_called_once() self.assertEqual( ctx.send.call_args.args[0], - '@LemonLemonishBeard#0042 :warning: Your 3.11 eval job has completed ' - 'with return code 0.\n\n```\n[No output]\n```' + "@LemonLemonishBeard#0042 :warning: Your 3.11 eval job has completed " + "with return code 0.\n\n```\n[No output]\n```" ) - allowed_mentions = ctx.send.call_args.kwargs['allowed_mentions'] + allowed_mentions = ctx.send.call_args.kwargs["allowed_mentions"] expected_allowed_mentions = AllowedMentions(everyone=False, roles=False, users=[ctx.author]) self.assertEqual(allowed_mentions.to_dict(), expected_allowed_mentions.to_dict()) self.cog.post_job.assert_called_once_with(job) - self.cog.format_output.assert_called_once_with('') + self.cog.format_output.assert_called_once_with("") self.cog.upload_output.assert_not_called() async def test_send_job_with_paste_link(self): @@ -332,11 +332,11 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() - ctx.author.mention = '@LemonLemonishBeard#0042' + ctx.author.mention = "@LemonLemonishBeard#0042" eval_result = EvalResult("Way too long beard", 0) self.cog.post_job = AsyncMock(return_value=eval_result) - self.cog.format_output = AsyncMock(return_value=('Way too long beard', 'lookatmybeard.com')) + self.cog.format_output = AsyncMock(return_value=("Way too long beard", "lookatmybeard.com")) mocked_filter_cog = MagicMock() mocked_filter_cog.filter_snekbox_output = AsyncMock(return_value=(False, [])) @@ -348,20 +348,20 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): ctx.send.assert_called_once() self.assertEqual( ctx.send.call_args.args[0], - '@LemonLemonishBeard#0042 :white_check_mark: Your 3.11 eval job ' - 'has completed with return code 0.' - '\n\n```\nWay too long beard\n```\nFull output: lookatmybeard.com' + "@LemonLemonishBeard#0042 :white_check_mark: Your 3.11 eval job " + "has completed with return code 0." + "\n\n```\nWay too long beard\n```\nFull output: lookatmybeard.com" ) self.cog.post_job.assert_called_once_with(job) - self.cog.format_output.assert_called_once_with('Way too long beard') + self.cog.format_output.assert_called_once_with("Way too long beard") async def test_send_job_with_non_zero_eval(self): """Test the send_job function with a code returning a non-zero code.""" ctx = MockContext() ctx.message = MockMessage() ctx.send = AsyncMock() - ctx.author.mention = '@LemonLemonishBeard#0042' + ctx.author.mention = "@LemonLemonishBeard#0042" eval_result = EvalResult("ERROR", 127) self.cog.post_job = AsyncMock(return_value=eval_result) @@ -377,8 +377,8 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): ctx.send.assert_called_once() self.assertEqual( ctx.send.call_args.args[0], - '@LemonLemonishBeard#0042 :x: Your 3.11 eval job has completed with return code 127.' - '\n\n```\nERROR\n```' + "@LemonLemonishBeard#0042 :x: Your 3.11 eval job has completed with return code 127." + "\n\n```\nERROR\n```" ) self.cog.post_job.assert_called_once_with(job) @@ -436,11 +436,11 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): self.bot.wait_for.assert_has_awaits( ( call( - 'message_edit', + "message_edit", check=partial_mock(snekbox._cog.predicate_message_edit, ctx), timeout=snekbox._cog.REDO_TIMEOUT, ), - call('reaction_add', check=partial_mock(snekbox._cog.predicate_emoji_reaction, ctx), timeout=10) + call("reaction_add", check=partial_mock(snekbox._cog.predicate_emoji_reaction, ctx), timeout=10) ) ) ctx.message.add_reaction.assert_called_once_with(snekbox._cog.REDO_EMOJI) @@ -483,17 +483,17 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): def test_predicate_message_edit(self): """Test the predicate_message_edit function.""" - msg0 = MockMessage(id=1, content='abc') - msg1 = MockMessage(id=2, content='abcdef') - msg2 = MockMessage(id=1, content='abcdef') + 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') + (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}'): + with self.subTest(msg=f"Messages with {testname} return {expected}"): ctx = MockContext(message=ctx_msg) actual = snekbox._cog.predicate_message_edit(ctx, ctx_msg, new_msg) self.assertEqual(actual, expected) @@ -509,16 +509,16 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase): invalid_reaction_id.__str__.return_value = snekbox._cog.REDO_EMOJI invalid_user_id = MockUser(id=42) invalid_reaction_str = MockReaction(message=MockMessage(id=1)) - invalid_reaction_str.__str__.return_value = ':longbeard:' + 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') + (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}'): + with self.subTest(msg=f"Test with {testname} and expected return {expected}"): actual = snekbox._cog.predicate_emoji_reaction(valid_ctx, reaction, user) self.assertEqual(actual, expected) diff --git a/tests/bot/resources/test_resources.py b/tests/bot/resources/test_resources.py index 73937cfa6..77f92f100 100644 --- a/tests/bot/resources/test_resources.py +++ b/tests/bot/resources/test_resources.py @@ -7,7 +7,7 @@ class ResourceValidationTests(unittest.TestCase): """Validates resources used by the bot.""" def test_stars_valid(self): """The resource `bot/resources/stars.json` should contain a list of strings.""" - path = Path('bot', 'resources', 'stars.json') + path = Path("bot", "resources", "stars.json") content = path.read_text() data = json.loads(content) diff --git a/tests/bot/test_constants.py b/tests/bot/test_constants.py index f10d6fbe8..3492021ce 100644 --- a/tests/bot/test_constants.py +++ b/tests/bot/test_constants.py @@ -23,11 +23,7 @@ def is_annotation_instance(value: typing.Any, annotation: typing.Any) -> bool: def is_any_instance(value: typing.Any, types: typing.Collection) -> bool: """Return True if `value` is an instance of any type in `types`.""" - for type_ in types: - if is_annotation_instance(value, type_): - return True - - return False + return any(is_annotation_instance(value, type_) for type_ in types) class ConstantsTests(unittest.TestCase): @@ -39,7 +35,7 @@ class ConstantsTests(unittest.TestCase): sections = ( cls for (name, cls) in inspect.getmembers(constants) - if hasattr(cls, 'section') and isinstance(cls, type) + if hasattr(cls, "section") and isinstance(cls, type) ) for section in sections: for name, annotation in section.__annotations__.items(): diff --git a/tests/bot/test_converters.py b/tests/bot/test_converters.py index 1bb678db2..e5ccf27f7 100644 --- a/tests/bot/test_converters.py +++ b/tests/bot/test_converters.py @@ -1,6 +1,6 @@ import re import unittest -from datetime import MAXYEAR, datetime, timezone +from datetime import MAXYEAR, UTC, datetime from unittest.mock import MagicMock, patch from dateutil.relativedelta import relativedelta @@ -15,13 +15,13 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): @classmethod def setUpClass(cls): cls.context = MagicMock - cls.context.author = 'bob' + cls.context.author = "bob" - cls.fixed_utc_now = datetime.fromisoformat('2019-01-01T00:00:00+00:00') + cls.fixed_utc_now = datetime.fromisoformat("2019-01-01T00:00:00+00:00") async def test_package_name_for_valid(self): """PackageName returns valid package names unchanged.""" - test_values = ('foo', 'le_mon', 'num83r') + test_values = ("foo", "le_mon", "num83r") for name in test_values: with self.subTest(identifier=name): @@ -30,47 +30,46 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): async def test_package_name_for_invalid(self): """PackageName raises the proper exception for invalid package names.""" - test_values = ('text_with_a_dot.', 'UpperCaseName', 'dashed-name') + test_values = ("text_with_a_dot.", "UpperCaseName", "dashed-name") for name in test_values: - with self.subTest(identifier=name): - with self.assertRaises(BadArgument): - await PackageName.convert(self.context, name) + with self.subTest(identifier=name), self.assertRaises(BadArgument): + await PackageName.convert(self.context, name) async def test_duration_converter_for_valid(self): """Duration returns the correct `datetime` for valid duration strings.""" test_values = ( # Simple duration strings - ('1Y', {"years": 1}), - ('1y', {"years": 1}), - ('1year', {"years": 1}), - ('1years', {"years": 1}), - ('1m', {"months": 1}), - ('1month', {"months": 1}), - ('1months', {"months": 1}), - ('1w', {"weeks": 1}), - ('1W', {"weeks": 1}), - ('1week', {"weeks": 1}), - ('1weeks', {"weeks": 1}), - ('1d', {"days": 1}), - ('1D', {"days": 1}), - ('1day', {"days": 1}), - ('1days', {"days": 1}), - ('1h', {"hours": 1}), - ('1H', {"hours": 1}), - ('1hour', {"hours": 1}), - ('1hours', {"hours": 1}), - ('1M', {"minutes": 1}), - ('1minute', {"minutes": 1}), - ('1minutes', {"minutes": 1}), - ('1s', {"seconds": 1}), - ('1S', {"seconds": 1}), - ('1second', {"seconds": 1}), - ('1seconds', {"seconds": 1}), + ("1Y", {"years": 1}), + ("1y", {"years": 1}), + ("1year", {"years": 1}), + ("1years", {"years": 1}), + ("1m", {"months": 1}), + ("1month", {"months": 1}), + ("1months", {"months": 1}), + ("1w", {"weeks": 1}), + ("1W", {"weeks": 1}), + ("1week", {"weeks": 1}), + ("1weeks", {"weeks": 1}), + ("1d", {"days": 1}), + ("1D", {"days": 1}), + ("1day", {"days": 1}), + ("1days", {"days": 1}), + ("1h", {"hours": 1}), + ("1H", {"hours": 1}), + ("1hour", {"hours": 1}), + ("1hours", {"hours": 1}), + ("1M", {"minutes": 1}), + ("1minute", {"minutes": 1}), + ("1minutes", {"minutes": 1}), + ("1s", {"seconds": 1}), + ("1S", {"seconds": 1}), + ("1second", {"seconds": 1}), + ("1seconds", {"seconds": 1}), # Complex duration strings ( - '1y1m1w1d1H1M1S', + "1y1m1w1d1H1M1S", { "years": 1, "months": 1, @@ -81,13 +80,13 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): "seconds": 1 } ), - ('5y100S', {"years": 5, "seconds": 100}), - ('2w28H', {"weeks": 2, "hours": 28}), + ("5y100S", {"years": 5, "seconds": 100}), + ("2w28H", {"weeks": 2, "hours": 28}), # Duration strings with spaces - ('1 year 2 months', {"years": 1, "months": 2}), - ('1d 2H', {"days": 1, "hours": 2}), - ('1 week2 days', {"weeks": 1, "days": 2}), + ("1 year 2 months", {"years": 1, "months": 2}), + ("1d 2H", {"days": 1, "hours": 2}), + ("1 week2 days", {"weeks": 1, "days": 2}), ) converter = Duration() @@ -95,7 +94,7 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): for duration, duration_dict in test_values: expected_datetime = self.fixed_utc_now + relativedelta(**duration_dict) - with patch('bot.converters.datetime') as mock_datetime: + with patch("bot.converters.datetime") as mock_datetime: mock_datetime.now.return_value = self.fixed_utc_now with self.subTest(duration=duration, duration_dict=duration_dict): @@ -106,19 +105,19 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): """Duration raises the right exception for invalid duration strings.""" test_values = ( # Units in wrong order - '1d1w', - '1s1y', + "1d1w", + "1s1y", # Duplicated units - '1 year 2 years', - '1 M 10 minutes', + "1 year 2 years", + "1 M 10 minutes", # Unknown substrings - '1MVes', - '1y3breads', + "1MVes", + "1y3breads", # Missing amount - 'ym', + "ym", # Incorrect whitespace " 1y", @@ -126,15 +125,15 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): "1y 1m", # Garbage - 'Guido van Rossum', - 'lemon lemon lemon lemon lemon lemon lemon', + "Guido van Rossum", + "lemon lemon lemon lemon lemon lemon lemon", ) converter = Duration() for invalid_duration in test_values: with self.subTest(invalid_duration=invalid_duration): - exception_message = f'`{invalid_duration}` is not a valid duration string.' + exception_message = f"`{invalid_duration}` is not a valid duration string." with self.assertRaisesRegex(BadArgument, re.escape(exception_message)): await converter.convert(self.context, invalid_duration) @@ -151,44 +150,43 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): async def test_isodatetime_converter_for_valid(self): """ISODateTime converter returns correct datetime for valid datetime string.""" - utc = timezone.utc test_values = ( # `YYYY-mm-ddTHH:MM:SSZ` | `YYYY-mm-dd HH:MM:SSZ` - ('2019-09-02T02:03:05Z', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 02:03:05Z', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), + ("2019-09-02T02:03:05Z", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 02:03:05Z", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), # `YYYY-mm-ddTHH:MM:SS±HH:MM` | `YYYY-mm-dd HH:MM:SS±HH:MM` - ('2019-09-02T03:18:05+01:15', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 03:18:05+01:15', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02T00:48:05-01:15', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 00:48:05-01:15', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), + ("2019-09-02T03:18:05+01:15", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 03:18:05+01:15", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02T00:48:05-01:15", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 00:48:05-01:15", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), # `YYYY-mm-ddTHH:MM:SS±HHMM` | `YYYY-mm-dd HH:MM:SS±HHMM` - ('2019-09-02T03:18:05+0115', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 03:18:05+0115', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02T00:48:05-0115', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 00:48:05-0115', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), + ("2019-09-02T03:18:05+0115", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 03:18:05+0115", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02T00:48:05-0115", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 00:48:05-0115", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), # `YYYY-mm-ddTHH:MM:SS±HH` | `YYYY-mm-dd HH:MM:SS±HH` - ('2019-09-02 03:03:05+01', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02T01:03:05-01', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), + ("2019-09-02 03:03:05+01", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02T01:03:05-01", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), # `YYYY-mm-ddTHH:MM:SS` | `YYYY-mm-dd HH:MM:SS` - ('2019-09-02T02:03:05', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), - ('2019-09-02 02:03:05', datetime(2019, 9, 2, 2, 3, 5, tzinfo=utc)), + ("2019-09-02T02:03:05", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), + ("2019-09-02 02:03:05", datetime(2019, 9, 2, 2, 3, 5, tzinfo=UTC)), # `YYYY-mm-ddTHH:MM` | `YYYY-mm-dd HH:MM` - ('2019-11-12T09:15', datetime(2019, 11, 12, 9, 15, tzinfo=utc)), - ('2019-11-12 09:15', datetime(2019, 11, 12, 9, 15, tzinfo=utc)), + ("2019-11-12T09:15", datetime(2019, 11, 12, 9, 15, tzinfo=UTC)), + ("2019-11-12 09:15", datetime(2019, 11, 12, 9, 15, tzinfo=UTC)), # `YYYY-mm-dd` - ('2019-04-01', datetime(2019, 4, 1, tzinfo=utc)), + ("2019-04-01", datetime(2019, 4, 1, tzinfo=UTC)), # `YYYY-mm` - ('2019-02-01', datetime(2019, 2, 1, tzinfo=utc)), + ("2019-02-01", datetime(2019, 2, 1, tzinfo=UTC)), # `YYYY` - ('2025', datetime(2025, 1, 1, tzinfo=utc)), + ("2025", datetime(2025, 1, 1, tzinfo=UTC)), ) converter = ISODateTime() @@ -202,19 +200,19 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): """ISODateTime converter raises the correct exception for invalid datetime strings.""" test_values = ( # Make sure it doesn't interfere with the Duration converter - '1Y', - '1d', - '1H', + "1Y", + "1d", + "1H", # Check if it fails when only providing the optional time part - '10:10:10', - '10:00', + "10:10:10", + "10:00", # Invalid date format - '19-01-01', + "19-01-01", # Other non-valid strings - 'fisk the tag master', + "fisk the tag master", ) converter = ISODateTime() @@ -249,6 +247,8 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase): ) converter = HushDurationConverter() for invalid_minutes_string, exception_message in test_values: - with self.subTest(invalid_minutes_string=invalid_minutes_string, exception_message=exception_message): - with self.assertRaisesRegex(BadArgument, re.escape(exception_message)): - await converter.convert(self.context, invalid_minutes_string) + with ( + self.subTest(invalid_minutes_string=invalid_minutes_string, exception_message=exception_message), + self.assertRaisesRegex(BadArgument, re.escape(exception_message)), + ): + await converter.convert(self.context, invalid_minutes_string) diff --git a/tests/bot/test_decorators.py b/tests/bot/test_decorators.py index 3d450caa0..6a04123a7 100644 --- a/tests/bot/test_decorators.py +++ b/tests/bot/test_decorators.py @@ -142,6 +142,8 @@ class InWhitelistTests(unittest.TestCase): with unittest.mock.patch("bot.decorators.commands.check", new=lambda predicate: predicate): predicate = in_whitelist(**test_case.kwargs) - with self.subTest(test_description=test_case.description): - with self.assertRaisesRegex(InWhitelistCheckFailure, exception_message): - predicate(test_case.ctx) + with ( + self.subTest(test_description=test_case.description), + self.assertRaisesRegex(InWhitelistCheckFailure, exception_message), + ): + predicate(test_case.ctx) diff --git a/tests/bot/test_pagination.py b/tests/bot/test_pagination.py index 630f2516d..cf23f1948 100644 --- a/tests/bot/test_pagination.py +++ b/tests/bot/test_pagination.py @@ -8,39 +8,39 @@ class LinePaginatorTests(TestCase): def setUp(self): """Create a paginator for the test method.""" - self.paginator = pagination.LinePaginator(prefix='', suffix='', max_size=30, + self.paginator = pagination.LinePaginator(prefix="", suffix="", max_size=30, scale_to_size=50) def test_add_line_works_on_small_lines(self): """`add_line` should allow small lines to be added.""" - self.paginator.add_line('x' * (self.paginator.max_size - 3)) + self.paginator.add_line("x" * (self.paginator.max_size - 3)) # Note that the page isn't added to _pages until it's full. self.assertEqual(len(self.paginator._pages), 0) def test_add_line_works_on_long_lines(self): """After additional lines after `max_size` is exceeded should go on the next page.""" - self.paginator.add_line('x' * self.paginator.max_size) + self.paginator.add_line("x" * self.paginator.max_size) self.assertEqual(len(self.paginator._pages), 0) # Any additional lines should start a new page after `max_size` is exceeded. - self.paginator.add_line('x') + self.paginator.add_line("x") self.assertEqual(len(self.paginator._pages), 1) def test_add_line_continuation(self): """When `scale_to_size` is exceeded, remaining words should be split onto the next page.""" - self.paginator.add_line('zyz ' * (self.paginator.scale_to_size//4 + 1)) + self.paginator.add_line("zyz " * (self.paginator.scale_to_size//4 + 1)) self.assertEqual(len(self.paginator._pages), 1) def test_add_line_no_continuation(self): """If adding a new line to an existing page would exceed `max_size`, it should start a new page rather than using continuation. """ - self.paginator.add_line('z' * (self.paginator.max_size - 3)) - self.paginator.add_line('z') + self.paginator.add_line("z" * (self.paginator.max_size - 3)) + self.paginator.add_line("z") self.assertEqual(len(self.paginator._pages), 1) def test_add_line_truncates_very_long_words(self): """`add_line` should truncate if a single long word exceeds `scale_to_size`.""" - self.paginator.add_line('x' * (self.paginator.scale_to_size + 1)) + self.paginator.add_line("x" * (self.paginator.scale_to_size + 1)) # Note: item at index 1 is the truncated line, index 0 is prefix - self.assertEqual(self.paginator._current_page[1], 'x' * self.paginator.scale_to_size) + self.assertEqual(self.paginator._current_page[1], "x" * self.paginator.scale_to_size) diff --git a/tests/bot/utils/test_message_cache.py b/tests/bot/utils/test_message_cache.py index 04bfd28d1..ad3f4e8b6 100644 --- a/tests/bot/utils/test_message_cache.py +++ b/tests/bot/utils/test_message_cache.py @@ -157,9 +157,8 @@ class TestMessageCache(unittest.TestCase): cache.append(msg) for current_loop in test_cases: - with self.subTest(current_loop=current_loop): - with self.assertRaises(IndexError): - cache[current_loop] + with self.subTest(current_loop=current_loop), self.assertRaises(IndexError): + cache[current_loop] def test_slicing_with_unfilled_cache(self): """Test if slicing returns the correct messages if the cache is not yet fully filled.""" diff --git a/tests/bot/utils/test_time.py b/tests/bot/utils/test_time.py index 120d65176..6244a3548 100644 --- a/tests/bot/utils/test_time.py +++ b/tests/bot/utils/test_time.py @@ -1,5 +1,5 @@ import unittest -from datetime import datetime, timezone +from datetime import UTC, datetime from dateutil.relativedelta import relativedelta @@ -13,23 +13,23 @@ class TimeTests(unittest.TestCase): """humanize_delta should be able to handle unknown units, and will not abort.""" # Does not abort for unknown units, as the unit name is checked # against the attribute of the relativedelta instance. - actual = time.humanize_delta(relativedelta(days=2, hours=2), precision='elephants', max_units=2) - self.assertEqual(actual, '2 days and 2 hours') + actual = time.humanize_delta(relativedelta(days=2, hours=2), precision="elephants", max_units=2) + self.assertEqual(actual, "2 days and 2 hours") def test_humanize_delta_handle_high_units(self): """humanize_delta should be able to handle very high units.""" # Very high maximum units, but it only ever iterates over # each value the relativedelta might have. - actual = time.humanize_delta(relativedelta(days=2, hours=2), precision='hours', max_units=20) - self.assertEqual(actual, '2 days and 2 hours') + actual = time.humanize_delta(relativedelta(days=2, hours=2), precision="hours", max_units=20) + self.assertEqual(actual, "2 days and 2 hours") def test_humanize_delta_should_normal_usage(self): """Testing humanize delta.""" test_cases = ( - (relativedelta(days=2), 'seconds', 1, '2 days'), - (relativedelta(days=2, hours=2), 'seconds', 2, '2 days and 2 hours'), - (relativedelta(days=2, hours=2), 'seconds', 1, '2 days'), - (relativedelta(days=2, hours=2), 'days', 2, '2 days'), + (relativedelta(days=2), "seconds", 1, "2 days"), + (relativedelta(days=2, hours=2), "seconds", 2, "2 days and 2 hours"), + (relativedelta(days=2, hours=2), "seconds", 1, "2 days"), + (relativedelta(days=2, hours=2), "days", 2, "2 days"), ) for delta, precision, max_units, expected in test_cases: @@ -43,8 +43,8 @@ class TimeTests(unittest.TestCase): for max_units in test_cases: with self.subTest(max_units=max_units), self.assertRaises(ValueError) as error: - time.humanize_delta(relativedelta(days=2, hours=2), precision='hours', max_units=max_units) - self.assertEqual(str(error.exception), 'max_units must be positive.') + time.humanize_delta(relativedelta(days=2, hours=2), precision="hours", max_units=max_units) + self.assertEqual(str(error.exception), "max_units must be positive.") def test_format_with_duration_none_expiry(self): """format_with_duration should work for None expiry.""" @@ -52,9 +52,9 @@ class TimeTests(unittest.TestCase): (None, None, None, None), # To make sure that date_from and max_units are not touched - (None, 'Why hello there!', None, None), - (None, None, float('inf'), None), - (None, 'Why hello there!', float('inf'), None), + (None, "Why hello there!", None, None), + (None, None, float("inf"), None), + (None, "Why hello there!", float("inf"), None), ) for expiry, date_from, max_units, expected in test_cases: @@ -64,10 +64,10 @@ class TimeTests(unittest.TestCase): def test_format_with_duration_custom_units(self): """format_with_duration should work for custom max_units.""" test_cases = ( - ('3000-12-12T00:01:00Z', datetime(3000, 12, 11, 12, 5, 5, tzinfo=timezone.utc), 6, - '<t:32533488060:f> (11 hours, 55 minutes and 55 seconds)'), - ('3000-11-23T20:09:00Z', datetime(3000, 4, 25, 20, 15, tzinfo=timezone.utc), 20, - '<t:32531918940:f> (6 months, 28 days, 23 hours and 54 minutes)') + ("3000-12-12T00:01:00Z", datetime(3000, 12, 11, 12, 5, 5, tzinfo=UTC), 6, + "<t:32533488060:f> (11 hours, 55 minutes and 55 seconds)"), + ("3000-11-23T20:09:00Z", datetime(3000, 4, 25, 20, 15, tzinfo=UTC), 20, + "<t:32531918940:f> (6 months, 28 days, 23 hours and 54 minutes)") ) for expiry, date_from, max_units, expected in test_cases: @@ -76,23 +76,22 @@ class TimeTests(unittest.TestCase): def test_format_with_duration_normal_usage(self): """format_with_duration should work for normal usage, across various durations.""" - utc = timezone.utc test_cases = ( - ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5, tzinfo=utc), 2, - '<t:1576108860:f> (12 hours and 55 seconds)'), - ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5, tzinfo=utc), 1, '<t:1576108860:f> (12 hours)'), - ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59, tzinfo=utc), 2, '<t:1576108800:f> (1 minute)'), - ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15, tzinfo=utc), 2, - '<t:1574539740:f> (7 days and 23 hours)'), - ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15, tzinfo=utc), 2, - '<t:1574539740:f> (6 months and 28 days)'), - ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53, tzinfo=utc), 2, '<t:1574542680:f> (5 minutes)'), - ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0, tzinfo=utc), 2, '<t:1574553600:f> (1 minute)'), - ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0, tzinfo=utc), 2, - '<t:1574553540:f> (2 years and 4 months)'), - ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5, tzinfo=utc), 2, - '<t:1574553540:f> (9 minutes and 55 seconds)'), - (None, datetime(2019, 11, 23, 23, 49, 5), 2, None), + ("2019-12-12T00:01:00Z", datetime(2019, 12, 11, 12, 0, 5, tzinfo=UTC), 2, + "<t:1576108860:f> (12 hours and 55 seconds)"), + ("2019-12-12T00:01:00Z", datetime(2019, 12, 11, 12, 0, 5, tzinfo=UTC), 1, "<t:1576108860:f> (12 hours)"), + ("2019-12-12T00:00:00Z", datetime(2019, 12, 11, 23, 59, tzinfo=UTC), 2, "<t:1576108800:f> (1 minute)"), + ("2019-11-23T20:09:00Z", datetime(2019, 11, 15, 20, 15, tzinfo=UTC), 2, + "<t:1574539740:f> (7 days and 23 hours)"), + ("2019-11-23T20:09:00Z", datetime(2019, 4, 25, 20, 15, tzinfo=UTC), 2, + "<t:1574539740:f> (6 months and 28 days)"), + ("2019-11-23T20:58:00Z", datetime(2019, 11, 23, 20, 53, tzinfo=UTC), 2, "<t:1574542680:f> (5 minutes)"), + ("2019-11-24T00:00:00Z", datetime(2019, 11, 23, 23, 59, 0, tzinfo=UTC), 2, "<t:1574553600:f> (1 minute)"), + ("2019-11-23T23:59:00Z", datetime(2017, 7, 21, 23, 0, tzinfo=UTC), 2, + "<t:1574553540:f> (2 years and 4 months)"), + ("2019-11-23T23:59:00Z", datetime(2019, 11, 23, 23, 49, 5, tzinfo=UTC), 2, + "<t:1574553540:f> (9 minutes and 55 seconds)"), + (None, datetime(2019, 11, 23, 23, 49, 5, tzinfo=UTC), 2, None), ) for expiry, date_from, max_units, expected in test_cases: @@ -106,8 +105,8 @@ class TimeTests(unittest.TestCase): def test_until_expiration_with_duration_custom_units(self): """until_expiration should work for custom max_units.""" test_cases = ( - ('3000-12-12T00:01:00Z', '<t:32533488060:R>'), - ('3000-11-23T20:09:00Z', '<t:32531918940:R>') + ("3000-12-12T00:01:00Z", "<t:32533488060:R>"), + ("3000-11-23T20:09:00Z", "<t:32531918940:R>") ) for expiry, expected in test_cases: @@ -117,11 +116,11 @@ class TimeTests(unittest.TestCase): def test_until_expiration_normal_usage(self): """until_expiration should work for normal usage, across various durations.""" test_cases = ( - ('3000-12-12T00:01:00Z', '<t:32533488060:R>'), - ('3000-12-12T00:01:00Z', '<t:32533488060:R>'), - ('3000-12-12T00:00:00Z', '<t:32533488000:R>'), - ('3000-11-23T20:09:00Z', '<t:32531918940:R>'), - ('3000-11-23T20:09:00Z', '<t:32531918940:R>'), + ("3000-12-12T00:01:00Z", "<t:32533488060:R>"), + ("3000-12-12T00:01:00Z", "<t:32533488060:R>"), + ("3000-12-12T00:00:00Z", "<t:32533488000:R>"), + ("3000-11-23T20:09:00Z", "<t:32531918940:R>"), + ("3000-11-23T20:09:00Z", "<t:32531918940:R>"), ) for expiry, expected in test_cases: diff --git a/tests/helpers.py b/tests/helpers.py index 020f1aee5..bb12c4977 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -5,7 +5,7 @@ import itertools import logging import unittest.mock from asyncio import AbstractEventLoop -from typing import Iterable, Optional +from collections.abc import Iterable import discord from aiohttp import ClientSession @@ -79,7 +79,7 @@ class CustomMockMixin: additional_spec_asyncs = None def __init__(self, **kwargs): - name = kwargs.pop('name', None) # `name` has special meaning for Mock classes, so we need to set it manually. + name = kwargs.pop("name", None) # `name` has special meaning for Mock classes, so we need to set it manually. super().__init__(spec_set=self.spec_set, **kwargs) if self.additional_spec_asyncs: @@ -101,7 +101,7 @@ class CustomMockMixin: This override will look for an attribute called `child_mock_type` and use that as the type of the child mock. """ _new_name = kw.get("_new_name") - if _new_name in self.__dict__['_spec_asyncs']: + if _new_name in self.__dict__["_spec_asyncs"]: return unittest.mock.AsyncMock(**kw) _type = type(self) @@ -121,23 +121,23 @@ class CustomMockMixin: # Create a guild instance to get a realistic Mock of `discord.Guild` guild_data = { - 'id': 1, - 'name': 'guild', - 'region': 'Europe', - 'verification_level': 2, - 'default_notications': 1, - 'afk_timeout': 100, - 'icon': "icon.png", - 'banner': 'banner.png', - 'mfa_level': 1, - 'splash': 'splash.png', - 'system_channel_id': 464033278631084042, - 'description': 'mocking is fun', - 'max_presences': 10_000, - 'max_members': 100_000, - 'preferred_locale': 'UTC', - 'owner_id': 1, - 'afk_channel_id': 464033278631084042, + "id": 1, + "name": "guild", + "region": "Europe", + "verification_level": 2, + "default_notications": 1, + "afk_timeout": 100, + "icon": "icon.png", + "banner": "banner.png", + "mfa_level": 1, + "splash": "splash.png", + "system_channel_id": 464033278631084042, + "description": "mocking is fun", + "max_presences": 10_000, + "max_members": 100_000, + "preferred_locale": "UTC", + "owner_id": 1, + "afk_channel_id": 464033278631084042, } guild_instance = discord.Guild(data=guild_data, state=unittest.mock.MagicMock()) @@ -170,8 +170,8 @@ class MockGuild(CustomMockMixin, unittest.mock.Mock, HashableMixin): """ spec_set = guild_instance - def __init__(self, roles: Optional[Iterable[MockRole]] = None, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id), 'members': [], "chunked": True} + def __init__(self, roles: Iterable[MockRole] | None = None, **kwargs) -> None: + default_kwargs = {"id": next(self.discord_id), "members": [], "chunked": True} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) self.roles = [MockRole(name="@everyone", position=1, id=0)] @@ -180,7 +180,7 @@ class MockGuild(CustomMockMixin, unittest.mock.Mock, HashableMixin): # Create a Role instance to get a realistic Mock of `discord.Role` -role_data = {'name': 'role', 'id': 1} +role_data = {"name": "role", "id": 1} role_instance = discord.Role(guild=guild_instance, state=unittest.mock.MagicMock(), data=role_data) @@ -195,11 +195,11 @@ class MockRole(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin): def __init__(self, **kwargs) -> None: default_kwargs = { - 'id': next(self.discord_id), - 'name': 'role', - 'position': 1, - 'colour': discord.Colour(0xdeadbf), - 'permissions': discord.Permissions(), + "id": next(self.discord_id), + "name": "role", + "position": 1, + "colour": discord.Colour(0xdeadbf), + "permissions": discord.Permissions(), } super().__init__(**collections.ChainMap(kwargs, default_kwargs)) @@ -209,8 +209,8 @@ class MockRole(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin): if isinstance(self.permissions, int): self.permissions = discord.Permissions(self.permissions) - if 'mention' not in kwargs: - self.mention = f'&{self.name}' + if "mention" not in kwargs: + self.mention = f"&{self.name}" def __lt__(self, other): """Simplified position-based comparisons similar to those of `discord.Role`.""" @@ -222,7 +222,7 @@ class MockRole(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin): # Create a Member instance to get a realistic Mock of `discord.Member` -member_data = {'user': 'lemon', 'roles': [1], 'flags': 2} +member_data = {"user": "lemon", "roles": [1], "flags": 2} state_mock = unittest.mock.MagicMock() member_instance = discord.Member(data=member_data, guild=guild_instance, state=state_mock) @@ -236,8 +236,8 @@ class MockMember(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin """ spec_set = member_instance - def __init__(self, roles: Optional[Iterable[MockRole]] = None, **kwargs) -> None: - default_kwargs = {'name': 'member', 'id': next(self.discord_id), 'bot': False, "pending": False} + def __init__(self, roles: Iterable[MockRole] | None = None, **kwargs) -> None: + default_kwargs = {"name": "member", "id": next(self.discord_id), "bot": False, "pending": False} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) self.roles = [MockRole(name="@everyone", position=1, id=0)] @@ -245,7 +245,7 @@ class MockMember(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin self.roles.extend(roles) self.top_role = max(self.roles) - if 'mention' not in kwargs: + if "mention" not in kwargs: self.mention = f"@{self.name}" @@ -269,10 +269,10 @@ class MockUser(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin): spec_set = user_instance def __init__(self, **kwargs) -> None: - default_kwargs = {'name': 'user', 'id': next(self.discord_id), 'bot': False} + default_kwargs = {"name": "user", "id": next(self.discord_id), "bot": False} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) - if 'mention' not in kwargs: + if "mention" not in kwargs: self.mention = f"@{self.name}" @@ -331,16 +331,16 @@ class MockBot(CustomMockMixin, unittest.mock.MagicMock): # Create a TextChannel instance to get a realistic MagicMock of `discord.TextChannel` channel_data = { - 'id': 1, - 'type': 'TextChannel', - 'name': 'channel', - 'parent_id': 1234567890, - 'topic': 'topic', - 'position': 1, - 'nsfw': False, - 'last_message_id': 1, - 'bitrate': 1337, - 'user_limit': 25, + "id": 1, + "type": "TextChannel", + "name": "channel", + "parent_id": 1234567890, + "topic": "topic", + "position": 1, + "nsfw": False, + "last_message_id": 1, + "bitrate": 1337, + "user_limit": 25, } state = unittest.mock.MagicMock() guild = unittest.mock.MagicMock() @@ -360,10 +360,10 @@ class MockTextChannel(CustomMockMixin, unittest.mock.Mock, HashableMixin): spec_set = text_channel_instance def __init__(self, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id), 'name': 'channel', 'guild': MockGuild()} + default_kwargs = {"id": next(self.discord_id), "name": "channel", "guild": MockGuild()} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) - if 'mention' not in kwargs: + if "mention" not in kwargs: self.mention = f"#{self.name}" @@ -377,10 +377,10 @@ class MockVoiceChannel(CustomMockMixin, unittest.mock.Mock, HashableMixin): spec_set = voice_channel_instance def __init__(self, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id), 'name': 'channel', 'guild': MockGuild()} + default_kwargs = {"id": next(self.discord_id), "name": "channel", "guild": MockGuild()} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) - if 'mention' not in kwargs: + if "mention" not in kwargs: self.mention = f"#{self.name}" @@ -401,16 +401,16 @@ class MockDMChannel(CustomMockMixin, unittest.mock.Mock, HashableMixin): spec_set = dm_channel_instance def __init__(self, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id), 'recipient': MockUser(), "me": MockUser(), 'guild': None} + default_kwargs = {"id": next(self.discord_id), "recipient": MockUser(), "me": MockUser(), "guild": None} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) # Create CategoryChannel instance to get a realistic MagicMock of `discord.CategoryChannel` category_channel_data = { - 'id': 1, - 'type': discord.ChannelType.category, - 'name': 'category', - 'position': 1, + "id": 1, + "type": discord.ChannelType.category, + "name": "category", + "position": 1, } state = unittest.mock.MagicMock() @@ -422,26 +422,26 @@ category_channel_instance = discord.CategoryChannel( class MockCategoryChannel(CustomMockMixin, unittest.mock.Mock, HashableMixin): def __init__(self, **kwargs) -> None: - default_kwargs = {'id': next(self.discord_id)} + default_kwargs = {"id": next(self.discord_id)} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) # Create a Message instance to get a realistic MagicMock of `discord.Message` message_data = { - 'id': 1, - 'webhook_id': 431341013479718912, - 'attachments': [], - 'embeds': [], - 'application': {"id": 4, "description": "A Python Bot", "name": "Python Discord", "icon": None}, - 'activity': 'mocking', - 'channel': unittest.mock.MagicMock(), - 'edited_timestamp': '2019-10-14T15:33:48+00:00', - 'type': 'message', - 'pinned': False, - 'mention_everyone': False, - 'tts': None, - 'content': 'content', - 'nonce': None, + "id": 1, + "webhook_id": 431341013479718912, + "attachments": [], + "embeds": [], + "application": {"id": 4, "description": "A Python Bot", "name": "Python Discord", "icon": None}, + "activity": "mocking", + "channel": unittest.mock.MagicMock(), + "edited_timestamp": "2019-10-14T15:33:48+00:00", + "type": "message", + "pinned": False, + "mention_everyone": False, + "tts": None, + "content": "content", + "nonce": None, } state = unittest.mock.MagicMock() channel = unittest.mock.MagicMock() @@ -470,13 +470,13 @@ class MockContext(CustomMockMixin, unittest.mock.MagicMock): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.me = kwargs.get('me', MockMember()) - self.bot = kwargs.get('bot', MockBot()) - self.guild = kwargs.get('guild', MockGuild()) - self.author = kwargs.get('author', MockMember()) - self.channel = kwargs.get('channel', MockTextChannel()) - self.message = kwargs.get('message', MockMessage()) - self.invoked_from_error_handler = kwargs.get('invoked_from_error_handler', False) + self.me = kwargs.get("me", MockMember()) + self.bot = kwargs.get("bot", MockBot()) + self.guild = kwargs.get("guild", MockGuild()) + self.author = kwargs.get("author", MockMember()) + self.channel = kwargs.get("channel", MockTextChannel()) + self.message = kwargs.get("message", MockMessage()) + self.invoked_from_error_handler = kwargs.get("invoked_from_error_handler", False) class MockInteraction(CustomMockMixin, unittest.mock.MagicMock): @@ -489,13 +489,13 @@ class MockInteraction(CustomMockMixin, unittest.mock.MagicMock): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.me = kwargs.get('me', MockMember()) - self.client = kwargs.get('client', MockBot()) - self.guild = kwargs.get('guild', MockGuild()) - self.user = kwargs.get('user', MockMember()) - self.channel = kwargs.get('channel', MockTextChannel()) - self.message = kwargs.get('message', MockMessage()) - self.invoked_from_error_handler = kwargs.get('invoked_from_error_handler', False) + self.me = kwargs.get("me", MockMember()) + self.client = kwargs.get("client", MockBot()) + self.guild = kwargs.get("guild", MockGuild()) + self.user = kwargs.get("user", MockMember()) + self.channel = kwargs.get("channel", MockTextChannel()) + self.message = kwargs.get("message", MockMessage()) + self.invoked_from_error_handler = kwargs.get("invoked_from_error_handler", False) attachment_instance = discord.Attachment(data=unittest.mock.MagicMock(id=1), state=unittest.mock.MagicMock()) @@ -543,10 +543,10 @@ class MockMessage(CustomMockMixin, unittest.mock.MagicMock): spec_set = message_instance def __init__(self, **kwargs) -> None: - default_kwargs = {'attachments': []} + default_kwargs = {"attachments": []} super().__init__(**collections.ChainMap(kwargs, default_kwargs)) - self.author = kwargs.get('author', MockMember()) - self.channel = kwargs.get('channel', MockTextChannel()) + self.author = kwargs.get("author", MockMember()) + self.channel = kwargs.get("channel", MockTextChannel()) class MockInteractionMessage(MockMessage): @@ -556,10 +556,9 @@ class MockInteractionMessage(MockMessage): Instances of this class will follow the specifications of `discord.InteractionMessage` instances. For more information, see the `MockGuild` docstring. """ - pass -emoji_data = {'require_colons': True, 'managed': True, 'id': 1, 'name': 'hyperlemon'} +emoji_data = {"require_colons": True, "managed": True, "id": 1, "name": "hyperlemon"} emoji_instance = discord.Emoji(guild=MockGuild(), state=unittest.mock.MagicMock(), data=emoji_data) @@ -574,10 +573,10 @@ class MockEmoji(CustomMockMixin, unittest.mock.MagicMock): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.guild = kwargs.get('guild', MockGuild()) + self.guild = kwargs.get("guild", MockGuild()) -partial_emoji_instance = discord.PartialEmoji(animated=False, name='guido') +partial_emoji_instance = discord.PartialEmoji(animated=False, name="guido") class MockPartialEmoji(CustomMockMixin, unittest.mock.MagicMock): @@ -590,7 +589,7 @@ class MockPartialEmoji(CustomMockMixin, unittest.mock.MagicMock): spec_set = partial_emoji_instance -reaction_instance = discord.Reaction(message=MockMessage(), data={'me': True}, emoji=MockEmoji()) +reaction_instance = discord.Reaction(message=MockMessage(), data={"me": True}, emoji=MockEmoji()) class MockReaction(CustomMockMixin, unittest.mock.MagicMock): @@ -605,8 +604,8 @@ class MockReaction(CustomMockMixin, unittest.mock.MagicMock): def __init__(self, **kwargs) -> None: _users = kwargs.pop("users", []) super().__init__(**kwargs) - self.emoji = kwargs.get('emoji', MockEmoji()) - self.message = kwargs.get('message', MockMessage()) + self.emoji = kwargs.get("emoji", MockEmoji()) + self.message = kwargs.get("message", MockMessage()) user_iterator = unittest.mock.AsyncMock() user_iterator.__aiter__.return_value = _users diff --git a/tests/test_base.py b/tests/test_base.py index 365805a71..f1fb1a514 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -30,15 +30,19 @@ class LoggingTestCaseTests(unittest.TestCase): r"1 logs of DEBUG or higher were triggered on root:\n" r'<LogRecord: tests\.test_base, [\d]+, .+[/\\]tests[/\\]test_base\.py, [\d]+, "Log!">' ) - with self.assertRaisesRegex(AssertionError, msg_regex): - with LoggingTestCase.assertNotLogs(self, level=logging.DEBUG): - self.log.debug("Log!") + with ( + self.assertRaisesRegex(AssertionError, msg_regex), + LoggingTestCase.assertNotLogs(self, level=logging.DEBUG), + ): + self.log.debug("Log!") def test_assert_not_logs_reraises_unexpected_exception_in_managed_context(self): """Test if LoggingTestCase.assertNotLogs reraises an unexpected exception.""" - with self.assertRaises(ValueError, msg="test exception"): - with LoggingTestCase.assertNotLogs(self, level=logging.DEBUG): - raise ValueError("test exception") + with ( + self.assertRaises(ValueError, msg="test exception"), + LoggingTestCase.assertNotLogs(self, level=logging.DEBUG), + ): + raise ValueError("test exception") def test_assert_not_logs_restores_old_logging_settings(self): """Test if LoggingTestCase.assertNotLogs reraises an unexpected exception.""" @@ -56,9 +60,8 @@ class LoggingTestCaseTests(unittest.TestCase): def test_logging_test_case_works_with_logger_instance(self): """Test if the LoggingTestCase captures logging for provided logger.""" log = get_logger("new_logger") - with self.assertRaises(AssertionError): - with LoggingTestCase.assertNotLogs(self, logger=log): - log.info("Hello, this should raise an AssertionError") + with self.assertRaises(AssertionError), LoggingTestCase.assertNotLogs(self, logger=log): + log.info("Hello, this should raise an AssertionError") def test_logging_test_case_respects_alternative_logger(self): """Test if LoggingTestCase only checks the provided logger.""" diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b2686b1d0..fa7d0eb44 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -144,13 +144,13 @@ class DiscordMocksTests(unittest.TestCase): def test_mocks_allows_access_to_attributes_part_of_spec(self): """Accessing attributes that are valid for the objects they mock should succeed.""" mocks = ( - (helpers.MockGuild(), 'name'), - (helpers.MockRole(), 'hoist'), - (helpers.MockMember(), 'display_name'), - (helpers.MockBot(), 'user'), - (helpers.MockContext(), 'invoked_with'), - (helpers.MockTextChannel(), 'last_message'), - (helpers.MockMessage(), 'mention_everyone'), + (helpers.MockGuild(), "name"), + (helpers.MockRole(), "hoist"), + (helpers.MockMember(), "display_name"), + (helpers.MockBot(), "user"), + (helpers.MockContext(), "invoked_with"), + (helpers.MockTextChannel(), "last_message"), + (helpers.MockMessage(), "mention_everyone"), ) for mock, valid_attribute in mocks: @@ -161,8 +161,8 @@ class DiscordMocksTests(unittest.TestCase): msg = f"accessing valid attribute `{valid_attribute}` raised an AttributeError" self.fail(msg) - @unittest.mock.patch(f'{__name__}.DiscordMocksTests.subTest') - @unittest.mock.patch(f'{__name__}.getattr') + @unittest.mock.patch(f"{__name__}.DiscordMocksTests.subTest") + @unittest.mock.patch(f"{__name__}.getattr") def test_mock_allows_access_to_attributes_test(self, mock_getattr, mock_subtest): """The valid attribute test should raise an AssertionError after an AttributeError.""" mock_getattr.side_effect = AttributeError @@ -184,9 +184,8 @@ class DiscordMocksTests(unittest.TestCase): ) for mock in mocks: - with self.subTest(mock=mock): - with self.assertRaises(AttributeError): - mock.the_cake_is_a_lie + with self.subTest(mock=mock), self.assertRaises(AttributeError): + mock.the_cake_is_a_lie # noqa: B018 def test_mocks_use_mention_when_provided_as_kwarg(self): """The mock should use the passed `mention` instead of the default one if present.""" @@ -333,9 +332,9 @@ class MockObjectTests(unittest.TestCase): (helpers.MockBot, "owner_id"), (helpers.MockContext, "command_failed"), (helpers.MockMessage, "mention_everyone"), - (helpers.MockEmoji, 'managed'), - (helpers.MockPartialEmoji, 'url'), - (helpers.MockReaction, 'me'), + (helpers.MockEmoji, "managed"), + (helpers.MockPartialEmoji, "url"), + (helpers.MockReaction, "me"), ) for mock_type, valid_attribute in test_values: |