aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2021-12-08 13:51:45 +0100
committerGravatar GitHub <[email protected]>2021-12-08 13:51:45 +0100
commit5985ac1c193826380e8a16f44be2d9b91e1354b7 (patch)
tree02cce777db98e29ec165a27a087db67f815caf3e /tests
parentMerge remote-tracking branch 'upstream/main' into tag-groups (diff)
parentMerge pull request #1634 from Shivansh-007/modpings-schedule (diff)
Merge branch 'main' into tag-groups
Diffstat (limited to 'tests')
-rw-r--r--tests/base.py2
-rw-r--r--tests/bot/exts/backend/test_error_handler.py34
-rw-r--r--tests/bot/exts/filters/test_filtering.py40
-rw-r--r--tests/bot/exts/filters/test_token_remover.py4
-rw-r--r--tests/bot/exts/info/test_information.py15
-rw-r--r--tests/bot/exts/moderation/infraction/test_utils.py20
-rw-r--r--tests/bot/exts/moderation/test_incidents.py98
-rw-r--r--tests/bot/exts/moderation/test_silence.py64
-rw-r--r--tests/bot/test_converters.py50
-rw-r--r--tests/bot/utils/test_checks.py1
-rw-r--r--tests/bot/utils/test_time.py27
-rw-r--r--tests/helpers.py27
12 files changed, 274 insertions, 108 deletions
diff --git a/tests/base.py b/tests/base.py
index ab9287e9a..5e304ea9d 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -103,4 +103,4 @@ class CommandTestCase(unittest.IsolatedAsyncioTestCase):
with self.assertRaises(commands.MissingPermissions) as cm:
await cmd.can_run(ctx)
- self.assertCountEqual(permissions.keys(), cm.exception.missing_perms)
+ self.assertCountEqual(permissions.keys(), cm.exception.missing_permissions)
diff --git a/tests/bot/exts/backend/test_error_handler.py b/tests/bot/exts/backend/test_error_handler.py
index 382194a63..35fa0ee59 100644
--- a/tests/bot/exts/backend/test_error_handler.py
+++ b/tests/bot/exts/backend/test_error_handler.py
@@ -107,7 +107,7 @@ class ErrorHandlerTests(unittest.IsolatedAsyncioTestCase):
"""Should send error with `ctx.send` when error is `CommandOnCooldown`."""
self.ctx.reset_mock()
cog = ErrorHandler(self.bot)
- error = errors.CommandOnCooldown(10, 9)
+ error = errors.CommandOnCooldown(10, 9, type=None)
self.assertIsNone(await cog.on_command_error(self.ctx, error))
self.ctx.send.assert_awaited_once_with(error)
@@ -544,38 +544,6 @@ class IndividualErrorHandlerTests(unittest.IsolatedAsyncioTestCase):
push_scope_mock.set_extra.has_calls(set_extra_calls)
-class OtherErrorHandlerTests(unittest.IsolatedAsyncioTestCase):
- """Other `ErrorHandler` tests."""
-
- def setUp(self):
- self.bot = MockBot()
- self.ctx = MockContext()
-
- async def test_get_help_command_command_specified(self):
- """Should return coroutine of help command of specified command."""
- self.ctx.command = "foo"
- result = ErrorHandler.get_help_command(self.ctx)
- expected = self.ctx.send_help("foo")
- self.assertEqual(result.__qualname__, expected.__qualname__)
- self.assertEqual(result.cr_frame.f_locals, expected.cr_frame.f_locals)
-
- # Await coroutines to avoid warnings
- await result
- await expected
-
- async def test_get_help_command_no_command_specified(self):
- """Should return coroutine of help command."""
- self.ctx.command = None
- result = ErrorHandler.get_help_command(self.ctx)
- expected = self.ctx.send_help()
- self.assertEqual(result.__qualname__, expected.__qualname__)
- self.assertEqual(result.cr_frame.f_locals, expected.cr_frame.f_locals)
-
- # Await coroutines to avoid warnings
- await result
- await expected
-
-
class ErrorHandlerSetupTests(unittest.TestCase):
"""Tests for `ErrorHandler` `setup` function."""
diff --git a/tests/bot/exts/filters/test_filtering.py b/tests/bot/exts/filters/test_filtering.py
new file mode 100644
index 000000000..8ae59c1f1
--- /dev/null
+++ b/tests/bot/exts/filters/test_filtering.py
@@ -0,0 +1,40 @@
+import unittest
+from unittest.mock import patch
+
+from bot.exts.filters import filtering
+from tests.helpers import MockBot, autospec
+
+
+class FilteringCogTests(unittest.IsolatedAsyncioTestCase):
+ """Tests the `Filtering` cog."""
+
+ def setUp(self):
+ """Instantiate the bot and cog."""
+ self.bot = MockBot()
+ with patch("bot.utils.scheduling.create_task", new=lambda task, **_: task.close()):
+ self.cog = filtering.Filtering(self.bot)
+
+ @autospec(filtering.Filtering, "_get_filterlist_items", pass_mocks=False, return_value=["TOKEN"])
+ async def test_token_filter(self):
+ """Ensure that a filter token is correctly detected in a message."""
+ messages = {
+ "": False,
+ "no matches": False,
+ "TOKEN": True,
+
+ # See advisory https://github.com/python-discord/bot/security/advisories/GHSA-j8c3-8x46-8pp6
+ "https://google.com TOKEN": True,
+ "https://google.com something else": False,
+ }
+
+ for message, match in messages.items():
+ with self.subTest(input=message, match=match):
+ result, _ = await self.cog._has_watch_regex_match(message)
+
+ self.assertEqual(
+ match,
+ bool(result),
+ msg=f"Hit was {'expected' if match else 'not expected'} for this input."
+ )
+ if result:
+ self.assertEqual("TOKEN", result.group())
diff --git a/tests/bot/exts/filters/test_token_remover.py b/tests/bot/exts/filters/test_token_remover.py
index 05e790723..4db27269a 100644
--- a/tests/bot/exts/filters/test_token_remover.py
+++ b/tests/bot/exts/filters/test_token_remover.py
@@ -26,7 +26,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase):
self.msg.guild.get_member.return_value.bot = False
self.msg.guild.get_member.return_value.__str__.return_value = "Woody"
self.msg.author.__str__ = MagicMock(return_value=self.msg.author.name)
- self.msg.author.avatar_url_as.return_value = "picture-lemon.png"
+ self.msg.author.display_avatar.url = "picture-lemon.png"
def test_extract_user_id_valid(self):
"""Should consider user IDs valid if they decode into an integer ID."""
@@ -376,7 +376,7 @@ class TokenRemoverTests(unittest.IsolatedAsyncioTestCase):
colour=Colour(constants.Colours.soft_red),
title="Token removed!",
text=log_msg + "\n" + userid_log_message,
- thumbnail=self.msg.author.avatar_url_as.return_value,
+ thumbnail=self.msg.author.display_avatar.url,
channel_id=constants.Channels.mod_alerts,
ping_everyone=True,
)
diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py
index d8250befb..632287322 100644
--- a/tests/bot/exts/info/test_information.py
+++ b/tests/bot/exts/info/test_information.py
@@ -42,7 +42,7 @@ class InformationCogTests(unittest.IsolatedAsyncioTestCase):
embed = kwargs.pop('embed')
self.assertEqual(embed.title, "Role information (Total 1 role)")
- self.assertEqual(embed.colour, discord.Colour.blurple())
+ self.assertEqual(embed.colour, discord.Colour.og_blurple())
self.assertEqual(embed.description, f"\n`{self.moderator_role.id}` - {self.moderator_role.mention}\n")
async def test_role_info_command(self):
@@ -50,7 +50,7 @@ class InformationCogTests(unittest.IsolatedAsyncioTestCase):
dummy_role = helpers.MockRole(
name="Dummy",
id=112233445566778899,
- colour=discord.Colour.blurple(),
+ colour=discord.Colour.og_blurple(),
position=10,
members=[self.ctx.author],
permissions=discord.Permissions(0)
@@ -80,7 +80,7 @@ class InformationCogTests(unittest.IsolatedAsyncioTestCase):
admin_embed = admin_kwargs["embed"]
self.assertEqual(dummy_embed.title, "Dummy info")
- self.assertEqual(dummy_embed.colour, discord.Colour.blurple())
+ self.assertEqual(dummy_embed.colour, discord.Colour.og_blurple())
self.assertEqual(dummy_embed.fields[0].value, str(dummy_role.id))
self.assertEqual(dummy_embed.fields[1].value, f"#{dummy_role.colour.value:0>6x}")
@@ -417,14 +417,14 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
f"{COG_PATH}.basic_user_infraction_counts",
new=unittest.mock.AsyncMock(return_value=("Infractions", "basic infractions"))
)
- async def test_create_user_embed_uses_blurple_colour_when_user_has_no_roles(self):
- """The embed should be created with a blurple colour if the user has no assigned roles."""
+ async def test_create_user_embed_uses_og_blurple_colour_when_user_has_no_roles(self):
+ """The embed should be created with the og blurple colour if the user has no assigned roles."""
ctx = helpers.MockContext()
user = helpers.MockMember(id=217, colour=discord.Colour.default())
embed = await self.cog.create_user_embed(ctx, user)
- self.assertEqual(embed.colour, discord.Colour.blurple())
+ self.assertEqual(embed.colour, discord.Colour.og_blurple())
@unittest.mock.patch(
f"{COG_PATH}.basic_user_infraction_counts",
@@ -435,10 +435,9 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
ctx = helpers.MockContext()
user = helpers.MockMember(id=217, colour=0)
- user.avatar_url_as.return_value = "avatar url"
+ user.display_avatar.url = "avatar url"
embed = await self.cog.create_user_embed(ctx, user)
- user.avatar_url_as.assert_called_once_with(static_format="png")
self.assertEqual(embed.thumbnail.url, "avatar url")
diff --git a/tests/bot/exts/moderation/infraction/test_utils.py b/tests/bot/exts/moderation/infraction/test_utils.py
index eb256f1fd..72eebb254 100644
--- a/tests/bot/exts/moderation/infraction/test_utils.py
+++ b/tests/bot/exts/moderation/infraction/test_utils.py
@@ -139,14 +139,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Ban",
expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="No reason provided."
- ),
+ ) + utils.INFRACTION_APPEAL_SERVER_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.token_removed
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": True
},
{
@@ -157,14 +157,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Warning",
expires="N/A",
reason="Test reason."
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.token_removed
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
# Note that this test case asserts that the DM that *would* get sent to the user is formatted
@@ -177,14 +177,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Note",
expires="N/A",
reason="No reason provided."
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
{
@@ -195,14 +195,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Mute",
expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="Test"
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
{
@@ -213,14 +213,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Mute",
expires="N/A",
reason="foo bar" * 4000
- )[:4093] + "...",
+ )[:4093-utils.LONGEST_EXTRAS] + "..." + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": True
}
]
diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py
index c98edf08a..cfe0c4b03 100644
--- a/tests/bot/exts/moderation/test_incidents.py
+++ b/tests/bot/exts/moderation/test_incidents.py
@@ -3,13 +3,16 @@ import enum
import logging
import typing as t
import unittest
-from unittest.mock import AsyncMock, MagicMock, call, patch
+from unittest import mock
+from unittest.mock import AsyncMock, MagicMock, Mock, call, patch
import aiohttp
import discord
+from async_rediscache import RedisSession
from bot.constants import Colours
from bot.exts.moderation import incidents
+from bot.utils.messages import format_user
from tests.helpers import (
MockAsyncWebhook, MockAttachment, MockBot, MockMember, MockMessage, MockReaction, MockRole, MockTextChannel,
MockUser
@@ -276,6 +279,22 @@ class TestIncidents(unittest.IsolatedAsyncioTestCase):
the instance as they wish.
"""
+ session = None
+
+ async def flush(self):
+ """Flush everything from the database to prevent carry-overs between tests."""
+ with await self.session.pool as connection:
+ await connection.flushall()
+
+ async def asyncSetUp(self): # noqa: N802
+ self.session = RedisSession(use_fakeredis=True)
+ await self.session.connect()
+ await self.flush()
+
+ async def asyncTearDown(self): # noqa: N802
+ if self.session:
+ await self.session.close()
+
def setUp(self):
"""
Prepare a fresh `Incidents` instance for each test.
@@ -372,7 +391,7 @@ class TestArchive(TestIncidents):
# Define our own `incident` to be archived
incident = MockMessage(
content="this is an incident",
- author=MockUser(name="author_name", avatar_url="author_avatar"),
+ author=MockUser(name="author_name", display_avatar=Mock(url="author_avatar")),
id=123,
)
built_embed = MagicMock(discord.Embed, id=123) # We patch `make_embed` to return this
@@ -506,7 +525,7 @@ class TestProcessEvent(TestIncidents):
with patch("bot.exts.moderation.incidents.Incidents.make_confirmation_task", mock_task):
await self.cog_instance.process_event(
reaction=incidents.Signal.ACTIONED.value,
- incident=MockMessage(),
+ incident=MockMessage(id=123),
member=MockMember(roles=[MockRole(id=1)])
)
@@ -526,7 +545,7 @@ class TestProcessEvent(TestIncidents):
with patch("bot.exts.moderation.incidents.Incidents.make_confirmation_task", mock_task):
await self.cog_instance.process_event(
reaction=incidents.Signal.ACTIONED.value,
- incident=MockMessage(),
+ incident=MockMessage(id=123),
member=MockMember(roles=[MockRole(id=1)])
)
except asyncio.TimeoutError:
@@ -761,3 +780,74 @@ class TestOnMessage(TestIncidents):
await self.cog_instance.on_message(MockMessage())
mock_add_signals.assert_not_called()
+
+
+class TestMessageLinkEmbeds(TestIncidents):
+ """Tests for `extract_message_links` coroutine."""
+
+ async def test_shorten_text(self):
+ """Test all cases of text shortening by mocking messages."""
+ tests = {
+ "thisisasingleword"*10: "thisisasinglewordthisisasinglewordthisisasinglewor...",
+
+ "\n".join("Lets make a new line test".split()): "Lets\nmake\na...",
+
+ '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!"
+ "Hello, World!Hello, World!H..."
+ )
+ }
+
+ for content, expected_conversion in tests.items():
+ with self.subTest(content=content, expected_conversion=expected_conversion):
+ conversion = incidents.shorten_text(content)
+ self.assertEqual(conversion, expected_conversion)
+
+ async def extract_and_form_message_link_embeds(self):
+ """
+ Extract message links from a mocked message and form the message link embed.
+
+ Considers all types of message links, discord supports.
+ """
+ self.guild_id_patcher = mock.patch("bot.exts.backend.sync._cog.constants.Guild.id", 5)
+ self.guild_id = self.guild_id_patcher.start()
+
+ msg = MockMessage(id=555, content="Hello, World!" * 3000)
+ msg.channel.mention = "#lemonade-stand"
+
+ msg_links = [
+ # Valid Message links
+ f"https://discord.com/channels/{self.guild_id}/{msg.channel.discord_id}/{msg.discord_id}",
+ f"http://canary.discord.com/channels/{self.guild_id}/{msg.channel.discord_id}/{msg.discord_id}",
+
+ # Invalid Message links
+ f"https://discord.com/channels/{msg.channel.discord_id}/{msg.discord_id}",
+ f"https://discord.com/channels/{self.guild_id}/{msg.channel.discord_id}000/{msg.discord_id}",
+ ]
+
+ incident_msg = MockMessage(
+ id=777,
+ content=(
+ f"I would like to report the following messages, "
+ f"as they break our rules: \n{', '.join(msg_links)}"
+ )
+ )
+
+ with patch(
+ "bot.exts.moderation.incidents.Incidents.extract_message_links", AsyncMock()
+ ) as mock_extract_message_links:
+ embeds = mock_extract_message_links(incident_msg)
+ description = (
+ f"**Author:** {format_user(msg.author)}\n"
+ f"**Channel:** {msg.channel.mention} ({msg.channel.category}/#{msg.channel.name})\n"
+ f"**Content:** {('Hello, World!' * 3000)[:300] + '...'}\n"
+ )
+
+ # Check number of embeds returned with number of valid links
+ self.assertEqual(len(embeds), 2)
+
+ # Check for the embed descriptions
+ for embed in embeds:
+ self.assertEqual(embed.description, description)
diff --git a/tests/bot/exts/moderation/test_silence.py b/tests/bot/exts/moderation/test_silence.py
index ef8394be8..92ce3418a 100644
--- a/tests/bot/exts/moderation/test_silence.py
+++ b/tests/bot/exts/moderation/test_silence.py
@@ -431,7 +431,13 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
asyncio.run(self.cog._async_init()) # Populate instance attributes.
self.text_channel = MockTextChannel()
- self.text_overwrite = PermissionOverwrite(send_messages=True, add_reactions=False)
+ self.text_overwrite = PermissionOverwrite(
+ send_messages=True,
+ add_reactions=False,
+ create_private_threads=True,
+ create_public_threads=False,
+ send_messages_in_threads=True
+ )
self.text_channel.overwrites_for.return_value = self.text_overwrite
self.voice_channel = MockVoiceChannel()
@@ -502,9 +508,39 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
async def test_skipped_already_silenced(self):
"""Permissions were not set and `False` was returned for an already silenced channel."""
subtests = (
- (False, MockTextChannel(), PermissionOverwrite(send_messages=False, add_reactions=False)),
- (True, MockTextChannel(), PermissionOverwrite(send_messages=True, add_reactions=True)),
- (True, MockTextChannel(), PermissionOverwrite(send_messages=False, add_reactions=False)),
+ (
+ False,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=False,
+ add_reactions=False,
+ create_private_threads=False,
+ create_public_threads=False,
+ send_messages_in_threads=False
+ )
+ ),
+ (
+ True,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=True,
+ add_reactions=True,
+ create_private_threads=True,
+ create_public_threads=True,
+ send_messages_in_threads=True
+ )
+ ),
+ (
+ True,
+ MockTextChannel(),
+ PermissionOverwrite(
+ send_messages=False,
+ add_reactions=False,
+ create_private_threads=False,
+ create_public_threads=False,
+ send_messages_in_threads=False
+ )
+ ),
(False, MockVoiceChannel(), PermissionOverwrite(connect=False, speak=False)),
(True, MockVoiceChannel(), PermissionOverwrite(connect=True, speak=True)),
(True, MockVoiceChannel(), PermissionOverwrite(connect=False, speak=False)),
@@ -552,11 +588,16 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
await self.cog._set_silence_overwrites(self.text_channel)
new_overwrite_dict = dict(self.text_overwrite)
- # Remove 'send_messages' & 'add_reactions' keys because they were changed by the method.
- del prev_overwrite_dict['send_messages']
- del prev_overwrite_dict['add_reactions']
- del new_overwrite_dict['send_messages']
- del new_overwrite_dict['add_reactions']
+ # Remove related permission keys because they were changed by the method.
+ for perm_name in (
+ "send_messages",
+ "add_reactions",
+ "create_private_threads",
+ "create_public_threads",
+ "send_messages_in_threads"
+ ):
+ del prev_overwrite_dict[perm_name]
+ del new_overwrite_dict[perm_name]
self.assertDictEqual(prev_overwrite_dict, new_overwrite_dict)
@@ -594,7 +635,10 @@ class SilenceTests(unittest.IsolatedAsyncioTestCase):
async def test_cached_previous_overwrites(self):
"""Channel's previous overwrites were cached."""
- overwrite_json = '{"send_messages": true, "add_reactions": false}'
+ overwrite_json = (
+ '{"send_messages": true, "add_reactions": false, "create_private_threads": true, '
+ '"create_public_threads": false, "send_messages_in_threads": true}'
+ )
await self.cog._set_silence_overwrites(self.text_channel)
self.cog.previous_overwrites.set.assert_awaited_once_with(self.text_channel.id, overwrite_json)
diff --git a/tests/bot/test_converters.py b/tests/bot/test_converters.py
index c23d66663..1bb678db2 100644
--- a/tests/bot/test_converters.py
+++ b/tests/bot/test_converters.py
@@ -1,6 +1,6 @@
-import datetime
import re
import unittest
+from datetime import MAXYEAR, datetime, timezone
from unittest.mock import MagicMock, patch
from dateutil.relativedelta import relativedelta
@@ -17,7 +17,7 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase):
cls.context = MagicMock
cls.context.author = 'bob'
- cls.fixed_utc_now = datetime.datetime.fromisoformat('2019-01-01T00: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."""
@@ -96,7 +96,7 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase):
expected_datetime = self.fixed_utc_now + relativedelta(**duration_dict)
with patch('bot.converters.datetime') as mock_datetime:
- mock_datetime.utcnow.return_value = self.fixed_utc_now
+ mock_datetime.now.return_value = self.fixed_utc_now
with self.subTest(duration=duration, duration_dict=duration_dict):
converted_datetime = await converter.convert(self.context, duration)
@@ -142,52 +142,53 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase):
async def test_duration_converter_out_of_range(self, mock_datetime):
"""Duration converter should raise BadArgument if datetime raises a ValueError."""
mock_datetime.__add__.side_effect = ValueError
- mock_datetime.utcnow.return_value = mock_datetime
+ mock_datetime.now.return_value = mock_datetime
- duration = f"{datetime.MAXYEAR}y"
+ duration = f"{MAXYEAR}y"
exception_message = f"`{duration}` results in a datetime outside the supported range."
with self.assertRaisesRegex(BadArgument, re.escape(exception_message)):
await Duration().convert(self.context, duration)
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.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 02:03:05Z', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('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.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 03:18:05+01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02T00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 00:48:05-01:15', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('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.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 03:18:05+0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02T00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 00:48:05-0115', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('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.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02T01:03:05-01', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('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.datetime(2019, 9, 2, 2, 3, 5)),
- ('2019-09-02 02:03:05', datetime.datetime(2019, 9, 2, 2, 3, 5)),
+ ('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.datetime(2019, 11, 12, 9, 15)),
- ('2019-11-12 09:15', datetime.datetime(2019, 11, 12, 9, 15)),
+ ('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.datetime(2019, 4, 1)),
+ ('2019-04-01', datetime(2019, 4, 1, tzinfo=utc)),
# `YYYY-mm`
- ('2019-02-01', datetime.datetime(2019, 2, 1)),
+ ('2019-02-01', datetime(2019, 2, 1, tzinfo=utc)),
# `YYYY`
- ('2025', datetime.datetime(2025, 1, 1)),
+ ('2025', datetime(2025, 1, 1, tzinfo=utc)),
)
converter = ISODateTime()
@@ -195,7 +196,6 @@ class ConverterTests(unittest.IsolatedAsyncioTestCase):
for datetime_string, expected_dt in test_values:
with self.subTest(datetime_string=datetime_string, expected_dt=expected_dt):
converted_dt = await converter.convert(self.context, datetime_string)
- self.assertIsNone(converted_dt.tzinfo)
self.assertEqual(converted_dt, expected_dt)
async def test_isodatetime_converter_for_invalid(self):
diff --git a/tests/bot/utils/test_checks.py b/tests/bot/utils/test_checks.py
index 883465e0b..4ae11d5d3 100644
--- a/tests/bot/utils/test_checks.py
+++ b/tests/bot/utils/test_checks.py
@@ -32,6 +32,7 @@ class ChecksTests(unittest.IsolatedAsyncioTestCase):
async def test_has_no_roles_check_without_guild(self):
"""`has_no_roles_check` should return `False` when `Context.guild` is None."""
self.ctx.channel = MagicMock(DMChannel)
+ self.ctx.guild = None
self.assertFalse(await checks.has_no_roles_check(self.ctx))
async def test_has_no_roles_check_returns_false_with_unwanted_role(self):
diff --git a/tests/bot/utils/test_time.py b/tests/bot/utils/test_time.py
index 8edffd1c9..a3dcbfc0a 100644
--- a/tests/bot/utils/test_time.py
+++ b/tests/bot/utils/test_time.py
@@ -72,9 +72,9 @@ class TimeTests(unittest.TestCase):
def test_format_infraction_with_duration_custom_units(self):
"""format_infraction_with_duration should work for custom max_units."""
test_cases = (
- ('3000-12-12T00:01:00Z', datetime(3000, 12, 11, 12, 5, 5), 6,
+ ('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), 20,
+ ('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)')
)
@@ -84,16 +84,21 @@ class TimeTests(unittest.TestCase):
def test_format_infraction_with_duration_normal_usage(self):
"""format_infraction_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), 2, '<t:1576108860:f> (12 hours and 55 seconds)'),
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '<t:1576108860:f> (12 hours)'),
- ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '<t:1576108800:f> (1 minute)'),
- ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '<t:1574539740:f> (7 days and 23 hours)'),
- ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '<t:1574539740:f> (6 months and 28 days)'),
- ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '<t:1574542680:f> (5 minutes)'),
- ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '<t:1574553600:f> (1 minute)'),
- ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '<t:1574553540:f> (2 years and 4 months)'),
- ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2,
+ ('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),
)
diff --git a/tests/helpers.py b/tests/helpers.py
index 83b9b2363..9d4988d23 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -39,7 +39,7 @@ class HashableMixin(discord.mixins.EqualityComparable):
class ColourMixin:
- """A mixin for Mocks that provides the aliasing of color->colour like discord.py does."""
+ """A mixin for Mocks that provides the aliasing of (accent_)color->(accent_)colour like discord.py does."""
@property
def color(self) -> discord.Colour:
@@ -49,6 +49,14 @@ class ColourMixin:
def color(self, color: discord.Colour) -> None:
self.colour = color
+ @property
+ def accent_color(self) -> discord.Colour:
+ return self.accent_colour
+
+ @accent_color.setter
+ def accent_color(self, color: discord.Colour) -> None:
+ self.accent_colour = color
+
class CustomMockMixin:
"""
@@ -242,7 +250,13 @@ class MockMember(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin
# Create a User instance to get a realistic Mock of `discord.User`
-user_instance = discord.User(data=unittest.mock.MagicMock(), state=unittest.mock.MagicMock())
+_user_data_mock = collections.defaultdict(unittest.mock.MagicMock, {
+ "accent_color": 0
+})
+user_instance = discord.User(
+ data=unittest.mock.MagicMock(get=unittest.mock.Mock(side_effect=_user_data_mock.get)),
+ state=unittest.mock.MagicMock()
+)
class MockUser(CustomMockMixin, unittest.mock.Mock, ColourMixin, HashableMixin):
@@ -428,7 +442,12 @@ message_instance = discord.Message(state=state, channel=channel, data=message_da
# Create a Context instance to get a realistic MagicMock of `discord.ext.commands.Context`
-context_instance = Context(message=unittest.mock.MagicMock(), prefix=unittest.mock.MagicMock())
+context_instance = Context(
+ message=unittest.mock.MagicMock(),
+ prefix="$",
+ bot=MockBot(),
+ view=None
+)
context_instance.invoked_from_error_handler = None
@@ -537,7 +556,7 @@ class MockReaction(CustomMockMixin, unittest.mock.MagicMock):
self.__str__.return_value = str(self.emoji)
-webhook_instance = discord.Webhook(data=unittest.mock.MagicMock(), adapter=unittest.mock.MagicMock())
+webhook_instance = discord.Webhook(data=unittest.mock.MagicMock(), session=unittest.mock.MagicMock())
class MockAsyncWebhook(CustomMockMixin, unittest.mock.MagicMock):