Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ dev = [
"coverage==7.11.0",
"httpx==0.28.1",
"pre-commit==4.3.0",
"pytest==8.4.2",
"pytest==9.0.0",
"pytest-cov==7.0.0",
"pytest-subtests==0.14.1",
"pytest-xdist==3.8.0",
"ruff==0.14.2",
"taskipy==1.14.1",
Expand Down Expand Up @@ -96,8 +95,8 @@ combine-as-imports = true
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["ANN", "D"]

[tool.pytest.ini_options]
[tool.pytest]
# We don't use nose style tests so disable them in pytest.
# This stops pytest from running functions named `setup` in test files.
# See https://github.com/python-discord/bot/pull/2229#issuecomment-1204436420
addopts = "-p no:nose"
addopts = ["-p", "no:nose"]
Comment on lines +98 to +102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nose support was dropped in pytest 8.0.0 so this should no longer be necessary https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose

8 changes: 4 additions & 4 deletions tests/bot/exts/backend/sync/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ async def test_sync_message_edited(self):
(helpers.MockMessage(), ResponseCodeError(mock.MagicMock()), True),
)

for message, side_effect, should_edit in subtests:
with self.subTest(message=message, side_effect=side_effect, should_edit=should_edit):
for i, (message, side_effect, should_edit) in enumerate(subtests):
with self.subTest(test_case=i, has_message=message is not None, should_edit=should_edit):
TestSyncer._sync.side_effect = side_effect
ctx = helpers.MockContext()
ctx.send.return_value = message
Expand All @@ -58,8 +58,8 @@ async def test_sync_message_sent(self):
(helpers.MockContext(), helpers.MockMessage()),
)

for ctx, message in subtests:
with self.subTest(ctx=ctx, message=message):
for i, (ctx, _message) in enumerate(subtests):
with self.subTest(test_case=i, has_ctx=ctx is not None):
await TestSyncer.sync(self.guild, ctx)

if ctx is not None:
Expand Down
18 changes: 9 additions & 9 deletions tests/bot/exts/backend/sync/test_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class SyncCogTests(SyncCogTestCase):
@unittest.mock.patch("bot.exts.backend.sync._cog.create_task", new_callable=unittest.mock.MagicMock)
async def test_sync_cog_sync_on_load(self, mock_create_task: unittest.mock.MagicMock):
"""Sync function should be synced on cog load only if guild is found."""
for guild in (helpers.MockGuild(), None):
with self.subTest(guild=guild):
for i, guild in enumerate((helpers.MockGuild(), None)):
with self.subTest(test_case=i, has_guild=guild is not None):
mock_create_task.reset_mock()
self.bot.reset_mock()
self.RoleSyncer.reset_mock()
Expand Down Expand Up @@ -126,8 +126,8 @@ async def patch_user_helper(self, side_effect: BaseException) -> None:

async def test_sync_cog_patch_user(self):
"""A PATCH request should be sent and 404 errors ignored."""
for side_effect in (None, self.response_error(404)):
with self.subTest(side_effect=side_effect):
for i, side_effect in enumerate((None, self.response_error(404))):
with self.subTest(test_case=i, has_error=side_effect is not None):
await self.patch_user_helper(side_effect)

async def test_sync_cog_patch_user_non_404(self):
Expand Down Expand Up @@ -207,7 +207,7 @@ async def test_sync_cog_on_guild_role_update(self):

for should_put, attributes in subtests:
for attribute in attributes:
with self.subTest(should_put=should_put, changed_attribute=attribute):
with self.subTest(should_put=should_put, attribute=attribute):
self.bot.api_client.put.reset_mock()

after_role_data = role_data.copy()
Expand Down Expand Up @@ -372,8 +372,8 @@ async def on_member_join_helper(self, side_effect: Exception) -> dict:

async def test_sync_cog_on_member_join(self):
"""Should PUT user's data or POST it if the user doesn't exist."""
for side_effect in (None, self.response_error(404)):
with self.subTest(side_effect=side_effect):
for i, side_effect in enumerate((None, self.response_error(404))):
with self.subTest(test_case=i, has_error=side_effect is not None):
self.bot.api_client.post.reset_mock()
data = await self.on_member_join_helper(side_effect)

Expand Down Expand Up @@ -422,6 +422,6 @@ async def test_commands_require_admin(self):
self.cog.sync_users_command,
)

for cmd in cmds:
with self.subTest(cmd=cmd):
for i, cmd in enumerate(cmds):
with self.subTest(test_case=i):
await self.assertHasPermissionsCheck(cmd, {"administrator": True})
45 changes: 31 additions & 14 deletions tests/bot/exts/backend/test_error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,9 @@ async def test_error_handler_command_invoke_error(self):
}
)

for case in test_cases:
with self.subTest(args=case["args"], expect_mock_call=case["expect_mock_call"]):
for i, case in enumerate(test_cases):
mock_type = "send" if case["expect_mock_call"] == "send" else "mock_function"
with self.subTest(test_case=i, expect_mock_call=mock_type):
self.ctx.send.reset_mock()
self.assertIsNone(await self.cog.on_command_error(*case["args"]))
if case["expect_mock_call"] == "send":
Expand All @@ -161,8 +162,8 @@ async def test_error_handler_conversion_error(self):
}
)

for case in cases:
with self.subTest(**case):
for i, case in enumerate(cases):
with self.subTest(test_case=i):
self.assertIsNone(await self.cog.on_command_error(self.ctx, case["error"]))
case["mock_function_to_call"].assert_awaited_once_with(self.ctx, case["error"].original)

Expand All @@ -173,8 +174,8 @@ async def test_error_handler_unexpected_errors(self):
errors.ExtensionError(name="foo"),
)

for err in errs:
with self.subTest(error=err):
for i, err in enumerate(errs):
with self.subTest(test_case=i):
self.cog.handle_unexpected_error.reset_mock()
self.assertIsNone(await self.cog.on_command_error(self.ctx, err))
self.cog.handle_unexpected_error.assert_awaited_once_with(self.ctx, err)
Expand Down Expand Up @@ -251,8 +252,8 @@ async def test_try_silence_silence_arguments(self):
(MockTextChannel(), True)
)

for channel, kick in test_cases:
with self.subTest(kick=kick, channel=channel):
for i, (channel, kick) in enumerate(test_cases):
with self.subTest(test_case=i, kick=kick):
self.ctx.reset_mock()
self.ctx.invoked_with = "shh"

Expand Down Expand Up @@ -291,8 +292,8 @@ async def test_try_silence_unsilence(self):
("unshh", MockTextChannel())
)

for invoke, channel in test_cases:
with self.subTest(message=invoke, channel=channel):
for i, (invoke, channel) in enumerate(test_cases):
with self.subTest(test_case=i, message=invoke, has_channel=channel is not None):
self.bot.get_command.side_effect = (self.silence.silence, self.silence.unsilence)
self.ctx.reset_mock()

Expand Down Expand Up @@ -386,33 +387,39 @@ async def test_handle_input_error_handler_errors(self):
"""Should handle each error probably."""
test_cases = (
{
"error_type": "MissingRequiredArgument",
"error": errors.MissingRequiredArgument(MagicMock()),
"call_prepared": True
},
{
"error_type": "TooManyArguments",
"error": errors.TooManyArguments(),
"call_prepared": True
},
{
"error_type": "BadArgument",
"error": errors.BadArgument(),
"call_prepared": True
},
{
"error_type": "BadUnionArgument",
"error": errors.BadUnionArgument(MagicMock(), MagicMock(), MagicMock()),
"call_prepared": True
},
{
"error_type": "ArgumentParsingError",
"error": errors.ArgumentParsingError(),
"call_prepared": False
},
{
"error_type": "UserInputError",
"error": errors.UserInputError(),
"call_prepared": True
}
)

for case in test_cases:
with self.subTest(error=case["error"], call_prepared=case["call_prepared"]):
with self.subTest(error_type=case["error_type"], call_prepared=case["call_prepared"]):
self.ctx.reset_mock()
self.cog.send_error_with_help = AsyncMock()
self.assertIsNone(await self.cog.handle_user_input_error(self.ctx, case["error"]))
Expand All @@ -426,33 +433,39 @@ async def test_handle_check_failure_errors(self):
"""Should await `ctx.send` when error is check failure."""
test_cases = (
{
"error_type": "BotMissingPermissions",
"error": errors.BotMissingPermissions(MagicMock()),
"call_ctx_send": True
},
{
"error_type": "BotMissingRole",
"error": errors.BotMissingRole(MagicMock()),
"call_ctx_send": True
},
{
"error_type": "BotMissingAnyRole",
"error": errors.BotMissingAnyRole(MagicMock()),
"call_ctx_send": True
},
{
"error_type": "NoPrivateMessage",
"error": errors.NoPrivateMessage(),
"call_ctx_send": True
},
{
"error_type": "InWhitelistCheckFailure",
"error": InWhitelistCheckFailure(1234),
"call_ctx_send": True
},
{
"error_type": "ResponseCodeError",
"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"]):
with self.subTest(error_type=case["error_type"], 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"]:
Expand All @@ -465,25 +478,29 @@ async def test_handle_api_error(self, log_mock):
"""Should `ctx.send` on HTTP error codes, and log at correct level."""
test_cases = (
{
"status": 400,
"error": ResponseCodeError(AsyncMock(status=400)),
"log_level": "error"
},
{
"status": 404,
"error": ResponseCodeError(AsyncMock(status=404)),
"log_level": "debug"
},
{
"status": 550,
"error": ResponseCodeError(AsyncMock(status=550)),
"log_level": "warning"
},
{
"status": 1000,
"error": ResponseCodeError(AsyncMock(status=1000)),
"log_level": "warning"
}
)

for case in test_cases:
with self.subTest(error=case["error"], log_level=case["log_level"]):
with self.subTest(status=case["status"], log_level=case["log_level"]):
self.ctx.reset_mock()
log_mock.reset_mock()
await self.cog.handle_api_error(self.ctx, case["error"])
Expand All @@ -500,7 +517,7 @@ async def test_handle_api_error(self, log_mock):
async def test_handle_unexpected_error(self, log_mock, new_scope_mock):
"""Should `ctx.send` this error, error log this and sent to Sentry."""
for case in (None, MockGuild()):
with self.subTest(guild=case):
with self.subTest(has_guild=case is not None):
self.ctx.reset_mock()
log_mock.reset_mock()
new_scope_mock.reset_mock()
Expand Down
4 changes: 2 additions & 2 deletions tests/bot/exts/filtering/test_settings_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ def test_filtering_dms_when_necessary(self):
(False, MockTextChannel(), True)
)

for apply_in_dms, channel, expected in cases:
with self.subTest(apply_in_dms=apply_in_dms, channel=channel):
for i, (apply_in_dms, channel, expected) in enumerate(cases):
with self.subTest(test_case=i, apply_in_dms=apply_in_dms):
filter_dms = FilterDM(filter_dm=apply_in_dms)
self.ctx.channel = channel

Expand Down
8 changes: 4 additions & 4 deletions tests/bot/exts/info/test_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,22 @@ async def test_user_command_helper_method_get_requests(self):
},
)

for test_value in test_values:
for i, test_value in enumerate(test_values):
helper_method = test_value["helper_method"]
endpoint, params = test_value["expected_args"]

with self.subTest(method=helper_method, endpoint=endpoint, params=params):
with self.subTest(test_case=i, endpoint=endpoint):
await helper_method(self.member)
self.bot.api_client.get.assert_called_once_with(endpoint, params=params)
self.bot.api_client.get.reset_mock()

async def _method_subtests(self, method, test_values, default_header):
"""Helper method that runs the subtests for the different helper methods."""
for test_value in test_values:
for i, test_value in enumerate(test_values):
api_response = test_value["api response"]
expected_lines = test_value["expected_lines"]

with self.subTest(method=method, api_response=api_response, expected_lines=expected_lines):
with self.subTest(test_case=i):
self.bot.api_client.get.return_value = api_response

expected_output = "\n".join(expected_lines)
Expand Down
22 changes: 11 additions & 11 deletions tests/bot/exts/moderation/infraction/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ async def test_post_user(self):
}
]

for case in test_cases:
for i, case in enumerate(test_cases):
user = case["user"]
post_result = case["post_result"]
raise_error = case["raise_error"]
payload = case["payload"]

with self.subTest(user=user, post_result=post_result, raise_error=raise_error, payload=payload):
with self.subTest(test_case=i, has_error=raise_error is not None):
self.bot.api_client.post.reset_mock(side_effect=True)
self.ctx.bot.api_client.post.return_value = post_result

Expand Down Expand Up @@ -235,8 +235,8 @@ async def test_send_infraction_embed(self, send_private_embed_mock):
}
]

for case in test_cases:
with self.subTest(args=case["args"], expected=case["expected_output"], send=case["send_result"]):
for i, case in enumerate(test_cases):
with self.subTest(test_case=i, send=case["send_result"]):
send_private_embed_mock.reset_mock()

send_private_embed_mock.return_value = case["send_result"]
Expand All @@ -259,7 +259,7 @@ async def test_notify_pardon(self, send_private_embed_mock):
test_case((self.user, "Test title", "Example content", Icons.user_update), Icons.user_update, False)
]

for case in test_cases:
for i, case in enumerate(test_cases):
expected = Embed(
description="Example content",
colour=Colours.soft_green
Expand All @@ -268,7 +268,7 @@ async def test_notify_pardon(self, send_private_embed_mock):
icon_url=case.icon
)

with self.subTest(args=case.args, expected=expected):
with self.subTest(test_case=i):
send_private_embed_mock.reset_mock()

send_private_embed_mock.return_value = case.send_result
Expand All @@ -288,13 +288,13 @@ async def test_send_private_embed(self):
test_case = namedtuple("test_case", ["expected_output", "raised_exception"])
test_cases = [
test_case(True, None),
test_case(False, HTTPException(AsyncMock(), AsyncMock())),
test_case(False, Forbidden(AsyncMock(), AsyncMock())),
test_case(False, NotFound(AsyncMock(), AsyncMock()))
test_case(False, HTTPException(AsyncMock(), "test error")),
test_case(False, Forbidden(AsyncMock(), "test error")),
test_case(False, NotFound(AsyncMock(), "test error"))
]

for case in test_cases:
with self.subTest(expected=case.expected_output, raised=case.raised_exception):
for i, case in enumerate(test_cases):
with self.subTest(test_case=i, expected=case.expected_output):
self.user.send.reset_mock(side_effect=True)
self.user.send.side_effect = case.raised_exception

Expand Down
Loading