From 6cfb036c303d65b46d01ededaca6c8d1ac0a7a95 Mon Sep 17 00:00:00 2001 From: Emma Samms Date: Thu, 30 Oct 2025 06:38:56 -0400 Subject: [PATCH 1/2] Enhance user guidance in unverified command channel --- src/cmds/automation/auto_verify.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cmds/automation/auto_verify.py b/src/cmds/automation/auto_verify.py index 98c538d..2077765 100644 --- a/src/cmds/automation/auto_verify.py +++ b/src/cmds/automation/auto_verify.py @@ -28,7 +28,12 @@ async def on_message(self, ctx: Message) -> None: # Return if the message was sent by the bot to avoid recursion. if ctx.author.bot: return - + # When a user types in un-verified-bot-commands, hold their hand in finding the how-to-talk channel so they can verify. + if ctx.channel.id == 1430556712313688225: + await ctx.reply( + "Hello! Welcome to the Hack The Box Discord! In-order to access the full server, please verify your account by following the instructions in <#1432333413980835840>.", + mention_author=True, + ) try: await self.process_reverification(ctx.author) except VerificationError as exc: From 5fa766ff7828d4255bd97c50f427d9f4e70e15ef Mon Sep 17 00:00:00 2001 From: Dimosthenis Schizas Date: Fri, 19 Dec 2025 10:53:46 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20auto=5Fveri?= =?UTF-8?q?fy=20and=20fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix "In-order" typo to "In order" in welcome message - Add test coverage for on_message welcome feature in auto_verify - Fix broken assertion in test_ban.py (called_once_with -> assert_called_once_with) --- src/cmds/automation/auto_verify.py | 8 ++- tests/src/cmds/automation/__init__.py | 0 tests/src/cmds/automation/test_auto_verify.py | 61 +++++++++++++++++++ tests/src/helpers/test_ban.py | 24 ++++---- 4 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 tests/src/cmds/automation/__init__.py create mode 100644 tests/src/cmds/automation/test_auto_verify.py diff --git a/src/cmds/automation/auto_verify.py b/src/cmds/automation/auto_verify.py index 2077765..e3db24c 100644 --- a/src/cmds/automation/auto_verify.py +++ b/src/cmds/automation/auto_verify.py @@ -16,7 +16,7 @@ def __init__(self, bot: Bot): async def process_reverification(self, member: Member | User) -> None: """Re-verifation process for a member. - + TODO: Reimplement once it's possible to fetch link state from the HTB Account. """ raise VerificationError("Not implemented") @@ -31,13 +31,15 @@ async def on_message(self, ctx: Message) -> None: # When a user types in un-verified-bot-commands, hold their hand in finding the how-to-talk channel so they can verify. if ctx.channel.id == 1430556712313688225: await ctx.reply( - "Hello! Welcome to the Hack The Box Discord! In-order to access the full server, please verify your account by following the instructions in <#1432333413980835840>.", + "Hello! Welcome to the Hack The Box Discord! In order to access the full server, please verify your account by following the instructions in <#1432333413980835840>.", mention_author=True, ) try: await self.process_reverification(ctx.author) except VerificationError as exc: - logger.debug(f"HTB Discord link for user {ctx.author.name} with ID {ctx.author.id} not found", exc_info=exc) + logger.debug( + f"HTB Discord link for user {ctx.author.name} with ID {ctx.author.id} not found", exc_info=exc + ) @commands.Cog.listener() @commands.cooldown(1, 3600, commands.BucketType.user) diff --git a/tests/src/cmds/automation/__init__.py b/tests/src/cmds/automation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/cmds/automation/test_auto_verify.py b/tests/src/cmds/automation/test_auto_verify.py new file mode 100644 index 0000000..3b1cb54 --- /dev/null +++ b/tests/src/cmds/automation/test_auto_verify.py @@ -0,0 +1,61 @@ +from unittest import mock + +import pytest + +from src.cmds.automation import auto_verify +from tests import helpers + + +class TestMessageHandler: + """Test the `MessageHandler` cog.""" + + @pytest.mark.asyncio + async def test_on_message_in_unverified_channel_sends_welcome(self, bot): + """Test that a welcome message is sent when user posts in unverified channel.""" + cog = auto_verify.MessageHandler(bot) + + # Create a mock message in the specific unverified channel + channel = helpers.MockTextChannel(id=1430556712313688225) + author = helpers.MockMember(bot=False) + message = helpers.MockMessage(channel=channel, author=author) + message.reply = mock.AsyncMock() + + await cog.on_message(message) + + # Verify reply was called with welcome message + message.reply.assert_called_once() + call_args = message.reply.call_args + assert "Welcome to the Hack The Box Discord" in call_args[0][0] + assert call_args[1]["mention_author"] is True + + @pytest.mark.asyncio + async def test_on_message_in_other_channel_no_welcome(self, bot): + """Test that no welcome is sent in other channels.""" + cog = auto_verify.MessageHandler(bot) + + # Create a mock message in a different channel + channel = helpers.MockTextChannel(id=999999999999999999) + author = helpers.MockMember(bot=False) + message = helpers.MockMessage(channel=channel, author=author) + message.reply = mock.AsyncMock() + + await cog.on_message(message) + + # Verify reply was NOT called + message.reply.assert_not_called() + + @pytest.mark.asyncio + async def test_on_message_from_bot_returns_early(self, bot): + """Test that bot messages are ignored.""" + cog = auto_verify.MessageHandler(bot) + + # Create a message from a bot in the unverified channel + channel = helpers.MockTextChannel(id=1430556712313688225) + author = helpers.MockMember(bot=True) + message = helpers.MockMessage(channel=channel, author=author) + message.reply = mock.AsyncMock() + + await cog.on_message(message) + + # Reply should not be called for bot messages + message.reply.assert_not_called() diff --git a/tests/src/helpers/test_ban.py b/tests/src/helpers/test_ban.py index 1771928..e39fe26 100644 --- a/tests/src/helpers/test_ban.py +++ b/tests/src/helpers/test_ban.py @@ -4,7 +4,6 @@ import pytest from discord import Forbidden, HTTPException -from datetime import datetime, timezone from src.helpers.ban import _check_member, _dm_banned_member, ban_member from src.helpers.responses import SimpleResponse @@ -12,12 +11,11 @@ class TestBanHelpers: - @pytest.mark.asyncio async def test__check_member_staff_member(self, bot, guild, member): author = helpers.MockMember(name="Author User") member_is_staff = mock.Mock(return_value=True) - with mock.patch('src.helpers.ban.member_is_staff', member_is_staff): + with mock.patch("src.helpers.ban.member_is_staff", member_is_staff): response = await _check_member(bot, guild, member, author) assert isinstance(response, SimpleResponse) assert response.message == "You cannot ban another staff member." @@ -27,7 +25,7 @@ async def test__check_member_staff_member(self, bot, guild, member): async def test__check_member_regular_member(self, bot, guild, member): author = helpers.MockMember(name="Author User") member_is_staff = mock.Mock(return_value=False) - with mock.patch('src.helpers.ban.member_is_staff', member_is_staff): + with mock.patch("src.helpers.ban.member_is_staff", member_is_staff): response = await _check_member(bot, guild, member, author) assert response is None @@ -37,7 +35,7 @@ async def test__check_member_user(self, bot, guild, user): bot.get_member_or_user = AsyncMock() bot.get_member_or_user.return_value = user response = await _check_member(bot, guild, user, author) - assert await bot.get_member_or_user.called_once_with(guild, user.id) + bot.get_member_or_user.assert_called_once_with(guild, user.id) assert response is None @pytest.mark.asyncio @@ -110,7 +108,6 @@ def __init__(self, status, reason): class TestBanMember: - @pytest.mark.asyncio async def test_ban_member_valid_duration(self, bot, guild, member, author): duration = "1d" @@ -134,8 +131,9 @@ async def test_ban_member_valid_duration(self, bot, guild, member, author): result = await ban_member(bot, guild, member, duration, reason, evidence) assert isinstance(result, SimpleResponse) - assert result.message == f"{member.display_name} ({member.id}) has been banned until {expected_date} " \ - f"(UTC)." + assert ( + result.message == f"{member.display_name} ({member.id}) has been banned until {expected_date} (UTC)." + ) @pytest.mark.asyncio async def test_ban_member_invalid_duration(self, bot, guild, member, author): @@ -196,7 +194,7 @@ async def test_ban_member_no_reason_success(self, bot, guild, member, author): @pytest.mark.asyncio async def test_ban_member_no_author_success(self, bot, guild, member): - duration = '500w' + duration = "500w" reason = "" evidence = "Some evidence" member.display_name = "Banned Member" @@ -216,7 +214,7 @@ async def test_ban_member_no_author_success(self, bot, guild, member): @pytest.mark.asyncio async def test_ban_already_exists(self, bot, guild, member, author): - duration = '500w' + duration = "500w" reason = "" evidence = "Some evidence" member.display_name = "Banned Member" @@ -238,7 +236,7 @@ async def test_ban_already_exists(self, bot, guild, member, author): async def test_ban_member_staff(self, ctx, bot, guild): ctx.user = helpers.MockMember(id=1, name="Test User") user = helpers.MockMember(id=2, name="Banned User") - with patch('src.helpers.ban.member_is_staff', return_value=True): + with patch("src.helpers.ban.member_is_staff", return_value=True): response = await ban_member( bot, guild, user, "1d", "spamming", "some evidence", author=ctx.user, needs_approval=True ) @@ -250,7 +248,7 @@ async def test_ban_member_staff(self, ctx, bot, guild): async def test_ban_member_bot(self, ctx, bot, guild): ctx.user = helpers.MockMember(id=1, name="Test User") member = helpers.MockMember(id=2, name="Bot Member", bot=True) - with patch('src.helpers.ban.member_is_staff', return_value=False): + with patch("src.helpers.ban.member_is_staff", return_value=False): response = await ban_member( bot, guild, member, "1d", "spamming", "some evidence", author=ctx.user, needs_approval=True ) @@ -261,7 +259,7 @@ async def test_ban_member_bot(self, ctx, bot, guild): @pytest.mark.asyncio async def test_ban_self(self, ctx, bot, guild): ctx.user = helpers.MockMember(id=1, name="Test User") - with patch('src.helpers.ban.member_is_staff', return_value=False): + with patch("src.helpers.ban.member_is_staff", return_value=False): response = await ban_member( bot, guild, ctx.user, "1d", "spamming", "some evidence", author=ctx.user, needs_approval=True )