From 1e14def3b6476da38d9ff231d8043b8ba53331bd Mon Sep 17 00:00:00 2001 From: Arvid Date: Sat, 1 Mar 2025 17:39:07 +0100 Subject: [PATCH 1/8] feat: Adds the ability to send public/private messages in different channels. #1477 Adds the ability to send valentines messages publicly or privately to a person. If private, the message is sent in DMs (if possible) and if public it is sent in the current channel that the command was invoked in. --- .../holidays/valentines/be_my_valentine.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 8167979478..2e83db70c2 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -41,19 +41,19 @@ async def lovefest_role(self, ctx: commands.Context) -> None: """ raise MovedCommandError(MOVED_COMMAND) - @commands.cooldown(1, 1800, commands.BucketType.user) + # @commands.cooldown(1, 1800, commands.BucketType.user) @commands.group(name="bemyvalentine", invoke_without_command=True) async def send_valentine( - self, ctx: commands.Context, user: discord.Member, *, valentine_type: str | None = None + self, ctx: commands.Context, user: discord.Member, privacy_type: str | None = None, valentine_type: str | None = None ) -> None: """ Send a valentine to a specified user with the lovefest role. - syntax: .bemyvalentine [user] [p/poem/c/compliment/or you can type your own valentine message] + syntax: .bemyvalentine [user] [public/private] [p/poem/c/compliment/or you can type your own valentine message] (optional) - example: .bemyvalentine Iceman#6508 p (sends a poem to Iceman) - example: .bemyvalentine Iceman Hey I love you, wanna hang around ? (sends the custom message to Iceman) + example: .bemyvalentine private Iceman#6508 p (sends a private poem to Iceman) + example: .bemyvalentine public Iceman Hey I love you, wanna hang around ? (sends the custom message publicly to Iceman) NOTE : AVOID TAGGING THE USER MOST OF THE TIMES.JUST TRIM THE '@' when using this command. """ if ctx.guild is None: @@ -64,10 +64,18 @@ async def send_valentine( raise commands.UserInputError( f"You cannot send a valentine to {user} as they do not have the lovefest role!" ) + + if privacy_type not in ["public", "private"]: + # Privacy type wrongfully specified. + raise commands.UserInputError( + f"Specify if you want the message to be sent privately or publicly!" + ) + + # COMMENTED FOR TESTING PURPOSES - if user == ctx.author: - # Well a user can't valentine himself/herself. - raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") + # if user == ctx.author: + # # Well a user can't valentine himself/herself. + # raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") emoji_1, emoji_2 = self.random_emoji() channel = self.bot.get_channel(Channels.sir_lancebot_playground) @@ -78,7 +86,20 @@ async def send_valentine( description=f"{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**", color=Colours.pink ) - await channel.send(user.mention, embed=embed) + + if privacy_type.lower() == "private": + # Send the message privately if "private" was speicified + try: + await user.send(embed=embed) + await user.send(f"Your valentine has been **privately** delivered to {user.display_name}!") + except discord.Forbidden: + await ctx.send(f"I couldn't send a private message to {user.display_name}. They may have DMs disabled.") + else: + # Send the message publicly if "public" was speicified + try: + await ctx.send(user.mention, embed=embed) + except discord.Forbidden: + await ctx.send(f"I couldn't send a private message to {user.display_name}. They may have DMs disabled.") @commands.cooldown(1, 1800, commands.BucketType.user) @send_valentine.command(name="secret") From f8b64010c70b3e5c5d224fe46186752438d7586a Mon Sep 17 00:00:00 2001 From: Arvid Date: Sat, 1 Mar 2025 17:40:56 +0100 Subject: [PATCH 2/8] feat: Sets channel "#general" as whitelisted. #1477 Sets channel "#general" as whitelisted in order to allow testing of invoking the ".bemyvalentine" command and receive the message in the same channel (away from "#sir-lancebot-playground") if "public" was specified in the command. --- bot/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index bf53c4b438..77332f444e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -47,6 +47,7 @@ class EnvConfig( class _Channels(EnvConfig, env_prefix="channels_"): + general: int = 123123123123 algos_and_data_structs: int = 650401909852864553 bot_commands: int = 267659945086812160 community_meta: int = 267659945086812160 @@ -312,6 +313,7 @@ class _Reddit(EnvConfig, env_prefix="reddit_"): # Whitelisted channels WHITELISTED_CHANNELS = ( + Channels.general, Channels.bot_commands, Channels.sir_lancebot_playground, Channels.off_topic_0, From b422b75eabe548f658ac0c0b0de7ec0c3c738d7d Mon Sep 17 00:00:00 2001 From: Arvid Gussarsson Date: Tue, 4 Mar 2025 14:06:14 +0100 Subject: [PATCH 3/8] feat: Adds functionality to provide privacy for sender. #1477 Adds functionality that instantly deletes messages if "anon" type was specified. Notifications and who is typing will still be sent, but it still provides some anonymity. Also fixes bug that sends the confirmation message to the receiver of the message (should be sender). --- .../holidays/valentines/be_my_valentine.py | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 2e83db70c2..5d8ee1e3c6 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -44,7 +44,7 @@ async def lovefest_role(self, ctx: commands.Context) -> None: # @commands.cooldown(1, 1800, commands.BucketType.user) @commands.group(name="bemyvalentine", invoke_without_command=True) async def send_valentine( - self, ctx: commands.Context, user: discord.Member, privacy_type: str | None = None, valentine_type: str | None = None + self, ctx: commands.Context, user: discord.Member, privacy_type: str | None = None, anon: str | None = None, valentine_type: str | None = None ) -> None: """ Send a valentine to a specified user with the lovefest role. @@ -56,6 +56,24 @@ async def send_valentine( example: .bemyvalentine public Iceman Hey I love you, wanna hang around ? (sends the custom message publicly to Iceman) NOTE : AVOID TAGGING THE USER MOST OF THE TIMES.JUST TRIM THE '@' when using this command. """ + + + if anon.lower() == "anon": + # Delete the message containing the command right after it was sent to enforce anonymity. + try: + await ctx.message.delete() + except discord.Forbidden: + await ctx.send("I can't delete your message! Please check my permissions.") + + + if anon not in ["anon", "signed"]: + # Anonymity type wrongfully specified. + raise commands.UserInputError( + f"Specify if you want the message to be anonymous or not!" + ) + + + if ctx.guild is None: # This command should only be used in the server raise commands.UserInputError("You are supposed to use this command in the server.") @@ -81,17 +99,25 @@ async def send_valentine( channel = self.bot.get_channel(Channels.sir_lancebot_playground) valentine, title = self.valentine_check(valentine_type) - embed = discord.Embed( - title=f"{emoji_1} {title} {user.display_name} {emoji_2}", - description=f"{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**", - color=Colours.pink - ) - + if anon.lower() == "anon": + embed = discord.Embed( + title=f"{emoji_1} {title} {user.display_name} {emoji_2}", + description=f"{valentine} \n **{emoji_2}From an anonymous admirer{emoji_1}**", + color=Colours.pink + ) + + else: + embed = discord.Embed( + title=f"{emoji_1} {title} {user.display_name} {emoji_2}", + description=f"{valentine} \n **{emoji_2}From {ctx.author}{emoji_1}**", + color=Colours.pink + ) + if privacy_type.lower() == "private": # Send the message privately if "private" was speicified try: await user.send(embed=embed) - await user.send(f"Your valentine has been **privately** delivered to {user.display_name}!") + await ctx.author.send(f"Your valentine has been **privately** delivered to {user.display_name}!") except discord.Forbidden: await ctx.send(f"I couldn't send a private message to {user.display_name}. They may have DMs disabled.") else: From 197a45aaaab2ad0e9633f57aca4ce0834547388c Mon Sep 17 00:00:00 2001 From: Arvid Gussarsson Date: Tue, 4 Mar 2025 14:08:43 +0100 Subject: [PATCH 4/8] doc: Updates documentation for new signed feature. #1477 Updates the documentation for the new anon/signed attribute to the .bemyvalentine command. Also fixes error in order attributes should appear in. --- bot/exts/holidays/valentines/be_my_valentine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 5d8ee1e3c6..70b52ce0b1 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -49,11 +49,11 @@ async def send_valentine( """ Send a valentine to a specified user with the lovefest role. - syntax: .bemyvalentine [user] [public/private] [p/poem/c/compliment/or you can type your own valentine message] + syntax: .bemyvalentine [user] [public/private] [anon/signed] [p/poem/c/compliment/or you can type your own valentine message] (optional) - example: .bemyvalentine private Iceman#6508 p (sends a private poem to Iceman) - example: .bemyvalentine public Iceman Hey I love you, wanna hang around ? (sends the custom message publicly to Iceman) + example: .bemyvalentine Iceman#6508 private anon p (sends an anonymous private poem through DM to Iceman) + example: .bemyvalentine Iceman public signed Hey I love you, wanna hang around ? (sends the custom message publicly and signed to Iceman in the current channel) NOTE : AVOID TAGGING THE USER MOST OF THE TIMES.JUST TRIM THE '@' when using this command. """ From 760522d813f98b82eac8bf5c749333f5b6a13f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Yu=20Stjernstr=C3=B6m?= Date: Thu, 6 Mar 2025 16:42:47 +0100 Subject: [PATCH 5/8] add test for new feature --- .../holidays/valentines/be_my_valentine.py | 4 +- .../holidays/valentines/test_bemyvalentine.py | 323 ++++++++++++++++++ 2 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 bot/exts/holidays/valentines/test_bemyvalentine.py diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 70b52ce0b1..c37433cf6d 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -91,9 +91,9 @@ async def send_valentine( # COMMENTED FOR TESTING PURPOSES - # if user == ctx.author: + if user == ctx.author: # # Well a user can't valentine himself/herself. - # raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") + raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") emoji_1, emoji_2 = self.random_emoji() channel = self.bot.get_channel(Channels.sir_lancebot_playground) diff --git a/bot/exts/holidays/valentines/test_bemyvalentine.py b/bot/exts/holidays/valentines/test_bemyvalentine.py new file mode 100644 index 0000000000..be112008b4 --- /dev/null +++ b/bot/exts/holidays/valentines/test_bemyvalentine.py @@ -0,0 +1,323 @@ + +import pytest +import discord +# Import AsyncMock and MagicMock for creating fake asynchronous and regular objects for testing +from unittest.mock import AsyncMock, MagicMock +from discord.ext import commands +from bot.constants import Roles + +# Import the BeMyValentine cog (the part of the bot that handles sending valentines) to be tested +from .be_my_valentine import BeMyValentine + + + +@pytest.mark.asyncio +async def test_send_valentine_user_without_lovefest_role(): + """Test that sending a valentine to a user without the lovefest role raises UserInputError.""" + # Create a fake bot instance using MagicMock + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context (ctx) using MagicMock + ctx = MagicMock() + # Set the delete method on the message to an AsyncMock since deletion is asynchronous + ctx.message.delete = AsyncMock() + # Create a fake command invoker (author) for the context + ctx.author = MagicMock() + # Create a fake user object representing the intended recipient + user = MagicMock() + # Simulate that the user does not have any roles by assigning an empty list + user.roles = [] # User does not have the lovefest role + + # Assert that a UserInputError is raised when the command is called with a user missing the lovefest role + with pytest.raises(commands.UserInputError, match="You cannot send a valentine to .* as they do not have the lovefest role!"): + # Call the command callback with parameters for a public, signed valentine and expect an error due to missing role + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="signed", valentine_type="p") + + # Verify that the command did not attempt to delete the message since the process failed early + ctx.message.delete.assert_not_called() # No message should be deleted in this case + + + +@pytest.mark.asyncio +async def test_send_valentine_self_valentine(): + """Test that a user cannot send a valentine to themselves.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Set the delete method on the message to an AsyncMock + ctx.message.delete = AsyncMock() + # Use the same object for the user to simulate a self-send scenario + user = ctx.author # Self-send + + # Simulate that the user has the lovefest role by adding a role with the correct ID + user.roles = [MagicMock(id=Roles.lovefest)] + + # Assert that sending a valentine to oneself raises a UserInputError with the expected message + with pytest.raises(commands.UserInputError, match="Come on, you can't send a valentine to yourself"): + # Call the command callback; self-send should trigger the error before any message is sent + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="signed", valentine_type="p") + + # Ensure that no attempt is made to delete the command message since the error occurs before deletion + ctx.message.delete.assert_not_called() # No need to delete the message in this case + + + +@pytest.mark.asyncio +async def test_send_valentine_invalid_privacy_type(): + """Test that an invalid privacy type raises UserInputError.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Create a fake recipient user + user = MagicMock() + # Simulate that the user has the lovefest role + user.roles = [MagicMock(id=Roles.lovefest)] # User has lovefest role + + # Assert that using an invalid privacy type (here, "invalid") raises a UserInputError with the correct message + with pytest.raises(commands.UserInputError, match="Specify if you want the message to be sent privately or publicly!"): + # Call the command callback with an invalid privacy type to trigger the error + await cog.send_valentine.callback(cog, ctx, user, privacy_type="invalid", anon="signed", valentine_type="p") + + + +@pytest.mark.asyncio +async def test_send_valentine_invalid_anon_type(): + """Test that an invalid anonymity type raises UserInputError.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Create a fake recipient user + user = MagicMock() + # Simulate that the user has the lovefest role + user.roles = [MagicMock(id=Roles.lovefest)] # User has lovefest role + + # Assert that an invalid anonymity type (here, "invalid") causes a UserInputError with the expected message + with pytest.raises(commands.UserInputError, match="Specify if you want the message to be anonymous or not!"): + # Call the command callback with an invalid anonymity type to trigger the error + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="invalid", valentine_type="p") + + +@pytest.mark.asyncio +async def test_send_valentine_public_signed(): + """Test that a public, signed valentine is sent successfully.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Set the delete method on the command message as an AsyncMock (should not be used in this case) + ctx.message.delete = AsyncMock() + # Set the send method on the context as an AsyncMock since the command sends a message publicly + ctx.send = AsyncMock() + # Create a fake recipient user object + user = MagicMock() + # Simulate that the recipient user has the lovefest role + user.roles = [MagicMock(id=Roles.lovefest)] # User has lovefest role + # Define a display name for the recipient (used in the message embed) + user.display_name = "Recipient" + + # Stub the random_emoji method to return fixed emojis for predictable output during testing + cog.random_emoji = MagicMock(return_value=("💖", "💕")) + # Stub the valentine_check method to return a sample valentine message and title + cog.valentine_check = MagicMock(return_value=("A lovely poem", "A poem dedicated to")) + + # Call the send_valentine callback with valid parameters for a public, signed valentine + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="signed", valentine_type="p") + + # Assert that the context's send method was called to send the valentine publicly + ctx.send.assert_awaited() # Message should be sent publicly + # Assert that the command message was not deleted since deletion is only required for anonymous messages + ctx.message.delete.assert_not_called() # No need to delete the message in this case + + + +@pytest.mark.asyncio +async def test_send_valentine_private_anon(): + """Test that a private, anonymous valentine is sent successfully.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Set the delete method on the command message as an AsyncMock for potential deletion + ctx.message.delete = AsyncMock() + # Set the send method on the author to simulate sending a DM confirmation back to the command invoker + ctx.author.send = AsyncMock() + # Create a fake recipient user object + user = MagicMock() + # Simulate that the recipient has the lovefest role + user.roles = [MagicMock(id=Roles.lovefest)] # User has lovefest role + # Define a display name for the recipient used in messages + user.display_name = "Recipient" + # Set the recipient's send method as an AsyncMock to simulate sending the DM valentine + user.send = AsyncMock() + + # Stub the random_emoji method to return fixed emojis for predictable testing output + cog.random_emoji = MagicMock(return_value=("💖", "💕")) + # Stub the valentine_check method to return a sample valentine message and title + cog.valentine_check = MagicMock(return_value=("A lovely poem", "A poem dedicated to")) + + # Call the send_valentine callback with parameters for a private, anonymous valentine + await cog.send_valentine.callback(cog, ctx, user, privacy_type="private", anon="anon", valentine_type="p") + + # Assert that the recipient's send method was awaited, indicating a DM was sent + user.send.assert_awaited() # DM should be sent + # Assert that a confirmation DM was sent to the command invoker with the correct message + ctx.author.send.assert_awaited_with(f"Your valentine has been **privately** delivered to {user.display_name}!") + # Assert that the original command message was deleted to maintain anonymity + ctx.message.delete.assert_awaited() # Original command message should be deleted + + +@pytest.mark.asyncio +async def test_send_valentine_private_anon_dm_disabled(): + """Test that an error is raised when sending a private valentine to a user with DMs disabled.""" + # Create a fake bot instance + bot = MagicMock() + # Instantiate the BeMyValentine cog with the fake bot + cog = BeMyValentine(bot) + + # Create a fake command context + ctx = MagicMock() + # Create a fake author for the command context + ctx.author = MagicMock() + # Set the delete method on the command message as an AsyncMock for potential deletion + ctx.message.delete = AsyncMock() + # Set the send method on the context as an AsyncMock to simulate sending error messages publicly + ctx.send = AsyncMock() + # Create a fake recipient user object + user = MagicMock() + # Simulate that the recipient has the lovefest role + user.roles = [MagicMock(id=Roles.lovefest)] # User has lovefest role + # Define a display name for the recipient + user.display_name = "Recipient" + + # Create a fake discord.Forbidden exception to simulate a scenario where the recipient's DMs are disabled + forbidden_exception = discord.Forbidden(response=MagicMock(), message="Forbidden") + # Set the recipient's send method to raise the Forbidden exception when called + user.send = AsyncMock(side_effect=forbidden_exception) + + # Stub the random_emoji method to return fixed emojis for testing + cog.random_emoji = MagicMock(return_value=("💖", "💕")) + # Stub the valentine_check method to return a sample valentine message and title + cog.valentine_check = MagicMock(return_value=("A lovely poem", "A poem dedicated to")) + + # Call the send_valentine callback with parameters for a private, anonymous valentine where the recipient's DMs are disabled + await cog.send_valentine.callback(cog, ctx, user, privacy_type="private", anon="anon", valentine_type="p") + + # Assert that the context's send method was awaited with a message indicating that the DM could not be delivered + ctx.send.assert_awaited_with(f"I couldn't send a private message to {user.display_name}. They may have DMs disabled.") + # Assert that the original command message was deleted even in the error scenario + ctx.message.delete.assert_awaited() # Original command message should be deleted + + + +@pytest.mark.asyncio +async def test_send_valentine_dm_channel(): + """Test that using the command in a DM (ctx.guild is None) raises UserInputError.""" + bot = MagicMock() + cog = BeMyValentine(bot) + + ctx = MagicMock() + ctx.guild = None # Simulate a DM channel (no guild) + ctx.message.delete = AsyncMock() + ctx.author = MagicMock() + + user = MagicMock() + user.roles = [MagicMock(id=Roles.lovefest)] + user.display_name = "Recipient" + + with pytest.raises(commands.UserInputError, match="You are supposed to use this command in the server."): + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="signed", valentine_type="p") + + + +@pytest.mark.asyncio +async def test_send_valentine_deletion_failure(): + """ + Test that if deleting the command message fails (raises discord.Forbidden), + the bot sends an error message and continues processing. + """ + bot = MagicMock() + cog = BeMyValentine(bot) + + ctx = MagicMock() + # Simulate deletion failure by having the delete method raise a Forbidden exception + ctx.message.delete = AsyncMock(side_effect=discord.Forbidden(response=MagicMock(), message="Forbidden")) + ctx.author = MagicMock() + ctx.author.send = AsyncMock() + ctx.send = AsyncMock() # Used to send the error message for deletion failure + ctx.guild = MagicMock() # Ensure the command is executed in a guild + + user = MagicMock() + user.roles = [MagicMock(id=Roles.lovefest)] + user.display_name = "Recipient" + user.send = AsyncMock() + + cog.random_emoji = MagicMock(return_value=("💖", "💕")) + cog.valentine_check = MagicMock(return_value=("A lovely poem", "A poem dedicated to")) + + await cog.send_valentine.callback(cog, ctx, user, privacy_type="private", anon="anon", valentine_type="p") + + ctx.message.delete.assert_awaited() # Confirm deletion was attempted + # Confirm that an error message was sent due to deletion failure + ctx.send.assert_any_await("I can't delete your message! Please check my permissions.") + user.send.assert_awaited() # Confirm that a DM was sent to the recipient + ctx.author.send.assert_awaited_with(f"Your valentine has been **privately** delivered to {user.display_name}!") + + +@pytest.mark.asyncio +async def test_send_valentine_public_send_failure(): + """ + Test that if sending a public message fails (raises discord.Forbidden), + the bot catches the exception and sends a fallback error message. + """ + bot = MagicMock() + cog = BeMyValentine(bot) + + ctx = MagicMock() + ctx.author = MagicMock() + ctx.message.delete = AsyncMock() # Not used since anon is "signed" + ctx.send = AsyncMock() + # Simulate failure on the first call to ctx.send and then success on the fallback call + ctx.send.side_effect = [discord.Forbidden(response=MagicMock(), message="Forbidden"), None] + ctx.guild = MagicMock() + + user = MagicMock() + user.roles = [MagicMock(id=Roles.lovefest)] + user.display_name = "Recipient" + + cog.random_emoji = MagicMock(return_value=("💖", "💕")) + cog.valentine_check = MagicMock(return_value=("A lovely poem", "A poem dedicated to")) + + await cog.send_valentine.callback(cog, ctx, user, privacy_type="public", anon="signed", valentine_type="p") + + # Confirm that the fallback error message was sent after the public message send failed + ctx.send.assert_any_await(f"I couldn't send a private message to {user.display_name}. They may have DMs disabled.") From 72bccd8f61a4843844589014dc588d70def6eff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Yu=20Stjernstr=C3=B6m?= Date: Thu, 6 Mar 2025 21:26:57 +0100 Subject: [PATCH 6/8] Update be_my_valentine.py --- bot/exts/holidays/valentines/be_my_valentine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index c37433cf6d..4f55baf84f 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -92,7 +92,7 @@ async def send_valentine( # COMMENTED FOR TESTING PURPOSES if user == ctx.author: - # # Well a user can't valentine himself/herself. + # Well a user can't valentine himself/herself. raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") emoji_1, emoji_2 = self.random_emoji() From e11691785b74116bfe285a1e40597dc8a1af3bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Yu=20Stjernstr=C3=B6m?= Date: Thu, 6 Mar 2025 21:32:44 +0100 Subject: [PATCH 7/8] Update be_my_valentine.py --- bot/exts/holidays/valentines/be_my_valentine.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 4f55baf84f..3b071e20fb 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -89,10 +89,9 @@ async def send_valentine( f"Specify if you want the message to be sent privately or publicly!" ) - # COMMENTED FOR TESTING PURPOSES if user == ctx.author: - # Well a user can't valentine himself/herself. + # Well a user can't valentine himself/herself. raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") emoji_1, emoji_2 = self.random_emoji() From 62fab9b10ab89bc44166f7fead339b9d423cdf71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Stjernstr=C3=B6m?= Date: Fri, 7 Mar 2025 16:08:22 +0100 Subject: [PATCH 8/8] refactor: moved test file --- tests/__init__.py | 0 tests/exts/__init__.py | 0 tests/exts/holidays/__init__.py | 0 tests/exts/holidays/valentines/__init__.py | 0 {bot => tests}/exts/holidays/valentines/test_bemyvalentine.py | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/__init__.py create mode 100644 tests/exts/__init__.py create mode 100644 tests/exts/holidays/__init__.py create mode 100644 tests/exts/holidays/valentines/__init__.py rename {bot => tests}/exts/holidays/valentines/test_bemyvalentine.py (99%) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/exts/__init__.py b/tests/exts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/exts/holidays/__init__.py b/tests/exts/holidays/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/exts/holidays/valentines/__init__.py b/tests/exts/holidays/valentines/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bot/exts/holidays/valentines/test_bemyvalentine.py b/tests/exts/holidays/valentines/test_bemyvalentine.py similarity index 99% rename from bot/exts/holidays/valentines/test_bemyvalentine.py rename to tests/exts/holidays/valentines/test_bemyvalentine.py index be112008b4..9a5d00804a 100644 --- a/bot/exts/holidays/valentines/test_bemyvalentine.py +++ b/tests/exts/holidays/valentines/test_bemyvalentine.py @@ -7,7 +7,7 @@ from bot.constants import Roles # Import the BeMyValentine cog (the part of the bot that handles sending valentines) to be tested -from .be_my_valentine import BeMyValentine +from bot.exts.holidays.valentines.be_my_valentine import BeMyValentine