From f622a68bb1ef074481f8767130cf13d2039df698 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:11:43 -0300 Subject: [PATCH 1/3] v3.0.6 --- src/database/dal/gw2/gw2_session_chars_dal.py | 16 +- src/gw2/cogs/characters.py | 36 ++- src/gw2/cogs/worlds.py | 91 +++--- src/gw2/cogs/wvw.py | 76 +++-- src/gw2/tools/gw2_utils.py | 47 ++- tests/integration/test_session_flow.py | 36 +-- tests/unit/database/test_dal.py | 40 +-- tests/unit/gw2/cogs/test_characters.py | 273 ++++++++++-------- tests/unit/gw2/cogs/test_worlds.py | 17 ++ tests/unit/gw2/tools/test_gw2_utils.py | 14 +- 10 files changed, 361 insertions(+), 285 deletions(-) diff --git a/src/database/dal/gw2/gw2_session_chars_dal.py b/src/database/dal/gw2/gw2_session_chars_dal.py index bae8e736..b53bebc0 100644 --- a/src/database/dal/gw2/gw2_session_chars_dal.py +++ b/src/database/dal/gw2/gw2_session_chars_dal.py @@ -10,20 +10,14 @@ def __init__(self, db_session, log): self.db_utils = DBUtilsAsync(db_session) self.log = log - async def insert_session_char(self, gw2_api, api_characters, insert_args: dict): - for char_name in api_characters: - uri = f"characters/{char_name}/core" - current_char = await gw2_api.call_api(uri, insert_args["api_key"]) - name = current_char["name"] - profession = current_char["profession"] - deaths = current_char["deaths"] - + async def insert_session_char(self, characters_data: list[dict], insert_args: dict): + for char in characters_data: stmt = Gw2SessionChars( session_id=insert_args["session_id"], user_id=insert_args["user_id"], - name=name, - profession=profession, - deaths=deaths, + name=char["name"], + profession=char["profession"], + deaths=char["deaths"], start=insert_args["start"], end=insert_args["end"], ) diff --git a/src/gw2/cogs/characters.py b/src/gw2/cogs/characters.py index 4eb04e40..82078161 100644 --- a/src/gw2/cogs/characters.py +++ b/src/gw2/cogs/characters.py @@ -1,9 +1,11 @@ +import asyncio import discord from discord.ext import commands from src.bot.tools import bot_utils, chat_formatting from src.database.dal.gw2.gw2_key_dal import Gw2KeyDal from src.gw2.cogs.gw2 import GuildWars2 from src.gw2.constants import gw2_messages +from src.gw2.tools import gw2_utils from src.gw2.tools.gw2_client import Gw2Client from src.gw2.tools.gw2_cooldowns import GW2CoolDowns @@ -49,9 +51,16 @@ async def characters(ctx): if "characters" not in permissions or "account" not in permissions: return await bot_utils.send_error_msg(ctx, gw2_messages.API_KEY_NO_PERMISSION, True) + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching your character data... (this may take a moment)" + ) + try: - await ctx.message.channel.typing() - api_req_acc = await gw2_api.call_api("account", api_key) + # Fetch account info and all characters in parallel + api_req_acc, characters_data = await asyncio.gather( + gw2_api.call_api("account", api_key), + gw2_api.call_api("characters?ids=all", api_key), + ) color = ctx.bot.settings["gw2"]["EmbedColor"] embed = discord.Embed( @@ -62,20 +71,17 @@ async def characters(ctx): embed.set_thumbnail(url=ctx.message.author.display_avatar.url) embed.set_author(name=ctx.message.author.display_name, icon_url=ctx.message.author.display_avatar.url) - api_req_characters = await gw2_api.call_api("characters", api_key) - for char_name in api_req_characters: - await ctx.message.channel.typing() - current_char = await gw2_api.call_api(f"characters/{char_name}/core", api_key) - days = (current_char["age"] / 60) / 24 - created = current_char["created"].split("T", 1)[0] + for char in characters_data: + days = (char["age"] / 60) / 24 + created = char["created"].split("T", 1)[0] embed.add_field( - name=char_name, + name=char["name"], value=chat_formatting.inline( - f"Race: {current_char['race']}\n" - f"Gender: {current_char['gender']}\n" - f"Profession: {current_char['profession']}\n" - f"Level: {current_char['level']}\n" - f"Deaths: {current_char['deaths']}\n" + f"Race: {char['race']}\n" + f"Gender: {char['gender']}\n" + f"Profession: {char['profession']}\n" + f"Level: {char['level']}\n" + f"Deaths: {char['deaths']}\n" f"Age: {round(days)} days\n" f"Date: {created}\n" ), @@ -84,9 +90,11 @@ async def characters(ctx): embed.set_footer( icon_url=ctx.bot.user.display_avatar.url, text=f"{bot_utils.get_current_date_time_str_long()} UTC" ) + await progress_msg.delete() await bot_utils.send_embed(ctx, embed) except Exception as e: + await progress_msg.delete() await bot_utils.send_error_msg(ctx, e) return ctx.bot.log.error(ctx, e) diff --git a/src/gw2/cogs/worlds.py b/src/gw2/cogs/worlds.py index 90b318bf..5931698c 100644 --- a/src/gw2/cogs/worlds.py +++ b/src/gw2/cogs/worlds.py @@ -1,3 +1,4 @@ +import asyncio import discord from discord.ext import commands from src.bot.tools import bot_utils, chat_formatting @@ -41,34 +42,14 @@ async def worlds_na(ctx): if not result: return None - gw2_api = Gw2Client(ctx.bot) - embed_na = discord.Embed(description=chat_formatting.inline(gw2_messages.NA_SERVERS_TITLE)) - - failed_worlds = [] - - for world in worlds_ids: - wid = world["id"] - try: - await ctx.message.channel.typing() - matches = await gw2_api.call_api(f"wvw/matches?world={wid}") - if wid < 2001: - tier_number = matches["id"].replace("1-", "") - embed_na.add_field( - name=world["name"], - value=chat_formatting.inline(f"T{tier_number} {world['population']}"), - ) - except Exception as e: - # Log the error but continue with other worlds - failed_worlds.append(f"{world['name']} (ID: {wid})") - ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {wid}): {e}") - continue + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching NA world data... (this may take a moment)" + ) - # Add footer if some worlds failed - if failed_worlds: - embed_na.set_footer( - text=f"⚠️ Failed to load: {', '.join(failed_worlds[:3])}" + ("..." if len(failed_worlds) > 3 else "") - ) + na_worlds = [w for w in worlds_ids if w["id"] < 2001] + embed_na, failed_worlds = await _fetch_worlds_matches(ctx, na_worlds, gw2_messages.NA_SERVERS_TITLE, "1-") + await progress_msg.delete() await _send_paginated_worlds_embed(ctx, embed_na) return None @@ -86,36 +67,50 @@ async def worlds_eu(ctx): if not result: return None - gw2_api = Gw2Client(ctx.bot) - embed_eu = discord.Embed(description=chat_formatting.inline(gw2_messages.EU_SERVERS_TITLE)) + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching EU world data... (this may take a moment)" + ) + eu_worlds = [w for w in worlds_ids if w["id"] > 2001] + embed_eu, failed_worlds = await _fetch_worlds_matches(ctx, eu_worlds, gw2_messages.EU_SERVERS_TITLE, "2-") + + await progress_msg.delete() + await _send_paginated_worlds_embed(ctx, embed_eu) + return None + + +async def _fetch_worlds_matches(ctx, worlds, title, region_prefix): + """Fetch WvW match data for a list of worlds in parallel.""" + gw2_api = Gw2Client(ctx.bot) + embed = discord.Embed(description=chat_formatting.inline(title)) failed_worlds = [] + sem = asyncio.Semaphore(5) + + async def _fetch_match(world): + async with sem: + return await gw2_api.call_api(f"wvw/matches?world={world['id']}") - for world in worlds_ids: - wid = world["id"] - try: - await ctx.message.channel.typing() - matches = await gw2_api.call_api(f"wvw/matches?world={wid}") - if wid > 2001: - tier_number = matches["id"].replace("2-", "") - embed_eu.add_field( - name=world["name"], - value=chat_formatting.inline(f"T{tier_number} {world['population']}"), - ) - except Exception as e: - # Log the error but continue with other worlds - failed_worlds.append(f"{world['name']} (ID: {wid})") - ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {wid}): {e}") + tasks = [_fetch_match(w) for w in worlds] + results = await asyncio.gather(*tasks, return_exceptions=True) + + for world, result in zip(worlds, results, strict=True): + if isinstance(result, Exception): + failed_worlds.append(f"{world['name']} (ID: {world['id']})") + ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {world['id']}): {result}") continue + tier_number = result["id"].replace(region_prefix, "") + embed.add_field( + name=world["name"], + value=chat_formatting.inline(f"T{tier_number} {world['population']}"), + ) - # Add footer if some worlds failed if failed_worlds: - embed_eu.set_footer( - text=f"⚠️ Failed to load: {', '.join(failed_worlds[:3])}" + ("..." if len(failed_worlds) > 3 else "") + embed.set_footer( + text=f"\u26a0\ufe0f Failed to load: {', '.join(failed_worlds[:3])}" + + ("..." if len(failed_worlds) > 3 else "") ) - await _send_paginated_worlds_embed(ctx, embed_eu) - return None + return embed, failed_worlds async def _send_paginated_worlds_embed(ctx, embed): diff --git a/src/gw2/cogs/wvw.py b/src/gw2/cogs/wvw.py index bfc5a22b..a77bcb27 100644 --- a/src/gw2/cogs/wvw.py +++ b/src/gw2/cogs/wvw.py @@ -1,3 +1,4 @@ +import asyncio import discord from discord.ext import commands from src.bot.tools import bot_utils, chat_formatting @@ -78,19 +79,25 @@ async def info(self, ctx, *, world: str = None): return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}\n{world}") return None - try: - await ctx.message.channel.typing() - matches = await gw2_api.call_api(f"wvw/matches?world={wid}") + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching WvW info... (this may take a moment)" + ) - # Resolve world info: WR team IDs vs legacy worlds + try: + # Fetch match data and world info in parallel when possible if is_wr_team_id(wid): + matches = await gw2_api.call_api(f"wvw/matches?world={wid}") world_name = get_team_name(wid) or f"Team {wid}" population = "N/A" else: - worldinfo = await gw2_api.call_api(f"worlds?id={wid}") + matches, worldinfo = await asyncio.gather( + gw2_api.call_api(f"wvw/matches?world={wid}"), + gw2_api.call_api(f"worlds?id={wid}"), + ) world_name = worldinfo["name"] population = worldinfo["population"] except Exception as e: + await progress_msg.delete() await bot_utils.send_error_msg(ctx, e) return ctx.bot.log.error(ctx, e) @@ -149,6 +156,7 @@ async def info(self, ctx, *, world: str = None): embed.add_field(name="Deaths", value=chat_formatting.inline(deaths)) embed.add_field(name="K/D ratio", value=chat_formatting.inline(str(kd))) embed.add_field(name="Population", value=chat_formatting.inline(population), inline=False) + await progress_msg.delete() await bot_utils.send_embed(ctx, embed) return None @@ -176,20 +184,32 @@ async def match(self, ctx, *, world: str = None): return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}") return None + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching WvW match data... (this may take a moment)" + ) + try: - await ctx.message.channel.typing() matches = await gw2_api.call_api(f"wvw/matches?world={wid}") tier = _resolve_tier(matches) - green_worlds_names = await _get_map_names_embed_values(ctx, "green", matches) - blue_worlds_names = await _get_map_names_embed_values(ctx, "blue", matches) - red_worlds_names = await _get_map_names_embed_values(ctx, "red", matches) - - green_values = await _get_match_embed_values("green", matches) - blue_values = await _get_match_embed_values("blue", matches) - red_values = await _get_match_embed_values("red", matches) + ( + green_worlds_names, + blue_worlds_names, + red_worlds_names, + green_values, + blue_values, + red_values, + ) = await asyncio.gather( + _get_map_names_embed_values(ctx, "green", matches), + _get_map_names_embed_values(ctx, "blue", matches), + _get_map_names_embed_values(ctx, "red", matches), + _get_match_embed_values("green", matches), + _get_match_embed_values("blue", matches), + _get_match_embed_values("red", matches), + ) except Exception as e: + await progress_msg.delete() await bot_utils.send_error_msg(ctx, e) return ctx.bot.log.error(ctx, e) @@ -201,6 +221,7 @@ async def match(self, ctx, *, world: str = None): embed.add_field(name="--------------------", value=green_values) embed.add_field(name="--------------------", value=blue_values) embed.add_field(name="--------------------", value=red_values) + await progress_msg.delete() await bot_utils.send_embed(ctx, embed) return None @@ -228,20 +249,32 @@ async def kdr(self, ctx, *, world: str = None): return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}") return None + progress_msg = await gw2_utils.send_progress_embed( + ctx, "Please wait, I'm fetching WvW K/D data... (this may take a moment)" + ) + try: - await ctx.message.channel.typing() matches = await gw2_api.call_api(f"wvw/matches?world={wid}") tier = _resolve_tier(matches) - green_worlds_names = await _get_map_names_embed_values(ctx, "green", matches) - blue_worlds_names = await _get_map_names_embed_values(ctx, "blue", matches) - red_worlds_names = await _get_map_names_embed_values(ctx, "red", matches) - - green_values = await _get_kdr_embed_values("green", matches) - blue_values = await _get_kdr_embed_values("blue", matches) - red_values = await _get_kdr_embed_values("red", matches) + ( + green_worlds_names, + blue_worlds_names, + red_worlds_names, + green_values, + blue_values, + red_values, + ) = await asyncio.gather( + _get_map_names_embed_values(ctx, "green", matches), + _get_map_names_embed_values(ctx, "blue", matches), + _get_map_names_embed_values(ctx, "red", matches), + _get_kdr_embed_values("green", matches), + _get_kdr_embed_values("blue", matches), + _get_kdr_embed_values("red", matches), + ) except Exception as e: + await progress_msg.delete() await bot_utils.send_error_msg(ctx, e) return ctx.bot.log.error(ctx, e) @@ -253,6 +286,7 @@ async def kdr(self, ctx, *, world: str = None): embed.add_field(name="--------------------", value=green_values) embed.add_field(name="--------------------", value=blue_values) embed.add_field(name="--------------------", value=red_values) + await progress_msg.delete() await bot_utils.send_embed(ctx, embed) return None diff --git a/src/gw2/tools/gw2_utils.py b/src/gw2/tools/gw2_utils.py index 01cd8cfd..b2ec93e7 100644 --- a/src/gw2/tools/gw2_utils.py +++ b/src/gw2/tools/gw2_utils.py @@ -85,6 +85,16 @@ class Gw2Servers(Enum): Millers_Sound = "Miller's Sound [DE]" +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.""" + 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) + + async def send_msg(ctx: commands.Context, description: str, dm: bool = False) -> None: """Send a GW2-themed embed message.""" color = ctx.bot.settings["gw2"]["EmbedColor"] @@ -115,17 +125,28 @@ async def calculate_user_achiev_points(ctx: commands.Context, api_req_acc_achiev async def _fetch_achievement_data_in_batches(gw2_api: Gw2Client, user_achievements: list[dict]) -> list[dict]: - """Fetch achievement data from API in batches of 200.""" + """Fetch achievement data from API in parallel batches of 200.""" batch_size = 200 - all_achievement_data = [] + sem = asyncio.Semaphore(5) + async def _fetch_batch(batch): + async with sem: + achievement_ids = [str(ach["id"]) for ach in batch] + ids_string = ",".join(achievement_ids) + return await gw2_api.call_api(f"achievements?ids={ids_string}") + + batch_tasks = [] for i in range(0, len(user_achievements), batch_size): batch = user_achievements[i : i + batch_size] - achievement_ids = [str(ach["id"]) for ach in batch] - ids_string = ",".join(achievement_ids) + batch_tasks.append(_fetch_batch(batch)) + + results = await asyncio.gather(*batch_tasks, return_exceptions=True) - api_response = await gw2_api.call_api(f"achievements?ids={ids_string}") - all_achievement_data.extend(api_response) + all_achievement_data = [] + for result in results: + if isinstance(result, Exception): + continue + all_achievement_data.extend(result) return all_achievement_data @@ -451,11 +472,12 @@ async def get_user_stats(bot: Bot, api_key: str) -> dict | None: gw2_api = Gw2Client(bot) try: - # Fetch data from multiple endpoints - account_data = await gw2_api.call_api("account", api_key) - wallet_data = await gw2_api.call_api("account/wallet", api_key) achievement_ids = ",".join(str(k) for k in ACHIEVEMENT_MAPPING) - achievements_data = await gw2_api.call_api(f"account/achievements?ids={achievement_ids}", api_key) + account_data, wallet_data, achievements_data = await asyncio.gather( + gw2_api.call_api("account", api_key), + gw2_api.call_api("account/wallet", api_key), + gw2_api.call_api(f"account/achievements?ids={achievement_ids}", api_key), + ) except Exception as e: bot.log.error(f"Error fetching user stats: {e}") @@ -512,10 +534,9 @@ async def insert_session_char( bot.log.debug(f"Attempting to insert {session_type} session chars for session {session_id}, user {member.id}") try: gw2_api = Gw2Client(bot) - characters_data = await gw2_api.call_api("characters", api_key) + characters_data = await gw2_api.call_api("characters?ids=all", api_key) insert_args = { - "api_key": api_key, "session_id": session_id, "user_id": member.id, "start": session_type == "start", @@ -523,7 +544,7 @@ async def insert_session_char( } gw2_session_chars_dal = Gw2SessionCharsDal(bot.db_session, bot.log) - await gw2_session_chars_dal.insert_session_char(gw2_api, characters_data, insert_args) + await gw2_session_chars_dal.insert_session_char(characters_data, insert_args) bot.log.debug(f"Successfully inserted {session_type} session chars for session {session_id}, user {member.id}") except Exception as e: diff --git a/tests/integration/test_session_flow.py b/tests/integration/test_session_flow.py index ef2ed813..bc8d2a9e 100644 --- a/tests/integration/test_session_flow.py +++ b/tests/integration/test_session_flow.py @@ -49,12 +49,10 @@ {"id": 300, "current": 10}, # keeps ] -MOCK_CHARACTERS = ["Warrior Prime", "Thief Shadow"] - -MOCK_CHAR_CORES = { - "Warrior Prime": {"name": "Warrior Prime", "profession": "Warrior", "deaths": 42}, - "Thief Shadow": {"name": "Thief Shadow", "profession": "Thief", "deaths": 10}, -} +MOCK_CHARACTERS_ALL = [ + {"name": "Warrior Prime", "profession": "Warrior", "deaths": 42}, + {"name": "Thief Shadow", "profession": "Thief", "deaths": 10}, +] # End-of-session data: wallet gold increased, character deaths increased MOCK_WALLET_DATA_END = [ @@ -78,10 +76,10 @@ {"id": 300, "current": 10}, ] -MOCK_CHAR_CORES_END = { - "Warrior Prime": {"name": "Warrior Prime", "profession": "Warrior", "deaths": 45}, # 3 more deaths - "Thief Shadow": {"name": "Thief Shadow", "profession": "Thief", "deaths": 12}, # 2 more deaths -} +MOCK_CHARACTERS_ALL_END = [ + {"name": "Warrior Prime", "profession": "Warrior", "deaths": 45}, # 3 more deaths + {"name": "Thief Shadow", "profession": "Thief", "deaths": 12}, # 2 more deaths +] def _mock_call_api_start(endpoint, api_key): @@ -90,13 +88,10 @@ def _mock_call_api_start(endpoint, api_key): return MOCK_ACCOUNT_DATA if endpoint == "account/wallet": return MOCK_WALLET_DATA - if endpoint == "account/achievements": + if endpoint.startswith("account/achievements"): return MOCK_ACHIEVEMENTS_DATA - if endpoint == "characters": - return MOCK_CHARACTERS - if endpoint.startswith("characters/") and endpoint.endswith("/core"): - char_name = endpoint.split("/")[1] - return MOCK_CHAR_CORES[char_name] + if endpoint == "characters?ids=all": + return MOCK_CHARACTERS_ALL raise ValueError(f"Unexpected endpoint: {endpoint}") @@ -106,13 +101,10 @@ def _mock_call_api_end(endpoint, api_key): return MOCK_ACCOUNT_DATA if endpoint == "account/wallet": return MOCK_WALLET_DATA_END - if endpoint == "account/achievements": + if endpoint.startswith("account/achievements"): return MOCK_ACHIEVEMENTS_DATA_END - if endpoint == "characters": - return MOCK_CHARACTERS - if endpoint.startswith("characters/") and endpoint.endswith("/core"): - char_name = endpoint.split("/")[1] - return MOCK_CHAR_CORES_END[char_name] + if endpoint == "characters?ids=all": + return MOCK_CHARACTERS_ALL_END raise ValueError(f"Unexpected endpoint: {endpoint}") diff --git a/tests/unit/database/test_dal.py b/tests/unit/database/test_dal.py index e38ed9d2..3586346c 100644 --- a/tests/unit/database/test_dal.py +++ b/tests/unit/database/test_dal.py @@ -935,73 +935,49 @@ def test_init(self): @pytest.mark.asyncio async def test_insert_session_char(self, mock_dal): """Test insert_session_char calls insert for each character.""" - gw2_api = AsyncMock() - gw2_api.call_api.side_effect = [ + characters_data = [ {"name": "CharOne", "profession": "Warrior", "deaths": 5}, {"name": "CharTwo", "profession": "Elementalist", "deaths": 10}, ] - api_characters = ["CharOne", "CharTwo"] insert_args = { "session_id": 1, "user_id": 67890, - "api_key": "AAAABBBB-1111-2222-3333-444455556666", "start": True, "end": False, } - await mock_dal.insert_session_char(gw2_api, api_characters, insert_args) + await mock_dal.insert_session_char(characters_data, insert_args) - assert gw2_api.call_api.call_count == 2 - gw2_api.call_api.assert_any_call( - "characters/CharOne/core", - "AAAABBBB-1111-2222-3333-444455556666", - ) - gw2_api.call_api.assert_any_call( - "characters/CharTwo/core", - "AAAABBBB-1111-2222-3333-444455556666", - ) assert mock_dal.db_utils.insert.call_count == 2 @pytest.mark.asyncio async def test_insert_session_char_single_character(self, mock_dal): """Test insert_session_char with a single character.""" - gw2_api = AsyncMock() - gw2_api.call_api.return_value = { - "name": "Solo", - "profession": "Necromancer", - "deaths": 0, - } - api_characters = ["Solo"] + characters_data = [ + {"name": "Solo", "profession": "Necromancer", "deaths": 0}, + ] insert_args = { "session_id": 2, "user_id": 11111, - "api_key": "CCCCDDDD-5555-6666-7777-888899990000", "start": False, "end": True, } - await mock_dal.insert_session_char(gw2_api, api_characters, insert_args) + await mock_dal.insert_session_char(characters_data, insert_args) - gw2_api.call_api.assert_called_once_with( - "characters/Solo/core", - "CCCCDDDD-5555-6666-7777-888899990000", - ) mock_dal.db_utils.insert.assert_called_once() @pytest.mark.asyncio async def test_insert_session_char_empty_characters(self, mock_dal): """Test insert_session_char with an empty characters list.""" - gw2_api = AsyncMock() - api_characters = [] + characters_data = [] insert_args = { "session_id": 3, "user_id": 22222, - "api_key": "EEEEEEEE-0000-0000-0000-000000000000", } - await mock_dal.insert_session_char(gw2_api, api_characters, insert_args) + await mock_dal.insert_session_char(characters_data, insert_args) - gw2_api.call_api.assert_not_called() mock_dal.db_utils.insert.assert_not_called() @pytest.mark.asyncio diff --git a/tests/unit/gw2/cogs/test_characters.py b/tests/unit/gw2/cogs/test_characters.py index 12982dd7..b518a9a5 100644 --- a/tests/unit/gw2/cogs/test_characters.py +++ b/tests/unit/gw2/cogs/test_characters.py @@ -180,47 +180,55 @@ async def test_characters_successful_with_character_data( mock_client_instance.call_api = AsyncMock( side_effect=[ sample_account_data, # account call - ["CharOne", "CharTwo"], # characters list call - { # CharOne core data - "race": "Human", - "gender": "Male", - "profession": "Warrior", - "level": 80, - "deaths": 42, - "age": 432000, - "created": "2020-01-15T10:30:00Z", - }, - { # CharTwo core data - "race": "Asura", - "gender": "Female", - "profession": "Elementalist", - "level": 80, - "deaths": 15, - "age": 216000, - "created": "2021-06-20T08:00:00Z", - }, + [ # characters?ids=all call + { + "name": "CharOne", + "race": "Human", + "gender": "Male", + "profession": "Warrior", + "level": 80, + "deaths": 42, + "age": 432000, + "created": "2020-01-15T10:30:00Z", + }, + { + "name": "CharTwo", + "race": "Asura", + "gender": "Female", + "profession": "Elementalist", + "level": 80, + "deaths": 15, + "age": 216000, + "created": "2021-06-20T08:00:00Z", + }, + ], ] ) - with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: - with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: - mock_time.return_value = "2025-01-01 12:00:00" - await characters(mock_ctx) - mock_send.assert_called_once() - embed = mock_send.call_args[0][1] - assert embed.title == "Account Name" - assert "TestUser.1234" in embed.description - # Verify embed has fields for characters - assert len(embed.fields) == 2 - # Verify character fields contain expected data - char_one_field = embed.fields[0] - assert char_one_field.name == "CharOne" - assert "Human" in char_one_field.value - assert "Warrior" in char_one_field.value - assert "80" in char_one_field.value - char_two_field = embed.fields[1] - assert char_two_field.name == "CharTwo" - assert "Asura" in char_two_field.value - assert "Elementalist" in char_two_field.value + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: + with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: + mock_time.return_value = "2025-01-01 12:00:00" + await characters(mock_ctx) + mock_send.assert_called_once() + embed = mock_send.call_args[0][1] + assert embed.title == "Account Name" + assert "TestUser.1234" in embed.description + # Verify embed has fields for characters + assert len(embed.fields) == 2 + # Verify character fields contain expected data + char_one_field = embed.fields[0] + assert char_one_field.name == "CharOne" + assert "Human" in char_one_field.value + assert "Warrior" in char_one_field.value + assert "80" in char_one_field.value + char_two_field = embed.fields[1] + assert char_two_field.name == "CharTwo" + assert "Asura" in char_two_field.value + assert "Elementalist" in char_two_field.value @pytest.mark.asyncio async def test_characters_successful_character_age_calculation( @@ -239,26 +247,33 @@ async def test_characters_successful_character_age_calculation( mock_client_instance.call_api = AsyncMock( side_effect=[ sample_account_data, - ["TestChar"], - { - "race": "Norn", - "gender": "Male", - "profession": "Guardian", - "level": 80, - "deaths": 10, - "age": 144000, # 100 days in seconds/minutes -> (144000/60)/24 = 100 - "created": "2022-03-01T00:00:00Z", - }, + [ + { + "name": "TestChar", + "race": "Norn", + "gender": "Male", + "profession": "Guardian", + "level": 80, + "deaths": 10, + "age": 144000, # 100 days in seconds/minutes -> (144000/60)/24 = 100 + "created": "2022-03-01T00:00:00Z", + }, + ], ] ) - with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: - with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: - mock_time.return_value = "2025-01-01 12:00:00" - await characters(mock_ctx) - mock_send.assert_called_once() - embed = mock_send.call_args[0][1] - char_field = embed.fields[0] - assert "100" in char_field.value + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: + with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: + mock_time.return_value = "2025-01-01 12:00:00" + await characters(mock_ctx) + mock_send.assert_called_once() + embed = mock_send.call_args[0][1] + char_field = embed.fields[0] + assert "100" in char_field.value @pytest.mark.asyncio async def test_characters_successful_created_date_parsed(self, mock_ctx, sample_api_key_data, sample_account_data): @@ -274,26 +289,33 @@ async def test_characters_successful_created_date_parsed(self, mock_ctx, sample_ mock_client_instance.call_api = AsyncMock( side_effect=[ sample_account_data, - ["DateChar"], - { - "race": "Sylvari", - "gender": "Female", - "profession": "Ranger", - "level": 50, - "deaths": 5, - "age": 7200, - "created": "2023-07-15T14:30:00Z", - }, + [ + { + "name": "DateChar", + "race": "Sylvari", + "gender": "Female", + "profession": "Ranger", + "level": 50, + "deaths": 5, + "age": 7200, + "created": "2023-07-15T14:30:00Z", + }, + ], ] ) - with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: - with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: - mock_time.return_value = "2025-01-01 12:00:00" - await characters(mock_ctx) - mock_send.assert_called_once() - embed = mock_send.call_args[0][1] - char_field = embed.fields[0] - assert "2023-07-15" in char_field.value + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: + with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: + mock_time.return_value = "2025-01-01 12:00:00" + await characters(mock_ctx) + mock_send.assert_called_once() + embed = mock_send.call_args[0][1] + char_field = embed.fields[0] + assert "2023-07-15" in char_field.value @pytest.mark.asyncio async def test_characters_api_exception_during_execution(self, mock_ctx, sample_api_key_data): @@ -307,10 +329,15 @@ async def test_characters_api_exception_during_execution(self, mock_ctx, sample_ return_value={"name": "TestKey", "permissions": ["account", "characters"]} ) mock_client_instance.call_api = AsyncMock(side_effect=Exception("API connection error")) - with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error: - await characters(mock_ctx) - mock_error.assert_called_once() - mock_ctx.bot.log.error.assert_called_once() + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error: + await characters(mock_ctx) + mock_error.assert_called_once() + mock_ctx.bot.log.error.assert_called_once() @pytest.mark.asyncio async def test_characters_triggers_typing_indicator(self, mock_ctx, sample_api_key_data): @@ -336,27 +363,34 @@ async def test_characters_embed_has_thumbnail_and_author(self, mock_ctx, sample_ mock_client_instance.call_api = AsyncMock( side_effect=[ sample_account_data, - ["SingleChar"], - { - "race": "Charr", - "gender": "Male", - "profession": "Engineer", - "level": 80, - "deaths": 0, - "age": 1440, - "created": "2024-01-01T00:00:00Z", - }, + [ + { + "name": "SingleChar", + "race": "Charr", + "gender": "Male", + "profession": "Engineer", + "level": 80, + "deaths": 0, + "age": 1440, + "created": "2024-01-01T00:00:00Z", + }, + ], ] ) - with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: - with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: - mock_time.return_value = "2025-01-01 12:00:00" - await characters(mock_ctx) - mock_send.assert_called_once() - embed = mock_send.call_args[0][1] - assert embed.thumbnail.url == "https://example.com/avatar.png" - assert embed.author.name == "TestUser" - assert embed.author.icon_url == "https://example.com/avatar.png" + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: + with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: + mock_time.return_value = "2025-01-01 12:00:00" + await characters(mock_ctx) + mock_send.assert_called_once() + embed = mock_send.call_args[0][1] + assert embed.thumbnail.url == "https://example.com/avatar.png" + assert embed.author.name == "TestUser" + assert embed.author.icon_url == "https://example.com/avatar.png" @pytest.mark.asyncio async def test_characters_author_no_avatar(self, mock_ctx, sample_api_key_data, sample_account_data): @@ -379,26 +413,33 @@ async def test_characters_author_no_avatar(self, mock_ctx, sample_api_key_data, mock_client_instance.call_api = AsyncMock( side_effect=[ sample_account_data, - ["SingleChar"], - { - "race": "Charr", - "gender": "Male", - "profession": "Engineer", - "level": 80, - "deaths": 0, - "age": 1440, - "created": "2024-01-01T00:00:00Z", - }, + [ + { + "name": "SingleChar", + "race": "Charr", + "gender": "Male", + "profession": "Engineer", + "level": 80, + "deaths": 0, + "age": 1440, + "created": "2024-01-01T00:00:00Z", + }, + ], ] ) - with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: - with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: - mock_time.return_value = "2025-01-01 12:00:00" - await characters(mock_ctx) - mock_send.assert_called_once() - embed = mock_send.call_args[0][1] - assert embed.thumbnail.url == "https://example.com/default.png" - assert embed.author.icon_url == "https://example.com/default.png" + with patch( + "src.gw2.cogs.characters.gw2_utils.send_progress_embed", + new_callable=AsyncMock, + return_value=AsyncMock(), + ) as mock_progress: + with patch("src.gw2.cogs.characters.bot_utils.send_embed") as mock_send: + with patch("src.gw2.cogs.characters.bot_utils.get_current_date_time_str_long") as mock_time: + mock_time.return_value = "2025-01-01 12:00:00" + await characters(mock_ctx) + mock_send.assert_called_once() + embed = mock_send.call_args[0][1] + assert embed.thumbnail.url == "https://example.com/default.png" + assert embed.author.icon_url == "https://example.com/default.png" class TestCharactersSetup: diff --git a/tests/unit/gw2/cogs/test_worlds.py b/tests/unit/gw2/cogs/test_worlds.py index 8bd83d61..b42d28d2 100644 --- a/tests/unit/gw2/cogs/test_worlds.py +++ b/tests/unit/gw2/cogs/test_worlds.py @@ -106,6 +106,7 @@ def mock_ctx(self): async def test_worlds_na_get_worlds_ids_returns_false(self, mock_ctx): """Test worlds_na returns None when get_worlds_ids returns False.""" with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(False, None)) result = await worlds_na(mock_ctx) assert result is None @@ -121,6 +122,7 @@ async def test_worlds_na_successful_with_na_worlds(self, mock_ctx): matches_data = {"id": "1-3"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -145,6 +147,7 @@ async def test_worlds_na_skips_eu_worlds(self, mock_ctx): matches_data_eu = {"id": "2-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -166,6 +169,7 @@ async def test_worlds_na_exception_on_world_logs_warning(self, mock_ctx): matches_data = {"id": "1-3"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -190,6 +194,7 @@ async def test_worlds_na_failed_worlds_adds_footer(self, mock_ctx): ] with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -212,6 +217,7 @@ async def test_worlds_na_failed_worlds_footer_truncates_at_3(self, mock_ctx): ] with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -228,6 +234,7 @@ async def test_worlds_na_calls_send_paginated(self, mock_ctx): matches_data = {"id": "1-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -243,6 +250,7 @@ async def test_worlds_na_no_failed_worlds_no_footer(self, mock_ctx): matches_data = {"id": "1-2"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -286,6 +294,7 @@ def mock_ctx(self): async def test_worlds_eu_get_worlds_ids_returns_false(self, mock_ctx): """Test worlds_eu returns None when get_worlds_ids returns False.""" with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(False, None)) result = await worlds_eu(mock_ctx) assert result is None @@ -301,6 +310,7 @@ async def test_worlds_eu_successful_with_eu_worlds(self, mock_ctx): matches_data = {"id": "2-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -325,6 +335,7 @@ async def test_worlds_eu_skips_na_worlds(self, mock_ctx): matches_data_eu = {"id": "2-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -346,6 +357,7 @@ async def test_worlds_eu_exception_on_world_logs_warning(self, mock_ctx): matches_data = {"id": "2-2"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -369,6 +381,7 @@ async def test_worlds_eu_failed_worlds_adds_footer(self, mock_ctx): ] with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -391,6 +404,7 @@ async def test_worlds_eu_failed_worlds_footer_truncates_at_3(self, mock_ctx): ] with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -407,6 +421,7 @@ async def test_worlds_eu_tier_number_replaces_2_prefix(self, mock_ctx): matches_data = {"id": "2-4"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -778,6 +793,7 @@ async def test_worlds_na_exact_2001_boundary(self, mock_ctx): matches_data = {"id": "2-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value @@ -798,6 +814,7 @@ async def test_worlds_eu_exact_2001_boundary(self, mock_ctx): matches_data = {"id": "2-1"} with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils: + mock_utils.send_progress_embed = AsyncMock(return_value=AsyncMock()) mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids)) with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client: mock_client_instance = mock_client.return_value diff --git a/tests/unit/gw2/tools/test_gw2_utils.py b/tests/unit/gw2/tools/test_gw2_utils.py index 5ea7e679..1d848e83 100644 --- a/tests/unit/gw2/tools/test_gw2_utils.py +++ b/tests/unit/gw2/tools/test_gw2_utils.py @@ -1211,10 +1211,10 @@ def mock_member(self): @pytest.mark.asyncio async def test_successful_insert(self, mock_bot, mock_member): - """Test successful session character insert (lines 415-428).""" + """Test successful session character insert.""" with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class: mock_client = mock_client_class.return_value - characters_data = [{"name": "CharName", "level": 80}] + characters_data = [{"name": "CharName", "profession": "Warrior", "deaths": 5}] mock_client.call_api = AsyncMock(return_value=characters_data) with patch("src.gw2.tools.gw2_utils.Gw2SessionCharsDal") as mock_dal: @@ -1223,14 +1223,12 @@ async def test_successful_insert(self, mock_bot, mock_member): await insert_session_char(mock_bot, mock_member, "api-key", 42, "start") - mock_client.call_api.assert_called_once_with("characters", "api-key") + mock_client.call_api.assert_called_once_with("characters?ids=all", "api-key") mock_instance.insert_session_char.assert_called_once() call_args = mock_instance.insert_session_char.call_args[0] - assert call_args[0] == mock_client # gw2_api - assert call_args[1] == characters_data - insert_args = call_args[2] - assert insert_args["api_key"] == "api-key" + assert call_args[0] == characters_data + insert_args = call_args[1] assert insert_args["session_id"] == 42 assert insert_args["user_id"] == 12345 assert insert_args["start"] is True @@ -1250,7 +1248,7 @@ async def test_insert_end_session_type(self, mock_bot, mock_member): await insert_session_char(mock_bot, mock_member, "api-key", 42, "end") call_args = mock_instance.insert_session_char.call_args[0] - insert_args = call_args[2] + insert_args = call_args[1] assert insert_args["start"] is False assert insert_args["end"] is True From 37e600988ea84de3562f3a41f12897b4e2985564 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:14:42 -0300 Subject: [PATCH 2/3] v3.0.6 --- tests/integration/test_gw2_session_chars_dal.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_gw2_session_chars_dal.py b/tests/integration/test_gw2_session_chars_dal.py index 8f574d5a..41949a42 100644 --- a/tests/integration/test_gw2_session_chars_dal.py +++ b/tests/integration/test_gw2_session_chars_dal.py @@ -5,7 +5,6 @@ from src.database.dal.gw2.gw2_session_chars_dal import Gw2SessionCharsDal from src.database.dal.gw2.gw2_sessions_dal import Gw2SessionsDal from src.database.models.gw2_models import Gw2SessionChars -from unittest.mock import AsyncMock pytestmark = [pytest.mark.integration, pytest.mark.asyncio] @@ -40,21 +39,21 @@ async def test_insert_session_char_with_start_field(db_session, log): } ) - gw2_api = AsyncMock() - gw2_api.call_api.return_value = { - "name": "TestChar", - "profession": "Warrior", - "deaths": 10, - } + characters_data = [ + { + "name": "TestChar", + "profession": "Warrior", + "deaths": 10, + } + ] insert_args = { "session_id": session_id, "user_id": USER_ID, - "api_key": API_KEY, "start": True, "end": False, } # Should no longer raise IntegrityError now that start/end are passed - await chars_dal.insert_session_char(gw2_api, ["TestChar"], insert_args) + await chars_dal.insert_session_char(characters_data, insert_args) # Verify the data was inserted correctly results = await chars_dal.get_all_start_characters(USER_ID) From b09e4f35f461c71af6a2bdd01ff450a41b5f9848 Mon Sep 17 00:00:00 2001 From: ddc <34492089+ddc@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:20:59 -0300 Subject: [PATCH 3/3] v3.0.6 --- src/gw2/cogs/worlds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gw2/cogs/worlds.py b/src/gw2/cogs/worlds.py index 5931698c..b5af3150 100644 --- a/src/gw2/cogs/worlds.py +++ b/src/gw2/cogs/worlds.py @@ -47,7 +47,7 @@ async def worlds_na(ctx): ) na_worlds = [w for w in worlds_ids if w["id"] < 2001] - embed_na, failed_worlds = await _fetch_worlds_matches(ctx, na_worlds, gw2_messages.NA_SERVERS_TITLE, "1-") + embed_na, _ = await _fetch_worlds_matches(ctx, na_worlds, gw2_messages.NA_SERVERS_TITLE, "1-") await progress_msg.delete() await _send_paginated_worlds_embed(ctx, embed_na) @@ -72,7 +72,7 @@ async def worlds_eu(ctx): ) eu_worlds = [w for w in worlds_ids if w["id"] > 2001] - embed_eu, failed_worlds = await _fetch_worlds_matches(ctx, eu_worlds, gw2_messages.EU_SERVERS_TITLE, "2-") + embed_eu, _ = await _fetch_worlds_matches(ctx, eu_worlds, gw2_messages.EU_SERVERS_TITLE, "2-") await progress_msg.delete() await _send_paginated_worlds_embed(ctx, embed_eu)