From 590be58a72b10261a6172d6ac996ba17fd3ce73e Mon Sep 17 00:00:00 2001 From: ddc Date: Fri, 8 May 2026 21:03:52 -0300 Subject: [PATCH] v3.0.14 --- src/bot/cogs/open_ai.py | 8 +- src/bot/tools/bot_utils.py | 16 +- src/gw2/cogs/account.py | 39 +--- src/gw2/tools/gw2_utils.py | 10 +- tests/unit/bot/cogs/test_open_ai.py | 18 +- tests/unit/bot/tools/test_bot_utils_extra.py | 16 +- tests/unit/gw2/cogs/test_account.py | 177 ------------------- 7 files changed, 38 insertions(+), 246 deletions(-) diff --git a/src/bot/cogs/open_ai.py b/src/bot/cogs/open_ai.py index fc2c63a..de678a2 100644 --- a/src/bot/cogs/open_ai.py +++ b/src/bot/cogs/open_ai.py @@ -1,6 +1,6 @@ import discord from discord.ext import commands -from openai import OpenAI +from openai import AsyncOpenAI from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam from src.bot.constants.settings import get_bot_settings from src.bot.discord_bot import Bot @@ -14,7 +14,7 @@ class OpenAi(commands.Cog): def __init__(self, bot: Bot) -> None: self.bot = bot self._bot_settings = get_bot_settings() - self._openai_client: OpenAI = OpenAI(api_key=self._bot_settings.openai_api_key) + self._openai_client: AsyncOpenAI = AsyncOpenAI(api_key=self._bot_settings.openai_api_key) @commands.command() @commands.cooldown(1, CoolDowns.OpenAI.value, commands.BucketType.user) @@ -57,8 +57,8 @@ async def _get_ai_response(self, message: str) -> str: ChatCompletionUserMessageParam(role="user", content=message), ] - # Use the correct OpenAI API endpoint - response = self._openai_client.chat.completions.create( + # Use the correct OpenAI API endpoint (async — does not block the event loop) + response = await self._openai_client.chat.completions.create( model=model, messages=messages, max_completion_tokens=1000, diff --git a/src/bot/tools/bot_utils.py b/src/bot/tools/bot_utils.py index 2b41aca..cd66eba 100644 --- a/src/bot/tools/bot_utils.py +++ b/src/bot/tools/bot_utils.py @@ -123,7 +123,7 @@ def _is_transient_discord_error(e: discord.HTTPException) -> bool: return (isinstance(status, int) and status >= 500) or code == 40062 -async def _send_with_retry(ctx, send_method, *args, max_attempts: int = 3, base_delay: float = 1.0, **kwargs): +async def send_with_retry(ctx, send_method, *args, max_attempts: int = 3, base_delay: float = 1.0, **kwargs): """Call send_method(*args, **kwargs) and retry on transient Discord errors. On the first transient failure, posts a one-time "retrying" notice to the channel. @@ -163,11 +163,11 @@ async def send_embed(ctx, embed, dm=False): if is_private_message(ctx): # Already in DM, just send the embed - await _send_with_retry(ctx, ctx.author.send, embed=embed) + await send_with_retry(ctx, ctx.author.send, embed=embed) elif dm: # Send to DM and notify in channel try: - await _send_with_retry(ctx, ctx.author.send, embed=embed) + await send_with_retry(ctx, ctx.author.send, embed=embed) notification_embed = discord.Embed( description="📬 Response sent to your DM", color=discord.Color.green() ) @@ -175,13 +175,13 @@ async def send_embed(ctx, embed, dm=False): name=ctx.author.display_name, icon_url=ctx.author.avatar.url if ctx.author.avatar else ctx.author.default_avatar.url, ) - await _send_with_retry(ctx, ctx.send, embed=notification_embed) + await send_with_retry(ctx, ctx.send, embed=notification_embed) except discord.Forbidden, discord.HTTPException: # DM failed, fall back to sending in the channel - await _send_with_retry(ctx, ctx.send, embed=embed) + await send_with_retry(ctx, ctx.send, embed=embed) else: # Send to channel - await _send_with_retry(ctx, ctx.send, embed=embed) + await send_with_retry(ctx, ctx.send, embed=embed) except (discord.Forbidden, discord.HTTPException) as e: ctx.bot.log.error(f"Failed to send message: {e}") if dm or is_private_message(ctx): @@ -240,10 +240,10 @@ def _dict_to_embed(data: dict) -> discord.Embed: async def send_and_save(self, ctx) -> None: """Send the first page and save all pages to the database. - Uses _send_with_retry so transient Discord errors (5xx, code 40062) are + Uses send_with_retry so transient Discord errors (5xx, code 40062) are retried before propagating to the command error handler. """ - msg = await _send_with_retry(ctx, ctx.send, embed=self.pages[0], view=self) + msg = await send_with_retry(ctx, ctx.send, embed=self.pages[0], view=self) self.message = msg from src.database.dal.bot.embed_pages_dal import EmbedPagesDal diff --git a/src/gw2/cogs/account.py b/src/gw2/cogs/account.py index 6735cf5..ca7f0d2 100644 --- a/src/gw2/cogs/account.py +++ b/src/gw2/cogs/account.py @@ -11,23 +11,6 @@ from src.gw2.tools.gw2_cooldowns import GW2CoolDowns -async def _keep_typing_alive(ctx, stop_event): - """Helper to keep Discord typing indicator alive during long operations.""" - try: - while not stop_event.is_set(): - try: - await ctx.message.channel.typing() - await asyncio.sleep(4) # Renew every 4 seconds (Discord typing lasts ~5s) - except asyncio.CancelledError: - raise # Re-raise CancelledError - except discord.HTTPException, discord.Forbidden: - # Handle Discord API errors gracefully and stop the loop - break - except asyncio.CancelledError: - # Clean up and re-raise CancelledError as required - raise - - async def _fetch_guild_info_standalone(gw2_api, guild_id, api_key, ctx): """Helper to fetch individual guild information.""" try: @@ -83,10 +66,6 @@ async def account(ctx): if "account" not in permissions: return await bot_utils.send_error_msg(ctx, gw2_messages.API_KEY_NO_PERMISSION, True) - # Initialize variables for cleanup - stop_typing = None - typing_task = None - try: # Send progress message as embed color = ctx.bot.settings["gw2"]["EmbedColor"] @@ -95,11 +74,7 @@ async def account(ctx): color=color, ) progress_embed.set_author(name=ctx.message.author.display_name, icon_url=ctx.message.author.display_avatar.url) - progress_msg = await ctx.send(embed=progress_embed) - - # Start background typing keeper - stop_typing = asyncio.Event() - typing_task = asyncio.create_task(_keep_typing_alive(ctx, stop_typing)) + progress_msg = await bot_utils.send_with_retry(ctx, ctx.send, embed=progress_embed) # Fetch basic account info and server info in parallel account_task = gw2_api.call_api("account", api_key) @@ -251,23 +226,11 @@ async def limited_guild_fetch(task): text=f"{bot_utils.get_current_date_time_str_long()} UTC", ) - # Stop the background typing task - stop_typing.set() - typing_task.cancel() - # Clean up progress message and send final result await progress_msg.delete() await bot_utils.send_embed(ctx, embed) except Exception as e: - # Stop the background typing task if it exists - if stop_typing is not None and typing_task is not None: - try: - stop_typing.set() - typing_task.cancel() - except AttributeError, RuntimeError: - # Handle cases where task is already done or event is invalid - pass await bot_utils.send_error_msg(ctx, e) return ctx.bot.log.error(ctx, e) diff --git a/src/gw2/tools/gw2_utils.py b/src/gw2/tools/gw2_utils.py index a378f8b..8ba3357 100644 --- a/src/gw2/tools/gw2_utils.py +++ b/src/gw2/tools/gw2_utils.py @@ -89,11 +89,17 @@ class Gw2Servers(Enum): async def send_progress_embed( ctx: commands.Context, message: str = "Please wait, I'm fetching data from GW2 API... (this may take a moment)" ) -> discord.Message: - """Send a progress embed that can be deleted when the operation completes.""" + """Send a progress embed that can be deleted when the operation completes. + + Uses send_with_retry so transient Discord errors (5xx, code 40062) are retried + instead of bubbling up to the command error handler. + """ + from src.bot.tools.bot_utils import send_with_retry + color = ctx.bot.settings["gw2"]["EmbedColor"] embed = discord.Embed(description=f"\U0001f504 **{message}**", color=color) embed.set_author(name=ctx.message.author.display_name, icon_url=ctx.message.author.display_avatar.url) - return await ctx.send(embed=embed) + return await send_with_retry(ctx, ctx.send, embed=embed) async def send_msg(ctx: commands.Context, description: str, dm: bool = False) -> None: diff --git a/tests/unit/bot/cogs/test_open_ai.py b/tests/unit/bot/cogs/test_open_ai.py index 7365f60..1524bc3 100644 --- a/tests/unit/bot/cogs/test_open_ai.py +++ b/tests/unit/bot/cogs/test_open_ai.py @@ -28,7 +28,7 @@ def mock_bot(): @pytest.fixture def openai_cog(mock_bot): """Create an OpenAi cog instance.""" - with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.OpenAI"): + with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.AsyncOpenAI"): mock_settings.return_value = MagicMock(openai_api_key="test-key", openai_model="gpt-3.5-turbo") return OpenAi(mock_bot) @@ -79,7 +79,7 @@ class TestOpenAi: def test_init(self, mock_bot): """Test OpenAi cog initialization.""" - with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.OpenAI"): + with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.AsyncOpenAI"): mock_settings.return_value = MagicMock(openai_api_key="test-key", openai_model="gpt-3.5-turbo") cog = OpenAi(mock_bot) assert cog.bot == mock_bot @@ -140,7 +140,7 @@ async def test_get_ai_response_success( # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_openai_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_openai_response) openai_cog._openai_client = mock_client result = await openai_cog._get_ai_response("What is Python?") @@ -173,7 +173,7 @@ async def test_get_ai_response_with_leading_trailing_spaces( # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_openai_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_openai_response) openai_cog._openai_client = mock_client result = await openai_cog._get_ai_response("Test message") @@ -250,7 +250,7 @@ async def test_ai_command_with_different_models(self, mock_send_embed, openai_co # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_openai_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_openai_response) openai_cog._openai_client = mock_client await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text="Test question") @@ -303,7 +303,7 @@ async def test_get_ai_response_system_message_content( # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_openai_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_openai_response) openai_cog._openai_client = mock_client await openai_cog._get_ai_response("Test message") @@ -323,7 +323,7 @@ async def test_get_ai_response_api_parameters( # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_openai_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_openai_response) openai_cog._openai_client = mock_client await openai_cog._get_ai_response("Test message") @@ -348,7 +348,7 @@ async def test_setup_function(self, mock_bot): """Test the setup function.""" from src.bot.cogs.open_ai import setup - with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.OpenAI"): + with patch("src.bot.cogs.open_ai.get_bot_settings") as mock_settings, patch("src.bot.cogs.open_ai.AsyncOpenAI"): mock_settings.return_value = MagicMock(openai_api_key="test-key", openai_model="gpt-3.5-turbo") await setup(mock_bot) @@ -414,7 +414,7 @@ async def test_get_ai_response_empty_response(self, mock_get_settings, openai_co # Mock the client instance directly mock_client = MagicMock() - mock_client.chat.completions.create.return_value = mock_response + mock_client.chat.completions.create = AsyncMock(return_value=mock_response) openai_cog._openai_client = mock_client result = await openai_cog._get_ai_response("Test message") diff --git a/tests/unit/bot/tools/test_bot_utils_extra.py b/tests/unit/bot/tools/test_bot_utils_extra.py index 99c6273..87fb345 100644 --- a/tests/unit/bot/tools/test_bot_utils_extra.py +++ b/tests/unit/bot/tools/test_bot_utils_extra.py @@ -1107,7 +1107,7 @@ def _make_http_exception(status: int, code: int = 0) -> discord.HTTPException: class TestSendWithRetry: - """Test _send_with_retry helper for transient Discord errors.""" + """Test send_with_retry helper for transient Discord errors.""" @pytest.fixture def mock_ctx(self): @@ -1121,7 +1121,7 @@ def mock_ctx(self): async def test_success_on_first_attempt_no_retry(self, mock_ctx): """Happy path: send_method called once, no notice sent.""" send = AsyncMock(return_value="ok") - result = await bot_utils._send_with_retry(mock_ctx, send, embed="x") + result = await bot_utils.send_with_retry(mock_ctx, send, embed="x") assert result == "ok" send.assert_awaited_once_with(embed="x") mock_ctx.send.assert_not_called() @@ -1131,7 +1131,7 @@ async def test_retries_on_500_then_succeeds(self, mock_ctx): """500 error → retry, second attempt succeeds; one channel notice sent.""" send = AsyncMock(side_effect=[_make_http_exception(500), "ok"]) with patch("src.bot.tools.bot_utils.asyncio.sleep", new_callable=AsyncMock): - result = await bot_utils._send_with_retry(mock_ctx, send, embed="x") + result = await bot_utils.send_with_retry(mock_ctx, send, embed="x") assert result == "ok" assert send.await_count == 2 # Notice sent exactly once @@ -1144,7 +1144,7 @@ async def test_retries_on_429_code_40062(self, mock_ctx): """429 with code 40062 is treated as transient and retried.""" send = AsyncMock(side_effect=[_make_http_exception(429, code=40062), "ok"]) with patch("src.bot.tools.bot_utils.asyncio.sleep", new_callable=AsyncMock): - result = await bot_utils._send_with_retry(mock_ctx, send) + result = await bot_utils.send_with_retry(mock_ctx, send) assert result == "ok" assert send.await_count == 2 @@ -1154,7 +1154,7 @@ async def test_does_not_retry_on_403_forbidden(self, mock_ctx): forbidden = discord.Forbidden(MagicMock(status=403), {"message": "no", "code": 50007}) send = AsyncMock(side_effect=forbidden) with pytest.raises(discord.Forbidden): - await bot_utils._send_with_retry(mock_ctx, send) + await bot_utils.send_with_retry(mock_ctx, send) send.assert_awaited_once() mock_ctx.send.assert_not_called() @@ -1163,7 +1163,7 @@ async def test_does_not_retry_on_429_other_code(self, mock_ctx): """429 without code 40062 is not retried by this helper.""" send = AsyncMock(side_effect=_make_http_exception(429, code=20016)) with pytest.raises(discord.HTTPException): - await bot_utils._send_with_retry(mock_ctx, send) + await bot_utils.send_with_retry(mock_ctx, send) send.assert_awaited_once() @pytest.mark.asyncio @@ -1172,7 +1172,7 @@ async def test_exhausts_retries_then_raises(self, mock_ctx): send = AsyncMock(side_effect=_make_http_exception(500)) with patch("src.bot.tools.bot_utils.asyncio.sleep", new_callable=AsyncMock): with pytest.raises(discord.HTTPException): - await bot_utils._send_with_retry(mock_ctx, send, max_attempts=3) + await bot_utils.send_with_retry(mock_ctx, send, max_attempts=3) assert send.await_count == 3 # Notice sent at most once even across multiple failed attempts assert mock_ctx.send.call_count == 1 @@ -1183,6 +1183,6 @@ async def test_notice_failure_does_not_break_retry(self, mock_ctx): mock_ctx.send.side_effect = _make_http_exception(500) send = AsyncMock(side_effect=[_make_http_exception(500), "ok"]) with patch("src.bot.tools.bot_utils.asyncio.sleep", new_callable=AsyncMock): - result = await bot_utils._send_with_retry(mock_ctx, send) + result = await bot_utils.send_with_retry(mock_ctx, send) assert result == "ok" assert send.await_count == 2 diff --git a/tests/unit/gw2/cogs/test_account.py b/tests/unit/gw2/cogs/test_account.py index cba8790..e6e4ed6 100644 --- a/tests/unit/gw2/cogs/test_account.py +++ b/tests/unit/gw2/cogs/test_account.py @@ -1,7 +1,5 @@ """Comprehensive tests for GW2 account cog.""" -import asyncio -import discord import pytest from src.gw2.cogs.account import GW2Account, account from src.gw2.constants import gw2_messages @@ -408,100 +406,6 @@ async def test_setup_function_adds_cog(self): assert isinstance(cog_instance, GW2Account) -class TestKeepTypingAlive: - """Test cases for the _keep_typing_alive helper function.""" - - @pytest.fixture - def mock_ctx(self): - """Create a mock context with async typing.""" - ctx = MagicMock() - ctx.message = MagicMock() - ctx.message.channel = MagicMock() - ctx.message.channel.typing = AsyncMock() - return ctx - - @pytest.mark.asyncio - async def test_keep_typing_normal_operation(self, mock_ctx): - """Test that typing() is called and loop exits when event is set.""" - from src.gw2.cogs.account import _keep_typing_alive - - stop_event = asyncio.Event() - - # Immediately set the event so the while loop exits on first check - stop_event.set() - - await _keep_typing_alive(mock_ctx, stop_event) - - # The while condition is checked first; since it's already set, the loop body never runs - mock_ctx.message.channel.typing.assert_not_called() - - @pytest.mark.asyncio - async def test_keep_typing_calls_typing_then_stops(self, mock_ctx): - """Test that typing() is called at least once before event is set.""" - from src.gw2.cogs.account import _keep_typing_alive - - stop_event = asyncio.Event() - call_count = 0 - - original_typing = mock_ctx.message.channel.typing - - async def typing_side_effect(): - nonlocal call_count - call_count += 1 - # Set the stop event after first typing call - stop_event.set() - - mock_ctx.message.channel.typing = AsyncMock(side_effect=typing_side_effect) - - with patch("src.gw2.cogs.account.asyncio.sleep", new_callable=AsyncMock): - await _keep_typing_alive(mock_ctx, stop_event) - - assert call_count >= 1 - - @pytest.mark.asyncio - async def test_keep_typing_cancelled_error_propagates(self, mock_ctx): - """Test that CancelledError is re-raised from the outer handler.""" - from src.gw2.cogs.account import _keep_typing_alive - - stop_event = asyncio.Event() - - # Make typing raise CancelledError - mock_ctx.message.channel.typing = AsyncMock(side_effect=asyncio.CancelledError) - - with pytest.raises(asyncio.CancelledError): - await _keep_typing_alive(mock_ctx, stop_event) - - @pytest.mark.asyncio - async def test_keep_typing_http_exception_breaks_loop(self, mock_ctx): - """Test that discord.HTTPException causes the loop to break gracefully.""" - from src.gw2.cogs.account import _keep_typing_alive - - stop_event = asyncio.Event() - - mock_response = MagicMock(status=500) - mock_ctx.message.channel.typing = AsyncMock(side_effect=discord.HTTPException(mock_response, "server error")) - - # Should not raise, just break out of loop - await _keep_typing_alive(mock_ctx, stop_event) - - mock_ctx.message.channel.typing.assert_called_once() - - @pytest.mark.asyncio - async def test_keep_typing_forbidden_breaks_loop(self, mock_ctx): - """Test that discord.Forbidden causes the loop to break gracefully.""" - from src.gw2.cogs.account import _keep_typing_alive - - stop_event = asyncio.Event() - - mock_response = MagicMock(status=403) - mock_ctx.message.channel.typing = AsyncMock(side_effect=discord.Forbidden(mock_response, "forbidden")) - - # Forbidden is a subclass of HTTPException, so it is caught too - await _keep_typing_alive(mock_ctx, stop_event) - - mock_ctx.message.channel.typing.assert_called_once() - - class TestFetchGuildInfoStandalone: """Test cases for the _fetch_guild_info_standalone helper function.""" @@ -606,9 +510,6 @@ async def test_account_command_full_success_path(self, mock_ctx, sample_account_ patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -621,12 +522,6 @@ async def test_account_command_full_success_path(self, mock_ctx, sample_account_ ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) # ctx.send was called for the progress embed @@ -638,10 +533,6 @@ async def test_account_command_full_success_path(self, mock_ctx, sample_account_ # progress_msg.delete was called to remove the progress message progress_msg.delete.assert_called_once() - # The stop event was set and typing task was cancelled - mock_stop_event.set.assert_called_once() - mock_task.cancel.assert_called_once() - # Verify API calls assert mock_client_instance.call_api.call_count == 2 mock_client_instance.call_api.assert_any_call("account", "test-api-key-12345") @@ -687,9 +578,6 @@ async def test_account_command_exception_handler_with_active_typing_task(self, m patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal, patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error_msg, - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -698,20 +586,11 @@ async def test_account_command_exception_handler_with_active_typing_task(self, m # First call_api (account) raises, after progress msg and typing task created mock_client_instance.call_api = AsyncMock(side_effect=RuntimeError("API exploded")) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) mock_error_msg.assert_called_once() mock_ctx.bot.log.error.assert_called_once() - # Stop event and task cleanup should have been called in the except block - mock_stop_event.set.assert_called_once() - mock_task.cancel.assert_called_once() - @pytest.mark.asyncio async def test_account_command_all_permissions(self, mock_ctx, sample_account_data_no_guilds, sample_world_data): """Test account command with all optional permissions (characters, progression, pvp).""" @@ -728,9 +607,6 @@ async def test_account_command_all_permissions(self, mock_ctx, sample_account_da patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, patch( "src.gw2.cogs.account.gw2_utils.calculate_user_achiev_points", new_callable=AsyncMock, @@ -753,11 +629,6 @@ async def test_account_command_all_permissions(self, mock_ctx, sample_account_da ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) mock_send_embed.assert_called_once() @@ -795,9 +666,6 @@ async def test_account_command_optional_task_failure_is_skipped( patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -811,11 +679,6 @@ async def test_account_command_optional_task_failure_is_skipped( ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) # The command should still succeed despite characters failing @@ -862,9 +725,6 @@ async def test_account_command_guild_handling(self, mock_ctx, sample_world_data) patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -879,11 +739,6 @@ async def test_account_command_guild_handling(self, mock_ctx, sample_world_data) ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) mock_send_embed.assert_called_once() @@ -936,9 +791,6 @@ async def test_account_command_guild_fetch_exception_skipped(self, mock_ctx, sam patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, patch( "src.gw2.cogs.account._fetch_guild_info_standalone", new_callable=AsyncMock, @@ -956,11 +808,6 @@ async def test_account_command_guild_fetch_exception_skipped(self, mock_ctx, sam ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) mock_send_embed.assert_called_once() @@ -997,9 +844,6 @@ async def test_account_command_commander_no(self, mock_ctx, sample_world_data): patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -1012,11 +856,6 @@ async def test_account_command_commander_no(self, mock_ctx, sample_world_data): ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) embed = mock_send_embed.call_args[0][1] @@ -1049,9 +888,6 @@ async def test_account_command_access_normalization(self, mock_ctx, sample_world patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -1064,11 +900,6 @@ async def test_account_command_access_normalization(self, mock_ctx, sample_world ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) embed = mock_send_embed.call_args[0][1] @@ -1111,9 +942,6 @@ async def test_account_author_no_avatar(self, mock_ctx, sample_world_data): patch("src.gw2.cogs.account.Gw2Client") as mock_client, patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send_embed, patch("src.gw2.cogs.account.bot_utils.get_current_date_time_str_long", return_value="2024-01-01 12:00:00"), - patch("src.gw2.cogs.account._keep_typing_alive", new=MagicMock()), - patch("src.gw2.cogs.account.asyncio.create_task") as mock_create_task, - patch("src.gw2.cogs.account.asyncio.Event") as mock_event_cls, ): mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data) @@ -1126,11 +954,6 @@ async def test_account_author_no_avatar(self, mock_ctx, sample_world_data): ] ) - mock_stop_event = MagicMock() - mock_event_cls.return_value = mock_stop_event - mock_task = MagicMock() - mock_create_task.return_value = mock_task - await account(mock_ctx) mock_send_embed.assert_called_once()