-
-
-# [Install Guide - Wiki](https://ddc.github.io/DiscordBot)
-+ Using Docker
- + git clone https://github.com/ddc/DiscordBot.git
- + BOT_TOKEN variable needs to be inside the .env file
- + sudo systemctl enable docker
- + docker-compose up --build -d
-
-# Games Included
-+ [Guild Wars 2](https://www.guildwars2.com)
-
-
-# OpenAI Command
-| Commands | Description |
-|:---------------|:------------------------------------------------|
-| ai <_message_> | Asks OpenAI, message will be on discord embeded |
-
-
-# Admin/Mod Commands
-| Commands | Description |
-|:---------------------------------------|:------------------------------------|
-| admin cc [add,edit,remove] <_command_> | Add, edit or remove custom commands |
-| admin botgame <_new game_> | Change game that bot is playing |
-
-# Config Commands
-| Commands | Description |
-|:-------------------------------------------------|:----------------------------------------------|
-| admin config list | List all bot configurations |
-| admin config servermessage [on , off] | Show message when a server gets updated |
-| admin config membermessage [on , off] | Show message when someone updates the profile |
-| admin config joinmessage [on , off] | Show message when a user joins the server |
-| admin config leavemessage [on , off] | Show message when a user leaves the server |
-| admin config blockinvisible [on , off] | Block messages from invisible members |
-| admin config botreactions [on , off] | Bot will react to member words |
-| admin config pfilter [on , off] <_channel name_> | Profanity Filter (blocks swear words) |
-
-# Misc Commands
-| Commands | Description |
-|:-------------------------|:-----------------------------------------|
-| about | Displays bot info |
-| echo | Shows your msg again |
-| ping | Test latency by receiving a ping message |
-| roll | Rolls random number |
-| pepe | Posts a random Pepe from imgur url |
-| tts <_message_> | Send TTS as .mp3 to channel |
-| serverinfo | Shows server's informations |
-| userinfo <_member#1234_> | Shows discord user informations |
-| lmgtfy <_link_> | Creates a lmgtfy link |
-| invites | List active invites link for the server |
-
-# Bot Owner Commands
-| Commands | Description |
-|:------------------------------------------|:--------------------------------|
-| owner servers | Display all servers in database |
-| owner prefix <_new prefix_> | Change bot prefix for commands |
-| owner botdescription <_new description_> | Change bot description |
-
-# GW2 Commands
-| Commands | Description |
-|:------------------------------------------------|:---------------------------------------------|
-| gw2 config list | List all gw2 configurations in the server |
-| gw2 config session [on , off] | Bot should record users last sessions |
-| gw2 wvw [match, info, kdr] <_world name_> | Info about a wvw match |
-| gw2 key [add, update, remove, info] <_api key_> | Add/Update/Remove/Info - GW2 APIkey managing |
-| gw2 account | General information about your GW2 account |
-| gw2 worlds [na, eu] | List all worlds by timezone |
-| gw2 wiki <_name to search_> | Search the Guild wars 2 wiki |
-| gw2 info <_info to search_> | Information about a given name/skill/rune |
+
+
+
+ DiscordBot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A simple Discord bot with OpenAI support and server administration tools.
-# Acknowledgements
-+ [OpenAI API](https://openai.com/api)
-+ [Guild Wars 2 API](https://wiki.guildwars2.com/wiki/API:2)
-+ [Discord Bot Api](https://discordapp.com/developers/applications/me)
-+ [PostgreSQL](https://www.postgresql.org)
-+ [Git](https://git-scm.com/download)
+## Table of Contents
+- [Features](#features)
+- [Prerequisites](#prerequisites)
+- [Installation](#installation)
+- [Configuration](#configuration)
+- [Commands](#commands)
+ - [OpenAI](#openai-commands)
+ - [Admin/Mod](#adminmod-commands)
+ - [Config](#config-commands)
+ - [Custom Commands](#custom-commands)
+ - [Misc](#misc-commands)
+ - [Dice Rolls](#dice-rolls-commands)
+ - [Bot Owner](#bot-owner-commands)
+ - [GW2](#gw2-commands)
+ - [GW2 Config](#gw2-config-commands)
+ - [GW2 Key](#gw2-key-commands)
+ - [GW2 WvW](#gw2-wvw-commands)
+- [Development and Testing](#development-and-testing)
+- [Acknowledgements](#acknowledgements)
+- [Support](#support)
+- [License](#license)
+
+
+# Features
+- OpenAI integration for AI-powered responses
+- Guild Wars 2 API integration (accounts, WvW, sessions, wiki)
+- Server administration and moderation tools
+- Custom commands, profanity filtering, and text-to-speech
+- PostgreSQL database with Alembic migrations
+- Docker deployment with automatic database migrations
+
+
+# Prerequisites
+- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
+- A [Discord Bot Token](https://discord.com/developers/applications)
+- _(Optional)_ An [OpenAI API Key](https://platform.openai.com/api-keys) for AI commands
+- _(Optional)_ A [Guild Wars 2 API Key](https://account.arena.net/applications) for GW2 commands
+
+
+# Installation
+
+### 1. Clone the repository
+```shell
+git clone https://github.com/ddc/DiscordBot.git
+cd DiscordBot
+```
+
+### 2. Configure environment variables
+```shell
+cp .env.example .env
+```
+
+Edit the `.env` file and set the required values:
+```ini
+# Required
+BOT_TOKEN=your_discord_bot_token
+
+# Optional
+OPENAI_API_KEY=your_openai_api_key
+
+# Database (defaults work with the included docker-compose)
+POSTGRESQL_HOST=discordbot_database
+POSTGRESQL_PORT=5432
+POSTGRESQL_USER=postgres
+POSTGRESQL_PASSWORD=postgres
+POSTGRESQL_DATABASE=discordbot
+```
+
+See [Configuration](#configuration) for all available options.
+
+### 3. Start the bot
+```shell
+sudo systemctl enable docker
+docker-compose up --build -d
+```
+
+This will:
+1. Build the Docker image
+2. Start the PostgreSQL database
+3. Run database migrations automatically
+4. Start the bot
+
+### 4. Verify the bot is running
+```shell
+docker-compose logs -f discordbot
+```
+
+For the full installation guide, see the [Wiki](https://ddc.github.io/DiscordBot).
+
+
+# Configuration
+
+All configuration is done through environment variables in the `.env` file.
+
+### Bot Settings
+| Variable | Default | Description |
+|:--------------------------|:------------------|:-----------------------------------------------------------|
+| `BOT_TOKEN` | | Discord bot token **(required)** |
+| `BOT_PREFIX` | `!` | Command prefix |
+| `BOT_EMBED_COLOR` | `green` | Default embed color |
+| `BOT_EMBED_OWNER_COLOR` | `dark_purple` | Owner command embed color |
+| `BOT_ALLOWED_DM_COMMANDS` | `owner,about,gw2` | Commands allowed in DMs |
+| `BOT_BOT_REACTION_WORDS` | `stupid,noob` | Words that trigger bot reactions |
+| `BOT_EXCLUSIVE_USERS` | | Restrict bot to specific users (comma-separated IDs) |
+| `BOT_BG_ACTIVITY_TIMER` | `0` | Background activity rotation timer (seconds, 0 = disabled) |
+
+### OpenAI Settings
+| Variable | Default | Description |
+|:-------------------|:--------------|:--------------------|
+| `OPENAI_API_KEY` | | OpenAI API key |
+| `BOT_OPENAI_MODEL` | `gpt-4o-mini` | OpenAI model to use |
+
+### PostgreSQL Settings
+| Variable | Default | Description |
+|:----------------------|:----------------------|:------------------|
+| `POSTGRESQL_HOST` | `discordbot_database` | Database host |
+| `POSTGRESQL_PORT` | `5432` | Database port |
+| `POSTGRESQL_USER` | `postgres` | Database user |
+| `POSTGRESQL_PASSWORD` | `postgres` | Database password |
+| `POSTGRESQL_DATABASE` | `discordbot` | Database name |
+| `POSTGRESQL_SCHEMA` | `public` | Database schema |
+### Logging Settings
+| Variable | Default | Description |
+|:-------------------|:------------------|:----------------------|
+| `LOG_LEVEL` | `INFO` | Log level |
+| `LOG_TIMEZONE` | `UTC` | Log timezone |
+| `LOG_DIRECTORY` | `/app/DiscordBot` | Log file directory |
+| `LOG_DAYS_TO_KEEP` | `30` | Log retention in days |
-## Development
-Must have UV installed. See [UV Installation Guide](https://uv.run/docs/getting-started/installation)
+See [.env.example](.env.example) for the complete list of configuration options including cooldowns, SSL, connection pooling, and retry settings.
-### Building DEV Environment and Running Tests
+
+# Commands
+
+### OpenAI Commands
+| Command | Description |
+|:---------------|:-------------------------------------------------|
+| `ai ` | Ask OpenAI for assistance, response as embed |
+
+### Admin/Mod Commands
+| Command | Description |
+|:---------------------------|:--------------------------------|
+| `admin botgame ` | Change game that bot is playing |
+
+### Config Commands
+| Command | Description |
+|:-------------------------------------------|:---------------------------------------------|
+| `admin config list` | List all bot configurations |
+| `admin config joinmessage [on, off]` | Toggle message when a user joins the server |
+| `admin config leavemessage [on, off]` | Toggle message when a user leaves the server |
+| `admin config servermessage [on, off]` | Toggle message when a server gets updated |
+| `admin config membermessage [on, off]` | Toggle message when someone updates profile |
+| `admin config blockinvisible [on, off]` | Block messages from invisible members |
+| `admin config botreactions [on, off]` | Toggle bot reactions to member words |
+| `admin config pfilter [on, off] ` | Configure profanity filter per channel |
+
+### Custom Commands
+| Command | Description |
+|:--------------------------------------|:-----------------------------------|
+| `admin cc add ` | Add a new custom command |
+| `admin cc edit ` | Edit an existing custom command |
+| `admin cc remove ` | Remove a custom command |
+| `admin cc removeall` | Remove all custom commands |
+| `admin cc list` | List all custom commands |
+
+### Misc Commands
+| Command | Description |
+|:--------------------|:----------------------------------------|
+| `about` | Display bot info |
+| `echo ` | Show your message again |
+| `ping` | Test latency |
+| `pepe` | Post a random Pepe image |
+| `tts ` | Send text-to-speech as .mp3 to channel |
+| `serverinfo` | Show server information |
+| `userinfo ` | Show Discord user information |
+| `lmgtfy ` | Create a LMGTFY link |
+| `invites` | List active invite links for the server |
+
+### Dice Rolls Commands
+| Command | Description |
+|:-------------------------|:------------------------------------------|
+| `roll` | Roll a die (defaults to 100) |
+| `roll ` | Roll a die with specified size |
+| `roll results` | Display all dice rolls from the server |
+| `roll reset` | Delete all dice rolls (admin only) |
+
+### Bot Owner Commands
+| Command | Description |
+|:-----------------------------------------|:--------------------------------|
+| `owner servers` | Display all servers in database |
+| `owner prefix ` | Change bot prefix for commands |
+| `owner botdescription ` | Update bot description |
+
+### GW2 Commands
+| Command | Description |
+|:--------------------|:------------------------------------------|
+| `gw2 account` | Display your GW2 account information |
+| `gw2 characters` | Display your GW2 characters information |
+| `gw2 session` | Display your last game session data |
+| `gw2 worlds na` | List all NA worlds with WvW tier |
+| `gw2 worlds eu` | List all EU worlds with WvW tier |
+| `gw2 wiki ` | Search the Guild Wars 2 wiki |
+| `gw2 info ` | Information about a given name/skill/rune |
+
+### GW2 Config Commands
+| Command | Description |
+|:-------------------------------|:--------------------------------------|
+| `gw2 config list` | List all GW2 configurations |
+| `gw2 config session [on, off]` | Toggle recording of user sessions |
+
+### GW2 Key Commands
+| Command | Description |
+|:---------------------------|:------------------------------|
+| `gw2 key add [api_key]` | Add your first GW2 API key |
+| `gw2 key update [api_key]` | Update your existing API key |
+| `gw2 key remove` | Remove your GW2 API key |
+| `gw2 key info` | Show your API key information |
+
+### GW2 WvW Commands
+| Command | Description |
+|:------------------------|:-----------------------|
+| `gw2 wvw info [world]` | Info about a WvW world |
+| `gw2 wvw match [world]` | WvW match scores |
+| `gw2 wvw kdr [world]` | WvW kill/death ratios |
+
+
+# Development and Testing
+
+Requires [UV](https://docs.astral.sh/uv/getting-started/installation/) to be installed.
+
+### Setup
```shell
-uv venv
-uv sync --all-extras
+uv sync --all-extras --all-groups
+```
+
+### Running Tests
+```shell
+# Unit tests
poe test
+
+# Integration tests (requires Docker for testcontainers)
+poe test-integration
+
+# All tests (unit + integration + hadolint + docker)
+poe tests
```
+### Other Tasks
+```shell
+# Run linter (ruff)
+poe linter
+
+# Update all dev dependencies
+poe updatedev
+# Run database migrations
+poe migration
-# License
-Released under the [MIT](LICENSE).
+# Profile unit tests
+poe profile
+# Profile integration tests
+poe profile-integration
+```
+
+
+# Acknowledgements
+- [Discord Bot API](https://discord.com/developers/applications)
+- [OpenAI API](https://openai.com/api)
+- [Guild Wars 2 API](https://wiki.guildwars2.com/wiki/API:2)
+- [PostgreSQL](https://www.postgresql.org)
-# Buy me a cup of coffee
-+ [GitHub Sponsor](https://github.com/sponsors/ddc)
-+ [ko-fi](https://ko-fi.com/ddcsta)
-+ [Paypal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
+# Support
+If you find this project helpful, consider supporting development:
+
+- [GitHub Sponsor](https://github.com/sponsors/ddc)
+- [ko-fi](https://ko-fi.com/ddcsta)
+- [PayPal](https://www.paypal.com/ncp/payment/6G9Z78QHUD4RJ)
+
+
+# License
+Released under the [MIT License](LICENSE)
diff --git a/assets/DiscordBot-icon.svg b/assets/DiscordBot-icon.svg
new file mode 100644
index 00000000..f9b23646
--- /dev/null
+++ b/assets/DiscordBot-icon.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index d094529c..acf5ec61 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,66 +1,66 @@
[project]
name = "DiscordBot"
-version = "3.0.1"
-description = "A Bot for Discord"
+version = "3.0.2"
+description = "A simple Discord bot with OpenAI support and server administration tools"
urls.Repository = "https://github.com/ddc/DiscordBot"
urls.Homepage = "https://github.com/ddc/DiscordBot"
-license = {text = "MIT"}
+license = { text = "MIT" }
readme = "README.md"
authors = [
- {name = "Daniel Costa", email = "ddcsoftwares@proton.me"},
+ { name = "Daniel Costa", email = "ddcsoftwares@proton.me" },
]
maintainers = [
- {name = "Daniel Costa"},
+ { name = "Daniel Costa" },
]
keywords = [
- "python", "python3", "python-3",
- "DiscordBot", "discord-bots", "bot",
- "discord-py", "discord"
+ "python", "python3", "python-3",
+ "DiscordBot", "discord-bots", "bot",
+ "discord-py", "discord"
]
classifiers = [
- "Topic :: Communications :: Chat",
- "Development Status :: 5 - Production/Stable",
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.14",
- "Operating System :: OS Independent",
- "Environment :: Other Environment",
- "Intended Audience :: Developers",
- "Natural Language :: English",
+ "Topic :: Communications :: Chat",
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.14",
+ "Operating System :: OS Independent",
+ "Environment :: Other Environment",
+ "Intended Audience :: Developers",
+ "Natural Language :: English",
]
requires-python = ">=3.14"
dependencies = [
- "alembic>=1.18.4",
- "beautifulsoup4>=4.14.3",
- "better-profanity>=0.7.0",
- "ddcdatabases[postgres]>=3.0.10",
- "discord-py>=2.6.4",
- "gTTS>=2.5.4",
- "openai>=2.20.0",
- "PyNaCl>=1.6.2",
- "pythonLogs>=6.0.2",
+ "alembic>=1.18.4",
+ "beautifulsoup4>=4.14.3",
+ "better-profanity>=0.7.0",
+ "ddcdatabases[postgres]>=3.0.10",
+ "discord-py>=2.6.4",
+ "gTTS>=2.5.4",
+ "openai>=2.21.0",
+ "PyNaCl>=1.6.2",
+ "pythonLogs>=6.0.2",
]
[dependency-groups]
dev = [
- "pytest-asyncio>=1.3.0",
- "pytest-cov>=7.0.0",
- "testcontainers[postgres]>=4.14.1",
- "poethepoet>=0.41.0",
- "ruff>=0.15.0",
- "black>=26.1.0",
+ "pytest-asyncio>=1.3.0",
+ "pytest-cov>=7.0.0",
+ "testcontainers[postgres]>=4.14.1",
+ "poethepoet>=0.41.0",
+ "ruff>=0.15.1",
]
[tool.poe.tasks]
-linter.shell = "uv run ruff check --fix . && uv run black ."
-profile.sequence = ["linter", {shell = "uv run python -m cProfile -o cprofile_unit.prof -m pytest tests/unit --no-cov"}]
-profile-integration.sequence = ["linter", {shell = "uv run python -m cProfile -o cprofile_integration.prof -m pytest tests/integration --no-cov"}]
-test.sequence = ["linter", {shell = "uv run pytest"}]
-test-integration.sequence = ["linter", {shell = "uv run pytest tests/integration --no-cov"}]
+linter.shell = "uv run ruff check --fix . && uv run ruff format ."
+profile = "uv run python -m cProfile -o cprofile_unit.prof -m pytest tests/unit --no-cov"
+profile-integration = "uv run python -m cProfile -o cprofile_integration.prof -m pytest tests/integration --no-cov"
+test = "uv run pytest tests/unit"
+test-integration = "uv run pytest tests/integration --no-cov"
hadolint.shell = "docker run --rm -i -v $(pwd)/.hadolint.yaml:/.config/hadolint.yaml:ro hadolint/hadolint < Dockerfile"
-test-docker.shell = "uv run pytest tests/docker -v --no-cov"
+test-docker = "uv run pytest tests/docker -v --no-cov"
+migration = "uv run --frozen alembic upgrade head"
+tests.sequence = ["test", "test-integration", "hadolint", "test-docker"]
updatedev.sequence = ["linter", {shell = "uv lock --upgrade && uv sync --all-extras --group dev"}]
-migration.shell = "uv run --frozen alembic upgrade head"
[tool.pytest.ini_options]
addopts = "-v --import-mode=importlib --cov --cov-report=term --cov-report=xml --junitxml=junit.xml"
@@ -70,35 +70,31 @@ testpaths = ["tests/unit"]
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope = "function"
markers = [
- "integration: marks tests as integration tests",
- "docker: Docker and compose file tests",
+ "integration: marks tests as integration tests",
+ "docker: Docker and compose file tests",
]
[tool.coverage.run]
omit = [
- "tests/*",
- "*/__init__.py",
+ "tests/*",
+ "*/__init__.py",
]
[tool.coverage.report]
show_missing = true
skip_covered = false
exclude_lines = [
- "pragma: no cover",
- "def __repr__",
- "if self.debug:",
- "if settings.DEBUG",
- "raise AssertionError",
- "raise NotImplementedError",
- "if 0:",
- "if __name__ == .__main__.:",
- "class .*\\bProtocol\\):",
- "@(abc\\.)?abstractmethod",
+ "pragma: no cover",
+ "def __repr__",
+ "if self.debug:",
+ "if settings.DEBUG",
+ "raise AssertionError",
+ "raise NotImplementedError",
+ "if 0:",
+ "if __name__ == .__main__.:",
+ "class .*\\bProtocol\\):",
+ "@(abc\\.)?abstractmethod",
]
-[tool.black]
-line-length = 120
-skip-string-normalization = true
-
[tool.ruff]
line-length = 120
target-version = "py314"
diff --git a/src/__main__.py b/src/__main__.py
index cd912639..6dc25d90 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -99,7 +99,7 @@ async def main() -> None:
def run_bot() -> None:
- print(messages.BOT_STARTING.format(variables.TIME_BEFORE_START))
+ print(messages.bot_starting(variables.TIME_BEFORE_START))
time.sleep(variables.TIME_BEFORE_START)
try:
diff --git a/src/bot/cogs/admin/admin.py b/src/bot/cogs/admin/admin.py
index 53fbf1e5..59119e92 100644
--- a/src/bot/cogs/admin/admin.py
+++ b/src/bot/cogs/admin/admin.py
@@ -44,7 +44,7 @@ async def botgame(self, ctx: commands.Context, *, game: str) -> None:
await self.bot.change_presence(activity=activity)
# Create success embed
- embed = self._create_admin_embed(f"```{messages.BOT_ANNOUNCE_PLAYING.format(game)}```")
+ embed = self._create_admin_embed(f"```{messages.bot_announce_playing(game)}```")
embed.set_author(
name=self.bot.user.display_name,
icon_url=self.bot.user.avatar.url if self.bot.user.avatar else None,
@@ -58,7 +58,7 @@ async def _warn_about_bg_activity_timer(self, ctx: commands.Context) -> None:
"""Show warning if background activity timer will override the game status."""
bg_activity_timer = self.bot.settings["bot"]["BGActivityTimer"]
if bg_activity_timer and bg_activity_timer > 0:
- bg_task_warning = messages.BG_TASK_WARNING.format(bg_activity_timer)
+ bg_task_warning = messages.bg_task_warning(bg_activity_timer)
warning_embed = self._create_admin_embed(bg_task_warning)
await bot_utils.send_embed(ctx, warning_embed, False)
diff --git a/src/bot/cogs/admin/config.py b/src/bot/cogs/admin/config.py
index a769b471..2ef866b8 100644
--- a/src/bot/cogs/admin/config.py
+++ b/src/bot/cogs/admin/config.py
@@ -244,7 +244,7 @@ async def config_pfilter(ctx: commands.Context, *, subcommand_passed: str) -> No
case _:
raise commands.BadArgument(message="BadArgument")
- msg = messages.CONFIG_PFILTER.format(status.upper(), channel.name)
+ msg = messages.config_pfilter(status.upper(), channel.name)
embed = discord.Embed(description=msg, color=color)
await bot_utils.send_embed(ctx, embed)
return None
@@ -273,7 +273,7 @@ async def config_list(ctx: commands.Context) -> None:
# Format channel names
if pf:
- channel_names_lst = [channel['channel_name'] for channel in pf]
+ channel_names_lst = [channel["channel_name"] for channel in pf]
channel_names = "\n".join(channel_names_lst)
else:
channel_names = messages.NO_CHANNELS_LISTED
@@ -314,7 +314,7 @@ async def config_list(ctx: commands.Context) -> None:
inline=True,
)
embed.add_field(
- name=f"🎭 {messages.CONFIG_BOT_WORD_REACTIONS.format(ctx.guild.name)}",
+ name=f"🎭 {messages.CONFIG_BOT_WORD_REACTIONS}",
value=f"{on}" if sc["bot_word_reactions"] else f"{off}",
inline=True,
)
@@ -412,9 +412,9 @@ async def _handle_update(
# Set updating state and disable all buttons
self._updating = True
for item in self.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
item.disabled = True
- if hasattr(item, 'style'):
+ if hasattr(item, "style"):
item.style = discord.ButtonStyle.gray
# Defer the response to allow editing the original message
@@ -466,7 +466,7 @@ async def _handle_update(
async def _restore_buttons(self):
"""Restore button states and colors."""
for item in self.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
item.disabled = False
# Restore original button colors
@@ -500,7 +500,7 @@ async def _create_updated_embed(self):
# Format channel names
if pf:
- channel_names_lst = [channel['channel_name'] for channel in pf]
+ channel_names_lst = [channel["channel_name"] for channel in pf]
channel_names = "\n".join(channel_names_lst)
else:
channel_names = messages.NO_CHANNELS_LISTED
@@ -543,7 +543,7 @@ async def _create_updated_embed(self):
inline=True,
)
embed.add_field(
- name=f"🎭 {messages.CONFIG_BOT_WORD_REACTIONS.format(self.ctx.guild.name)}",
+ name=f"🎭 {messages.CONFIG_BOT_WORD_REACTIONS}",
value=f"{on}" if self.server_config["bot_word_reactions"] else f"{off}",
inline=True,
)
diff --git a/src/bot/cogs/admin/custom_cmd.py b/src/bot/cogs/admin/custom_cmd.py
index dd7b50b0..eb85bbd1 100644
--- a/src/bot/cogs/admin/custom_cmd.py
+++ b/src/bot/cogs/admin/custom_cmd.py
@@ -149,8 +149,7 @@ async def remove_custom_command(ctx: commands.Context, cmd_name: str) -> None:
command_names = {cmd["name"] for cmd in server_commands}
if cmd_name not in command_names:
error_msg = (
- f"{messages.CUSTOM_COMMAND_UNABLE_REMOVE}\n"
- f"{messages.COMMAND_NOT_FOUND}: {chat_formatting.inline(cmd_name)}"
+ f"{messages.CUSTOM_COMMAND_UNABLE_REMOVE}\n{messages.COMMAND_NOT_FOUND}: {chat_formatting.inline(cmd_name)}"
)
return await bot_utils.send_error_msg(ctx, error_msg)
diff --git a/src/bot/cogs/dice_rolls.py b/src/bot/cogs/dice_rolls.py
index 2d4664d3..a1fba412 100644
--- a/src/bot/cogs/dice_rolls.py
+++ b/src/bot/cogs/dice_rolls.py
@@ -79,7 +79,7 @@ async def roll_results(self, ctx: commands.Context) -> None:
server_rolls = await dice_rolls_dal.get_all_server_rolls(ctx.guild.id, dice_size)
if not server_rolls:
- return await bot_utils.send_error_msg(ctx, messages.NO_DICE_SIZE_ROLLS.format(dice_size))
+ return await bot_utils.send_error_msg(ctx, messages.no_dice_size_rolls(dice_size))
embed = self._create_results_embed(ctx, server_rolls, dice_size)
await bot_utils.send_embed(ctx, embed)
diff --git a/src/bot/cogs/events/on_command.py b/src/bot/cogs/events/on_command.py
index 968ef145..b6e49d54 100644
--- a/src/bot/cogs/events/on_command.py
+++ b/src/bot/cogs/events/on_command.py
@@ -55,22 +55,22 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.command_logger = CommandLogger(bot)
- @self.bot.event
- async def on_command(ctx: commands.Context) -> None:
- """Handle command execution event.
-
- Called when a valid command gets executed successfully.
- This is useful for logging command usage and monitoring.
-
- Args:
- ctx: The command context containing information about the command execution
- """
- try:
- # Log command execution for monitoring purposes
- self.command_logger.log_command_execution(ctx)
- # Future: Add command usage statistics, rate limiting, etc.
- except Exception as e:
- self.bot.log.error(f"Error in on_command event: {e}")
+ @commands.Cog.listener()
+ async def on_command(self, ctx: commands.Context) -> None:
+ """Handle command execution event.
+
+ Called when a valid command gets executed successfully.
+ This is useful for logging command usage and monitoring.
+
+ Args:
+ ctx: The command context containing information about the command execution
+ """
+ try:
+ # Log command execution for monitoring purposes
+ self.command_logger.log_command_execution(ctx)
+ # Future: Add command usage statistics, rate limiting, etc.
+ except Exception as e:
+ self.bot.log.error(f"Error in on_command event: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_command_error.py b/src/bot/cogs/events/on_command_error.py
index 62f456c0..8bcf0a4f 100644
--- a/src/bot/cogs/events/on_command_error.py
+++ b/src/bot/cogs/events/on_command_error.py
@@ -65,11 +65,13 @@ def build_missing_argument(context: ErrorContext) -> str:
@staticmethod
def build_check_failure(context: ErrorContext) -> str:
"""Build message for check failure error."""
- if "not admin" in context.error_msg:
- return f"{messages.NOT_ADMIN_USE_COMMAND}: `{context.command}`"
- elif "not owner" in context.error_msg:
- return f"{messages.BOT_OWNERS_ONLY_COMMAND}: `{context.command}`"
- return context.error_msg
+ match context.error_msg:
+ case msg if "not admin" in msg:
+ return f"{messages.NOT_ADMIN_USE_COMMAND}: `{context.command}`"
+ case msg if "not owner" in msg:
+ return f"{messages.BOT_OWNERS_ONLY_COMMAND}: `{context.command}`"
+ case _:
+ return context.error_msg
@staticmethod
def build_bad_argument(context: ErrorContext) -> str:
@@ -97,8 +99,8 @@ def build_command_invoke_error(context: ErrorContext) -> str:
"NoOptionError",
): f"{messages.NO_OPTION_FOUND}: `{context.error_msg.split()[7] if len(context.error_msg.split()) > 7 else 'unknown'}`",
("GW2 API",): (
- str(context.error_msg).split(',')[1].strip().split('?')[0]
- if ',' in str(context.error_msg) and len(str(context.error_msg).split(',')) > 1
+ str(context.error_msg).split(",")[1].strip().split("?")[0]
+ if "," in str(context.error_msg) and len(str(context.error_msg).split(",")) > 1
else str(context.error_msg)
),
("No text to send to TTS API",): messages.INVALID_MESSAGE,
@@ -184,13 +186,14 @@ async def _handle_check_failure(self, context: ErrorContext, should_log: bool) -
async def _handle_bad_argument(self, context: ErrorContext, should_log: bool) -> None:
"""Handle BadArgument error."""
# Extract bad argument from message content
- if context.error_msg == "BadArgument_Gw2ConfigStatus":
- context.bad_argument = context.ctx.message.clean_content.split()[3]
- elif context.error_msg == "BadArgument_Gw2ConfigServer":
- bad_server_list = context.ctx.message.clean_content.split()[4:]
- context.bad_argument = " ".join(bad_server_list)
- else:
- context.bad_argument = context.ctx.message.clean_content.replace(context.command, "").strip()
+ match context.error_msg:
+ case "BadArgument_Gw2ConfigStatus":
+ context.bad_argument = context.ctx.message.clean_content.split()[3]
+ case "BadArgument_Gw2ConfigServer":
+ bad_server_list = context.ctx.message.clean_content.split()[4:]
+ context.bad_argument = " ".join(bad_server_list)
+ case _:
+ context.bad_argument = context.ctx.message.clean_content.replace(context.command, "").strip()
error_msg = self.message_builder.build_bad_argument(context)
await self._send_error_message(context.ctx, error_msg, should_log)
diff --git a/src/bot/cogs/events/on_connect.py b/src/bot/cogs/events/on_connect.py
index 57546c20..991789a1 100644
--- a/src/bot/cogs/events/on_connect.py
+++ b/src/bot/cogs/events/on_connect.py
@@ -134,22 +134,22 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.connection_handler = ConnectionHandler(bot)
- @self.bot.event
- async def on_connect() -> None:
- """Handle bot connection event.
-
- Called when the client has successfully connected to Discord.
- This is not the same as the client being fully prepared (see on_ready for that).
-
- This event handles:
- - Database synchronization
- - Guild verification
- - Connection logging
- """
- try:
- await self.connection_handler.process_connection()
- except Exception as e:
- self.bot.log.error(f"Critical error in on_connect event: {e}")
+ @commands.Cog.listener()
+ async def on_connect(self) -> None:
+ """Handle bot connection event.
+
+ Called when the client has successfully connected to Discord.
+ This is not the same as the client being fully prepared (see on_ready for that).
+
+ This event handles:
+ - Database synchronization
+ - Guild verification
+ - Connection logging
+ """
+ try:
+ await self.connection_handler.process_connection()
+ except Exception as e:
+ self.bot.log.error(f"Critical error in on_connect event: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_disconnect.py b/src/bot/cogs/events/on_disconnect.py
index 6f83f5ab..ae1d8fb6 100644
--- a/src/bot/cogs/events/on_disconnect.py
+++ b/src/bot/cogs/events/on_disconnect.py
@@ -14,26 +14,22 @@ def __init__(self, bot: Bot) -> None:
"""
self.bot = bot
- @self.bot.event
- async def on_disconnect() -> None:
- """Handle bot disconnect event.
-
- Called when the client has disconnected from Discord,
- or a connection attempt to Discord has failed.
- This could happen through:
- - Internet disconnection
- - Explicit calls to close
- - Discord terminating the connection
- """
- try:
- bot.log.warning(
- messages.BOT_DISCONNECTED.format(bot.user)
- if hasattr(messages, 'BOT_DISCONNECTED')
- else f"Bot {bot.user} disconnected from Discord"
- )
- except Exception as e:
- # Fallback logging in case of critical failure
- print(f"Bot disconnected - logging failed: {e}")
+ @commands.Cog.listener()
+ async def on_disconnect(self) -> None:
+ """Handle bot disconnect event.
+
+ Called when the client has disconnected from Discord,
+ or a connection attempt to Discord has failed.
+ This could happen through:
+ - Internet disconnection
+ - Explicit calls to close
+ - Discord terminating the connection
+ """
+ try:
+ self.bot.log.warning(messages.bot_disconnected(self.bot.user))
+ except Exception as e:
+ # Fallback logging in case of critical failure
+ print(f"Bot disconnected - logging failed: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_guild_channel_create.py b/src/bot/cogs/events/on_guild_channel_create.py
index b4f2b976..24a84871 100644
--- a/src/bot/cogs/events/on_guild_channel_create.py
+++ b/src/bot/cogs/events/on_guild_channel_create.py
@@ -5,14 +5,14 @@ class OnGuildChannelCreate(commands.Cog):
def __init__(self, bot):
self.bot = bot
- @self.bot.event
- async def on_guild_channel_create(channel):
- """
- Called when a channel gets created
- :param channel: abc.GuildChannel
- :return: None
- """
- pass
+ @commands.Cog.listener()
+ async def on_guild_channel_create(self, channel):
+ """
+ Called when a channel gets created
+ :param channel: abc.GuildChannel
+ :return: None
+ """
+ pass
async def setup(bot):
diff --git a/src/bot/cogs/events/on_guild_channel_delete.py b/src/bot/cogs/events/on_guild_channel_delete.py
index 73b2c102..a3f289e9 100644
--- a/src/bot/cogs/events/on_guild_channel_delete.py
+++ b/src/bot/cogs/events/on_guild_channel_delete.py
@@ -5,14 +5,14 @@ class OnGuildChannelDelete(commands.Cog):
def __init__(self, bot):
self.bot = bot
- @self.bot.event
- async def on_guild_channel_delete(channel):
- """
- Called when a channel gets deleted
- :param channel: abc.GuildChannel
- :return: None
- """
- pass
+ @commands.Cog.listener()
+ async def on_guild_channel_delete(self, channel):
+ """
+ Called when a channel gets deleted
+ :param channel: abc.GuildChannel
+ :return: None
+ """
+ pass
async def setup(bot):
diff --git a/src/bot/cogs/events/on_guild_channel_update.py b/src/bot/cogs/events/on_guild_channel_update.py
index 3e714c99..f0cf9b93 100644
--- a/src/bot/cogs/events/on_guild_channel_update.py
+++ b/src/bot/cogs/events/on_guild_channel_update.py
@@ -5,15 +5,15 @@ class OnGuildChannelUpdate(commands.Cog):
def __init__(self, bot):
self.bot = bot
- @self.bot.event
- async def on_guild_channel_update(before, after):
- """
- Called when a channel gets updated
- :param before: abc.GuildChannel
- :param after: abc.GuildChannel
- :return: None
- """
- pass
+ @commands.Cog.listener()
+ async def on_guild_channel_update(self, before, after):
+ """
+ Called when a channel gets updated
+ :param before: abc.GuildChannel
+ :param after: abc.GuildChannel
+ :return: None
+ """
+ pass
async def setup(bot):
diff --git a/src/bot/cogs/events/on_guild_join.py b/src/bot/cogs/events/on_guild_join.py
index ff05c6f9..5cb1f5b5 100644
--- a/src/bot/cogs/events/on_guild_join.py
+++ b/src/bot/cogs/events/on_guild_join.py
@@ -12,7 +12,7 @@ class WelcomeMessageBuilder:
@staticmethod
def build_welcome_message(bot_name: str, prefix: str, games_included: str) -> str:
"""Build the welcome message text."""
- return messages.GUILD_JOIN_BOT_MESSAGE.format(bot_name, prefix, games_included, prefix, prefix)
+ return messages.guild_join_bot_message(bot_name, prefix, games_included)
@staticmethod
def build_welcome_embed(bot: Bot, message: str) -> discord.Embed:
@@ -41,7 +41,7 @@ def _set_footer(embed: discord.Embed, bot: Bot) -> None:
"""Set footer with developer information."""
try:
author = bot.get_user(bot.owner_id)
- python_version = "Python {}.{}.{}".format(*sys.version_info[:3])
+ python_version = f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
if author and author.avatar:
embed.set_footer(icon_url=author.avatar.url, text=f"Developed by {author} | {python_version}")
@@ -49,7 +49,7 @@ def _set_footer(embed: discord.Embed, bot: Bot) -> None:
embed.set_footer(text=f"Developed by Bot Owner | {python_version}")
except AttributeError, discord.HTTPException:
# Fallback if owner information is not available or HTTP error occurs
- python_version = "Python {}.{}.{}".format(*sys.version_info[:3])
+ python_version = f"Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
embed.set_footer(text=f"Discord Bot | {python_version}")
@@ -60,23 +60,23 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.message_builder = WelcomeMessageBuilder()
- @self.bot.event
- async def on_guild_join(guild: discord.Guild) -> None:
- """Handle bot joining a new guild."""
- # Register server in database
- await bot_utils.insert_server(bot, guild)
-
- # Build welcome message and embed
- games_included = "".join(variables.GAMES_INCLUDED)
- welcome_text = self.message_builder.build_welcome_message(
- bot.user.name,
- bot.command_prefix,
- games_included,
- )
- welcome_embed = self.message_builder.build_welcome_embed(bot, welcome_text)
-
- # Send welcome message to system channel
- await bot_utils.send_msg_to_system_channel(bot.log, guild, welcome_embed, welcome_text)
+ @commands.Cog.listener()
+ async def on_guild_join(self, guild: discord.Guild) -> None:
+ """Handle bot joining a new guild."""
+ # Register server in database
+ await bot_utils.insert_server(self.bot, guild)
+
+ # Build welcome message and embed
+ games_included = "".join(variables.GAMES_INCLUDED)
+ welcome_text = self.message_builder.build_welcome_message(
+ self.bot.user.name,
+ self.bot.command_prefix,
+ games_included,
+ )
+ welcome_embed = self.message_builder.build_welcome_embed(self.bot, welcome_text)
+
+ # Send welcome message to system channel
+ await bot_utils.send_msg_to_system_channel(self.bot.log, guild, welcome_embed, welcome_text)
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_guild_remove.py b/src/bot/cogs/events/on_guild_remove.py
index f77583bb..8b755f3f 100644
--- a/src/bot/cogs/events/on_guild_remove.py
+++ b/src/bot/cogs/events/on_guild_remove.py
@@ -47,26 +47,26 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.cleanup_handler = GuildCleanupHandler(bot)
- @self.bot.event
- async def on_guild_remove(guild: discord.Guild) -> None:
- """Handle guild removal event.
-
- Called when the bot is removed from a guild (server).
- This includes being kicked, banned, or the server being deleted.
-
- Args:
- guild: The Discord guild the bot was removed from
- """
- try:
- self.bot.log.info(f"Bot removed from guild: {guild.name} (ID: {guild.id})")
-
- # Clean up server data from database
- cleanup_success = await self.cleanup_handler.cleanup_server_data(guild)
-
- if not cleanup_success:
- self.bot.log.warning(f"Database cleanup may be incomplete for guild: {guild.name}")
- except Exception as e:
- self.bot.log.error(f"Error handling guild removal for {guild.name}: {e}")
+ @commands.Cog.listener()
+ async def on_guild_remove(self, guild: discord.Guild) -> None:
+ """Handle guild removal event.
+
+ Called when the bot is removed from a guild (server).
+ This includes being kicked, banned, or the server being deleted.
+
+ Args:
+ guild: The Discord guild the bot was removed from
+ """
+ try:
+ self.bot.log.info(f"Bot removed from guild: {guild.name} (ID: {guild.id})")
+
+ # Clean up server data from database
+ cleanup_success = await self.cleanup_handler.cleanup_server_data(guild)
+
+ if not cleanup_success:
+ self.bot.log.warning(f"Database cleanup may be incomplete for guild: {guild.name}")
+ except Exception as e:
+ self.bot.log.error(f"Error handling guild removal for {guild.name}: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_guild_update.py b/src/bot/cogs/events/on_guild_update.py
index d1331f96..e01fc49e 100644
--- a/src/bot/cogs/events/on_guild_update.py
+++ b/src/bot/cogs/events/on_guild_update.py
@@ -17,29 +17,29 @@ def __init__(self, bot: Bot) -> None:
"""
self.bot = bot
- @self.bot.event
- async def on_guild_update(before: discord.Guild, after: discord.Guild) -> None:
- """Handle guild update event.
+ @commands.Cog.listener()
+ async def on_guild_update(self, before: discord.Guild, after: discord.Guild) -> None:
+ """Handle guild update event.
- Called when a guild is updated (name, icon, owner changes, etc.).
+ Called when a guild is updated (name, icon, owner changes, etc.).
- Args:
- before: The guild before the update
- after: The guild after the update
- """
- try:
- embed, msg = self._create_base_embed()
+ Args:
+ before: The guild before the update
+ after: The guild after the update
+ """
+ try:
+ embed, msg = self._create_base_embed()
- # Check for changes and update embed/message
- self._handle_icon_changes(before, after, embed, msg)
- self._handle_name_changes(before, after, embed, msg)
- self._handle_owner_changes(before, after, embed, msg)
+ # Check for changes and update embed/message
+ self._handle_icon_changes(before, after, embed, msg)
+ self._handle_name_changes(before, after, embed, msg)
+ self._handle_owner_changes(before, after, embed, msg)
- # Send notification if changes were detected
- await self._send_notification_if_enabled(after, embed, msg)
+ # Send notification if changes were detected
+ await self._send_notification_if_enabled(after, embed, msg)
- except Exception as e:
- self.bot.log.error(f"Error in on_guild_update for {after.name}: {e}")
+ except Exception as e:
+ self.bot.log.error(f"Error in on_guild_update for {after.name}: {e}")
def _create_base_embed(self):
"""Create the base embed and message for guild updates."""
@@ -63,7 +63,7 @@ def _handle_icon_changes(self, before, after, embed, msg):
if str(before.icon.url) != str(after.icon.url):
self._set_thumbnail_if_icon_exists(after, embed)
embed.add_field(name=messages.NEW_SERVER_ICON, value="")
- icon_url = after.icon.url if after.icon else 'None'
+ icon_url = after.icon.url if after.icon else "None"
msg.append(f"{messages.NEW_SERVER_ICON}: \n{icon_url}\n")
@staticmethod
diff --git a/src/bot/cogs/events/on_member_join.py b/src/bot/cogs/events/on_member_join.py
index ffdc8030..cc984f77 100644
--- a/src/bot/cogs/events/on_member_join.py
+++ b/src/bot/cogs/events/on_member_join.py
@@ -125,21 +125,21 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.join_handler = MemberJoinHandler(bot)
- @self.bot.event
- async def on_member_join(member: discord.Member) -> None:
- """Handle member join event.
-
- Called when a member joins a guild where the bot is present.
- Sends welcome messages if configured to do so.
-
- Args:
- member: The Discord member who joined the guild
- """
- try:
- self.bot.log.info(f"Member joined: {member} in guild: {member.guild.name}")
- await self.join_handler.process_member_join(member)
- except Exception as e:
- self.bot.log.error(f"Critical error in on_member_join for {member}: {e}")
+ @commands.Cog.listener()
+ async def on_member_join(self, member: discord.Member) -> None:
+ """Handle member join event.
+
+ Called when a member joins a guild where the bot is present.
+ Sends welcome messages if configured to do so.
+
+ Args:
+ member: The Discord member who joined the guild
+ """
+ try:
+ self.bot.log.info(f"Member joined: {member} in guild: {member.guild.name}")
+ await self.join_handler.process_member_join(member)
+ except Exception as e:
+ self.bot.log.error(f"Critical error in on_member_join for {member}: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_member_remove.py b/src/bot/cogs/events/on_member_remove.py
index 056485e0..9452aef3 100644
--- a/src/bot/cogs/events/on_member_remove.py
+++ b/src/bot/cogs/events/on_member_remove.py
@@ -129,22 +129,22 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.leave_handler = MemberLeaveHandler(bot)
- @self.bot.event
- async def on_member_remove(member: discord.Member) -> None:
- """Handle member remove event.
-
- Called when a member leaves a guild where the bot is present.
- This includes being kicked, banned, or leaving voluntarily.
- Sends farewell messages if configured to do so.
-
- Args:
- member: The Discord member who left the guild
- """
- try:
- self.bot.log.info(f"Member left: {member} from guild: {member.guild.name}")
- await self.leave_handler.process_member_leave(member)
- except Exception as e:
- self.bot.log.error(f"Critical error in on_member_remove for {member}: {e}")
+ @commands.Cog.listener()
+ async def on_member_remove(self, member: discord.Member) -> None:
+ """Handle member remove event.
+
+ Called when a member leaves a guild where the bot is present.
+ This includes being kicked, banned, or leaving voluntarily.
+ Sends farewell messages if configured to do so.
+
+ Args:
+ member: The Discord member who left the guild
+ """
+ try:
+ self.bot.log.info(f"Member left: {member} from guild: {member.guild.name}")
+ await self.leave_handler.process_member_leave(member)
+ except Exception as e:
+ self.bot.log.error(f"Critical error in on_member_remove for {member}: {e}")
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_member_update.py b/src/bot/cogs/events/on_member_update.py
index 2f4e224b..c9f48c2d 100644
--- a/src/bot/cogs/events/on_member_update.py
+++ b/src/bot/cogs/events/on_member_update.py
@@ -17,36 +17,36 @@ def __init__(self, bot: Bot) -> None:
"""
self.bot = bot
- @self.bot.event
- async def on_member_update(before: discord.Member, after: discord.Member) -> None:
- """Handle member update event.
-
- Called when a Member updates their profile.
- This includes changes to:
- - nickname
- - roles
- - pending status
- - flags
-
- Args:
- before: The member before the update
- after: The member after the update
- """
- try:
- if after.bot:
- return
-
- embed, msg = self._create_member_embed(after)
-
- # Check for changes and update embed/message
- self._handle_nickname_changes(before, after, embed, msg)
- self._handle_role_changes(before, after, embed, msg)
-
- # Send notification if changes were detected
- await self._send_notification_if_enabled(after, embed, msg)
-
- except Exception as e:
- self.bot.log.error(f"Error in on_member_update for {after}: {e}")
+ @commands.Cog.listener()
+ async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
+ """Handle member update event.
+
+ Called when a Member updates their profile.
+ This includes changes to:
+ - nickname
+ - roles
+ - pending status
+ - flags
+
+ Args:
+ before: The member before the update
+ after: The member after the update
+ """
+ try:
+ if after.bot:
+ return
+
+ embed, msg = self._create_member_embed(after)
+
+ # Check for changes and update embed/message
+ self._handle_nickname_changes(before, after, embed, msg)
+ self._handle_role_changes(before, after, embed, msg)
+
+ # Send notification if changes were detected
+ await self._send_notification_if_enabled(after, embed, msg)
+
+ except Exception as e:
+ self.bot.log.error(f"Error in on_member_update for {after}: {e}")
def _create_member_embed(self, member):
"""Create the base embed and message for member updates."""
diff --git a/src/bot/cogs/events/on_message.py b/src/bot/cogs/events/on_message.py
index 8224d2cc..e6939ae0 100644
--- a/src/bot/cogs/events/on_message.py
+++ b/src/bot/cogs/events/on_message.py
@@ -305,7 +305,7 @@ async def _handle_invisible_member(self, ctx: commands.Context) -> None:
"""Handle message from invisible member."""
await bot_utils.delete_message(ctx)
- message_text = messages.BLOCKED_INVIS_MESSAGE.format(ctx.guild.name)
+ message_text = messages.blocked_invis_message(ctx.guild.name)
embed = discord.Embed(
title="",
color=discord.Color.red(),
diff --git a/src/bot/cogs/events/on_presence_update.py b/src/bot/cogs/events/on_presence_update.py
index 531134cc..d65c6557 100644
--- a/src/bot/cogs/events/on_presence_update.py
+++ b/src/bot/cogs/events/on_presence_update.py
@@ -6,21 +6,21 @@ class OnPresenceUpdate(commands.Cog):
def __init__(self, bot):
self.bot = bot
- @self.bot.event
- async def on_presence_update(before, after):
- """
- Called when a Member updates their presence.
- This is called when one or more of the following things change:
- status
- activity
- :param before: discord.Member
- :param after: discord.Member
- :return: None
- """
- if after.bot:
- return
+ @commands.Cog.listener()
+ async def on_presence_update(self, before, after):
+ """
+ Called when a Member updates their presence.
+ This is called when one or more of the following things change:
+ status
+ activity
+ :param before: discord.Member
+ :param after: discord.Member
+ :return: None
+ """
+ if after.bot:
+ return
- await gw2_utils.check_gw2_game_activity(bot, before, after)
+ await gw2_utils.check_gw2_game_activity(self.bot, before, after)
async def setup(bot):
diff --git a/src/bot/cogs/events/on_ready.py b/src/bot/cogs/events/on_ready.py
index 668d42df..94adb26c 100644
--- a/src/bot/cogs/events/on_ready.py
+++ b/src/bot/cogs/events/on_ready.py
@@ -18,7 +18,7 @@ def print_startup_banner(version: str) -> None:
@staticmethod
def print_version_info() -> None:
"""Print version information for Python and Discord API."""
- python_version = "Python v{}.{}.{}".format(*sys.version_info[:3])
+ python_version = f"Python v{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
discord_version = f"Discord API v{discord.__version__}"
print(python_version)
print(discord_version)
@@ -51,32 +51,32 @@ def __init__(self, bot: Bot) -> None:
self.bot = bot
self.info_display = StartupInfoDisplay()
- @self.bot.event
- async def on_ready() -> None:
- """Handle bot ready event with comprehensive startup information."""
- try:
- # Get bot statistics
- bot_stats = bot_utils.get_bot_stats(bot)
-
- # Display startup information
- self.info_display.print_startup_banner(variables.VERSION)
- self.info_display.print_version_info()
- self.info_display.print_bot_info(bot)
- self.info_display.print_bot_stats(bot_stats)
- self.info_display.print_timestamp()
-
- # Log bot online status
- bot.log.info(messages.BOT_ONLINE.format(bot.user))
- except Exception as e:
- # Display startup information even if stats fail
- self.info_display.print_startup_banner(variables.VERSION)
- self.info_display.print_version_info()
- self.info_display.print_bot_info(bot)
- self.info_display.print_timestamp()
-
- # Log error and bot online status
- bot.log.error(f"Failed to get bot stats during startup: {e}")
- bot.log.info(messages.BOT_ONLINE.format(bot.user))
+ @commands.Cog.listener()
+ async def on_ready(self) -> None:
+ """Handle bot ready event with comprehensive startup information."""
+ try:
+ # Get bot statistics
+ bot_stats = bot_utils.get_bot_stats(self.bot)
+
+ # Display startup information
+ self.info_display.print_startup_banner(variables.VERSION)
+ self.info_display.print_version_info()
+ self.info_display.print_bot_info(self.bot)
+ self.info_display.print_bot_stats(bot_stats)
+ self.info_display.print_timestamp()
+
+ # Log bot online status
+ self.bot.log.info(messages.bot_online(self.bot.user))
+ except Exception as e:
+ # Display startup information even if stats fail
+ self.info_display.print_startup_banner(variables.VERSION)
+ self.info_display.print_version_info()
+ self.info_display.print_bot_info(self.bot)
+ self.info_display.print_timestamp()
+
+ # Log error and bot online status
+ self.bot.log.error(f"Failed to get bot stats during startup: {e}")
+ self.bot.log.info(messages.bot_online(self.bot.user))
async def setup(bot: Bot) -> None:
diff --git a/src/bot/cogs/events/on_user_update.py b/src/bot/cogs/events/on_user_update.py
index 6c44ed41..ee80dd17 100644
--- a/src/bot/cogs/events/on_user_update.py
+++ b/src/bot/cogs/events/on_user_update.py
@@ -8,53 +8,53 @@ class OnUserUpdate(commands.Cog):
def __init__(self, bot):
self.bot = bot
- @self.bot.event
- async def on_user_update(before, after):
- """
- Called when a User updates their profile.
- This is called before on_member_update event is triggered
- This is called when one or more of the following things change:
- avatar
- username
- discriminator
- :param before: discord.Member
- :param after: discord.Member
- :return: None
- """
- if after.bot:
- return
-
- msg = f"{messages.PROFILE_CHANGES}:\n\n"
- embed = bot_utils.get_embed(self)
- embed.set_author(name=after.display_name, icon_url=after.avatar.url)
- embed.set_footer(
- icon_url=self.bot.user.avatar.url,
- text=f"{bot_utils.get_current_date_time_str_long()} UTC",
- )
-
- if str(before.avatar.url) != str(after.avatar.url):
- embed.set_thumbnail(url=after.avatar.url)
- embed.add_field(name=messages.NEW_AVATAR, value="-->")
- msg += f"{messages.NEW_AVATAR}: \n{after.avatar.url}\n"
-
- if str(before.name) != str(after.name):
- if before.name is not None:
- embed.add_field(name=messages.PREVIOUS_NAME, value=str(before.name))
- embed.add_field(name=messages.NEW_NAME, value=str(after.name))
- msg += f"{messages.NEW_NAME}: `{after.name}`\n"
-
- if str(before.discriminator) != str(after.discriminator):
- if before.name is not None:
- embed.add_field(name=messages.PREVIOUS_DISCRIMINATOR, value=str(before.discriminator))
- embed.add_field(name=messages.NEW_DISCRIMINATOR, value=str(after.discriminator))
- msg += f"{messages.NEW_DISCRIMINATOR}: `{after.discriminator}`\n"
-
- if len(embed.fields) > 0:
- servers_dal = ServersDal(bot.db_session, bot.log)
- for guild in after.mutual_guilds:
- rs = await servers_dal.get_server(guild.id)
- if rs["msg_on_member_update"]:
- await bot_utils.send_msg_to_system_channel(self.bot.log, guild, embed, msg)
+ @commands.Cog.listener()
+ async def on_user_update(self, before, after):
+ """
+ Called when a User updates their profile.
+ This is called before on_member_update event is triggered
+ This is called when one or more of the following things change:
+ avatar
+ username
+ discriminator
+ :param before: discord.Member
+ :param after: discord.Member
+ :return: None
+ """
+ if after.bot:
+ return
+
+ msg = f"{messages.PROFILE_CHANGES}:\n\n"
+ embed = bot_utils.get_embed(self)
+ embed.set_author(name=after.display_name, icon_url=after.avatar.url)
+ embed.set_footer(
+ icon_url=self.bot.user.avatar.url,
+ text=f"{bot_utils.get_current_date_time_str_long()} UTC",
+ )
+
+ if str(before.avatar.url) != str(after.avatar.url):
+ embed.set_thumbnail(url=after.avatar.url)
+ embed.add_field(name=messages.NEW_AVATAR, value="-->")
+ msg += f"{messages.NEW_AVATAR}: \n{after.avatar.url}\n"
+
+ if str(before.name) != str(after.name):
+ if before.name is not None:
+ embed.add_field(name=messages.PREVIOUS_NAME, value=str(before.name))
+ embed.add_field(name=messages.NEW_NAME, value=str(after.name))
+ msg += f"{messages.NEW_NAME}: `{after.name}`\n"
+
+ if str(before.discriminator) != str(after.discriminator):
+ if before.name is not None:
+ embed.add_field(name=messages.PREVIOUS_DISCRIMINATOR, value=str(before.discriminator))
+ embed.add_field(name=messages.NEW_DISCRIMINATOR, value=str(after.discriminator))
+ msg += f"{messages.NEW_DISCRIMINATOR}: `{after.discriminator}`\n"
+
+ if len(embed.fields) > 0:
+ servers_dal = ServersDal(self.bot.db_session, self.bot.log)
+ for guild in after.mutual_guilds:
+ rs = await servers_dal.get_server(guild.id)
+ if rs["msg_on_member_update"]:
+ await bot_utils.send_msg_to_system_channel(self.bot.log, guild, embed, msg)
async def setup(bot):
diff --git a/src/bot/cogs/misc.py b/src/bot/cogs/misc.py
index be6f04c9..159fdfdb 100644
--- a/src/bot/cogs/misc.py
+++ b/src/bot/cogs/misc.py
@@ -222,7 +222,7 @@ async def about(self, ctx: commands.Context) -> None:
author = self.bot.get_user(self.bot.owner_id)
python_version = f"Python {'.'.join(map(str, sys.version_info[:3]))}"
games_included = self._get_games_included(variables.GAMES_INCLUDED)
- dev_info_msg = messages.DEV_INFO_MSG.format(variables.BOT_WEBPAGE_URL, variables.DISCORDPY_URL)
+ dev_info_msg = messages.dev_info_msg(variables.BOT_WEBPAGE_URL, variables.DISCORDPY_URL)
bot_stats = bot_utils.get_bot_stats(self.bot)
diff --git a/src/bot/constants/messages.py b/src/bot/constants/messages.py
index 96204828..835f8a92 100644
--- a/src/bot/constants/messages.py
+++ b/src/bot/constants/messages.py
@@ -1,9 +1,7 @@
#################################
# BOT
#################################
-BOT_ONLINE = "====> {0} IS ONLINE AND CONNECTED TO DISCORD <===="
BOT_TOKEN_NOT_FOUND = "BOT_TOKEN variable not found"
-BOT_STARTING = "Starting Bot in {0} secs"
BOT_TERMINATED = "Bot has been terminated."
BOT_STOPPED_CTRTC = "Bot stopped with Ctrl+C"
BOT_FATAL_ERROR_MAIN = "Fatal error in main()"
@@ -14,11 +12,33 @@
BOT_LOAD_SETTINGS_FAILED = "Failed to load settings"
BOT_LOAD_COGS_FAILED = "Failed to load cogs"
BOT_LOADED_ALL_COGS_SUCCESS = "Successfully loaded all cogs"
+
+
+def bot_online(bot_user) -> str:
+ return f"====> {bot_user} IS ONLINE AND CONNECTED TO DISCORD <===="
+
+
+def bot_starting(seconds: int) -> str:
+ return f"Starting Bot in {seconds} secs"
+
+
+def bot_disconnected(bot_user) -> str:
+ return f"Bot {bot_user} disconnected from Discord"
+
+
#################################
# EVENT ADMIN
#################################
-BOT_ANNOUNCE_PLAYING = "I'm now playing: {0}"
-BG_TASK_WARNING = "Background task running to update bot activity is ON\nActivity will change after {0} secs."
+
+
+def bot_announce_playing(game: str) -> str:
+ return f"I'm now playing: {game}"
+
+
+def bg_task_warning(seconds: int) -> str:
+ return f"Background task running to update bot activity is ON\nActivity will change after {seconds} secs."
+
+
#################################
# EVENT CONFIG
#################################
@@ -28,13 +48,18 @@
CONFIG_MEMBER = "Display a message when someone changes profile"
CONFIG_BLOCK_INVIS_MEMBERS = "Block messages from invisible members"
CONFIG_BOT_WORD_REACTIONS = "Bot word reactions"
-CONFIG_PFILTER = "Profanity Filter `{0}`\nChannel: `{1}`"
CONFIG_PFILTER_CHANNELS = "Channels with profanity filter activated"
+
+
+def config_pfilter(status: str, channel: str) -> str:
+ return f"Profanity Filter `{status}`\nChannel: `{channel}`"
+
+
CONFIG_CHANNEL_ID_INSTEAD_NAME = "Chnanel id should be used instead of its name!!!"
CONFIG_NOT_ACTIVATED_ERROR = "Profanity Filter could not be activated.\n"
MISING_REUIRED_ARGUMENT = "Missing required argument!!!"
CHANNEL_ID_NOT_FOUND = "Channel id not found"
-BOT_MISSING_MANAGE_MESSAGES_PERMISSION = "Bot does not have permission to \"Manage Messages\""
+BOT_MISSING_MANAGE_MESSAGES_PERMISSION = 'Bot does not have permission to "Manage Messages"'
NO_CHANNELS_LISTED = "No channels listed"
#################################
# EVENT CUSTOM COMMAND
@@ -73,18 +98,23 @@
"Direct messages are disable in your configuration.\n"
"If you want to receive messages from Bots, "
"you need to enable this option under Privacy & Safety:"
- "\"Allow direct messages from server members.\""
+ '"Allow direct messages from server members."'
)
#################################
# EVENT ON GUILD JOIN
#################################
-GUILD_JOIN_BOT_MESSAGE = (
- "Thanks for using *{0}*\n"
- "To learn more about this bot: `{1}about`\n"
- "Games included so far: `{2}`\n\n"
- "If you are an Admin and wish to list configurations: `{3}config list`\n"
- "To get a list of commands: `{4}help`"
-)
+
+
+def guild_join_bot_message(bot_name: str, prefix: str, games_included: str) -> str:
+ return (
+ f"Thanks for using *{bot_name}*\n"
+ f"To learn more about this bot: `{prefix}about`\n"
+ f"Games included so far: `{games_included}`\n\n"
+ f"If you are an Admin and wish to list configurations: `{prefix}config list`\n"
+ f"To get a list of commands: `{prefix}help`"
+ )
+
+
#################################
# EVENT ON GUILD UPDATE
#################################
@@ -121,17 +151,22 @@
BOT_REACT_STUPID = "I'm not stupid, fu ufk!!!"
BOT_REACT_RETARD = "I'm not retard, fu ufk!!!"
MESSAGE_CENSURED = "Your message was censored.\nPlease don't say offensive words in this channel."
-BLOCKED_INVIS_MESSAGE = (
- "You are Invisible (offline)\n"
- "Server \"{0}\" does not allow messages from invisible members.\n"
- "Please change your status if you want to send messages to this server."
-)
PRIVATE_BOT_MESSAGE = (
"This is a Private Bot.\n"
"You are not allowed to execute any commands.\n"
"Only a few users are allowed to use it.\n"
"Please don't insist. Thank You!!!"
)
+
+
+def blocked_invis_message(guild_name: str) -> str:
+ return (
+ "You are Invisible (offline)\n"
+ f'Server "{guild_name}" does not allow messages from invisible members.\n'
+ "Please change your status if you want to send messages to this server."
+ )
+
+
#################################
# EVENT ON USER UPDATE
#################################
@@ -148,7 +183,7 @@
"Direct messages are disable in your configuration.\n"
"If you want to receive messages from Bots, "
"you need to enable this option under Privacy & Safety:\n"
- "\"Allow direct messages from server members.\"\n"
+ '"Allow direct messages from server members."\n'
)
MESSAGE_REMOVED_FOR_PRIVACY = "Your message was removed for privacy."
DELETE_MESSAGE_NO_PERMISSION = "Bot does not have permission to delete messages."
@@ -162,9 +197,14 @@
MEMBER_HIGHEST_ROLL = "Your highest roll is now:"
MEMBER_HAS_HIGHEST_ROLL = "has the server highest roll with"
DICE_SIZE_HIGHER_ONE = "Dice size needs to be higher than 1"
-NO_DICE_SIZE_ROLLS = "There are no dice rolls of the size {0} in this server."
RESET_ALL_ROLLS = "Reset all rolls from this server"
DELETED_ALL_ROLLS = "Rolls from all members in this server have been deleted."
+
+
+def no_dice_size_rolls(dice_size) -> str:
+ return f"There are no dice rolls of the size {dice_size} in this server."
+
+
#################################
# MISC
#################################
@@ -178,10 +218,15 @@
JOINED_DISCORD_ON = "Joined Discord on"
JOINED_THIS_SERVER_ON = "Joined this server on"
LIST_COMMAND_CATEGORIES = "For a list of command categories"
-DEV_INFO_MSG = (
- "Developed as an open source project and hosted on [GitHub]({0})\n"
- "A python discord api wrapper: [discord.py]({1})\n"
-)
+
+
+def dev_info_msg(webpage_url: str, discordpy_url: str) -> str:
+ return (
+ f"Developed as an open source project and hosted on [GitHub]({webpage_url})\n"
+ f"A python discord api wrapper: [discord.py]({discordpy_url})\n"
+ )
+
+
#################################
# OWNER
#################################
diff --git a/src/bot/constants/variables.py b/src/bot/constants/variables.py
index 88aed8cd..6b90c27d 100644
--- a/src/bot/constants/variables.py
+++ b/src/bot/constants/variables.py
@@ -30,6 +30,8 @@ def _get_project_version() -> str:
def _discover_cogs() -> list[str]:
"""Discover and return all cog file paths in the correct loading order."""
+ from src.gw2.cogs import discover_gw2_cogs
+
bot_cogs_dir = Path("src") / "bot" / "cogs"
# Bot cogs - admin.py loads first for command group registration
bot_cogs = [str(bot_cogs_dir / "admin" / "admin.py")]
@@ -39,13 +41,7 @@ def _discover_cogs() -> list[str]:
admin_cogs = [str(p) for p in (bot_cogs_dir / "admin").glob("*.py") if p.name != _INIT_PY]
bot_cogs.extend(cog for cog in admin_cogs if cog not in bot_cogs)
- # GW2 cogs - gw2.py loads first for command group registration
- gw2_cogs_dir = Path("src") / "gw2" / "cogs"
- gw2_cogs = [str(gw2_cogs_dir / "gw2.py")]
- remaining_gw2 = [str(p) for p in gw2_cogs_dir.glob("*.py") if p.name != _INIT_PY]
- gw2_cogs.extend(cog for cog in remaining_gw2 if cog not in gw2_cogs)
-
- return bot_cogs + gw2_cogs
+ return bot_cogs + discover_gw2_cogs()
# Base directory (needed by functions above)
diff --git a/src/bot/discord_bot.py b/src/bot/discord_bot.py
index 298641a5..9c0a6887 100644
--- a/src/bot/discord_bot.py
+++ b/src/bot/discord_bot.py
@@ -53,8 +53,16 @@ def _load_settings(self) -> None:
# Load bot settings from environment variables
self.settings["bot"] = {
"BGActivityTimer": bot_settings.bg_activity_timer,
- "AllowedDMCommands": bot_settings.allowed_dm_commands,
- "BotReactionWords": bot_settings.bot_reaction_words,
+ "AllowedDMCommands": (
+ [cmd.strip() for cmd in bot_settings.allowed_dm_commands.split(",")]
+ if bot_settings.allowed_dm_commands
+ else None
+ ),
+ "BotReactionWords": (
+ [word.strip() for word in bot_settings.bot_reaction_words.split(",")]
+ if bot_settings.bot_reaction_words
+ else []
+ ),
"EmbedColor": bot_utils.get_color_settings(bot_settings.embed_color),
"EmbedOwnerColor": bot_utils.get_color_settings(bot_settings.embed_owner_color),
"ExclusiveUsers": bot_settings.exclusive_users,
diff --git a/src/bot/tools/background_tasks.py b/src/bot/tools/background_tasks.py
index ca1bd2f8..941656d7 100644
--- a/src/bot/tools/background_tasks.py
+++ b/src/bot/tools/background_tasks.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
import asyncio
import discord
import random
diff --git a/src/bot/tools/bot_utils.py b/src/bot/tools/bot_utils.py
index 5574b2cd..7566066e 100644
--- a/src/bot/tools/bot_utils.py
+++ b/src/bot/tools/bot_utils.py
@@ -289,5 +289,5 @@ def get_bot_stats(bot: commands.Bot) -> dict[str, str | datetime]:
"servers": f"{len(bot.guilds)} servers",
"users": f"({unique_users} users)({bot_users} bots)[{len(bot.users)} total]",
"channels": f"({text_channels} text)({voice_channels} voice)[{total_channels} total]",
- "start_time": getattr(bot, 'start_time', None) or get_current_date_time(),
+ "start_time": getattr(bot, "start_time", None) or get_current_date_time(),
}
diff --git a/src/bot/tools/custom_help.py b/src/bot/tools/custom_help.py
index c5ad28d9..38e29961 100644
--- a/src/bot/tools/custom_help.py
+++ b/src/bot/tools/custom_help.py
@@ -1,15 +1,111 @@
import discord
+import itertools
from discord.ext import commands
+class HelpPaginatorView(discord.ui.View):
+ """Interactive pagination view for help pages with Previous/Next buttons."""
+
+ def __init__(self, pages: list[str], author_id: int):
+ super().__init__(timeout=300)
+ self.pages = pages
+ self.current_page = 0
+ self.author_id = author_id
+ self.message: discord.Message | None = None
+ self._update_buttons()
+
+ def _format_page(self) -> str:
+ page_header = f"**Page {self.current_page + 1}/{len(self.pages)}**\n"
+ return page_header + self.pages[self.current_page]
+
+ def _update_buttons(self):
+ self.previous_button.disabled = self.current_page == 0
+ self.page_indicator.label = f"{self.current_page + 1}/{len(self.pages)}"
+ self.next_button.disabled = self.current_page == len(self.pages) - 1
+
+ @discord.ui.button(label="\u25c0", style=discord.ButtonStyle.secondary)
+ async def previous_button(self, interaction: discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.author_id:
+ return await interaction.response.send_message(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+ self.current_page -= 1
+ self._update_buttons()
+ await interaction.response.edit_message(content=self._format_page(), view=self)
+
+ @discord.ui.button(label="1/1", style=discord.ButtonStyle.secondary, disabled=True)
+ async def page_indicator(self, interaction: discord.Interaction, button: discord.ui.Button):
+ await interaction.response.defer()
+
+ @discord.ui.button(label="\u25b6", style=discord.ButtonStyle.secondary)
+ async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.author_id:
+ return await interaction.response.send_message(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+ self.current_page += 1
+ self._update_buttons()
+ await interaction.response.edit_message(content=self._format_page(), view=self)
+
+ async def on_timeout(self):
+ for item in self.children:
+ item.disabled = True
+ try:
+ if self.message:
+ await self.message.edit(view=self)
+ except discord.NotFound, discord.HTTPException:
+ pass
+
+
class CustomHelpCommand(commands.DefaultHelpCommand):
"""Custom help command that sends DM notifications to the channel."""
def __init__(self, **options):
# Increase page size for bigger window
- options.setdefault('paginator', commands.Paginator(prefix='```', suffix='```', max_size=2000))
+ options.setdefault("paginator", commands.Paginator(prefix="```", suffix="```", max_size=2000))
super().__init__(**options)
+ async def send_bot_help(self, mapping):
+ """Override to add per-group subcommand pages after the overview page."""
+ ctx = self.context
+ bot = ctx.bot
+
+ if bot.description:
+ self.paginator.add_line(bot.description, empty=True)
+
+ no_category = f"\u200b{self.no_category}:"
+
+ def get_category(command, *, _no_category=no_category):
+ cog = command.cog
+ return f"{cog.qualified_name}:" if cog is not None else _no_category
+
+ filtered = await self.filter_commands(bot.commands, sort=True, key=get_category)
+ max_size = self.get_max_size(filtered)
+ to_iterate = itertools.groupby(filtered, key=get_category)
+
+ groups = []
+ for category, cmds in to_iterate:
+ cmds = sorted(cmds, key=lambda c: c.name) if self.sort_commands else list(cmds)
+ self.add_indented_commands(cmds, heading=category, max_size=max_size)
+ for cmd in cmds:
+ if isinstance(cmd, commands.Group):
+ groups.append(cmd)
+
+ note = self.get_ending_note()
+ if note:
+ self.paginator.add_line()
+ self.paginator.add_line(note)
+
+ # Add a separate page for each group with its subcommands
+ for group in groups:
+ subcommands = await self.filter_commands(group.commands, sort=self.sort_commands)
+ if subcommands:
+ self.paginator.close_page()
+ heading = f"{group.qualified_name} subcommands:"
+ self.add_indented_commands(subcommands, heading=heading, max_size=self.get_max_size(subcommands))
+
+ await self.send_pages()
+
async def send_pages(self):
"""Override send_pages to send as paginated regular messages."""
destination = self.get_destination()
@@ -38,23 +134,21 @@ async def send_pages(self):
await self._send_pages_to_destination(destination)
async def _send_pages_to_dm(self):
- """Send paginated messages to user's DM."""
- for page_num, page in enumerate(self.paginator.pages, 1):
- if len(self.paginator.pages) > 1:
- # Add page numbers for multiple pages
- page_header = f"**Page {page_num}/{len(self.paginator.pages)}**\n"
- content = page_header + page
- else:
- content = page
- await self.context.author.send(content)
+ """Send help pages to user's DM with button pagination."""
+ pages = self.paginator.pages
+ if len(pages) == 1:
+ await self.context.author.send(pages[0])
+ else:
+ view = HelpPaginatorView(pages, self.context.author.id)
+ msg = await self.context.author.send(content=view._format_page(), view=view)
+ view.message = msg
async def _send_pages_to_destination(self, destination):
- """Send paginated messages to specified destination."""
- for page_num, page in enumerate(self.paginator.pages, 1):
- if len(self.paginator.pages) > 1:
- # Add page numbers for multiple pages
- page_header = f"**Page {page_num}/{len(self.paginator.pages)}**\n"
- content = page_header + page
- else:
- content = page
- await destination.send(content)
+ """Send help pages to specified destination with button pagination."""
+ pages = self.paginator.pages
+ if len(pages) == 1:
+ await destination.send(pages[0])
+ else:
+ view = HelpPaginatorView(pages, self.context.author.id)
+ msg = await destination.send(content=view._format_page(), view=view)
+ view.message = msg
diff --git a/src/bot/tools/pepe.py b/src/bot/tools/pepe.py
index 8ccbc642..b5030b2c 100644
--- a/src/bot/tools/pepe.py
+++ b/src/bot/tools/pepe.py
@@ -1,102 +1,102 @@
pepedatabase = [
- 'https://i.imgur.com/klkeMme.png',
- 'https://i.imgur.com/yMz6s30.png',
- 'https://i.imgur.com/c7OUZyV.png',
- 'https://i.imgur.com/85tffg2.png',
- 'https://i.imgur.com/DLDe5gG.png',
- 'https://i.imgur.com/EbOxDzB.png',
- 'https://i.imgur.com/wxEW6Qn.png',
- 'https://i.imgur.com/8KMtiGC.png',
- 'https://i.imgur.com/iRUd8rI.png',
- 'https://i.imgur.com/1pR1FiB.png',
- 'https://i.imgur.com/zXno1OF.png',
- 'https://i.imgur.com/RJPUjAx.png',
- 'https://i.imgur.com/oBbgpk7.png',
- 'https://i.imgur.com/XUA5p5k.png',
- 'https://i.imgur.com/P5GYYWJ.png',
- 'https://i.imgur.com/RW3hkvw.png',
- 'https://i.imgur.com/vMc0wjk.png',
- 'https://i.imgur.com/a02IV0p.png',
- 'https://i.imgur.com/C9HcBU3.png',
- 'https://i.imgur.com/LIYDk27.png',
- 'https://i.imgur.com/Yf8Pcsz.png',
- 'https://i.imgur.com/VIYwB7E.png',
- 'https://i.imgur.com/t1rqzy9.png',
- 'https://i.imgur.com/QriJh6a.png',
- 'https://i.imgur.com/mpj7TAW.png',
- 'https://i.imgur.com/3unhsqY.png',
- 'https://i.imgur.com/L0sj23T.png',
- 'https://i.imgur.com/aDW7N0x.png',
- 'https://i.imgur.com/WwFtyWv.png',
- 'https://i.imgur.com/8dOwi9q.png',
- 'https://i.imgur.com/EL7hmKz.png',
- 'https://i.imgur.com/VoqKhML.png',
- 'https://i.imgur.com/Ry7wYAC.png',
- 'https://i.imgur.com/7rSPEnc.png',
- 'https://i.imgur.com/tSrV0Kk.png',
- 'https://i.imgur.com/YMo3FCC.png',
- 'https://i.imgur.com/QqwYHFm.png',
- 'https://i.imgur.com/ICRqk4t.png',
- 'https://i.imgur.com/1lnY9Ec.png',
- 'https://i.imgur.com/JSdMtKo.png',
- 'https://i.imgur.com/pF7DpfZ.png',
- 'https://i.imgur.com/gyU3RvV.png',
- 'https://i.imgur.com/I3LeJ3H.png',
- 'https://i.imgur.com/7DM4y2x.png',
- 'https://i.imgur.com/nSMLDtw.png',
- 'https://i.imgur.com/jLpcAiV.png',
- 'https://i.imgur.com/6dDQVVj.png',
- 'https://i.imgur.com/6lbFsHn.png',
- 'https://i.imgur.com/gSk1JHS.png',
- 'https://i.imgur.com/8Vl2j66.png',
- 'https://i.imgur.com/ZgNIkzg.png',
- 'https://i.imgur.com/9kSRLBK.png',
- 'https://i.imgur.com/186etyr.png',
- 'https://i.imgur.com/z6X0ly7.png',
- 'https://i.imgur.com/4xzpie7.png',
- 'https://i.imgur.com/EGw6Xz7.png',
- 'https://i.imgur.com/Dk7M23N.png',
- 'https://i.imgur.com/HvK6DGX.png',
- 'https://i.imgur.com/F5Si2qo.png',
- 'https://i.imgur.com/8ytbWYt.png',
- 'https://i.imgur.com/vsz5yKk.png',
- 'https://i.imgur.com/HDK0Xw2.png',
- 'https://i.imgur.com/tlvEJkM.png',
- 'https://i.imgur.com/oqPTXoj.png',
- 'https://i.imgur.com/aXQaVW4.png',
- 'https://i.imgur.com/fepY07Z.png',
- 'https://i.imgur.com/WLG760e.png',
- 'https://i.imgur.com/1fnolXU.png',
- 'https://i.imgur.com/822fa0N.png',
- 'https://i.imgur.com/Z8BHwCY.png',
- 'https://i.imgur.com/4efGIER.png',
- 'https://i.imgur.com/gvLWCIX.png',
- 'https://i.imgur.com/IpIcL7q.png',
- 'https://i.imgur.com/VSlc5Gv.png',
- 'https://i.imgur.com/hpmARP7.png',
- 'https://i.imgur.com/XBLQdEG.png',
- 'https://i.imgur.com/nSs2AhR.png',
- 'https://i.imgur.com/Pce6wWI.png',
- 'https://i.imgur.com/bb1J7bu.png',
- 'https://i.imgur.com/75hinzs.png',
- 'https://i.imgur.com/UYrpJsm.png',
- 'https://i.imgur.com/d5Bo0o3.png',
- 'https://i.imgur.com/Tbd1nKx.png',
- 'https://i.imgur.com/pBki9NQ.png',
- 'https://i.imgur.com/3PfY6vY.png',
- 'https://i.imgur.com/mibPYHe.png',
- 'https://i.imgur.com/GEPqZnV.png',
- 'https://i.imgur.com/Acm01Xv.png',
- 'https://i.imgur.com/K0jnnSg.png',
- 'https://i.imgur.com/HAHvjzM.png',
- 'https://i.imgur.com/T8XerLn.png',
- 'https://i.imgur.com/sn74Yt5.png',
- 'https://i.imgur.com/LqLvjEC.png',
- 'https://i.imgur.com/vORym8X.png',
- 'https://i.imgur.com/HJLjrdk.png',
- 'https://i.imgur.com/StllvTP.png',
- 'https://i.imgur.com/P8MDXEw.png',
- 'https://i.imgur.com/cW9NrvO.png',
- 'https://i.imgur.com/XbZa2lr.png',
- 'https://i.imgur.com/UaBpIId.png',
+ "https://i.imgur.com/klkeMme.png",
+ "https://i.imgur.com/yMz6s30.png",
+ "https://i.imgur.com/c7OUZyV.png",
+ "https://i.imgur.com/85tffg2.png",
+ "https://i.imgur.com/DLDe5gG.png",
+ "https://i.imgur.com/EbOxDzB.png",
+ "https://i.imgur.com/wxEW6Qn.png",
+ "https://i.imgur.com/8KMtiGC.png",
+ "https://i.imgur.com/iRUd8rI.png",
+ "https://i.imgur.com/1pR1FiB.png",
+ "https://i.imgur.com/zXno1OF.png",
+ "https://i.imgur.com/RJPUjAx.png",
+ "https://i.imgur.com/oBbgpk7.png",
+ "https://i.imgur.com/XUA5p5k.png",
+ "https://i.imgur.com/P5GYYWJ.png",
+ "https://i.imgur.com/RW3hkvw.png",
+ "https://i.imgur.com/vMc0wjk.png",
+ "https://i.imgur.com/a02IV0p.png",
+ "https://i.imgur.com/C9HcBU3.png",
+ "https://i.imgur.com/LIYDk27.png",
+ "https://i.imgur.com/Yf8Pcsz.png",
+ "https://i.imgur.com/VIYwB7E.png",
+ "https://i.imgur.com/t1rqzy9.png",
+ "https://i.imgur.com/QriJh6a.png",
+ "https://i.imgur.com/mpj7TAW.png",
+ "https://i.imgur.com/3unhsqY.png",
+ "https://i.imgur.com/L0sj23T.png",
+ "https://i.imgur.com/aDW7N0x.png",
+ "https://i.imgur.com/WwFtyWv.png",
+ "https://i.imgur.com/8dOwi9q.png",
+ "https://i.imgur.com/EL7hmKz.png",
+ "https://i.imgur.com/VoqKhML.png",
+ "https://i.imgur.com/Ry7wYAC.png",
+ "https://i.imgur.com/7rSPEnc.png",
+ "https://i.imgur.com/tSrV0Kk.png",
+ "https://i.imgur.com/YMo3FCC.png",
+ "https://i.imgur.com/QqwYHFm.png",
+ "https://i.imgur.com/ICRqk4t.png",
+ "https://i.imgur.com/1lnY9Ec.png",
+ "https://i.imgur.com/JSdMtKo.png",
+ "https://i.imgur.com/pF7DpfZ.png",
+ "https://i.imgur.com/gyU3RvV.png",
+ "https://i.imgur.com/I3LeJ3H.png",
+ "https://i.imgur.com/7DM4y2x.png",
+ "https://i.imgur.com/nSMLDtw.png",
+ "https://i.imgur.com/jLpcAiV.png",
+ "https://i.imgur.com/6dDQVVj.png",
+ "https://i.imgur.com/6lbFsHn.png",
+ "https://i.imgur.com/gSk1JHS.png",
+ "https://i.imgur.com/8Vl2j66.png",
+ "https://i.imgur.com/ZgNIkzg.png",
+ "https://i.imgur.com/9kSRLBK.png",
+ "https://i.imgur.com/186etyr.png",
+ "https://i.imgur.com/z6X0ly7.png",
+ "https://i.imgur.com/4xzpie7.png",
+ "https://i.imgur.com/EGw6Xz7.png",
+ "https://i.imgur.com/Dk7M23N.png",
+ "https://i.imgur.com/HvK6DGX.png",
+ "https://i.imgur.com/F5Si2qo.png",
+ "https://i.imgur.com/8ytbWYt.png",
+ "https://i.imgur.com/vsz5yKk.png",
+ "https://i.imgur.com/HDK0Xw2.png",
+ "https://i.imgur.com/tlvEJkM.png",
+ "https://i.imgur.com/oqPTXoj.png",
+ "https://i.imgur.com/aXQaVW4.png",
+ "https://i.imgur.com/fepY07Z.png",
+ "https://i.imgur.com/WLG760e.png",
+ "https://i.imgur.com/1fnolXU.png",
+ "https://i.imgur.com/822fa0N.png",
+ "https://i.imgur.com/Z8BHwCY.png",
+ "https://i.imgur.com/4efGIER.png",
+ "https://i.imgur.com/gvLWCIX.png",
+ "https://i.imgur.com/IpIcL7q.png",
+ "https://i.imgur.com/VSlc5Gv.png",
+ "https://i.imgur.com/hpmARP7.png",
+ "https://i.imgur.com/XBLQdEG.png",
+ "https://i.imgur.com/nSs2AhR.png",
+ "https://i.imgur.com/Pce6wWI.png",
+ "https://i.imgur.com/bb1J7bu.png",
+ "https://i.imgur.com/75hinzs.png",
+ "https://i.imgur.com/UYrpJsm.png",
+ "https://i.imgur.com/d5Bo0o3.png",
+ "https://i.imgur.com/Tbd1nKx.png",
+ "https://i.imgur.com/pBki9NQ.png",
+ "https://i.imgur.com/3PfY6vY.png",
+ "https://i.imgur.com/mibPYHe.png",
+ "https://i.imgur.com/GEPqZnV.png",
+ "https://i.imgur.com/Acm01Xv.png",
+ "https://i.imgur.com/K0jnnSg.png",
+ "https://i.imgur.com/HAHvjzM.png",
+ "https://i.imgur.com/T8XerLn.png",
+ "https://i.imgur.com/sn74Yt5.png",
+ "https://i.imgur.com/LqLvjEC.png",
+ "https://i.imgur.com/vORym8X.png",
+ "https://i.imgur.com/HJLjrdk.png",
+ "https://i.imgur.com/StllvTP.png",
+ "https://i.imgur.com/P8MDXEw.png",
+ "https://i.imgur.com/cW9NrvO.png",
+ "https://i.imgur.com/XbZa2lr.png",
+ "https://i.imgur.com/UaBpIId.png",
]
diff --git a/src/database/dal/bot/servers_dal.py b/src/database/dal/bot/servers_dal.py
index 361f8396..20665ad6 100644
--- a/src/database/dal/bot/servers_dal.py
+++ b/src/database/dal/bot/servers_dal.py
@@ -18,7 +18,7 @@ async def insert_server(self, server_id: int, name: str):
name=name,
)
# On conflict, update the name in case it changed
- stmt = stmt.on_conflict_do_update(index_elements=['id'], set_={'name': name})
+ stmt = stmt.on_conflict_do_update(index_elements=["id"], set_={"name": name})
await self.db_utils.execute(stmt)
async def update_server(self, before: discord.Guild, after: discord.Guild):
diff --git a/src/database/dal/gw2/gw2_session_chars_dal.py b/src/database/dal/gw2/gw2_session_chars_dal.py
index 71bc877a..c1e7e02b 100644
--- a/src/database/dal/gw2/gw2_session_chars_dal.py
+++ b/src/database/dal/gw2/gw2_session_chars_dal.py
@@ -23,15 +23,17 @@ async def insert_session_char(self, gw2_api, api_characters, insert_args: dict):
name=name,
profession=profession,
deaths=deaths,
+ start=insert_args["start"],
+ end=insert_args["end"],
)
await self.db_utils.insert(stmt)
async def get_all_start_characters(self, user_id: int):
- stmt = select(*self.columns).where(Gw2SessionChars.user_id == user_id, Gw2SessionChars.start is True)
+ stmt = select(*self.columns).where(Gw2SessionChars.user_id == user_id, Gw2SessionChars.start.is_(True))
results = await self.db_utils.fetchall(stmt, True)
return results
async def get_all_end_characters(self, user_id: int):
- stmt = select(*self.columns).where(Gw2SessionChars.user_id == user_id, Gw2SessionChars.end is True)
+ stmt = select(*self.columns).where(Gw2SessionChars.user_id == user_id, Gw2SessionChars.end.is_(True))
results = await self.db_utils.fetchall(stmt, True)
return results
diff --git a/src/database/migrations/versions/0001_create_functions.py b/src/database/migrations/versions/0001_create_functions.py
index 2b126785..4f2b7dcb 100644
--- a/src/database/migrations/versions/0001_create_functions.py
+++ b/src/database/migrations/versions/0001_create_functions.py
@@ -10,7 +10,7 @@
from collections.abc import Sequence
from ddcDatabases.postgresql import get_postgresql_settings
-revision: str = '0001'
+revision: str = "0001"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
diff --git a/src/database/migrations/versions/0002_bot_configs.py b/src/database/migrations/versions/0002_bot_configs.py
index 763efb4a..157ce2e4 100644
--- a/src/database/migrations/versions/0002_bot_configs.py
+++ b/src/database/migrations/versions/0002_bot_configs.py
@@ -13,8 +13,8 @@
from src.database.models.bot_models import BotConfigs
# revision identifiers, used by Alembic.
-revision: str = '0002'
-down_revision: str | None = '0001'
+revision: str = "0002"
+down_revision: str | None = "0001"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -22,16 +22,16 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'bot_configs',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('prefix', sa.CHAR(length=1), server_default=variables.PREFIX, nullable=False),
- sa.Column('author_id', sa.BigInteger(), server_default=variables.AUTHOR_ID, nullable=False),
- sa.Column('url', sa.String(), server_default=variables.BOT_WEBPAGE_URL, nullable=False),
- sa.Column('description', sa.String(), server_default=variables.DESCRIPTION, nullable=False),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "bot_configs",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("prefix", sa.CHAR(length=1), server_default=variables.PREFIX, nullable=False),
+ sa.Column("author_id", sa.BigInteger(), server_default=variables.AUTHOR_ID, nullable=False),
+ sa.Column("url", sa.String(), server_default=variables.BOT_WEBPAGE_URL, nullable=False),
+ sa.Column("description", sa.String(), server_default=variables.DESCRIPTION, nullable=False),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
op.execute(
sa.insert(BotConfigs).values(
@@ -53,6 +53,6 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_bot_configs_tr ON bot_configs')
- op.drop_table('bot_configs')
+ op.execute("DROP TRIGGER IF EXISTS before_update_bot_configs_tr ON bot_configs")
+ op.drop_table("bot_configs")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0003_servers.py b/src/database/migrations/versions/0003_servers.py
index 0f848332..d98855ec 100644
--- a/src/database/migrations/versions/0003_servers.py
+++ b/src/database/migrations/versions/0003_servers.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0003'
-down_revision: str | None = '0002'
+revision: str = "0003"
+down_revision: str | None = "0002"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,22 +20,22 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'servers',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('name', sa.String(), nullable=True),
- sa.Column('msg_on_join', sa.Boolean(), server_default='1', nullable=False),
- sa.Column('msg_on_leave', sa.Boolean(), server_default='1', nullable=False),
- sa.Column('msg_on_server_update', sa.Boolean(), server_default='1', nullable=False),
- sa.Column('msg_on_member_update', sa.Boolean(), server_default='1', nullable=False),
- sa.Column('block_invis_members', sa.Boolean(), server_default='0', nullable=False),
- sa.Column('bot_word_reactions', sa.Boolean(), server_default='1', nullable=False),
- sa.Column('updated_by', sa.BigInteger(), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "servers",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("name", sa.String(), nullable=True),
+ sa.Column("msg_on_join", sa.Boolean(), server_default="1", nullable=False),
+ sa.Column("msg_on_leave", sa.Boolean(), server_default="1", nullable=False),
+ sa.Column("msg_on_server_update", sa.Boolean(), server_default="1", nullable=False),
+ sa.Column("msg_on_member_update", sa.Boolean(), server_default="1", nullable=False),
+ sa.Column("block_invis_members", sa.Boolean(), server_default="0", nullable=False),
+ sa.Column("bot_word_reactions", sa.Boolean(), server_default="1", nullable=False),
+ sa.Column("updated_by", sa.BigInteger(), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
- op.create_index(op.f('ix_servers_id'), 'servers', ['id'], unique=True)
+ op.create_index(op.f("ix_servers_id"), "servers", ["id"], unique=True)
op.execute("""
CREATE TRIGGER before_update_servers_tr
BEFORE UPDATE ON servers
@@ -47,7 +47,7 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_servers_tr ON servers')
- op.drop_index(op.f('ix_servers_id'), table_name='servers')
- op.drop_table('servers')
+ op.execute("DROP TRIGGER IF EXISTS before_update_servers_tr ON servers")
+ op.drop_index(op.f("ix_servers_id"), table_name="servers")
+ op.drop_table("servers")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0004_custom_commands.py b/src/database/migrations/versions/0004_custom_commands.py
index dcc4076e..11d59658 100644
--- a/src/database/migrations/versions/0004_custom_commands.py
+++ b/src/database/migrations/versions/0004_custom_commands.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0004'
-down_revision: str | None = '0003'
+revision: str = "0004"
+down_revision: str | None = "0003"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,20 +20,20 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'custom_commands',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('server_id', sa.BigInteger(), nullable=False),
- sa.Column('name', sa.String(), nullable=False),
- sa.Column('description', sa.String(), nullable=False),
- sa.Column('created_by', sa.BigInteger(), nullable=True),
- sa.Column('updated_by', sa.BigInteger(), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.ForeignKeyConstraint(['server_id'], ['servers.id'], ondelete='CASCADE'),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "custom_commands",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("server_id", sa.BigInteger(), nullable=False),
+ sa.Column("name", sa.String(), nullable=False),
+ sa.Column("description", sa.String(), nullable=False),
+ sa.Column("created_by", sa.BigInteger(), nullable=True),
+ sa.Column("updated_by", sa.BigInteger(), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
- op.create_index(op.f('ix_custom_commands_server_id'), 'custom_commands', ['server_id'], unique=False)
+ op.create_index(op.f("ix_custom_commands_server_id"), "custom_commands", ["server_id"], unique=False)
op.execute("""
CREATE TRIGGER before_update_custom_commands_tr
BEFORE UPDATE ON custom_commands
@@ -45,7 +45,7 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_custom_commands_tr ON custom_commands')
- op.drop_index(op.f('ix_custom_commands_server_id'), table_name='custom_commands')
- op.drop_table('custom_commands')
+ op.execute("DROP TRIGGER IF EXISTS before_update_custom_commands_tr ON custom_commands")
+ op.drop_index(op.f("ix_custom_commands_server_id"), table_name="custom_commands")
+ op.drop_table("custom_commands")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0005_profanity_filters.py b/src/database/migrations/versions/0005_profanity_filters.py
index 09514956..cf6ad0cd 100644
--- a/src/database/migrations/versions/0005_profanity_filters.py
+++ b/src/database/migrations/versions/0005_profanity_filters.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0005'
-down_revision: str | None = '0004'
+revision: str = "0005"
+down_revision: str | None = "0004"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,19 +20,19 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'profanity_filters',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('server_id', sa.BigInteger(), nullable=False),
- sa.Column('channel_id', sa.BigInteger(), nullable=False),
- sa.Column('channel_name', sa.String(), nullable=False),
- sa.Column('created_by', sa.BigInteger(), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.ForeignKeyConstraint(['server_id'], ['servers.id'], ondelete='CASCADE'),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "profanity_filters",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("server_id", sa.BigInteger(), nullable=False),
+ sa.Column("channel_id", sa.BigInteger(), nullable=False),
+ sa.Column("channel_name", sa.String(), nullable=False),
+ sa.Column("created_by", sa.BigInteger(), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
- op.create_index(op.f('ix_profanity_filters_server_id'), 'profanity_filters', ['server_id'], unique=False)
+ op.create_index(op.f("ix_profanity_filters_server_id"), "profanity_filters", ["server_id"], unique=False)
op.execute("""
CREATE TRIGGER before_update_profanity_filters_tr
BEFORE UPDATE ON profanity_filters
@@ -44,7 +44,7 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_profanity_filters_tr ON profanity_filters')
- op.drop_index(op.f('ix_profanity_filters_server_id'), table_name='profanity_filters')
- op.drop_table('profanity_filters')
+ op.execute("DROP TRIGGER IF EXISTS before_update_profanity_filters_tr ON profanity_filters")
+ op.drop_index(op.f("ix_profanity_filters_server_id"), table_name="profanity_filters")
+ op.drop_table("profanity_filters")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0006_dice_rolls.py b/src/database/migrations/versions/0006_dice_rolls.py
index b9e2f30f..96d69dc1 100644
--- a/src/database/migrations/versions/0006_dice_rolls.py
+++ b/src/database/migrations/versions/0006_dice_rolls.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0006'
-down_revision: str | None = '0005'
+revision: str = "0006"
+down_revision: str | None = "0005"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,20 +20,20 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'dice_rolls',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('server_id', sa.BigInteger(), nullable=False),
- sa.Column('user_id', sa.BigInteger(), nullable=False),
- sa.Column('roll', sa.Integer(), nullable=False),
- sa.Column('dice_size', sa.Integer(), nullable=False),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.ForeignKeyConstraint(['server_id'], ['servers.id'], ondelete='CASCADE'),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "dice_rolls",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("server_id", sa.BigInteger(), nullable=False),
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("roll", sa.Integer(), nullable=False),
+ sa.Column("dice_size", sa.Integer(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
- op.create_index(op.f('ix_dice_rolls_server_id'), 'dice_rolls', ['server_id'], unique=False)
- op.create_index(op.f('ix_dice_rolls_user_id'), 'dice_rolls', ['user_id'], unique=False)
+ op.create_index(op.f("ix_dice_rolls_server_id"), "dice_rolls", ["server_id"], unique=False)
+ op.create_index(op.f("ix_dice_rolls_user_id"), "dice_rolls", ["user_id"], unique=False)
op.execute("""
CREATE TRIGGER before_update_dice_rolls_tr
BEFORE UPDATE ON dice_rolls
@@ -45,8 +45,8 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_dice_rolls_tr ON dice_rolls')
- op.drop_index(op.f('ix_dice_rolls_user_id'), table_name='dice_rolls')
- op.drop_index(op.f('ix_dice_rolls_server_id'), table_name='dice_rolls')
- op.drop_table('dice_rolls')
+ op.execute("DROP TRIGGER IF EXISTS before_update_dice_rolls_tr ON dice_rolls")
+ op.drop_index(op.f("ix_dice_rolls_user_id"), table_name="dice_rolls")
+ op.drop_index(op.f("ix_dice_rolls_server_id"), table_name="dice_rolls")
+ op.drop_table("dice_rolls")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0007_gw2_keys.py b/src/database/migrations/versions/0007_gw2_keys.py
index 4b7b041e..054d20bb 100644
--- a/src/database/migrations/versions/0007_gw2_keys.py
+++ b/src/database/migrations/versions/0007_gw2_keys.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0007'
-down_revision: str | None = '0006'
+revision: str = "0007"
+down_revision: str | None = "0006"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,19 +20,19 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'gw2_keys',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('user_id', sa.BigInteger(), nullable=False),
- sa.Column('name', sa.String(), nullable=True),
- sa.Column('gw2_acc_name', sa.String(), nullable=False),
- sa.Column('server', sa.String(), nullable=False),
- sa.Column('permissions', sa.String(), nullable=False),
- sa.Column('key', sa.String(), nullable=False),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
- sa.UniqueConstraint('user_id'),
+ "gw2_keys",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("name", sa.String(), nullable=True),
+ sa.Column("gw2_acc_name", sa.String(), nullable=False),
+ sa.Column("server", sa.String(), nullable=False),
+ sa.Column("permissions", sa.String(), nullable=False),
+ sa.Column("key", sa.String(), nullable=False),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
+ sa.UniqueConstraint("user_id"),
)
op.execute("""
CREATE TRIGGER before_update_gw2_keys_tr
@@ -45,6 +45,6 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_gw2_keys_tr ON gw2_keys')
- op.drop_table('gw2_keys')
+ op.execute("DROP TRIGGER IF EXISTS before_update_gw2_keys_tr ON gw2_keys")
+ op.drop_table("gw2_keys")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0008_gw2_configs.py b/src/database/migrations/versions/0008_gw2_configs.py
index 8e96cd16..770e0387 100644
--- a/src/database/migrations/versions/0008_gw2_configs.py
+++ b/src/database/migrations/versions/0008_gw2_configs.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0008'
-down_revision: str | None = '0007'
+revision: str = "0008"
+down_revision: str | None = "0007"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,17 +20,17 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'gw2_configs',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('server_id', sa.BigInteger(), nullable=False),
- sa.Column('session', sa.Boolean(), server_default='0', nullable=False),
- sa.Column('updated_by', sa.BigInteger(), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.ForeignKeyConstraint(['server_id'], ['servers.id'], ondelete='CASCADE'),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
- sa.UniqueConstraint('server_id'),
+ "gw2_configs",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("server_id", sa.BigInteger(), nullable=False),
+ sa.Column("session", sa.Boolean(), server_default="0", nullable=False),
+ sa.Column("updated_by", sa.BigInteger(), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.ForeignKeyConstraint(["server_id"], ["servers.id"], ondelete="CASCADE"),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
+ sa.UniqueConstraint("server_id"),
)
op.execute("""
CREATE TRIGGER before_update_gw2_configs_tr
@@ -43,6 +43,6 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_gw2_configs_tr ON gw2_configs')
- op.drop_table('gw2_configs')
+ op.execute("DROP TRIGGER IF EXISTS before_update_gw2_configs_tr ON gw2_configs")
+ op.drop_table("gw2_configs")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0009_gw2_sessions.py b/src/database/migrations/versions/0009_gw2_sessions.py
index 7d63ff5f..0536d941 100644
--- a/src/database/migrations/versions/0009_gw2_sessions.py
+++ b/src/database/migrations/versions/0009_gw2_sessions.py
@@ -12,8 +12,8 @@
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
-revision: str = '0009'
-down_revision: str | None = '0008'
+revision: str = "0009"
+down_revision: str | None = "0008"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -21,16 +21,16 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'gw2_sessions',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('user_id', sa.BigInteger(), nullable=False),
- sa.Column('acc_name', sa.String(), nullable=False),
- sa.Column('start', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
- sa.Column('end', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
+ "gw2_sessions",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("acc_name", sa.String(), nullable=False),
+ sa.Column("start", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
+ sa.Column("end", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
)
op.execute("""
CREATE TRIGGER before_update_gw2_sessions_tr
@@ -43,6 +43,6 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_gw2_sessions_tr ON gw2_sessions')
- op.drop_table('gw2_sessions')
+ op.execute("DROP TRIGGER IF EXISTS before_update_gw2_sessions_tr ON gw2_sessions")
+ op.drop_table("gw2_sessions")
# ### end Alembic commands ###
diff --git a/src/database/migrations/versions/0010_gw2_session_chars.py b/src/database/migrations/versions/0010_gw2_session_chars.py
index 79a0896d..f8ce8d55 100644
--- a/src/database/migrations/versions/0010_gw2_session_chars.py
+++ b/src/database/migrations/versions/0010_gw2_session_chars.py
@@ -11,8 +11,8 @@
from collections.abc import Sequence
# revision identifiers, used by Alembic.
-revision: str = '0010'
-down_revision: str | None = '0009'
+revision: str = "0010"
+down_revision: str | None = "0009"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
@@ -20,24 +20,24 @@
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
- 'gw2_session_chars',
- sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
- sa.Column('session_id', sa.BigInteger(), nullable=False),
- sa.Column('user_id', sa.BigInteger(), nullable=False),
- sa.Column('name', sa.String(), nullable=False),
- sa.Column('profession', sa.String(), nullable=False),
- sa.Column('deaths', sa.Integer(), nullable=False),
- sa.Column('start', sa.Boolean(), nullable=False),
- sa.Column('end', sa.Boolean(), nullable=True),
- sa.Column('updated_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
- sa.Column('created_at', sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ "gw2_session_chars",
+ sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
+ sa.Column("session_id", sa.BigInteger(), nullable=False),
+ sa.Column("user_id", sa.BigInteger(), nullable=False),
+ sa.Column("name", sa.String(), nullable=False),
+ sa.Column("profession", sa.String(), nullable=False),
+ sa.Column("deaths", sa.Integer(), nullable=False),
+ sa.Column("start", sa.Boolean(), nullable=False),
+ sa.Column("end", sa.Boolean(), nullable=True),
+ sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
+ sa.Column("created_at", sa.DateTime(), server_default=sa.text("(now() at time zone 'utc')"), nullable=False),
sa.ForeignKeyConstraint(
- ['session_id'],
- ['gw2_sessions.id'],
+ ["session_id"],
+ ["gw2_sessions.id"],
),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('id'),
- sa.UniqueConstraint('name'),
+ sa.PrimaryKeyConstraint("id"),
+ sa.UniqueConstraint("id"),
+ sa.UniqueConstraint("name"),
)
op.execute("""
CREATE TRIGGER before_update_gw2_session_chars_tr
@@ -50,6 +50,6 @@ def upgrade() -> None:
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
- op.execute('DROP TRIGGER IF EXISTS before_update_gw2_session_chars_tr ON gw2_session_chars')
- op.drop_table('gw2_session_chars')
+ op.execute("DROP TRIGGER IF EXISTS before_update_gw2_session_chars_tr ON gw2_session_chars")
+ op.drop_table("gw2_session_chars")
# ### end Alembic commands ###
diff --git a/src/gw2/cogs/__init__.py b/src/gw2/cogs/__init__.py
index e69de29b..a561c9e2 100644
--- a/src/gw2/cogs/__init__.py
+++ b/src/gw2/cogs/__init__.py
@@ -0,0 +1,10 @@
+from pathlib import Path
+
+
+def discover_gw2_cogs() -> list[str]:
+ """Discover GW2 cog file paths with gw2.py first for command group registration."""
+ cogs_dir = Path("src") / "gw2" / "cogs"
+ cogs = [str(cogs_dir / "gw2.py")]
+ remaining = [str(p) for p in cogs_dir.glob("*.py") if p.name != "__init__.py"]
+ cogs.extend(c for c in remaining if c not in cogs)
+ return cogs
diff --git a/src/gw2/cogs/account.py b/src/gw2/cogs/account.py
index 02c47ec1..ac3f97f7 100644
--- a/src/gw2/cogs/account.py
+++ b/src/gw2/cogs/account.py
@@ -5,6 +5,7 @@
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.constants.gw2_teams import get_team_name, is_wr_team_id
from src.gw2.tools import gw2_utils
from src.gw2.tools.gw2_client import Gw2Client
from src.gw2.tools.gw2_cooldowns import GW2CoolDowns
@@ -40,7 +41,7 @@ async def _fetch_guild_info_standalone(gw2_api, guild_id, api_key, ctx):
class GW2Account(GuildWars2):
- """(Commands related to users account)"""
+ """Guild Wars 2 commands for account information."""
def __init__(self, bot):
super().__init__(bot)
@@ -49,8 +50,11 @@ def __init__(self, bot):
@GW2Account.gw2.command()
@commands.cooldown(1, GW2CoolDowns.Account.seconds, commands.BucketType.user)
async def account(ctx):
- """(General information about your GW2 account)
+ """Display general information about your Guild Wars 2 account.
+
Required API permissions: account
+
+ Usage:
gw2 account
"""
@@ -59,8 +63,8 @@ async def account(ctx):
rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
if not rs:
msg = gw2_messages.NO_API_KEY
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
api_key = str(rs[0]["key"])
@@ -71,9 +75,9 @@ async def account(ctx):
is_valid_key = await gw2_api.check_api_key(api_key)
if not isinstance(is_valid_key, dict):
msg = f"{is_valid_key.args[1]}\n"
- msg += gw2_messages.INVALID_API_KEY_HELP_MESSAGE.format(ctx.prefix)
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg += gw2_messages.INVALID_API_KEY_HELP_MESSAGE
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
if "account" not in permissions:
@@ -104,23 +108,24 @@ async def account(ctx):
api_req_acc = await account_task
server_id = api_req_acc["world"]
- # Now fetch server info
- server_task = gw2_api.call_api(f"worlds/{server_id}", api_key)
-
# Prepare basic account data
acc_name = api_req_acc["name"]
access_normalized = []
for each in api_req_acc["access"]:
normalized = "".join([f" {c.upper()}" if c.isupper() or c.isdigit() else c for c in each]).lstrip()
access_normalized.append(normalized)
- access = '\n'.join(access_normalized)
+ access = "\n".join(access_normalized)
is_commander = "Yes" if api_req_acc["commander"] else "No"
- # Get server info
- api_req_server = await server_task
- server_name = api_req_server["name"]
- population = api_req_server["population"]
+ # Resolve server name and population (WR team IDs vs legacy worlds)
+ if is_wr_team_id(server_id):
+ server_name = get_team_name(server_id) or f"Team {server_id}"
+ population = "N/A"
+ else:
+ api_req_server = await gw2_api.call_api(f"worlds/{server_id}", api_key)
+ server_name = api_req_server["name"]
+ population = api_req_server["population"]
# Create base embed
color = ctx.bot.settings["gw2"]["EmbedColor"]
@@ -131,6 +136,12 @@ async def account(ctx):
embed.add_field(name="Commander Tag", value=chat_formatting.inline(is_commander))
embed.add_field(name="Server", value=chat_formatting.inline(f"{server_name} ({population})"))
+ # Add WvW Team field if available
+ wvw_team_id = api_req_acc.get("wvw", {}).get("team_id")
+ if wvw_team_id:
+ team_name = get_team_name(wvw_team_id) or f"Team {wvw_team_id}"
+ embed.add_field(name="WvW Team", value=chat_formatting.inline(team_name))
+
# Prepare optional API calls based on permissions
optional_tasks = []
@@ -171,7 +182,7 @@ async def account(ctx):
name="Achievements Points", value=chat_formatting.inline(str(achiev_points)), inline=False
)
- wvwrank = api_req_acc["wvw_rank"]
+ wvwrank = api_req_acc.get("wvw", {}).get("rank") or api_req_acc.get("wvw_rank", 0)
wvw_title = gw2_utils.get_wvw_rank_title(int(wvwrank))
embed.add_field(
name="WvW Rank", value=chat_formatting.inline(f"{wvw_title} ({wvwrank})"), inline=False
diff --git a/src/gw2/cogs/characters.py b/src/gw2/cogs/characters.py
index 9f102405..61bd8dcb 100644
--- a/src/gw2/cogs/characters.py
+++ b/src/gw2/cogs/characters.py
@@ -9,7 +9,7 @@
class GW2Characters(GuildWars2):
- """(Commands related to users characters)"""
+ """Guild Wars 2 commands for character information."""
def __init__(self, bot):
super().__init__(bot)
@@ -18,8 +18,11 @@ def __init__(self, bot):
@GW2Characters.gw2.command()
@commands.cooldown(1, GW2CoolDowns.Characters.seconds, commands.BucketType.user)
async def characters(ctx):
- """(General information about your GW2 characters)
- Required API permissions: account
+ """Display information about your Guild Wars 2 characters.
+
+ Required API permissions: account, characters
+
+ Usage:
gw2 characters
"""
@@ -28,8 +31,8 @@ async def characters(ctx):
rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
if not rs:
msg = gw2_messages.NO_API_KEY
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
api_key = str(rs[0]["key"])
@@ -37,9 +40,9 @@ async def characters(ctx):
is_valid_key = await gw2_api.check_api_key(api_key)
if not isinstance(is_valid_key, dict):
msg = f"{is_valid_key.args[1]}\n"
- msg += gw2_messages.INVALID_API_KEY_HELP_MESSAGE.format(ctx.prefix)
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg += gw2_messages.INVALID_API_KEY_HELP_MESSAGE
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
permissions = str(rs[0]["permissions"])
diff --git a/src/gw2/cogs/config.py b/src/gw2/cogs/config.py
index 51107aa9..68f22b4f 100644
--- a/src/gw2/cogs/config.py
+++ b/src/gw2/cogs/config.py
@@ -9,7 +9,7 @@
class GW2Config(GuildWars2):
- """(Guild Wars 2 Configuration Commands - Admin)"""
+ """Guild Wars 2 configuration commands for server settings management."""
def __init__(self, bot):
super().__init__(bot)
@@ -18,9 +18,11 @@ def __init__(self, bot):
@GuildWars2.gw2.group()
@Checks.check_is_admin()
async def config(ctx):
- """(Guild Wars 2 Configuration Commands - Admin)
- gw2 config list
- gw2 config session [on | off]
+ """Guild Wars 2 server configuration commands.
+
+ Available subcommands:
+ gw2 config list - List all GW2 configurations
+ gw2 config session [on | off] - Toggle session recording
"""
await bot_utils.invoke_subcommand(ctx, "gw2 config")
@@ -29,8 +31,10 @@ async def config(ctx):
@config.command(name="list")
@commands.cooldown(1, GW2CoolDowns.Config.seconds, commands.BucketType.user)
async def config_list(ctx):
- """(List all Guild Wars 2 Current Server Configurations)
- gw2 config list
+ """List all Guild Wars 2 configurations for the current server.
+
+ Usage:
+ gw2 config list
"""
color = ctx.bot.settings["gw2"]["EmbedColor"]
@@ -41,7 +45,7 @@ async def config_list(ctx):
name=f"{gw2_messages.CONFIG_TITLE} {ctx.guild.name}",
icon_url=guild_icon_url,
)
- embed.set_footer(text=gw2_messages.CONFIG_MORE_INFO.format(ctx.prefix))
+ embed.set_footer(text=gw2_messages.config_more_info(ctx.prefix))
gw2_configs = Gw2ConfigsDal(ctx.bot.db_session, ctx.bot.log)
rs = await gw2_configs.get_gw2_server_configs(ctx.guild.id)
@@ -73,9 +77,11 @@ async def config_list(ctx):
@config.command(name="session")
@commands.cooldown(1, GW2CoolDowns.Config.seconds, commands.BucketType.user)
async def config_session(ctx, subcommand_passed: str):
- """(Configure Guild Wars 2 Sessions)
- gw2 config session on
- gw2 config session off
+ """Toggle Guild Wars 2 session recording.
+
+ Usage:
+ gw2 config session on
+ gw2 config session off
"""
match subcommand_passed:
@@ -127,9 +133,9 @@ async def _handle_update(
# Set updating state and disable all buttons
self._updating = True
for item in self.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
item.disabled = True
- if hasattr(item, 'style'):
+ if hasattr(item, "style"):
item.style = discord.ButtonStyle.gray
# Defer the response to allow editing the original message
@@ -190,7 +196,7 @@ async def _handle_update(
async def _restore_buttons(self):
"""Restore button states and colors."""
for item in self.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
item.disabled = False
# Restore original button colors
@@ -208,7 +214,7 @@ async def _create_updated_embed(self):
name=f"{gw2_messages.CONFIG_TITLE} {self.ctx.guild.name}",
icon_url=guild_icon_url,
)
- embed.set_footer(text=gw2_messages.CONFIG_MORE_INFO.format(self.ctx.prefix))
+ embed.set_footer(text=gw2_messages.config_more_info(self.ctx.prefix))
# Format status indicators
on = chat_formatting.green_text("ON")
diff --git a/src/gw2/cogs/gw2.py b/src/gw2/cogs/gw2.py
index d6f94b3f..20eae223 100644
--- a/src/gw2/cogs/gw2.py
+++ b/src/gw2/cogs/gw2.py
@@ -3,20 +3,27 @@
class GuildWars2(commands.Cog):
+ """Guild Wars 2 commands for account management, WvW, and wiki search."""
+
def __init__(self, bot):
self.bot = bot
@commands.group(name="gw2")
async def gw2(self, ctx):
- """(Guild Wars 2 Commands)
- gw2 config list
- gw2 config session [on | off]
- gw2 wvw [match | info | kdr] world_name
- gw2 key [add | remove | info] api_key
- gw2 account
- gw2 worlds
- gw2 wiki name_to_search
- gw2 info info_to_search
+ """Guild Wars 2 commands.
+
+ Available subcommands:
+ gw2 config list - List all GW2 configurations
+ gw2 config session [on | off] - Toggle session recording
+ gw2 wvw [match | info | kdr] - WvW match information
+ gw2 key [add | update | info] - Manage API keys
+ gw2 key remove - Remove your API key
+ gw2 account - Show account information
+ gw2 characters - Show character information
+ gw2 session - Show last game session
+ gw2 worlds [na | eu] - List all worlds
+ gw2 wiki - Search the GW2 wiki
+ gw2 info - Info about a name/skill/rune
"""
await bot_utils.invoke_subcommand(ctx, "gw2")
diff --git a/src/gw2/cogs/key.py b/src/gw2/cogs/key.py
index e43c63f8..7f6139c5 100644
--- a/src/gw2/cogs/key.py
+++ b/src/gw2/cogs/key.py
@@ -8,218 +8,308 @@
from src.gw2.tools.gw2_cooldowns import GW2CoolDowns
-class GW2Key(GuildWars2):
- """(Commands related to GW2 API keys)"""
-
- def __init__(self, bot):
- super().__init__(bot)
-
-
-@GW2Key.gw2.group()
-async def key(ctx):
- """(Commands related to GW2 API keys)
- To generate an API key, head to https://account.arena.net, and log in.
- In the "Applications" tab, generate a new key with all permissions.
- Required API permissions: account
+def _get_user_id(ctx_or_interaction):
+ if isinstance(ctx_or_interaction, discord.Interaction):
+ return ctx_or_interaction.user.id
+ return ctx_or_interaction.message.author.id
- Note: Only one API key per user is supported.
-
- gw2 key add (Adds your first GW2 API key)
- gw2 key update (Updates/replaces your existing API key)
- gw2 key remove (Removes your GW2 API key from the bot)
- gw2 key info (Shows information about your GW2 API key)
- """
-
- await bot_utils.invoke_subcommand(ctx, "gw2 key")
+async def _send_success(ctx_or_interaction, msg, color):
+ if isinstance(ctx_or_interaction, discord.Interaction):
+ embed = discord.Embed(description=msg, color=color)
+ await ctx_or_interaction.followup.send(embed=embed, ephemeral=True)
+ else:
+ await bot_utils.send_msg(ctx_or_interaction, msg, True, color)
-@key.command(name="add")
-@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
-async def add(ctx, api_key: str):
- """(Adds your first GW2 API key)
- This command only works if you don't have an existing key.
- Required API permissions: account
- gw2 key add
- """
+async def _send_error(ctx_or_interaction, msg):
+ msg = str(msg)
+ if isinstance(ctx_or_interaction, discord.Interaction):
+ embed = discord.Embed(
+ description=chat_formatting.error(msg),
+ color=discord.Color.red(),
+ )
+ await ctx_or_interaction.followup.send(embed=embed, ephemeral=True)
+ else:
+ await bot_utils.send_error_msg(ctx_or_interaction, msg, True)
- await bot_utils.delete_message(ctx, warning=True)
- user_id = ctx.message.author.id
- embed_color = ctx.bot.settings["gw2"]["EmbedColor"]
- # checking API Key with gw2 servers
- gw2_api = Gw2Client(ctx.bot)
+async def _validate_api_key(bot, api_key):
+ """Validate API key with GW2 servers and return account info dict."""
+ gw2_api = Gw2Client(bot)
is_valid_key = await gw2_api.check_api_key(api_key)
if not isinstance(is_valid_key, dict):
- return await bot_utils.send_error_msg(ctx, f"{is_valid_key.args[1]}\n`{api_key}`", True)
+ raise ValueError(f"{is_valid_key.args[1]}\n`{api_key}`")
key_name = is_valid_key["name"]
permissions = ",".join(is_valid_key["permissions"])
- try:
- # getting gw2 acc name
- api_req_acc_info = await gw2_api.call_api("account", api_key)
- gw2_acc_name = api_req_acc_info["name"]
- member_server_id = api_req_acc_info["world"]
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e, True)
- return ctx.bot.log.error(ctx, e)
+ api_req_acc_info = await gw2_api.call_api("account", api_key)
+ gw2_acc_name = api_req_acc_info["name"]
+ member_server_id = api_req_acc_info["world"]
- try:
- # getting gw2 server name
- uri = f"worlds/{member_server_id}"
- api_req_server = await gw2_api.call_api(uri, api_key)
- gw2_server_name = api_req_server["name"]
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e, True)
- ctx.bot.log.error(ctx, e)
- return None
+ uri = f"worlds/{member_server_id}"
+ api_req_server = await gw2_api.call_api(uri, api_key)
+ gw2_server_name = api_req_server["name"]
- api_key_args = {
- "user_id": user_id,
+ return {
"key_name": key_name,
- "gw2_acc_name": gw2_acc_name,
- "server_name": gw2_server_name,
"permissions": permissions,
- "api_key": api_key,
+ "gw2_acc_name": gw2_acc_name,
+ "gw2_server_name": gw2_server_name,
}
- # searching if API key in local database
- gw2_key_dal = Gw2KeyDal(ctx.bot.db_session, ctx.bot.log)
- # Check if user already has any API key - ADD command should only work for first-time users
+async def _process_add_key(ctx_or_interaction, api_key, bot, prefix):
+ """Validate and add a new GW2 API key for the user."""
+ user_id = _get_user_id(ctx_or_interaction)
+ embed_color = bot.settings["gw2"]["EmbedColor"]
+
+ try:
+ key_info = await _validate_api_key(bot, api_key)
+ except Exception as e:
+ bot.log.error(f"API key validation failed for user {user_id}: {e}")
+ return await _send_error(ctx_or_interaction, e)
+
+ gw2_key_dal = Gw2KeyDal(bot.db_session, bot.log)
+
existing_user_key = await gw2_key_dal.get_api_key_by_user(user_id)
if existing_user_key:
error_msg = (
- "❌ **You already have an API key registered.**\n\n"
+ f"You already have an API key registered.\n"
f"Current key: `{existing_user_key[0]['name']}` for account `{existing_user_key[0]['gw2_acc_name']}`\n\n"
- "**Options:**\n"
- f"• To update your key: `{ctx.prefix}gw2 key update `\n"
- f"• To view your current key: `{ctx.prefix}gw2 key info`\n"
- f"• To remove your key first: `{ctx.prefix}gw2 key remove`\n\n"
- "💡 **Tip:** Use `update` command to replace your existing key."
+ f"To update your key: `{prefix}gw2 key update `\n"
+ f"To view your current key: `{prefix}gw2 key info`\n"
+ f"To remove your key first: `{prefix}gw2 key remove`"
)
- await bot_utils.send_error_msg(ctx, error_msg, True)
- return None
+ return await _send_error(ctx_or_interaction, error_msg)
- # Check if this exact API key is already used by someone else
rs = await gw2_key_dal.get_api_key(api_key)
if rs:
- await bot_utils.send_error_msg(ctx, gw2_messages.KEY_ALREADY_IN_USE, True)
- return None
+ return await _send_error(ctx_or_interaction, gw2_messages.KEY_ALREADY_IN_USE)
- # If we get here, user has no existing key and the API key is not in use
try:
- await gw2_key_dal.insert_api_key(api_key_args)
- msg = gw2_messages.KEY_ADDED_SUCCESSFULLY.format(key_name, gw2_server_name)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
- await bot_utils.send_msg(ctx, msg, True, embed_color)
- return None
- except Exception as e:
- ctx.bot.log.error(f"Error inserting API key for user {user_id}: {e}")
- error_msg = (
- "❌ **Failed to add API key.**\n\n"
- "This could be due to a database constraint or connection issue. "
- "Please try again later or contact an administrator if the problem persists."
+ await gw2_key_dal.insert_api_key(
+ {
+ "user_id": user_id,
+ "key_name": key_info["key_name"],
+ "gw2_acc_name": key_info["gw2_acc_name"],
+ "server_name": key_info["gw2_server_name"],
+ "permissions": key_info["permissions"],
+ "api_key": api_key,
+ }
)
- await bot_utils.send_error_msg(ctx, error_msg, True)
- return None
-
+ msg = gw2_messages.key_added_successfully(key_info["key_name"], key_info["gw2_server_name"])
+ msg += gw2_messages.key_more_info_help(prefix)
+ await _send_success(ctx_or_interaction, msg, embed_color)
+ except Exception as e:
+ bot.log.error(f"Error inserting API key for user {user_id}: {e}")
+ await _send_error(ctx_or_interaction, "Failed to add API key. Please try again later.")
-@key.command(name="update", aliases=["replace"])
-@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
-async def update(ctx, api_key: str):
- """(Updates your existing GW2 API key)
- This command only works if you already have a key registered.
- Required API permissions: account
- gw2 key update
- """
- await bot_utils.delete_message(ctx, warning=True)
- user_id = ctx.message.author.id
- embed_color = ctx.bot.settings["gw2"]["EmbedColor"]
+async def _process_update_key(ctx_or_interaction, api_key, bot, prefix):
+ """Validate and update an existing GW2 API key for the user."""
+ user_id = _get_user_id(ctx_or_interaction)
+ embed_color = bot.settings["gw2"]["EmbedColor"]
- # Check if user has an existing key - UPDATE command requires existing key
- gw2_key_dal = Gw2KeyDal(ctx.bot.db_session, ctx.bot.log)
+ gw2_key_dal = Gw2KeyDal(bot.db_session, bot.log)
existing_user_key = await gw2_key_dal.get_api_key_by_user(user_id)
if not existing_user_key:
error_msg = (
- "❌ **You don't have an API key registered yet.**\n\n"
- "**Options:**\n"
- f"• To add your first key: `{ctx.prefix}gw2 key add `\n"
- f"• For more help: `{ctx.prefix}help gw2 key`\n\n"
- "💡 **Tip:** Use `add` command for your first key."
+ f"You don't have an API key registered yet.\n\n"
+ f"To add your first key: `{prefix}gw2 key add `\n"
+ f"For more help: `{prefix}help gw2 key`"
)
- await bot_utils.send_error_msg(ctx, error_msg, True)
- return None
-
- # checking API Key with gw2 servers
- gw2_api = Gw2Client(ctx.bot)
- is_valid_key = await gw2_api.check_api_key(api_key)
- if not isinstance(is_valid_key, dict):
- return await bot_utils.send_error_msg(ctx, f"{is_valid_key.args[1]}\n`{api_key}`", True)
-
- key_name = is_valid_key["name"]
- permissions = ",".join(is_valid_key["permissions"])
-
- try:
- # getting gw2 acc name
- api_req_acc_info = await gw2_api.call_api("account", api_key)
- gw2_acc_name = api_req_acc_info["name"]
- member_server_id = api_req_acc_info["world"]
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e, True)
- return ctx.bot.log.error(ctx, e)
+ return await _send_error(ctx_or_interaction, error_msg)
try:
- # getting gw2 server name
- uri = f"worlds/{member_server_id}"
- api_req_server = await gw2_api.call_api(uri, api_key)
- gw2_server_name = api_req_server["name"]
+ key_info = await _validate_api_key(bot, api_key)
except Exception as e:
- await bot_utils.send_error_msg(ctx, e, True)
- ctx.bot.log.error(ctx, e)
- return None
+ bot.log.error(f"API key validation failed for user {user_id}: {e}")
+ return await _send_error(ctx_or_interaction, e)
- api_key_args = {
- "user_id": user_id,
- "key_name": key_name,
- "gw2_acc_name": gw2_acc_name,
- "server_name": gw2_server_name,
- "permissions": permissions,
- "api_key": api_key,
- }
-
- # Check if this exact API key is already used by someone else
rs = await gw2_key_dal.get_api_key(api_key)
if rs and rs[0]["user_id"] != user_id:
- await bot_utils.send_error_msg(ctx, gw2_messages.KEY_ALREADY_IN_USE, True)
- return None
+ return await _send_error(ctx_or_interaction, gw2_messages.KEY_ALREADY_IN_USE)
- # Update the existing key
try:
- await gw2_key_dal.update_api_key(api_key_args)
- old_key_name = existing_user_key[0]['name']
- msg = gw2_messages.KEY_REPLACED_SUCCESSFULLY.format(old_key_name, key_name, gw2_server_name)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
- await bot_utils.send_msg(ctx, msg, True, embed_color)
- return None
+ await gw2_key_dal.update_api_key(
+ {
+ "user_id": user_id,
+ "key_name": key_info["key_name"],
+ "gw2_acc_name": key_info["gw2_acc_name"],
+ "server_name": key_info["gw2_server_name"],
+ "permissions": key_info["permissions"],
+ "api_key": api_key,
+ }
+ )
+ old_key_name = existing_user_key[0]["name"]
+ msg = gw2_messages.key_replaced_successfully(old_key_name, key_info["key_name"], key_info["gw2_server_name"])
+ msg += gw2_messages.key_more_info_help(prefix)
+ await _send_success(ctx_or_interaction, msg, embed_color)
except Exception as e:
- ctx.bot.log.error(f"Error updating API key for user {user_id}: {e}")
- error_msg = (
- "❌ **Failed to update API key.**\n\n"
- "This could be due to a database constraint or connection issue. "
- "Please try again later or contact an administrator if the problem persists."
+ bot.log.error(f"Error updating API key for user {user_id}: {e}")
+ await _send_error(ctx_or_interaction, "Failed to update API key. Please try again later.")
+
+
+class ApiKeyModal(discord.ui.Modal, title="Enter GW2 API Key"):
+ """Modal dialog for secure API key input."""
+
+ api_key_input = discord.ui.TextInput(
+ label="API Key",
+ placeholder="Paste your GW2 API key here",
+ style=discord.TextStyle.short,
+ required=True,
+ min_length=10,
+ )
+
+ def __init__(self, bot, mode: str, prefix: str):
+ super().__init__()
+ self.bot = bot
+ self.mode = mode
+ self.prefix = prefix
+
+ async def on_submit(self, interaction: discord.Interaction):
+ await interaction.response.defer(ephemeral=True)
+ api_key = self.api_key_input.value.strip()
+ if self.mode == "add":
+ await _process_add_key(interaction, api_key, self.bot, self.prefix)
+ else:
+ await _process_update_key(interaction, api_key, self.bot, self.prefix)
+
+
+class ApiKeyView(discord.ui.View):
+ """View with a button that opens the API key modal."""
+
+ def __init__(self, bot, mode: str, prefix: str):
+ super().__init__(timeout=300)
+ self.bot = bot
+ self.mode = mode
+ self.prefix = prefix
+ self.message = None
+
+ @discord.ui.button(label="Enter API Key", emoji="\U0001f511", style=discord.ButtonStyle.primary)
+ async def enter_key(self, interaction: discord.Interaction, button: discord.ui.Button):
+ modal = ApiKeyModal(self.bot, self.mode, self.prefix)
+ await interaction.response.send_modal(modal)
+
+ async def on_timeout(self):
+ for item in self.children:
+ item.disabled = True
+ try:
+ if self.message:
+ await self.message.edit(view=self)
+ except discord.NotFound, discord.HTTPException:
+ pass
+
+
+class GW2Key(GuildWars2):
+ """Guild Wars 2 commands for API key management."""
+
+ def __init__(self, bot):
+ super().__init__(bot)
+
+
+@GW2Key.gw2.group()
+async def key(ctx):
+ """Manage your Guild Wars 2 API keys.
+
+ To generate an API key, head to https://account.arena.net and log in.
+ In the "Applications" tab, generate a new key with all permissions.
+ Only one API key per user is supported.
+
+ Available subcommands:
+ gw2 key add [api_key] - Add your first GW2 API key
+ gw2 key update [api_key] - Update your existing API key
+ gw2 key remove - Remove your GW2 API key
+ gw2 key info - Show your API key information
+ """
+
+ await bot_utils.invoke_subcommand(ctx, "gw2 key")
+
+
+@key.command(name="add")
+@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
+async def add(ctx, api_key: str = None):
+ """Add your first Guild Wars 2 API key.
+
+ This command only works if you don't have an existing key.
+ If no key is provided, a secure input dialog will be sent to your DM.
+ Required API permissions: account
+
+ Usage:
+ gw2 key add
+ gw2 key add XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+ """
+
+ if api_key is None:
+ view = ApiKeyView(ctx.bot, "add", ctx.prefix)
+ embed = discord.Embed(
+ description=(
+ "Click the button below to securely enter your GW2 API key.\n\n"
+ "To generate an API key, head to https://account.arena.net\n"
+ "In the **Applications** tab, generate a new key with **account** permissions."
+ ),
+ color=ctx.bot.settings["gw2"]["EmbedColor"],
)
- await bot_utils.send_error_msg(ctx, error_msg, True)
- return None
+ message = await ctx.author.send(embed=embed, view=view)
+ view.message = message
+ if not bot_utils.is_private_message(ctx):
+ notification = discord.Embed(
+ description="\U0001f4ec Secure API key input sent to your DM",
+ color=discord.Color.green(),
+ )
+ await ctx.send(embed=notification)
+ return
+
+ await bot_utils.delete_message(ctx, warning=True)
+ await _process_add_key(ctx, api_key, ctx.bot, ctx.prefix)
+
+
+@key.command(name="update", aliases=["replace"])
+@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
+async def update(ctx, api_key: str = None):
+ """Update your existing Guild Wars 2 API key.
+
+ This command only works if you already have a key registered.
+ If no key is provided, a secure input dialog will be sent to your DM.
+ Required API permissions: account
+
+ Usage:
+ gw2 key update
+ gw2 key update XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+ """
+
+ if api_key is None:
+ view = ApiKeyView(ctx.bot, "update", ctx.prefix)
+ embed = discord.Embed(
+ description=(
+ "Click the button below to securely enter your new GW2 API key.\n\n"
+ "This will replace your existing API key."
+ ),
+ color=ctx.bot.settings["gw2"]["EmbedColor"],
+ )
+ message = await ctx.author.send(embed=embed, view=view)
+ view.message = message
+ if not bot_utils.is_private_message(ctx):
+ notification = discord.Embed(
+ description="\U0001f4ec Secure API key input sent to your DM",
+ color=discord.Color.green(),
+ )
+ await ctx.send(embed=notification)
+ return
+
+ await bot_utils.delete_message(ctx, warning=True)
+ await _process_update_key(ctx, api_key, ctx.bot, ctx.prefix)
@key.command(name="remove")
@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
async def remove(ctx):
- """(Removes your GW2 API key from the bot)
- gw2 key remove
+ """Remove your Guild Wars 2 API key from the bot.
+
+ Usage:
+ gw2 key remove
"""
user_id = ctx.message.author.id
@@ -227,9 +317,9 @@ async def remove(ctx):
rs = await gw2_key_dal.get_api_key_by_user(user_id)
if not rs:
- msg = gw2_messages.NO_API_KEY.format(ctx.prefix)
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg = gw2_messages.NO_API_KEY
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
else:
color = ctx.bot.settings["gw2"]["EmbedColor"]
@@ -241,8 +331,10 @@ async def remove(ctx):
@key.command(name="info", aliases=["list"])
@commands.cooldown(1, GW2CoolDowns.ApiKeys.seconds, commands.BucketType.user)
async def info(ctx):
- """(Shows information about your GW2 API key)
- gw2 key info
+ """Display information about your Guild Wars 2 API key.
+
+ Usage:
+ gw2 key info
"""
user_id = ctx.message.author.id
diff --git a/src/gw2/cogs/misc.py b/src/gw2/cogs/misc.py
index c0306de9..05003503 100644
--- a/src/gw2/cogs/misc.py
+++ b/src/gw2/cogs/misc.py
@@ -10,7 +10,7 @@
class GW2Misc(GuildWars2):
- """(Commands related to GW2)"""
+ """Guild Wars 2 miscellaneous commands for wiki search and item info."""
def __init__(self, bot):
super().__init__(bot)
@@ -19,8 +19,11 @@ def __init__(self, bot):
@GW2Misc.gw2.command()
@commands.cooldown(1, GW2CoolDowns.Misc.seconds, commands.BucketType.user)
async def wiki(ctx, *, search):
- """(Search the Guild wars 2 wiki)
- gw2 wiki name_to_search
+ """Search the Guild Wars 2 wiki.
+
+ Usage:
+ gw2 wiki elementalist
+ gw2 wiki ascended armor
"""
if len(search) > 300:
@@ -35,7 +38,7 @@ async def wiki(ctx, *, search):
await ctx.message.channel.typing()
async with ctx.bot.aiosession.get(full_wiki_url) as r:
results = await r.text()
- soup = BeautifulSoup(results, 'html.parser')
+ soup = BeautifulSoup(results, "html.parser")
posts = soup.find_all("div", {"class": "mw-search-result-heading"})[:50]
total_posts = len(posts)
if not posts:
@@ -54,7 +57,7 @@ async def wiki(ctx, *, search):
while i <= times_to_run:
post = posts[i]
post = post.a
- url = wiki_url + post['href']
+ url = wiki_url + post["href"]
url = url.replace(")", "\\)")
keyword = search.lower().replace("+", " ")
found = False
@@ -75,7 +78,7 @@ async def wiki(ctx, *, search):
except IndexError:
pass
- embed.description = gw2_messages.DISPLAYIN_WIKI_SEARCH_TITLE.format(len(embed.fields), keyword.title())
+ embed.description = gw2_messages.displaying_wiki_search_title(len(embed.fields), keyword.title())
else:
embed.add_field(name=gw2_messages.NO_RESULTS, value=f"[{gw2_messages.CLICK_HERE}]({full_wiki_url})")
@@ -86,8 +89,13 @@ async def wiki(ctx, *, search):
@GW2Misc.gw2.command()
@commands.cooldown(1, GW2CoolDowns.Misc.seconds, commands.BucketType.user)
async def info(ctx, *, skill):
- """(Information about a given name/skill/rune)
- gw2 info info_to_search
+ """Display information about a given name, skill, or rune.
+
+ Shows wiki description and Trading Post prices when available.
+
+ Usage:
+ gw2 info Eternity
+ gw2 info Superior Rune of the Scholar
"""
await ctx.message.channel.typing()
@@ -108,7 +116,7 @@ async def info(ctx, *, skill):
skill_icon_url = ""
results = await r.text()
- soup = BeautifulSoup(results, 'html.parser')
+ soup = BeautifulSoup(results, "html.parser")
for br in soup.find_all("br"):
br.replace_with("\n")
@@ -141,8 +149,8 @@ async def info(ctx, *, skill):
f"https://www.gw2bltc.com/en/item/{item_id}-{skill_sanitized.replace('_', '-').lower()}"
)
tp_results = await tp_r.text()
- sell_td = BeautifulSoup(tp_results, 'html.parser').find_all("td", {"id": "sell-price"})
- buy_td = BeautifulSoup(tp_results, 'html.parser').find_all("td", {"id": "buy-price"})
+ sell_td = BeautifulSoup(tp_results, "html.parser").find_all("td", {"id": "sell-price"})
+ buy_td = BeautifulSoup(tp_results, "html.parser").find_all("td", {"id": "buy-price"})
tp_sell_price = gw2_utils.format_gold(sell_td[0]["data-price"])
tp_buy_price = gw2_utils.format_gold(buy_td[0]["data-price"])
skill_description = (
diff --git a/src/gw2/cogs/sessions.py b/src/gw2/cogs/sessions.py
index 110bb476..41a6d8d1 100644
--- a/src/gw2/cogs/sessions.py
+++ b/src/gw2/cogs/sessions.py
@@ -12,7 +12,7 @@
class GW2Session(GuildWars2):
- """(Commands related to GW2 player last game session)"""
+ """Guild Wars 2 commands for player session tracking."""
def __init__(self, bot):
super().__init__(bot)
@@ -21,19 +21,18 @@ def __init__(self, bot):
@GW2Session.gw2.command()
@commands.cooldown(1, GW2CoolDowns.Session.seconds, commands.BucketType.user)
async def session(ctx):
- """(Info about the gw2 player last game session)
+ """Display information about your last Guild Wars 2 game session.
- Your API Key needs to have the following permissions:
- Account, Characters, Progression, Wallet
- 60 secs default cooldown
+ Required API permissions: Account, Characters, Progression, Wallet
Requirements:
- 1) Start discord, make sure you are not set to invisible
- 1) Add GW2 API Key (gw2 key add api_key)
- 2) Need to show, on discord, that you are playing Guild Wars 2, change this on options
- 3) Start gw2
+ 1) Start Discord, make sure you are not set to invisible
+ 2) Add GW2 API Key (gw2 key add api_key)
+ 3) Show on Discord that you are playing Guild Wars 2
+ 4) Start GW2
- gw2 session
+ Usage:
+ gw2 session
"""
user_id = ctx.message.author.id
@@ -41,14 +40,14 @@ async def session(ctx):
rs_api_key = await gw2_key_dal.get_api_key_by_user(user_id)
if not rs_api_key:
msg = gw2_messages.NO_API_KEY
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
+ msg += gw2_messages.key_add_info_help(ctx.prefix)
+ msg += gw2_messages.key_more_info_help(ctx.prefix)
return await bot_utils.send_error_msg(ctx, msg)
gw2_configs = Gw2ConfigsDal(ctx.bot.db_session, ctx.bot.log)
rs_gw2_sc = await gw2_configs.get_gw2_server_configs(ctx.guild.id)
if len(rs_gw2_sc) == 0 or (len(rs_gw2_sc) > 0 and not rs_gw2_sc[0]["session"]):
- return await bot_utils.send_warning_msg(ctx, gw2_messages.SESSION_NOT_ACTIVE.format(ctx.prefix))
+ return await bot_utils.send_warning_msg(ctx, gw2_messages.session_not_active(ctx.prefix))
api_key = rs_api_key[0]["key"]
gw2_server = rs_api_key[0]["server"]
@@ -75,8 +74,8 @@ async def session(ctx):
error_msg += "- wallet is OK\n" if wallet is True else "- wallet is MISSING\n"
error_msg += (
f"{gw2_messages.ADD_RIGHT_API_KEY_PERMISSIONS}\n"
- f"{gw2_messages.KEY_ADD_INFO_HELP}"
- f"{gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)}"
+ f"{gw2_messages.key_add_info_help(ctx.prefix)}"
+ f"{gw2_messages.key_more_info_help(ctx.prefix)}"
)
return await bot_utils.send_error_msg(ctx, error_msg)
diff --git a/src/gw2/cogs/worlds.py b/src/gw2/cogs/worlds.py
index 6f65bbef..ddd80302 100644
--- a/src/gw2/cogs/worlds.py
+++ b/src/gw2/cogs/worlds.py
@@ -1,4 +1,3 @@
-import asyncio
import discord
from discord.ext import commands
from src.bot.tools import bot_utils, chat_formatting
@@ -9,8 +8,58 @@
from src.gw2.tools.gw2_cooldowns import GW2CoolDowns
+class EmbedPaginatorView(discord.ui.View):
+ """Interactive pagination view for embed pages with Previous/Next buttons."""
+
+ def __init__(self, pages: list[discord.Embed], author_id: int):
+ super().__init__(timeout=300)
+ self.pages = pages
+ self.current_page = 0
+ self.author_id = author_id
+ self.message: discord.Message | None = None
+ self._update_buttons()
+
+ def _update_buttons(self):
+ self.previous_button.disabled = self.current_page == 0
+ self.page_indicator.label = f"{self.current_page + 1}/{len(self.pages)}"
+ self.next_button.disabled = self.current_page == len(self.pages) - 1
+
+ @discord.ui.button(label="\u25c0", style=discord.ButtonStyle.secondary)
+ async def previous_button(self, interaction: discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.author_id:
+ return await interaction.response.send_message(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+ self.current_page -= 1
+ self._update_buttons()
+ await interaction.response.edit_message(embed=self.pages[self.current_page], view=self)
+
+ @discord.ui.button(label="1/1", style=discord.ButtonStyle.secondary, disabled=True)
+ async def page_indicator(self, interaction: discord.Interaction, button: discord.ui.Button):
+ await interaction.response.defer()
+
+ @discord.ui.button(label="\u25b6", style=discord.ButtonStyle.secondary)
+ async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button):
+ if interaction.user.id != self.author_id:
+ return await interaction.response.send_message(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+ self.current_page += 1
+ self._update_buttons()
+ await interaction.response.edit_message(embed=self.pages[self.current_page], view=self)
+
+ async def on_timeout(self):
+ for item in self.children:
+ item.disabled = True
+ try:
+ if self.message:
+ await self.message.edit(view=self)
+ except discord.NotFound, discord.HTTPException:
+ pass
+
+
class GW2Worlds(GuildWars2):
- """(Guild Wars 2 List of Worlds Commands)"""
+ """Guild Wars 2 commands for listing worlds and WvW tiers."""
def __init__(self, bot):
super().__init__(bot)
@@ -18,9 +67,11 @@ def __init__(self, bot):
@GW2Worlds.gw2.group()
async def worlds(ctx):
- """(List all worlds)
- gw2 worlds na
- gw2 worlds eu
+ """List all Guild Wars 2 worlds by region.
+
+ Available subcommands:
+ gw2 worlds na - List all NA worlds with WvW tier
+ gw2 worlds eu - List all EU worlds with WvW tier
"""
await bot_utils.invoke_subcommand(ctx, "gw2 worlds")
@@ -29,8 +80,10 @@ async def worlds(ctx):
@worlds.command(name="na")
@commands.cooldown(1, GW2CoolDowns.Worlds.seconds, commands.BucketType.user)
async def worlds_na(ctx):
- """(List all NA worlds and wvw tier)
- gw2 worlds na
+ """List all North American worlds with WvW tier and population.
+
+ Usage:
+ gw2 worlds na
"""
result, worlds_ids = await gw2_utils.get_worlds_ids(ctx)
@@ -72,8 +125,10 @@ async def worlds_na(ctx):
@worlds.command(name="eu")
@commands.cooldown(1, GW2CoolDowns.Worlds.seconds, commands.BucketType.user)
async def worlds_eu(ctx):
- """(List all EU worlds and wvw tier)
- gw2 worlds eu
+ """List all European worlds with WvW tier and population.
+
+ Usage:
+ gw2 worlds eu
"""
result, worlds_ids = await gw2_utils.get_worlds_ids(ctx)
@@ -114,7 +169,7 @@ async def worlds_eu(ctx):
async def _send_paginated_worlds_embed(ctx, embed):
"""
- Send worlds with pagination using reactions for navigation
+ Send worlds with pagination using buttons for navigation
"""
max_fields = 25
color = ctx.bot.settings["gw2"]["EmbedColor"]
@@ -138,80 +193,16 @@ async def _send_paginated_worlds_embed(ctx, embed):
page_number = (i // max_fields) + 1
total_pages = (total_fields + max_fields - 1) // max_fields
-
- # Check if we're in a DM for the footer message
- if isinstance(ctx.channel, discord.DMChannel):
- footer_text = (
- f"Page {page_number}/{total_pages} • Use reactions to navigate (reactions won't disappear in DMs)"
- )
- else:
- footer_text = f"Page {page_number}/{total_pages}"
-
- page_embed.set_footer(text=footer_text)
+ page_embed.set_footer(text=f"Page {page_number}/{total_pages}")
pages.append(page_embed)
if len(pages) == 1:
await ctx.send(embed=pages[0])
return
- # Check if we're in a DM channel
- is_dm = isinstance(ctx.channel, discord.DMChannel)
-
- # Send first page with reactions
- current_page = 0
- message = await ctx.send(embed=pages[current_page])
-
- # Add reaction controls with small delays to ensure they're all added
- try:
- await message.add_reaction("⬅️")
- await asyncio.sleep(0.2)
- await message.add_reaction("➡️")
- await asyncio.sleep(0.2)
- except discord.HTTPException as e:
- ctx.bot.log.error(f"Failed to add pagination reactions: {e}")
- # Send without pagination if reactions fail
- await ctx.send(embed=pages[0])
- return
-
- def check(react, react_user):
- return (
- react_user == ctx.author
- and react.message.id == message.id
- and str(react.emoji) in ["⬅️", "➡️"]
- and not react_user.bot # Ensure it's not a bot reaction
- )
-
- timeout = 60
-
- try:
- while True:
- reaction, user = await ctx.bot.wait_for("reaction_add", timeout=timeout, check=check)
-
- emoji_str = str(reaction.emoji)
-
- if emoji_str == "➡️" and current_page < len(pages) - 1:
- current_page += 1
- await message.edit(embed=pages[current_page])
- elif emoji_str == "⬅️" and current_page > 0:
- current_page -= 1
- await message.edit(embed=pages[current_page])
-
- # Remove user's reaction to keep the interface clean (skip in DM channels)
- if not is_dm:
- try:
- await message.remove_reaction(reaction.emoji, user)
- except discord.Forbidden, discord.NotFound, discord.HTTPException:
- pass # Silently handle permission issues
-
- except TimeoutError:
- pass # Silently handle timeout
-
- # Clean up by removing all reactions (skip in DM channels)
- if not is_dm:
- try:
- await message.clear_reactions()
- except discord.Forbidden:
- pass # Silently handle permission issues
+ view = EmbedPaginatorView(pages, ctx.author.id)
+ msg = await ctx.send(embed=pages[0], view=view)
+ view.message = msg
async def setup(bot):
diff --git a/src/gw2/cogs/wvw.py b/src/gw2/cogs/wvw.py
index 0eb0d484..bfc5a22b 100644
--- a/src/gw2/cogs/wvw.py
+++ b/src/gw2/cogs/wvw.py
@@ -4,6 +4,7 @@
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.constants.gw2_teams import get_team_name, is_wr_team_id
from src.gw2.tools import gw2_utils
from src.gw2.tools.gw2_client import Gw2Client
from src.gw2.tools.gw2_cooldowns import GW2CoolDowns
@@ -11,66 +12,89 @@
class GW2WvW(GuildWars2):
- """(Commands related to GW2 World versus World)"""
+ """Guild Wars 2 World vs World commands."""
def __init__(self, bot):
super().__init__(bot)
@GuildWars2.gw2.group()
async def wvw(self, ctx):
- """(Guild Wars 2 Configuration Commands - Admin)
- gw2 wvw info world_name
- gw2 wvw match world_name
- gw2 wvw kdr world_name
+ """Guild Wars 2 World vs World commands.
+
+ Available subcommands:
+ gw2 wvw info [world] - Info about a WvW world
+ gw2 wvw match [world] - WvW match scores
+ gw2 wvw kdr [world] - WvW kill/death ratios
"""
await bot_utils.invoke_subcommand(ctx, "gw2 wvw")
+ async def _resolve_wvw_world_id(self, ctx, gw2_api, world, error_msg):
+ """Resolve a WvW world/team ID from world name or account data.
+
+ Returns the world/team ID, or None if resolution failed (error already sent).
+ """
+ if world:
+ return await gw2_utils.get_world_id(self.bot, world)
+
+ try:
+ gw2_key_dal = Gw2KeyDal(self.bot.db_session, self.bot.log)
+ rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
+ if not rs:
+ await bot_utils.send_error_msg(ctx, error_msg)
+ return None
+
+ api_key = rs[0]["key"]
+ results = await gw2_api.call_api("account", api_key)
+ # Prefer WR team_id over legacy world
+ return results.get("wvw", {}).get("team_id") or results["world"]
+ except APIKeyError:
+ await bot_utils.send_error_msg(ctx, error_msg)
+ return None
+ except Exception as e:
+ await bot_utils.send_error_msg(ctx, e)
+ self.bot.log.error(ctx, e)
+ return None
+
@wvw.command(name="info")
@commands.cooldown(1, GW2CoolDowns.Wvw.seconds, commands.BucketType.user)
async def info(self, ctx, *, world: str = None):
+ """Display WvW information for a world. Defaults to your account's world.
+
+ Usage:
+ gw2 wvw info
+ gw2 wvw info Blackgate
+ """
await ctx.message.channel.typing()
gw2_api = Gw2Client(self.bot)
no_api_key_msg = gw2_messages.NO_API_KEY
- no_api_key_msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- no_api_key_msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
-
- if not world:
- try:
- gw2_key_dal = Gw2KeyDal(self.bot.db_session, self.bot.log)
- rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
- if not rs:
- return await bot_utils.send_error_msg(ctx, no_api_key_msg)
-
- api_key = rs[0]["key"]
- results = await gw2_api.call_api("account", api_key)
- wid = results["world"]
- except APIKeyError:
- return await bot_utils.send_error_msg(ctx, no_api_key_msg)
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e)
- return self.bot.log.error(ctx, e)
- else:
- wid = await gw2_utils.get_world_id(self.bot, world)
+ no_api_key_msg += gw2_messages.key_add_info_help(ctx.prefix)
+ no_api_key_msg += gw2_messages.key_more_info_help(ctx.prefix)
+ wid = await self._resolve_wvw_world_id(ctx, gw2_api, world, no_api_key_msg)
if not wid:
- return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}\n{world}")
+ if world:
+ 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}")
- worldinfo = await gw2_api.call_api(f"worlds?id={wid}")
+
+ # Resolve world info: WR team IDs vs legacy worlds
+ if is_wr_team_id(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}")
+ world_name = worldinfo["name"]
+ population = worldinfo["population"]
except Exception as e:
await bot_utils.send_error_msg(ctx, e)
return ctx.bot.log.error(ctx, e)
- if wid < 2001:
- tier_number = matches["id"].replace("1-", "")
- tier = f"North America Tier {tier_number}"
- else:
- tier_number = matches["id"].replace("2-", "")
- tier = f"Europe Tier {tier_number}"
+ tier = _resolve_tier(matches)
worldcolor = None
for key, value in matches["all_worlds"].items():
@@ -90,7 +114,7 @@ async def info(self, ctx, *, world: str = None):
color = discord.Color.default()
ppt = 0
- score = format(matches["scores"][worldcolor], ',d')
+ score = format(matches["scores"][worldcolor], ",d")
victoryp = matches["victory_points"][worldcolor]
await ctx.message.channel.typing()
@@ -99,8 +123,6 @@ async def info(self, ctx, *, world: str = None):
if objective["owner"].lower() == worldcolor:
ppt += objective["points_tick"]
- population = worldinfo["population"]
-
if population == "VeryHigh":
population = "Very high"
@@ -113,13 +135,12 @@ async def info(self, ctx, *, world: str = None):
kd = round((kills / deaths), 3)
skirmish_now = len(matches["skirmishes"]) - 1
- skirmish = format(matches["skirmishes"][skirmish_now]["scores"][worldcolor], ',d')
+ skirmish = format(matches["skirmishes"][skirmish_now]["scores"][worldcolor], ",d")
- kills = format(matches["kills"][worldcolor], ',d')
- deaths = format(matches["deaths"][worldcolor], ',d')
- title = f"{worldinfo['name']}"
+ kills = format(matches["kills"][worldcolor], ",d")
+ deaths = format(matches["deaths"][worldcolor], ",d")
- embed = discord.Embed(title=title, description=tier, color=color)
+ embed = discord.Embed(title=world_name, description=tier, color=color)
embed.add_field(name="Score", value=chat_formatting.inline(score))
embed.add_field(name="Points per tick", value=chat_formatting.inline(ppt))
embed.add_field(name="Victory Points", value=chat_formatting.inline(victoryp))
@@ -134,50 +155,32 @@ async def info(self, ctx, *, world: str = None):
@wvw.command(name="match")
@commands.cooldown(1, GW2CoolDowns.Wvw.seconds, commands.BucketType.user)
async def match(self, ctx, *, world: str = None):
- """(Info about a wvw match. Defaults to account's world)
+ """Display WvW match scores. Defaults to your account's world.
- gw2 match
- gw2 match world_name
+ Usage:
+ gw2 wvw match
+ gw2 wvw match Blackgate
"""
await ctx.message.channel.typing()
gw2_api = Gw2Client(self.bot)
- if not world:
- try:
- gw2_key_dal = Gw2KeyDal(self.bot.db_session, self.bot.log)
- rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
- if not rs:
- msg = gw2_messages.MISSING_WORLD_NAME
- msg += gw2_messages.MATCH_WORLD_NAME_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
- return await bot_utils.send_error_msg(ctx, msg)
-
- api_key = rs[0]["key"]
- results = await gw2_api.call_api("account", api_key)
- wid = results["world"]
- except APIKeyError:
- return await bot_utils.send_error_msg(ctx, gw2_messages.NO_API_KEY)
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e)
- return self.bot.log.error(ctx, e)
- else:
- wid = await gw2_utils.get_world_id(self.bot, world)
+ no_key_msg = gw2_messages.MISSING_WORLD_NAME
+ no_key_msg += gw2_messages.match_world_name_help(ctx.prefix)
+ no_key_msg += gw2_messages.key_add_info_help(ctx.prefix)
+ no_key_msg += gw2_messages.key_more_info_help(ctx.prefix)
+ wid = await self._resolve_wvw_world_id(ctx, gw2_api, world, no_key_msg)
if not wid:
- return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
+ if world:
+ return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
+ return None
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-", "")
- tier = f"North America Tier {tier_number}"
- else:
- tier_number = matches["id"].replace("2-", "")
- tier = f"Europe Tier {tier_number}"
+ 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)
@@ -204,48 +207,32 @@ async def match(self, ctx, *, world: str = None):
@wvw.command(name="kdr")
@commands.cooldown(1, GW2CoolDowns.Wvw.seconds, commands.BucketType.user)
async def kdr(self, ctx, *, world: str = None):
- """(Info about a wvw kdr match. Defaults to account's world)
- gw2 kdr
- gw2 kdr world_name
+ """Display WvW kill/death ratios. Defaults to your account's world.
+
+ Usage:
+ gw2 wvw kdr
+ gw2 wvw kdr Blackgate
"""
await ctx.message.channel.typing()
gw2_api = Gw2Client(self.bot)
- if not world:
- try:
- gw2_key_dal = Gw2KeyDal(self.bot.db_session, self.bot.log)
- rs = await gw2_key_dal.get_api_key_by_user(ctx.message.author.id)
- if not rs:
- msg = gw2_messages.INVALID_WORLD_NAME
- msg += gw2_messages.MATCH_WORLD_NAME_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_ADD_INFO_HELP.format(ctx.prefix)
- msg += gw2_messages.KEY_MORE_INFO_HELP.format(ctx.prefix)
- return await bot_utils.send_error_msg(ctx, msg)
- api_key = rs[0]["key"]
- results = await gw2_api.call_api("account", api_key)
- wid = results["world"]
- except APIKeyError:
- return await bot_utils.send_error_msg(ctx, gw2_messages.NO_API_KEY)
- except Exception as e:
- await bot_utils.send_error_msg(ctx, e)
- return self.bot.log.error(ctx, e)
- else:
- wid = await gw2_utils.get_world_id(self.bot, world)
+ no_key_msg = gw2_messages.INVALID_WORLD_NAME
+ no_key_msg += gw2_messages.match_world_name_help(ctx.prefix)
+ no_key_msg += gw2_messages.key_add_info_help(ctx.prefix)
+ no_key_msg += gw2_messages.key_more_info_help(ctx.prefix)
+ wid = await self._resolve_wvw_world_id(ctx, gw2_api, world, no_key_msg)
if not wid:
- return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
+ if world:
+ return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
+ return None
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-", "")
- tier = f"{gw2_messages.NA_TIER_TITLE} {tier_number}"
- else:
- tier_number = matches["id"].replace("2-", "")
- tier = f"{gw2_messages.EU_TIER_TITLE}{tier_number}"
+ 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)
@@ -270,6 +257,17 @@ async def kdr(self, ctx, *, world: str = None):
return None
+def _resolve_tier(matches: dict) -> str:
+ """Resolve tier string from match ID (works for both legacy and WR matches)."""
+ match_id = matches["id"]
+ if match_id.startswith("1-"):
+ tier_number = match_id.replace("1-", "")
+ return f"{gw2_messages.NA_TIER_TITLE} {tier_number}"
+ else:
+ tier_number = match_id.replace("2-", "")
+ return f"{gw2_messages.EU_TIER_TITLE} {tier_number}"
+
+
async def _get_map_names_embed_values(ctx, map_color: str, matches):
primary_server_id = []
all_ids = matches["all_worlds"][map_color]
diff --git a/src/gw2/constants/gw2_messages.py b/src/gw2/constants/gw2_messages.py
index 5e29ee76..55e4a39b 100644
--- a/src/gw2/constants/gw2_messages.py
+++ b/src/gw2/constants/gw2_messages.py
@@ -17,24 +17,36 @@
#################################
API_KEY_MESSAGE_REMOVED = "Your message with your API Key was removed for privacy."
API_KEY_MESSAGE_REMOVED_DENIED = (
- "Bot does not have permission to delete the message with your API key.\n"
- "Missing bot permission: `Manage Messages`"
+ "Bot does not have permission to delete the message with your API key.\nMissing bot permission: `Manage Messages`"
)
#################################
# GW2 ACCOUNT/CHARACTERS
#################################
NO_API_KEY = "You dont have an API key registered.\n"
-KEY_ADD_INFO_HELP = "To add or replace an API key send a DM with: `{0}gw2 key add `\n"
-KEY_MORE_INFO_HELP = "To get info about your api key: `{0}gw2 key info`"
INVALID_API_KEY_HELP_MESSAGE = "This API Key is INVALID or no longer exists in gw2 api database.\n"
+
+
+def key_add_info_help(prefix: str) -> str:
+ return f"To add or replace an API key send a DM with: `{prefix}gw2 key add `\n"
+
+
+def key_more_info_help(prefix: str) -> str:
+ return f"To get info about your api key: `{prefix}gw2 key info`"
+
+
API_KEY_NO_PERMISSION = (
- "Your API key doesnt have permission to access your gw2 account.\n" "Please add one key with account permission."
+ "Your API key doesnt have permission to access your gw2 account.\nPlease add one key with account permission."
)
#################################
# GW2 CONFIG
#################################
CONFIG_TITLE = "Guild Wars 2 configurations for"
-CONFIG_MORE_INFO = "For more info: {0}help gw2 config"
+
+
+def config_more_info(prefix: str) -> str:
+ return f"For more info: {prefix}help gw2 config"
+
+
USER_SESSION_TITLE = "GW2 Users Session"
SESSION_ACTIVATED = "Last session `ACTIVATED`\nBot will now record Gw2 users last sessions."
SESSION_DEACTIVATED = "Last session `DEACTIVATED`\nBot will `NOT` record Gw2 users last sessions."
@@ -43,10 +55,16 @@
#################################
KEY_ALREADY_IN_USE = "That API key is already in use by someone else."
KEY_REMOVED_SUCCESSFULLY = "Your GW2 API Key has been deleted successfully."
-KEY_REPLACED_SUCCESSFULLY = "Your API key `{0}` was **replaced** with your new key: `{1}`\n" "Server: `{2}`\n"
-KEY_ADDED_SUCCESSFULLY = (
- "Your key was verified and was **added** to your discord account.\n" "Key: `{0}`\n" "Server: `{1}`\n"
-)
+
+
+def key_replaced_successfully(old: str, new: str, server: str) -> str:
+ return f"Your API key `{old}` was **replaced** with your new key: `{new}`\nServer: `{server}`\n"
+
+
+def key_added_successfully(key_name: str, server_name: str) -> str:
+ return f"Your key was verified and was **added** to your discord account.\nKey: `{key_name}`\nServer: `{server_name}`\n"
+
+
#################################
# GW2 MISC
#################################
@@ -54,13 +72,23 @@
WIKI_SEARCH_RESULTS = "Wiki Search Results"
NO_RESULTS = "No results!"
CLICK_HERE = "Click here"
-DISPLAYIN_WIKI_SEARCH_TITLE = "Displaying **{0}** closest titles that matches **{1}**"
+
+
+def displaying_wiki_search_title(count: int, keyword: str) -> str:
+ return f"Displaying **{count}** closest titles that matches **{keyword}**"
+
+
CLICK_ON_LINK = "Click on link above for more info !!!"
#################################
# GW2 SESSIONS
#################################
SESSION_TITLE = "GW2 Last Session"
-SESSION_NOT_ACTIVE = "Last session is not active on this server.\nTo activate use: `{0}gw2 config session on`"
+
+
+def session_not_active(prefix: str) -> str:
+ return f"Last session is not active on this server.\nTo activate use: `{prefix}gw2 config session on`"
+
+
SESSION_MISSING_PERMISSIONS_TITLE = "To use this command your API key needs to have the following permissions"
ADD_RIGHT_API_KEY_PERMISSIONS = (
"Please add another API key with permissions that are MISSING if you want to use this command."
@@ -102,7 +130,7 @@
"No records were found in your name.\n"
"You are probably trying to execute this command without playing the game.\n"
"Make sure your status is NOT set to invisible in discord.\n"
- "Make sure \"Display current running game as a status message\" is ON.\n"
+ 'Make sure "Display current running game as a status message" is ON.\n'
"Make sure to start discord on your Desktop FIRST before starting Guild Wars 2."
)
#################################
@@ -116,7 +144,12 @@
INVALID_WORLD_NAME = "Invalid world name"
MISSING_WORLD_NAME = "Missing World Name"
WORLD_COLOR_ERROR = "Could not resolve world's color"
-MATCH_WORLD_NAME_HELP = "Use `{0}gw2 match `\nOr register an API key on your account.\n"
+
+
+def match_world_name_help(prefix: str) -> str:
+ return f"Use `{prefix}gw2 match `\nOr register an API key on your account.\n"
+
+
WVW_KDR_TITLE = "WvW Kills/Death Ratings"
NA_TIER_TITLE = "North America Tier"
-EU_TIER_TITLE = "Europe America Tier"
+EU_TIER_TITLE = "Europe Tier"
diff --git a/src/gw2/constants/gw2_teams.py b/src/gw2/constants/gw2_teams.py
new file mode 100644
index 00000000..a92fa6de
--- /dev/null
+++ b/src/gw2/constants/gw2_teams.py
@@ -0,0 +1,49 @@
+"""World Restructuring (WR) team constants for Guild Wars 2 WvW.
+
+Since mid-2024, GW2 uses team-based matchmaking (World Restructuring) instead of
+server-based WvW. Team IDs (11xxx for NA, 12xxx for EU) are not in the /v2/worlds
+API, so names must be hardcoded.
+"""
+
+# NA teams: 11001-11012, EU teams: 12001-12015
+WR_TEAM_NAMES: dict[int, str] = {
+ # North America
+ 11001: "Team 1 (NA)",
+ 11002: "Team 2 (NA)",
+ 11003: "Team 3 (NA)",
+ 11004: "Team 4 (NA)",
+ 11005: "Team 5 (NA)",
+ 11006: "Team 6 (NA)",
+ 11007: "Team 7 (NA)",
+ 11008: "Team 8 (NA)",
+ 11009: "Team 9 (NA)",
+ 11010: "Team 10 (NA)",
+ 11011: "Team 11 (NA)",
+ 11012: "Team 12 (NA)",
+ # Europe
+ 12001: "Team 1 (EU)",
+ 12002: "Team 2 (EU)",
+ 12003: "Team 3 (EU)",
+ 12004: "Team 4 (EU)",
+ 12005: "Team 5 (EU)",
+ 12006: "Team 6 (EU)",
+ 12007: "Team 7 (EU)",
+ 12008: "Team 8 (EU)",
+ 12009: "Team 9 (EU)",
+ 12010: "Team 10 (EU)",
+ 12011: "Team 11 (EU)",
+ 12012: "Team 12 (EU)",
+ 12013: "Team 13 (EU)",
+ 12014: "Team 14 (EU)",
+ 12015: "Team 15 (EU)",
+}
+
+
+def is_wr_team_id(world_id: int) -> bool:
+ """Check if the given ID is a World Restructuring team ID (11xxx or 12xxx)."""
+ return 11001 <= world_id <= 12999
+
+
+def get_team_name(team_id: int) -> str | None:
+ """Get the team name for a WR team ID, or None if not found."""
+ return WR_TEAM_NAMES.get(team_id)
diff --git a/src/gw2/tools/gw2_client.py b/src/gw2/tools/gw2_client.py
index 1c4c0842..9a1a1232 100644
--- a/src/gw2/tools/gw2_client.py
+++ b/src/gw2/tools/gw2_client.py
@@ -58,20 +58,21 @@ async def _handle_api_error(self, response, endpoint):
init_msg = f"{response.status})({endpoint.split('?')[0]}"
- if response.status == 400:
- self._handle_400_error(response.status, err_msg, init_msg)
- elif response.status == 403:
- self._handle_403_error(response.status, err_msg, init_msg)
- elif response.status == 404:
- self._handle_404_error(response.status, endpoint)
- elif response.status == 429:
- self._handle_429_error(init_msg)
- elif response.status in (502, 504):
- self._handle_502_504_error(init_msg)
- elif response.status == 503:
- self._handle_503_error(init_msg, err_msg)
- else:
- self._handle_other_error(response, init_msg, err_msg)
+ match response.status:
+ case 400:
+ self._handle_400_error(response.status, err_msg, init_msg)
+ case 403:
+ self._handle_403_error(response.status, err_msg, init_msg)
+ case 404:
+ self._handle_404_error(response.status, endpoint)
+ case 429:
+ self._handle_429_error(init_msg)
+ case 502 | 504:
+ self._handle_502_504_error(init_msg)
+ case 503:
+ self._handle_503_error(init_msg, err_msg)
+ case _:
+ self._handle_other_error(response, init_msg, err_msg)
def _handle_400_error(self, status, err_msg, init_msg):
"""Handle 400 Bad Request errors."""
diff --git a/src/gw2/tools/gw2_cooldowns.py b/src/gw2/tools/gw2_cooldowns.py
index a3a06e33..71f42970 100644
--- a/src/gw2/tools/gw2_cooldowns.py
+++ b/src/gw2/tools/gw2_cooldowns.py
@@ -16,15 +16,15 @@ class GW2CoolDowns(Enum):
we use a tuple (value, unique_id) and access the actual cooldown via .value[0]
"""
- Account = (1 if variables.DEBUG else _gw2_settings.account_cooldown, 'account')
- ApiKeys = (1 if variables.DEBUG else _gw2_settings.api_keys_cooldown, 'api_keys')
- Characters = (1 if variables.DEBUG else _gw2_settings.characters_cooldown, 'characters')
- Config = (1 if variables.DEBUG else _gw2_settings.config_cooldown, 'config')
- Daily = (1 if variables.DEBUG else _gw2_settings.daily_cooldown, 'daily')
- Misc = (1 if variables.DEBUG else _gw2_settings.misc_cooldown, 'misc')
- Session = (1 if variables.DEBUG else _gw2_settings.session_cooldown, 'session')
- Worlds = (1 if variables.DEBUG else _gw2_settings.worlds_cooldown, 'worlds')
- Wvw = (1 if variables.DEBUG else _gw2_settings.wvw_cooldown, 'wvw')
+ Account = (1 if variables.DEBUG else _gw2_settings.account_cooldown, "account")
+ ApiKeys = (1 if variables.DEBUG else _gw2_settings.api_keys_cooldown, "api_keys")
+ Characters = (1 if variables.DEBUG else _gw2_settings.characters_cooldown, "characters")
+ Config = (1 if variables.DEBUG else _gw2_settings.config_cooldown, "config")
+ Daily = (1 if variables.DEBUG else _gw2_settings.daily_cooldown, "daily")
+ Misc = (1 if variables.DEBUG else _gw2_settings.misc_cooldown, "misc")
+ Session = (1 if variables.DEBUG else _gw2_settings.session_cooldown, "session")
+ Worlds = (1 if variables.DEBUG else _gw2_settings.worlds_cooldown, "worlds")
+ Wvw = (1 if variables.DEBUG else _gw2_settings.wvw_cooldown, "wvw")
def __str__(self) -> str:
"""Return the cooldown value as a string."""
diff --git a/src/gw2/tools/gw2_exceptions.py b/src/gw2/tools/gw2_exceptions.py
index 29877a9d..82191c00 100644
--- a/src/gw2/tools/gw2_exceptions.py
+++ b/src/gw2/tools/gw2_exceptions.py
@@ -9,7 +9,7 @@ def __init__(self, bot: Bot, msg: str):
self.bot = bot
self.message = msg
# Log error when exception is created
- if hasattr(bot, 'log') and bot.log:
+ if hasattr(bot, "log") and bot.log:
bot.log.error(f"GW2 API Error: {msg}")
diff --git a/src/gw2/tools/gw2_utils.py b/src/gw2/tools/gw2_utils.py
index ea5fac4b..50315d33 100644
--- a/src/gw2/tools/gw2_utils.py
+++ b/src/gw2/tools/gw2_utils.py
@@ -22,6 +22,7 @@ def __init__(self):
from src.database.dal.gw2.gw2_session_chars_dal import Gw2SessionCharsDal
from src.database.dal.gw2.gw2_sessions_dal import Gw2SessionsDal
from src.gw2.constants import gw2_messages
+from src.gw2.constants.gw2_teams import get_team_name, is_wr_team_id
from src.gw2.tools.gw2_client import Gw2Client
@@ -200,15 +201,38 @@ async def get_world_id(bot: Bot, world: str | None) -> int | None:
async def get_world_name_population(ctx: commands.Context, world_ids: str) -> list[str] | None:
- """Get world names and population data."""
- try:
- gw2_api = Gw2Client(ctx.bot)
- results = await gw2_api.call_api(f"worlds?ids={world_ids}")
+ """Get world names and population data.
- if not results:
+ Handles both legacy world IDs (1xxx/2xxx) via /v2/worlds API and
+ World Restructuring team IDs (11xxx/12xxx) via hardcoded lookup.
+ """
+ try:
+ id_list = [int(wid.strip()) for wid in world_ids.split(",") if wid.strip()]
+ if not id_list:
return None
- return [world["name"] for world in results]
+ legacy_ids = [wid for wid in id_list if not is_wr_team_id(wid)]
+
+ # Build name lookup from API for legacy IDs
+ legacy_names: dict[int, str] = {}
+ if legacy_ids:
+ gw2_api = Gw2Client(ctx.bot)
+ legacy_ids_str = ",".join(str(wid) for wid in legacy_ids)
+ results = await gw2_api.call_api(f"worlds?ids={legacy_ids_str}")
+ if results:
+ for world in results:
+ legacy_names[world["id"]] = world["name"]
+
+ # Resolve all IDs in original order
+ names: list[str] = []
+ for wid in id_list:
+ if is_wr_team_id(wid):
+ name = get_team_name(wid)
+ names.append(name if name else f"Team {wid}")
+ elif wid in legacy_names:
+ names.append(legacy_names[wid])
+
+ return names if names else None
except Exception as e:
ctx.bot.log.error(f"Error fetching world names for IDs {world_ids}: {e}")
@@ -344,9 +368,10 @@ async def get_user_stats(bot: Bot, api_key: str) -> dict | None:
def _create_initial_user_stats(account_data: dict) -> dict:
"""Create initial user stats structure."""
+ wvw_rank = account_data.get("wvw", {}).get("rank") or account_data.get("wvw_rank", 0)
return {
"acc_name": account_data["name"],
- "wvw_rank": account_data["wvw_rank"],
+ "wvw_rank": wvw_rank,
"gold": 0,
"karma": 0,
"laurels": 0,
@@ -438,19 +463,21 @@ def get_wvw_rank_title(rank: int) -> str:
def _get_wvw_rank_prefix(rank: int) -> str:
"""Get WvW rank prefix (Bronze, Silver, etc.)."""
- if 150 <= rank <= 619:
- return "Bronze"
- elif 620 <= rank <= 1394:
- return "Silver"
- elif 1395 <= rank <= 2544:
- return "Gold"
- elif 2545 <= rank <= 4094:
- return "Platinum"
- elif 4095 <= rank <= 6444:
- return "Mithril"
- elif rank >= 6445:
- return "Diamond"
- return ""
+ match rank:
+ case r if 150 <= r <= 619:
+ return "Bronze"
+ case r if 620 <= r <= 1394:
+ return "Silver"
+ case r if 1395 <= r <= 2544:
+ return "Gold"
+ case r if 2545 <= r <= 4094:
+ return "Platinum"
+ case r if 4095 <= r <= 6444:
+ return "Mithril"
+ case r if r >= 6445:
+ return "Diamond"
+ case _:
+ return ""
def _get_wvw_rank_title(rank: int) -> str:
@@ -490,25 +517,27 @@ def _get_wvw_rank_title(rank: int) -> str:
def get_pvp_rank_title(rank: int) -> str:
"""Get PvP rank title based on rank number."""
- if 1 <= rank <= 9:
- return "Rabbit"
- elif 10 <= rank <= 19:
- return "Deer"
- elif 20 <= rank <= 29:
- return "Dolyak"
- elif 30 <= rank <= 39:
- return "Wolf"
- elif 40 <= rank <= 49:
- return "Tiger"
- elif 50 <= rank <= 59:
- return "Bear"
- elif 60 <= rank <= 69:
- return "Shark"
- elif 70 <= rank <= 79:
- return "Phoenix"
- elif rank >= 80:
- return "Dragon"
- return ""
+ match rank:
+ case r if 1 <= r <= 9:
+ return "Rabbit"
+ case r if 10 <= r <= 19:
+ return "Deer"
+ case r if 20 <= r <= 29:
+ return "Dolyak"
+ case r if 30 <= r <= 39:
+ return "Wolf"
+ case r if 40 <= r <= 49:
+ return "Tiger"
+ case r if 50 <= r <= 59:
+ return "Bear"
+ case r if 60 <= r <= 69:
+ return "Shark"
+ case r if 70 <= r <= 79:
+ return "Phoenix"
+ case r if r >= 80:
+ return "Dragon"
+ case _:
+ return ""
def format_gold(currency: str) -> str:
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index d27755e7..d2cd7657 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -32,7 +32,7 @@
def postgres_container():
from testcontainers.postgres import PostgresContainer
- with PostgresContainer("postgres:17", driver=None) as pg:
+ with PostgresContainer("postgres:latest", driver=None) as pg:
yield pg
diff --git a/tests/integration/test_alembic_migrations.py b/tests/integration/test_alembic_migrations.py
index d1764c4e..01eee84e 100644
--- a/tests/integration/test_alembic_migrations.py
+++ b/tests/integration/test_alembic_migrations.py
@@ -54,7 +54,7 @@ async def _execute(db_session, stmt):
async def test_updated_at_function_exists(db_session):
rows = await _fetch_rows(
db_session,
- text("SELECT routine_name FROM information_schema.routines " "WHERE routine_name = 'updated_at_column_func'"),
+ text("SELECT routine_name FROM information_schema.routines WHERE routine_name = 'updated_at_column_func'"),
)
assert len(rows) == 1
assert rows[0]["routine_name"] == "updated_at_column_func"
@@ -83,9 +83,7 @@ async def test_all_triggers_exist(db_session):
rows = await _fetch_rows(
db_session,
text(
- "SELECT trigger_name FROM information_schema.triggers "
- "WHERE trigger_schema = 'public' "
- "ORDER BY trigger_name"
+ "SELECT trigger_name FROM information_schema.triggers WHERE trigger_schema = 'public' ORDER BY trigger_name"
),
)
trigger_names = [r["trigger_name"] for r in rows]
@@ -183,7 +181,7 @@ async def test_servers_columns(db_session):
async def test_servers_index_exists(db_session):
rows = await _fetch_rows(
db_session,
- text("SELECT indexname FROM pg_indexes " "WHERE tablename = 'servers' AND indexname = 'ix_servers_id'"),
+ text("SELECT indexname FROM pg_indexes WHERE tablename = 'servers' AND indexname = 'ix_servers_id'"),
)
assert len(rows) == 1
@@ -264,9 +262,7 @@ async def test_custom_commands_cascade_delete(db_session):
await _execute(db_session, text("INSERT INTO servers (id, name) VALUES (10003, 'Cascade CC')"))
await _execute(
db_session,
- text(
- "INSERT INTO custom_commands (server_id, name, description) " "VALUES (10003, 'temp', 'will be cascaded')"
- ),
+ text("INSERT INTO custom_commands (server_id, name, description) VALUES (10003, 'temp', 'will be cascaded')"),
)
await _execute(db_session, text("DELETE FROM servers WHERE id = 10003"))
rows = await _fetch_rows(
@@ -318,7 +314,7 @@ async def test_profanity_filters_cascade_delete(db_session):
await _execute(db_session, text("INSERT INTO servers (id, name) VALUES (10005, 'Cascade PF')"))
await _execute(
db_session,
- text("INSERT INTO profanity_filters (server_id, channel_id, channel_name) " "VALUES (10005, 80002, 'spam')"),
+ text("INSERT INTO profanity_filters (server_id, channel_id, channel_name) VALUES (10005, 80002, 'spam')"),
)
await _execute(db_session, text("DELETE FROM servers WHERE id = 10005"))
rows = await _fetch_rows(
@@ -350,7 +346,7 @@ async def test_dice_rolls_insert_and_read(db_session):
await _execute(db_session, text("INSERT INTO servers (id, name) VALUES (10006, 'Dice Server')"))
await _execute(
db_session,
- text("INSERT INTO dice_rolls (server_id, user_id, roll, dice_size) " "VALUES (10006, 555, 18, 20)"),
+ text("INSERT INTO dice_rolls (server_id, user_id, roll, dice_size) VALUES (10006, 555, 18, 20)"),
)
rows = await _fetch_rows(
db_session,
diff --git a/tests/integration/test_gw2_session_chars_dal.py b/tests/integration/test_gw2_session_chars_dal.py
index b2c1e62d..8f574d5a 100644
--- a/tests/integration/test_gw2_session_chars_dal.py
+++ b/tests/integration/test_gw2_session_chars_dal.py
@@ -28,9 +28,8 @@ async def _insert_char_directly(db_session, session_id, user_id, name, professio
await db_utils.insert(stmt)
-async def test_insert_session_char_missing_start_field(db_session, log):
- """The DAL's insert_session_char does not set the `start` boolean, which is NOT NULL.
- This test documents that the DAL raises IntegrityError against a real DB."""
+async def test_insert_session_char_with_start_field(db_session, log):
+ """The DAL's insert_session_char now correctly sets start/end booleans."""
sessions_dal = Gw2SessionsDal(db_session, log)
chars_dal = Gw2SessionCharsDal(db_session, log)
@@ -47,9 +46,20 @@ async def test_insert_session_char_missing_start_field(db_session, log):
"profession": "Warrior",
"deaths": 10,
}
- insert_args = {"session_id": session_id, "user_id": USER_ID, "api_key": API_KEY}
- with pytest.raises(IntegrityError):
- await chars_dal.insert_session_char(gw2_api, ["TestChar"], insert_args)
+ 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)
+
+ # Verify the data was inserted correctly
+ results = await chars_dal.get_all_start_characters(USER_ID)
+ assert isinstance(results, list)
+ assert len(results) >= 1
async def test_get_all_start_characters(db_session, log):
diff --git a/tests/unit/bot/cogs/admin/test_admin.py b/tests/unit/bot/cogs/admin/test_admin.py
index a7bf1487..0f335193 100644
--- a/tests/unit/bot/cogs/admin/test_admin.py
+++ b/tests/unit/bot/cogs/admin/test_admin.py
@@ -7,7 +7,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.admin.admin import Admin
from src.bot.constants import messages
@@ -69,7 +69,7 @@ def test_init(self, mock_bot):
assert cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.invoke_subcommand')
+ @patch("src.bot.cogs.admin.admin.bot_utils.invoke_subcommand")
async def test_admin_group_command(self, mock_invoke, admin_cog, mock_ctx):
"""Test admin group command."""
mock_invoke.return_value = "mock_command"
@@ -80,7 +80,7 @@ async def test_admin_group_command(self, mock_invoke, admin_cog, mock_ctx):
assert result == "mock_command"
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_command_success(self, mock_send_embed, admin_cog, mock_ctx):
"""Test successful botgame command execution."""
game = "Minecraft"
@@ -92,7 +92,7 @@ async def test_botgame_command_success(self, mock_send_embed, admin_cog, mock_ct
# Verify bot presence change
admin_cog.bot.change_presence.assert_called_once()
- activity_call = admin_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = admin_cog.bot.change_presence.call_args[1]["activity"]
assert isinstance(activity_call, discord.Game)
assert activity_call.name == f"{game} | !help"
@@ -101,12 +101,12 @@ async def test_botgame_command_success(self, mock_send_embed, admin_cog, mock_ct
embed_call = mock_send_embed.call_args_list[0]
embed = embed_call[0][1]
- assert messages.BOT_ANNOUNCE_PLAYING.format(game) in embed.description
+ assert messages.bot_announce_playing(game) in embed.description
assert embed.author.name == "TestBot"
assert embed.author.icon_url == "https://example.com/bot_avatar.png"
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_command_with_bg_timer_warning(self, mock_send_embed, admin_cog, mock_ctx):
"""Test botgame command with background activity timer warning."""
game = "Reading documentation"
@@ -119,11 +119,11 @@ async def test_botgame_command_with_bg_timer_warning(self, mock_send_embed, admi
# Check success embed
success_embed = mock_send_embed.call_args_list[0][0][1]
- assert messages.BOT_ANNOUNCE_PLAYING.format(game) in success_embed.description
+ assert messages.bot_announce_playing(game) in success_embed.description
# Check warning embed
warning_embed = mock_send_embed.call_args_list[1][0][1]
- assert messages.BG_TASK_WARNING.format(300) in warning_embed.description
+ assert messages.bg_task_warning(300) in warning_embed.description
# Verify warning embed was sent with dm=False
warning_call = mock_send_embed.call_args_list[1]
@@ -135,12 +135,12 @@ async def test_warn_about_bg_activity_timer_enabled(self, admin_cog, mock_ctx):
"""Test _warn_about_bg_activity_timer when timer is enabled."""
admin_cog.bot.settings["bot"]["BGActivityTimer"] = 600
- with patch('src.bot.cogs.admin.admin.bot_utils.send_embed') as mock_send_embed:
+ with patch("src.bot.cogs.admin.admin.bot_utils.send_embed") as mock_send_embed:
await admin_cog._warn_about_bg_activity_timer(mock_ctx)
mock_send_embed.assert_called_once()
embed = mock_send_embed.call_args[0][1]
- assert messages.BG_TASK_WARNING.format(600) in embed.description
+ assert messages.bg_task_warning(600) in embed.description
# Verify dm=False parameter
assert mock_send_embed.call_args[0][2] is False
@@ -149,7 +149,7 @@ async def test_warn_about_bg_activity_timer_disabled(self, admin_cog, mock_ctx):
"""Test _warn_about_bg_activity_timer when timer is disabled."""
admin_cog.bot.settings["bot"]["BGActivityTimer"] = 0
- with patch('src.bot.cogs.admin.admin.bot_utils.send_embed') as mock_send_embed:
+ with patch("src.bot.cogs.admin.admin.bot_utils.send_embed") as mock_send_embed:
await admin_cog._warn_about_bg_activity_timer(mock_ctx)
mock_send_embed.assert_not_called()
@@ -159,7 +159,7 @@ async def test_warn_about_bg_activity_timer_none(self, admin_cog, mock_ctx):
"""Test _warn_about_bg_activity_timer when timer is None."""
admin_cog.bot.settings["bot"]["BGActivityTimer"] = None
- with patch('src.bot.cogs.admin.admin.bot_utils.send_embed') as mock_send_embed:
+ with patch("src.bot.cogs.admin.admin.bot_utils.send_embed") as mock_send_embed:
await admin_cog._warn_about_bg_activity_timer(mock_ctx)
mock_send_embed.assert_not_called()
@@ -173,7 +173,7 @@ def test_create_admin_embed(self, admin_cog):
assert embed.description == description
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_with_special_characters(self, mock_send_embed, admin_cog, mock_ctx):
"""Test botgame command with special characters in game name."""
game = "Game with émojis 🎮 and spéciál chars!"
@@ -181,7 +181,7 @@ async def test_botgame_with_special_characters(self, mock_send_embed, admin_cog,
await admin_cog.botgame.callback(admin_cog, mock_ctx, game=game)
# Verify the game name is properly handled
- activity_call = admin_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = admin_cog.bot.change_presence.call_args[1]["activity"]
expected_name = f"{game} | !help"
assert activity_call.name == expected_name
@@ -190,7 +190,7 @@ async def test_botgame_with_special_characters(self, mock_send_embed, admin_cog,
assert game in embed.description
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_with_empty_game_name(self, mock_send_embed, admin_cog, mock_ctx):
"""Test botgame command with empty game name."""
game = ""
@@ -198,11 +198,11 @@ async def test_botgame_with_empty_game_name(self, mock_send_embed, admin_cog, mo
await admin_cog.botgame.callback(admin_cog, mock_ctx, game=game)
# Even empty game name should work
- activity_call = admin_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = admin_cog.bot.change_presence.call_args[1]["activity"]
assert activity_call.name == " | !help"
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_with_no_bot_avatar(self, mock_send_embed, admin_cog, mock_ctx):
"""Test botgame command when bot has no avatar."""
admin_cog.bot.user.avatar = None
@@ -220,10 +220,10 @@ async def test_botgame_different_prefix(self, admin_cog, mock_ctx):
admin_cog.bot.command_prefix = ("$",)
game = "Test Game"
- with patch('src.bot.cogs.admin.admin.bot_utils.send_embed'):
+ with patch("src.bot.cogs.admin.admin.bot_utils.send_embed"):
await admin_cog.botgame.callback(admin_cog, mock_ctx, game=game)
- activity_call = admin_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = admin_cog.bot.change_presence.call_args[1]["activity"]
assert activity_call.name == f"{game} | $help"
@pytest.mark.asyncio
@@ -239,7 +239,7 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.admin.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.admin.bot_utils.send_embed")
async def test_botgame_preserve_embed_formatting(self, mock_send_embed, admin_cog, mock_ctx):
"""Test that botgame preserves proper embed formatting."""
game = "Markdown **test** `code`"
@@ -249,21 +249,21 @@ async def test_botgame_preserve_embed_formatting(self, mock_send_embed, admin_co
embed = mock_send_embed.call_args[0][1]
# Should be wrapped in code block
assert "```" in embed.description
- assert messages.BOT_ANNOUNCE_PLAYING.format(game) in embed.description
+ assert messages.bot_announce_playing(game) in embed.description
def test_admin_cog_inheritance(self, admin_cog):
"""Test that Admin cog properly inherits from commands.Cog."""
assert isinstance(admin_cog, commands.Cog)
- assert hasattr(admin_cog, 'bot')
+ assert hasattr(admin_cog, "bot")
@pytest.mark.asyncio
async def test_botgame_activity_type(self, admin_cog, mock_ctx):
"""Test that botgame creates the correct activity type."""
game = "Test Activity"
- with patch('src.bot.cogs.admin.admin.bot_utils.send_embed'):
+ with patch("src.bot.cogs.admin.admin.bot_utils.send_embed"):
await admin_cog.botgame.callback(admin_cog, mock_ctx, game=game)
- activity_call = admin_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = admin_cog.bot.change_presence.call_args[1]["activity"]
assert isinstance(activity_call, discord.Game)
assert activity_call.type == discord.ActivityType.playing
diff --git a/tests/unit/bot/cogs/admin/test_config.py b/tests/unit/bot/cogs/admin/test_config.py
index e3e71f01..1469c501 100644
--- a/tests/unit/bot/cogs/admin/test_config.py
+++ b/tests/unit/bot/cogs/admin/test_config.py
@@ -8,7 +8,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.admin.config import Config, _get_switch_status
from src.bot.constants import messages
@@ -85,7 +85,7 @@ def test_init(self, mock_bot):
assert cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.invoke_subcommand')
+ @patch("src.bot.cogs.admin.config.bot_utils.invoke_subcommand")
async def test_config_group_command(self, mock_invoke, config_cog, mock_ctx):
"""Test config group command."""
mock_invoke.return_value = "mock_command"
@@ -140,8 +140,8 @@ def test_get_switch_status_mixed_case(self):
# Test join message configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_join_message_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling join messages."""
mock_dal = AsyncMock()
@@ -160,8 +160,8 @@ async def test_config_join_message_on(self, mock_send_embed, mock_dal_class, con
assert "ON" in embed.description
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_join_message_off(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test disabling join messages."""
mock_dal = AsyncMock()
@@ -179,8 +179,8 @@ async def test_config_join_message_off(self, mock_send_embed, mock_dal_class, co
# Test leave message configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_leave_message_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling leave messages."""
mock_dal = AsyncMock()
@@ -196,8 +196,8 @@ async def test_config_leave_message_on(self, mock_send_embed, mock_dal_class, co
# Test server message configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_server_message_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling server messages."""
mock_dal = AsyncMock()
@@ -213,8 +213,8 @@ async def test_config_server_message_on(self, mock_send_embed, mock_dal_class, c
# Test member message configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_member_message_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling member messages."""
mock_dal = AsyncMock()
@@ -230,8 +230,8 @@ async def test_config_member_message_on(self, mock_send_embed, mock_dal_class, c
# Test block invisible members configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_block_invis_members_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling blocking invisible members."""
mock_dal = AsyncMock()
@@ -247,8 +247,8 @@ async def test_config_block_invis_members_on(self, mock_send_embed, mock_dal_cla
# Test bot reactions configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_bot_word_reactions_on(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""Test enabling bot word reactions."""
mock_dal = AsyncMock()
@@ -264,8 +264,8 @@ async def test_config_bot_word_reactions_on(self, mock_send_embed, mock_dal_clas
# Test profanity filter configuration
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_pfilter_on_success(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -288,8 +288,8 @@ async def test_config_pfilter_on_success(
assert embed.color == discord.Color.green()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_pfilter_off_success(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -310,7 +310,7 @@ async def test_config_pfilter_off_success(
assert embed.color == discord.Color.red()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg', new_callable=AsyncMock)
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg", new_callable=AsyncMock)
async def test_config_pfilter_missing_arguments(self, mock_send_error, config_cog, mock_ctx):
"""Test profanity filter with missing arguments."""
from src.bot.cogs.admin.config import config_pfilter
@@ -322,7 +322,7 @@ async def test_config_pfilter_missing_arguments(self, mock_send_error, config_co
assert messages.MISING_REUIRED_ARGUMENT in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg', new_callable=AsyncMock)
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg", new_callable=AsyncMock)
async def test_config_pfilter_invalid_channel_id(self, mock_send_error, config_cog, mock_ctx):
"""Test profanity filter with invalid channel ID."""
from src.bot.cogs.admin.config import config_pfilter
@@ -334,7 +334,7 @@ async def test_config_pfilter_invalid_channel_id(self, mock_send_error, config_c
assert messages.CHANNEL_ID_NOT_FOUND in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg', new_callable=AsyncMock)
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg", new_callable=AsyncMock)
async def test_config_pfilter_channel_not_found(self, mock_send_error, config_cog, mock_ctx):
"""Test profanity filter with non-existent channel."""
mock_ctx.guild.text_channels = []
@@ -349,7 +349,7 @@ async def test_config_pfilter_channel_not_found(self, mock_send_error, config_co
assert messages.CHANNEL_ID_NOT_FOUND in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg")
async def test_config_pfilter_no_permissions(self, mock_send_error, config_cog, mock_ctx, mock_text_channel):
"""Test profanity filter without bot permissions."""
mock_ctx.guild.text_channels = [mock_text_channel]
@@ -379,11 +379,11 @@ async def test_config_pfilter_invalid_status(self, config_cog, mock_ctx, mock_te
# Test config list
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
- @patch('src.bot.cogs.admin.config.chat_formatting.green_text')
- @patch('src.bot.cogs.admin.config.chat_formatting.red_text')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
+ @patch("src.bot.cogs.admin.config.chat_formatting.green_text")
+ @patch("src.bot.cogs.admin.config.chat_formatting.red_text")
async def test_config_list_success(
self,
mock_red_text,
@@ -428,13 +428,13 @@ async def test_config_list_success(
# Check the embed sent to DM
dm_call_args = mock_ctx.author.send.call_args
- embed = dm_call_args[1]['embed'] # embed is passed as keyword argument
+ embed = dm_call_args[1]["embed"] # embed is passed as keyword argument
assert embed.author.name == "Configurations for Test Server"
assert len(embed.fields) == 7 # 6 config options + profanity filter
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_config_list_no_profanity_channels(
self, mock_pf_dal_class, mock_servers_dal_class, config_cog, mock_ctx
):
@@ -464,14 +464,14 @@ async def test_config_list_no_profanity_channels(
# Check the embed sent to DM
dm_call_args = mock_ctx.author.send.call_args
- embed = dm_call_args[1]['embed'] # embed is passed as keyword argument
+ embed = dm_call_args[1]["embed"] # embed is passed as keyword argument
# Check that the profanity filter field shows "No channels listed"
pf_field = next(field for field in embed.fields if "pfilter" in field.name.lower())
assert messages.NO_CHANNELS_LISTED in pf_field.value
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
async def test_config_list_no_guild_icon(self, mock_servers_dal_class, config_cog, mock_ctx):
"""Test listing configurations when guild has no icon."""
mock_ctx.guild.icon = None
@@ -487,7 +487,7 @@ async def test_config_list_no_guild_icon(self, mock_servers_dal_class, config_co
"bot_word_reactions": False,
}
- with patch('src.bot.cogs.admin.config.ProfanityFilterDal') as mock_pf_dal_class:
+ with patch("src.bot.cogs.admin.config.ProfanityFilterDal") as mock_pf_dal_class:
mock_pf_dal = AsyncMock()
mock_pf_dal_class.return_value = mock_pf_dal
mock_pf_dal.get_all_server_profanity_filter_channels.return_value = []
@@ -498,7 +498,7 @@ async def test_config_list_no_guild_icon(self, mock_servers_dal_class, config_co
# Check the embed sent to DM
dm_call_args = mock_ctx.author.send.call_args
- embed = dm_call_args[1]['embed'] # embed is passed as keyword argument
+ embed = dm_call_args[1]["embed"] # embed is passed as keyword argument
assert embed.author.icon_url is None
assert embed.thumbnail.url is None
@@ -520,11 +520,11 @@ def test_config_cog_inheritance(self, config_cog):
from src.bot.cogs.admin.admin import Admin
assert isinstance(config_cog, Admin)
- assert hasattr(config_cog, 'bot')
+ assert hasattr(config_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg")
async def test_config_pfilter_channel_get_returns_none(
self, mock_send_error, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -542,8 +542,8 @@ async def test_config_pfilter_channel_get_returns_none(
assert "98765" in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_pfilter_admin_permission_only(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -565,8 +565,8 @@ async def test_config_pfilter_admin_permission_only(
mock_send_embed.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_config_pfilter_manage_messages_permission_only(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
diff --git a/tests/unit/bot/cogs/admin/test_config_extra.py b/tests/unit/bot/cogs/admin/test_config_extra.py
index f4e8d147..aa3d5633 100644
--- a/tests/unit/bot/cogs/admin/test_config_extra.py
+++ b/tests/unit/bot/cogs/admin/test_config_extra.py
@@ -9,7 +9,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, PropertyMock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.admin.config import Config, ConfigView, _get_switch_status
from src.bot.constants import messages
@@ -127,8 +127,8 @@ class TestConfigPfilterChannelResolution:
"""Tests for config_pfilter channel resolution (around line 208)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_no_channel_specified_uses_current(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx
):
@@ -144,8 +144,8 @@ async def test_pfilter_no_channel_specified_uses_current(
mock_send_embed.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_channel_found_by_numeric_id(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -162,8 +162,8 @@ async def test_pfilter_channel_found_by_numeric_id(
mock_dal.insert_profanity_filter_channel.assert_called_once_with(99999, 22222, "general", 12345)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_channel_found_by_name(
self, mock_send_embed, mock_dal_class, config_cog, mock_ctx, mock_text_channel
):
@@ -174,7 +174,7 @@ async def test_pfilter_channel_found_by_name(
mock_dal = AsyncMock()
mock_dal_class.return_value = mock_dal
- with patch('src.bot.cogs.admin.config.discord.utils.get', return_value=mock_text_channel):
+ with patch("src.bot.cogs.admin.config.discord.utils.get", return_value=mock_text_channel):
from src.bot.cogs.admin.config import config_pfilter
await config_pfilter(mock_ctx, subcommand_passed="on general")
@@ -182,12 +182,12 @@ async def test_pfilter_channel_found_by_name(
mock_dal.insert_profanity_filter_channel.assert_called_once_with(99999, 22222, "general", 12345)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg', new_callable=AsyncMock)
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg", new_callable=AsyncMock)
async def test_pfilter_channel_not_found_by_id_or_name(self, mock_send_error, config_cog, mock_ctx):
"""When channel is not found by ID or name, sends error (lines 221-224)."""
mock_ctx.guild.get_channel.return_value = None
- with patch('src.bot.cogs.admin.config.discord.utils.get', return_value=None):
+ with patch("src.bot.cogs.admin.config.discord.utils.get", return_value=None):
from src.bot.cogs.admin.config import config_pfilter
await config_pfilter(mock_ctx, subcommand_passed="on nonexistent")
@@ -198,7 +198,7 @@ async def test_pfilter_channel_not_found_by_id_or_name(self, mock_send_error, co
assert "nonexistent" in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.bot_utils.send_error_msg', new_callable=AsyncMock)
+ @patch("src.bot.cogs.admin.config.bot_utils.send_error_msg", new_callable=AsyncMock)
async def test_pfilter_on_no_bot_permissions(self, mock_send_error, config_cog, mock_ctx):
"""When 'on' status with insufficient bot permissions, sends error (lines 231-233)."""
mock_ctx.guild.me.guild_permissions.administrator = False
@@ -214,8 +214,8 @@ async def test_pfilter_on_no_bot_permissions(self, mock_send_error, config_cog,
assert messages.BOT_MISSING_MANAGE_MESSAGES_PERMISSION in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_off_deletes(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""When 'off' status, deletes channel filter (lines 240-243)."""
mock_dal = AsyncMock()
@@ -238,8 +238,8 @@ async def test_pfilter_invalid_status_raises(self, config_cog, mock_ctx):
await config_pfilter(mock_ctx, subcommand_passed="maybe")
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_on_uppercase(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""When 'ON' (uppercase) status, inserts channel filter (line 229)."""
mock_dal = AsyncMock()
@@ -252,8 +252,8 @@ async def test_pfilter_on_uppercase(self, mock_send_embed, mock_dal_class, confi
mock_dal.insert_profanity_filter_channel.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
- @patch('src.bot.cogs.admin.config.bot_utils.send_embed')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
+ @patch("src.bot.cogs.admin.config.bot_utils.send_embed")
async def test_pfilter_off_uppercase(self, mock_send_embed, mock_dal_class, config_cog, mock_ctx):
"""When 'OFF' (uppercase) status, deletes channel filter (line 240)."""
mock_dal = AsyncMock()
@@ -304,7 +304,7 @@ async def test_handle_update_spam_clicking_wait_message(self, config_view, mock_
assert "Please wait" in interaction.response.send_message.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
async def test_handle_update_successful(self, mock_dal_class, config_view, mock_ctx, server_config):
"""When update succeeds, updates config and edits message (lines 414-458)."""
mock_dal = AsyncMock()
@@ -329,7 +329,7 @@ async def test_handle_update_successful(self, mock_dal_class, config_view, mock_
mock_child = MagicMock()
mock_child.disabled = False
mock_child.style = discord.ButtonStyle.success
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
await config_view._handle_update(interaction, button, "msg_on_join", update_method, "Join Messages")
interaction.response.defer.assert_called_once()
@@ -344,7 +344,7 @@ async def test_handle_update_successful(self, mock_dal_class, config_view, mock_
assert config_view._updating is False
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
async def test_handle_update_exception_error_message(self, mock_dal_class, config_view, mock_ctx, server_config):
"""When update raises exception, sends error message (lines 461-464)."""
interaction = MagicMock()
@@ -363,17 +363,17 @@ async def test_handle_update_exception_error_message(self, mock_dal_class, confi
mock_child = MagicMock()
mock_child.disabled = False
mock_child.style = discord.ButtonStyle.success
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
await config_view._handle_update(interaction, button, "msg_on_join", update_method, "Join Messages")
# Should have error response
last_call = interaction.edit_original_response.call_args_list[-1]
- assert "Error updating configuration" in last_call[1]['content']
+ assert "Error updating configuration" in last_call[1]["content"]
# _updating should be reset to False in finally block
assert config_view._updating is False
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
async def test_handle_update_toggles_from_false_to_true(self, mock_dal_class, config_view, mock_ctx, server_config):
"""When toggling from False to True, button becomes success style (lines 447-448)."""
mock_dal = AsyncMock()
@@ -398,14 +398,14 @@ async def test_handle_update_toggles_from_false_to_true(self, mock_dal_class, co
mock_child = MagicMock()
mock_child.disabled = False
mock_child.style = discord.ButtonStyle.success
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
await config_view._handle_update(interaction, button, "msg_on_leave", update_method, "Leave Messages")
assert server_config["msg_on_leave"] is True
assert button.style == discord.ButtonStyle.success
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ServersDal')
+ @patch("src.bot.cogs.admin.config.ServersDal")
async def test_handle_update_disables_buttons_during_processing(
self, mock_dal_class, config_view, mock_ctx, server_config
):
@@ -431,7 +431,7 @@ async def test_handle_update_disables_buttons_during_processing(
mock_child.disabled = False
mock_child.style = discord.ButtonStyle.success
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
await config_view._handle_update(interaction, button, "msg_on_join", update_method, "Join Messages")
# After completion, _updating should be False
@@ -453,7 +453,7 @@ async def test_restore_buttons_sets_correct_styles(self, config_view, server_con
# Use property mock for children iteration
with patch.object(
- type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
+ type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
):
await config_view._restore_buttons()
@@ -480,7 +480,7 @@ async def test_restore_buttons_enables_all_children(self, config_view, server_co
mock_child2.disabled = True
with patch.object(
- type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
+ type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
):
await config_view._restore_buttons()
@@ -492,7 +492,7 @@ class TestConfigViewCreateUpdatedEmbed:
"""Tests for ConfigView._create_updated_embed (lines 494-559)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_create_updated_embed_with_pfilter_channels(self, mock_dal_class, config_view, server_config):
"""Creates embed with profanity filter channels listed (lines 504-506)."""
mock_dal = AsyncMock()
@@ -513,7 +513,7 @@ async def test_create_updated_embed_with_pfilter_channels(self, mock_dal_class,
assert "random" in pf_field.value
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_create_updated_embed_without_pfilter_channels(self, mock_dal_class, config_view, server_config):
"""Creates embed without profanity filter channels (lines 507-508)."""
mock_dal = AsyncMock()
@@ -527,7 +527,7 @@ async def test_create_updated_embed_without_pfilter_channels(self, mock_dal_clas
assert messages.NO_CHANNELS_LISTED in pf_field.value
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_create_updated_embed_status_indicators(self, mock_dal_class, config_view, server_config):
"""Status indicators match config (lines 510-512, 522-556)."""
mock_dal = AsyncMock()
@@ -544,7 +544,7 @@ async def test_create_updated_embed_status_indicators(self, mock_dal_class, conf
assert "OFF" in leave_field.value
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_create_updated_embed_no_guild_icon(self, mock_dal_class, mock_ctx, server_config):
"""Handles no guild icon gracefully (line 518)."""
mock_ctx.guild.icon = None
@@ -566,7 +566,7 @@ async def test_create_updated_embed_no_guild_icon(self, mock_dal_class, mock_ctx
assert embed.thumbnail.url is None
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.config.ProfanityFilterDal')
+ @patch("src.bot.cogs.admin.config.ProfanityFilterDal")
async def test_create_updated_embed_footer(self, mock_dal_class, config_view, server_config):
"""Embed footer contains help info (line 558)."""
mock_dal = AsyncMock()
@@ -588,7 +588,7 @@ class TestConfigViewButtonCallbacks:
def _get_real_config_view(self, mock_ctx, server_config):
"""Create a ConfigView with real button methods but no event loop dependency."""
- with patch.object(discord.ui.View, '__init__', lambda self, **kwargs: None):
+ with patch.object(discord.ui.View, "__init__", lambda self, **kwargs: None):
view = object.__new__(ConfigView)
view.ctx = mock_ctx
view.server_config = server_config
@@ -714,7 +714,7 @@ async def test_on_timeout_disables_buttons(self, config_view):
mock_child2.disabled = False
with patch.object(
- type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
+ type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child1, mock_child2]
):
await config_view.on_timeout()
@@ -732,7 +732,7 @@ async def test_on_timeout_message_not_found(self, config_view):
mock_child = MagicMock()
mock_child.disabled = False
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
# Should not raise
await config_view.on_timeout()
@@ -748,7 +748,7 @@ async def test_on_timeout_http_exception(self, config_view):
mock_child = MagicMock()
mock_child.disabled = False
- with patch.object(type(config_view), 'children', new_callable=PropertyMock, return_value=[mock_child]):
+ with patch.object(type(config_view), "children", new_callable=PropertyMock, return_value=[mock_child]):
# Should not raise
await config_view.on_timeout()
@@ -815,7 +815,7 @@ class TestConfigViewButtonUpdateMethodLambdas:
def _get_real_config_view(self, mock_ctx, server_config):
"""Create a ConfigView with real button methods but no event loop dependency."""
- with patch.object(discord.ui.View, '__init__', lambda self, **kwargs: None):
+ with patch.object(discord.ui.View, "__init__", lambda self, **kwargs: None):
view = object.__new__(ConfigView)
view.ctx = mock_ctx
view.server_config = server_config
diff --git a/tests/unit/bot/cogs/admin/test_custom_cmd.py b/tests/unit/bot/cogs/admin/test_custom_cmd.py
index a84b6681..808df24f 100644
--- a/tests/unit/bot/cogs/admin/test_custom_cmd.py
+++ b/tests/unit/bot/cogs/admin/test_custom_cmd.py
@@ -7,7 +7,7 @@
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.admin.custom_cmd import CustomCommand
from src.bot.constants import messages
@@ -78,7 +78,7 @@ def test_init(self, mock_bot):
assert cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.invoke_subcommand')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.invoke_subcommand")
async def test_custom_command_group(self, mock_invoke, custom_cmd_cog, mock_ctx):
"""Test custom_command group command."""
mock_invoke.return_value = "mock_command"
@@ -92,9 +92,9 @@ async def test_custom_command_group(self, mock_invoke, custom_cmd_cog, mock_ctx)
# Test add command
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_add_custom_command_success(
self, mock_send_msg, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -117,8 +117,8 @@ async def test_add_custom_command_success(
assert "!hello" in success_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_add_custom_command_missing_args(self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx):
"""Test add command with missing arguments."""
from src.bot.cogs.admin.custom_cmd import add_custom_command
@@ -132,8 +132,8 @@ async def test_add_custom_command_missing_args(self, mock_send_error, mock_delet
assert messages.MISSING_REQUIRED_ARGUMENT_HELP_MESSAGE in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_add_custom_command_name_too_long(self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx):
"""Test add command with name too long."""
long_name = "a" * 21 # 21 characters, exceeds limit of 20
@@ -146,8 +146,8 @@ async def test_add_custom_command_name_too_long(self, mock_send_error, mock_dele
mock_send_error.assert_called_once_with(mock_ctx, messages.COMMAND_LENGHT_ERROR)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_add_custom_command_conflicts_with_bot_command(
self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx
):
@@ -164,9 +164,9 @@ async def test_add_custom_command_conflicts_with_bot_command(
assert "!help" in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg")
async def test_add_custom_command_already_exists(
self, mock_send_warning, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -189,9 +189,9 @@ async def test_add_custom_command_already_exists(
# Test edit command
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_edit_custom_command_success(
self, mock_send_msg, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx, mock_command_data
):
@@ -214,8 +214,8 @@ async def test_edit_custom_command_success(
assert "!hello" in success_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_edit_custom_command_missing_args(self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx):
"""Test edit command with missing arguments."""
from src.bot.cogs.admin.custom_cmd import edit_custom_command
@@ -229,9 +229,9 @@ async def test_edit_custom_command_missing_args(self, mock_send_error, mock_dele
assert messages.MISSING_REQUIRED_ARGUMENT_HELP_MESSAGE in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_edit_custom_command_no_commands_exist(
self, mock_send_error, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -248,9 +248,9 @@ async def test_edit_custom_command_no_commands_exist(
mock_send_error.assert_called_once_with(mock_ctx, messages.NO_CUSTOM_COMMANDS_FOUND)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_edit_custom_command_not_found(
self, mock_send_error, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx, mock_command_data
):
@@ -272,8 +272,8 @@ async def test_edit_custom_command_not_found(
# Test remove command
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_remove_custom_command_success(
self, mock_send_msg, mock_dal_class, custom_cmd_cog, mock_ctx, mock_command_data
):
@@ -295,8 +295,8 @@ async def test_remove_custom_command_success(
assert "!hello" in success_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg")
async def test_remove_custom_command_no_commands_exist(
self, mock_send_warning, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -312,8 +312,8 @@ async def test_remove_custom_command_no_commands_exist(
mock_send_warning.assert_called_once_with(mock_ctx, messages.NO_CUSTOM_COMMANDS_FOUND)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_remove_custom_command_not_found(
self, mock_send_error, mock_dal_class, custom_cmd_cog, mock_ctx, mock_command_data
):
@@ -335,8 +335,8 @@ async def test_remove_custom_command_not_found(
# Test removeall command
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_remove_all_custom_commands_success(
self, mock_send_msg, mock_dal_class, custom_cmd_cog, mock_ctx, mock_command_data
):
@@ -354,8 +354,8 @@ async def test_remove_all_custom_commands_success(
mock_send_msg.assert_called_once_with(mock_ctx, messages.CUSTOM_COMMAND_ALL_REMOVED)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_remove_all_custom_commands_no_commands_exist(
self, mock_send_error, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -372,11 +372,11 @@ async def test_remove_all_custom_commands_no_commands_exist(
# Test list command
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_embed')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short')
- @patch('src.bot.cogs.admin.custom_cmd.chat_formatting.inline')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_embed")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short")
+ @patch("src.bot.cogs.admin.custom_cmd.chat_formatting.inline")
async def test_list_custom_commands_success(
self,
mock_inline,
@@ -420,8 +420,8 @@ async def test_list_custom_commands_success(
assert embed.footer.text == "For more info: !help admin cc"
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_warning_msg")
async def test_list_custom_commands_no_commands_exist(
self, mock_send_warning, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -437,11 +437,11 @@ async def test_list_custom_commands_no_commands_exist(
mock_send_warning.assert_called_once_with(mock_ctx, messages.NO_CUSTOM_COMMANDS_FOUND)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_embed')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short')
- @patch('src.bot.cogs.admin.custom_cmd.chat_formatting.inline')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_embed")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short")
+ @patch("src.bot.cogs.admin.custom_cmd.chat_formatting.inline")
async def test_list_custom_commands_unknown_user(
self,
mock_inline,
@@ -475,11 +475,11 @@ async def test_list_custom_commands_unknown_user(
assert "Unknown User" in created_by_field.value
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_embed')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short')
- @patch('src.bot.cogs.admin.custom_cmd.chat_formatting.inline')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_embed")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.get_member_by_id")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.convert_datetime_to_str_short")
+ @patch("src.bot.cogs.admin.custom_cmd.chat_formatting.inline")
async def test_list_custom_commands_no_guild_icon(
self,
mock_inline,
@@ -531,12 +531,12 @@ def test_custom_command_cog_inheritance(self, custom_cmd_cog):
from src.bot.cogs.admin.admin import Admin
assert isinstance(custom_cmd_cog, Admin)
- assert hasattr(custom_cmd_cog, 'bot')
+ assert hasattr(custom_cmd_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_add_custom_command_case_insensitive_conflict(
self, mock_send_msg, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -548,7 +548,7 @@ async def test_add_custom_command_case_insensitive_conflict(
from src.bot.cogs.admin.custom_cmd import add_custom_command
- with patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg') as mock_send_error:
+ with patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg") as mock_send_error:
await add_custom_command(mock_ctx, subcommand_passed="help Some description")
mock_send_error.assert_called_once()
@@ -556,9 +556,9 @@ async def test_add_custom_command_case_insensitive_conflict(
assert messages.ALREADY_A_STANDARD_COMMAND in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_add_custom_command_with_spaces_in_description(
self, mock_send_msg, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -576,9 +576,9 @@ async def test_add_custom_command_with_spaces_in_description(
)
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.CustomCommandsDal')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.CustomCommandsDal")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_msg")
async def test_add_custom_command_exact_20_chars(
self, mock_send_msg, mock_delete_msg, mock_dal_class, custom_cmd_cog, mock_ctx
):
@@ -597,8 +597,8 @@ async def test_add_custom_command_exact_20_chars(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_add_custom_command_empty_args(self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx):
"""Test add command with completely empty arguments."""
from src.bot.cogs.admin.custom_cmd import add_custom_command
@@ -609,8 +609,8 @@ async def test_add_custom_command_empty_args(self, mock_send_error, mock_delete_
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.delete_message')
- @patch('src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.delete_message")
+ @patch("src.bot.cogs.admin.custom_cmd.bot_utils.send_error_msg")
async def test_edit_custom_command_empty_args(self, mock_send_error, mock_delete_msg, custom_cmd_cog, mock_ctx):
"""Test edit command with completely empty arguments."""
from src.bot.cogs.admin.custom_cmd import edit_custom_command
diff --git a/tests/unit/bot/cogs/test_dice_rolls.py b/tests/unit/bot/cogs/test_dice_rolls.py
index 5ac420df..7a129b38 100644
--- a/tests/unit/bot/cogs/test_dice_rolls.py
+++ b/tests/unit/bot/cogs/test_dice_rolls.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.dice_rolls import DiceRolls
from src.bot.constants import messages
@@ -54,16 +54,15 @@ def mock_ctx():
class TestDiceRolls:
-
@pytest.mark.asyncio
async def test_init(self, mock_bot):
cog = DiceRolls(mock_bot)
assert cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
async def test_roll_default_dice_size(self, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx):
# Setup
mock_random_instance = MagicMock()
@@ -87,9 +86,9 @@ async def test_roll_default_dice_size(self, mock_send_embed, mock_dal_class, moc
mock_send_embed.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
async def test_roll_custom_dice_size(self, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx):
# Setup
mock_ctx.subcommand_passed = "20"
@@ -112,7 +111,7 @@ async def test_roll_custom_dice_size(self, mock_send_embed, mock_dal_class, mock
mock_dal.insert_user_roll.assert_called_once_with(12345, 67890, 20, 15)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_error_msg")
async def test_roll_invalid_dice_size(self, mock_send_error, dice_cog, mock_ctx):
# Setup
mock_ctx.subcommand_passed = "invalid"
@@ -124,7 +123,7 @@ async def test_roll_invalid_dice_size(self, mock_send_error, dice_cog, mock_ctx)
mock_send_error.assert_called_once_with(mock_ctx, messages.DICE_SIZE_NOT_VALID)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_error_msg")
async def test_roll_dice_size_too_small(self, mock_send_error, dice_cog, mock_ctx):
# Setup
mock_ctx.subcommand_passed = "1"
@@ -136,10 +135,10 @@ async def test_roll_dice_size_too_small(self, mock_send_error, dice_cog, mock_ct
mock_send_error.assert_called_once_with(mock_ctx, messages.DICE_SIZE_HIGHER_ONE)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_new_personal_record(
self, mock_get_member, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -173,10 +172,10 @@ async def test_roll_new_personal_record(
assert messages.MEMBER_HIGHEST_ROLL in embed_call.description
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_new_server_record(
self, mock_get_member, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -215,9 +214,9 @@ async def test_roll_with_invoked_subcommand(self, dice_cog, mock_ctx):
mock_ctx.message.channel.typing.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_results_default_dice_size(
self, mock_get_member, mock_send_embed, mock_dal_class, dice_cog, mock_ctx
):
@@ -243,9 +242,9 @@ async def test_roll_results_default_dice_size(
mock_send_embed.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_results_custom_dice_size(
self, mock_get_member, mock_send_embed, mock_dal_class, dice_cog, mock_ctx
):
@@ -268,8 +267,8 @@ async def test_roll_results_custom_dice_size(
mock_dal.get_all_server_rolls.assert_called_once_with(12345, 20)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_error_msg")
async def test_roll_results_no_rolls_found(self, mock_send_error, mock_dal_class, dice_cog, mock_ctx):
# Setup
mock_dal = AsyncMock()
@@ -280,10 +279,10 @@ async def test_roll_results_no_rolls_found(self, mock_send_error, mock_dal_class
await dice_cog.roll_results.callback(dice_cog, mock_ctx)
# Verify
- mock_send_error.assert_called_once_with(mock_ctx, messages.NO_DICE_SIZE_ROLLS.format(100))
+ mock_send_error.assert_called_once_with(mock_ctx, messages.no_dice_size_rolls(100))
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_error_msg")
async def test_roll_results_invalid_dice_size(self, mock_send_error, dice_cog, mock_ctx):
# Setup
mock_ctx.message.content = "!roll results invalid"
@@ -295,8 +294,8 @@ async def test_roll_results_invalid_dice_size(self, mock_send_error, dice_cog, m
mock_send_error.assert_called_once_with(mock_ctx, messages.DICE_SIZE_NOT_VALID)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_msg')
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_msg")
async def test_roll_reset(self, mock_send_msg, mock_dal_class, dice_cog, mock_ctx):
# Setup
mock_dal = AsyncMock()
@@ -312,10 +311,10 @@ async def test_roll_reset(self, mock_send_msg, mock_dal_class, dice_cog, mock_ct
mock_send_msg.assert_called_once_with(mock_ctx, messages.DELETED_ALL_ROLLS)
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_existing_user_no_new_record(
self, mock_get_member, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -337,10 +336,10 @@ async def test_roll_existing_user_no_new_record(
mock_dal.update_user_roll.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_server_highest_user_is_current_user(
self, mock_get_member, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -365,10 +364,10 @@ async def test_roll_server_highest_user_is_current_user(
assert messages.MEMBER_SERVER_WINNER_ANOUNCE in embed_call.description
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_server_highest_user_is_different_user(
self, mock_get_member, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -396,9 +395,9 @@ async def test_roll_server_highest_user_is_different_user(
assert messages.MEMBER_HIGHEST_ROLL in embed_call.description
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
async def test_roll_server_max_roll_with_null_max_roll(
self, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx
):
@@ -434,10 +433,10 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
- @patch('src.bot.cogs.dice_rolls.chat_formatting.inline')
- @patch('src.bot.cogs.dice_rolls.bot_utils.get_member_by_id')
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
+ @patch("src.bot.cogs.dice_rolls.chat_formatting.inline")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.get_member_by_id")
async def test_roll_results_embed_formatting(
self, mock_get_member, mock_inline, mock_send_embed, mock_dal_class, dice_cog, mock_ctx
):
@@ -474,9 +473,9 @@ async def test_roll_results_embed_formatting(
assert embed.footer.text == f"{messages.RESET_ALL_ROLLS}: !roll reset"
@pytest.mark.asyncio
- @patch('src.bot.cogs.dice_rolls.random.SystemRandom')
- @patch('src.bot.cogs.dice_rolls.DiceRollsDal')
- @patch('src.bot.cogs.dice_rolls.bot_utils.send_embed')
+ @patch("src.bot.cogs.dice_rolls.random.SystemRandom")
+ @patch("src.bot.cogs.dice_rolls.DiceRollsDal")
+ @patch("src.bot.cogs.dice_rolls.bot_utils.send_embed")
async def test_roll_embed_properties(self, mock_send_embed, mock_dal_class, mock_random, dice_cog, mock_ctx):
# Setup
mock_random_instance = MagicMock()
diff --git a/tests/unit/bot/cogs/test_misc.py b/tests/unit/bot/cogs/test_misc.py
index 3331eaf7..b3109f1c 100644
--- a/tests/unit/bot/cogs/test_misc.py
+++ b/tests/unit/bot/cogs/test_misc.py
@@ -8,7 +8,7 @@
from unittest.mock import AsyncMock, MagicMock, Mock, patch
# Mock problematic imports before importing the module
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.misc import Misc
from src.bot.constants import messages, variables
@@ -107,20 +107,20 @@ def test_init(self, mock_bot):
"""Test Misc cog initialization."""
cog = Misc(mock_bot)
assert cog.bot == mock_bot
- assert hasattr(cog, '_random')
+ assert hasattr(cog, "_random")
# Test pepe command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.pepedatabase', ['https://example.com/pepe1.jpg', 'https://example.com/pepe2.jpg'])
+ @patch("src.bot.cogs.misc.pepedatabase", ["https://example.com/pepe1.jpg", "https://example.com/pepe2.jpg"])
async def test_pepe_command_success(self, misc_cog, mock_ctx):
"""Test successful pepe command execution."""
mock_ctx.subcommand_passed = None
- with patch.object(misc_cog._random, 'choice', return_value='https://example.com/pepe1.jpg'):
+ with patch.object(misc_cog._random, "choice", return_value="https://example.com/pepe1.jpg"):
await misc_cog.pepe.callback(misc_cog, mock_ctx)
mock_ctx.message.channel.typing.assert_called_once()
- mock_ctx.send.assert_called_once_with('https://example.com/pepe1.jpg')
+ mock_ctx.send.assert_called_once_with("https://example.com/pepe1.jpg")
@pytest.mark.asyncio
async def test_pepe_command_with_subcommand(self, misc_cog, mock_ctx):
@@ -132,8 +132,8 @@ async def test_pepe_command_with_subcommand(self, misc_cog, mock_ctx):
# Test TTS command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.gTTS')
- @patch('src.bot.cogs.misc.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.misc.gTTS")
+ @patch("src.bot.cogs.misc.bot_utils.send_error_msg")
async def test_tts_command_success(self, mock_send_error, mock_gtts_class, misc_cog, mock_ctx):
"""Test successful TTS command execution."""
# Setup TTS mock
@@ -148,13 +148,13 @@ async def test_tts_command_success(self, mock_send_error, mock_gtts_class, misc_
mock_ctx.send.assert_called_once()
# Verify file was sent
- sent_file = mock_ctx.send.call_args[1]['file']
+ sent_file = mock_ctx.send.call_args[1]["file"]
assert isinstance(sent_file, discord.File)
assert sent_file.filename == "TestUser.mp3"
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.gTTS')
- @patch('src.bot.cogs.misc.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.misc.gTTS")
+ @patch("src.bot.cogs.misc.bot_utils.send_error_msg")
async def test_tts_command_with_mentions(self, mock_send_error, mock_gtts_class, misc_cog, mock_ctx):
"""Test TTS command with user mentions."""
# Setup mock member
@@ -169,12 +169,12 @@ async def test_tts_command_with_mentions(self, mock_send_error, mock_gtts_class,
# Should process mention and call gTTS with processed text
mock_gtts_class.assert_called_once()
- processed_text = mock_gtts_class.call_args[1]['text']
+ processed_text = mock_gtts_class.call_args[1]["text"]
assert "@MentionedUser" in processed_text
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.gTTS')
- @patch('src.bot.cogs.misc.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.misc.gTTS")
+ @patch("src.bot.cogs.misc.bot_utils.send_error_msg")
async def test_tts_command_with_emojis(self, mock_send_error, mock_gtts_class, misc_cog, mock_ctx):
"""Test TTS command with custom emojis."""
mock_tts = MagicMock()
@@ -184,12 +184,12 @@ async def test_tts_command_with_emojis(self, mock_send_error, mock_gtts_class, m
# Should process emoji and call gTTS with processed text
mock_gtts_class.assert_called_once()
- processed_text = mock_gtts_class.call_args[1]['text']
+ processed_text = mock_gtts_class.call_args[1]["text"]
assert "smile" in processed_text
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.gTTS', side_effect=AssertionError("TTS Error"))
- @patch('src.bot.cogs.misc.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.misc.gTTS", side_effect=AssertionError("TTS Error"))
+ @patch("src.bot.cogs.misc.bot_utils.send_error_msg")
async def test_tts_command_error(self, mock_send_error, mock_gtts_class, misc_cog, mock_ctx):
"""Test TTS command with gTTS error."""
await misc_cog.tts.callback(misc_cog, mock_ctx, tts_text="Hello world")
@@ -197,7 +197,7 @@ async def test_tts_command_error(self, mock_send_error, mock_gtts_class, misc_co
mock_send_error.assert_called_once_with(mock_ctx, messages.INVALID_MESSAGE)
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.misc.bot_utils.send_error_msg")
async def test_tts_command_empty_text(self, mock_send_error, misc_cog, mock_ctx):
"""Test TTS command with empty processed text."""
await misc_cog.tts.callback(misc_cog, mock_ctx, tts_text="")
@@ -206,7 +206,7 @@ async def test_tts_command_empty_text(self, mock_send_error, misc_cog, mock_ctx)
# Test echo command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_msg')
+ @patch("src.bot.cogs.misc.bot_utils.send_msg")
async def test_echo_command(self, mock_send_msg, misc_cog, mock_ctx):
"""Test echo command."""
await misc_cog.echo.callback(misc_cog, mock_ctx, msg="Hello world!")
@@ -216,7 +216,7 @@ async def test_echo_command(self, mock_send_msg, misc_cog, mock_ctx):
# Test ping command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
async def test_ping_command_good_latency(self, mock_send_embed, misc_cog, mock_ctx):
"""Test ping command with good latency."""
mock_ctx.subcommand_passed = None
@@ -232,7 +232,7 @@ async def test_ping_command_good_latency(self, mock_send_embed, misc_cog, mock_c
assert embed.color == discord.Color.green()
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
async def test_ping_command_bad_latency(self, mock_send_embed, misc_cog, mock_ctx):
"""Test ping command with bad latency."""
mock_ctx.subcommand_passed = None
@@ -254,7 +254,7 @@ async def test_ping_command_with_subcommand(self, misc_cog, mock_ctx):
# Test lmgtfy command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_msg')
+ @patch("src.bot.cogs.misc.bot_utils.send_msg")
async def test_lmgtfy_command(self, mock_send_msg, misc_cog, mock_ctx):
"""Test lmgtfy command."""
await misc_cog.lmgtfy.callback(misc_cog, mock_ctx, user_msg="how to code in python")
@@ -268,8 +268,8 @@ async def test_lmgtfy_command(self, mock_send_msg, misc_cog, mock_ctx):
# Test invites command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_msg')
- @patch('src.bot.cogs.misc.chat_formatting.inline')
+ @patch("src.bot.cogs.misc.bot_utils.send_msg")
+ @patch("src.bot.cogs.misc.chat_formatting.inline")
async def test_invites_command_no_invites(self, mock_inline, mock_send_msg, misc_cog, mock_ctx):
"""Test invites command with no invites."""
mock_ctx.subcommand_passed = None
@@ -282,7 +282,7 @@ async def test_invites_command_no_invites(self, mock_inline, mock_send_msg, misc
mock_send_msg.assert_called_once_with(mock_ctx, "inline_text")
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
async def test_invites_command_with_invites(self, mock_send_embed, misc_cog, mock_ctx):
"""Test invites command with various invite types."""
mock_ctx.subcommand_passed = None
@@ -329,9 +329,9 @@ async def test_invites_command_with_subcommand(self, misc_cog, mock_ctx):
# Test serverinfo command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_current_date_time')
- @patch('src.bot.cogs.misc.bot_utils.convert_datetime_to_str_long')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_current_date_time")
+ @patch("src.bot.cogs.misc.bot_utils.convert_datetime_to_str_long")
async def test_serverinfo_command(
self, mock_convert_datetime, mock_get_current_time, mock_send_embed, misc_cog, mock_ctx
):
@@ -383,9 +383,9 @@ async def test_serverinfo_command_with_subcommand(self, misc_cog, mock_ctx):
# Test userinfo command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_object_member_by_str')
- @patch('src.bot.cogs.misc.bot_utils.get_current_date_time')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_object_member_by_str")
+ @patch("src.bot.cogs.misc.bot_utils.get_current_date_time")
async def test_userinfo_command_self(
self, mock_get_current_time, mock_get_member, mock_send_embed, misc_cog, mock_ctx
):
@@ -406,9 +406,9 @@ async def test_userinfo_command_self(
assert embed.color == discord.Color.blue()
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_object_member_by_str')
- @patch('src.bot.cogs.misc.bot_utils.get_current_date_time')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_object_member_by_str")
+ @patch("src.bot.cogs.misc.bot_utils.get_current_date_time")
async def test_userinfo_command_other_user(
self, mock_get_current_time, mock_get_member, mock_send_embed, misc_cog, mock_ctx, mock_member
):
@@ -429,8 +429,8 @@ async def test_userinfo_command_other_user(
# Test about command
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_bot_stats')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_bot_stats")
async def test_about_command(self, mock_get_bot_stats, mock_send_embed, misc_cog, mock_ctx):
"""Test about command."""
mock_ctx.subcommand_passed = None
@@ -621,7 +621,7 @@ def test_get_user_info(self, misc_cog, mock_ctx, mock_member):
role3.name = "Role3"
mock_member.roles = [role1, role2, role3]
- with patch('src.bot.cogs.misc.bot_utils.get_current_date_time', return_value=datetime.now(UTC)):
+ with patch("src.bot.cogs.misc.bot_utils.get_current_date_time", return_value=datetime.now(UTC)):
result = misc_cog._get_user_info(mock_ctx.guild, mock_member)
assert "created_on" in result
@@ -699,7 +699,7 @@ async def test_setup_function(self, mock_bot):
def test_misc_cog_inheritance(self, misc_cog):
"""Test that Misc cog properly inherits from commands.Cog."""
assert isinstance(misc_cog, commands.Cog)
- assert hasattr(misc_cog, 'bot')
+ assert hasattr(misc_cog, "bot")
def test_process_tts_text_no_special_tokens(self, misc_cog, mock_ctx):
"""Test _process_tts_text with no special tokens."""
@@ -718,9 +718,9 @@ def test_process_tts_text_with_special_tokens(self, misc_cog, mock_ctx):
# Test serverinfo with no icon (line 168)
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_current_date_time')
- @patch('src.bot.cogs.misc.bot_utils.convert_datetime_to_str_long')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_current_date_time")
+ @patch("src.bot.cogs.misc.bot_utils.convert_datetime_to_str_long")
async def test_serverinfo_no_icon(
self, mock_convert_datetime, mock_get_current_time, mock_send_embed, misc_cog, mock_ctx
):
@@ -752,9 +752,9 @@ async def test_serverinfo_no_icon(
# Test userinfo with no avatar (line 204)
@pytest.mark.asyncio
- @patch('src.bot.cogs.misc.bot_utils.send_embed')
- @patch('src.bot.cogs.misc.bot_utils.get_object_member_by_str')
- @patch('src.bot.cogs.misc.bot_utils.get_current_date_time')
+ @patch("src.bot.cogs.misc.bot_utils.send_embed")
+ @patch("src.bot.cogs.misc.bot_utils.get_object_member_by_str")
+ @patch("src.bot.cogs.misc.bot_utils.get_current_date_time")
async def test_userinfo_no_avatar(
self, mock_get_current_time, mock_get_member, mock_send_embed, misc_cog, mock_ctx
):
diff --git a/tests/unit/bot/cogs/test_open_ai.py b/tests/unit/bot/cogs/test_open_ai.py
index 39c83905..02ef5221 100644
--- a/tests/unit/bot/cogs/test_open_ai.py
+++ b/tests/unit/bot/cogs/test_open_ai.py
@@ -8,7 +8,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.open_ai import OpenAi
@@ -83,7 +83,7 @@ def test_init(self, mock_bot):
def test_openai_client_property_creates_client(self, openai_cog):
"""Test that openai_client property creates client on first access."""
- with patch('src.bot.cogs.open_ai.OpenAI') as mock_openai_class:
+ with patch("src.bot.cogs.open_ai.OpenAI") as mock_openai_class:
mock_client = MagicMock()
mock_openai_class.return_value = mock_client
@@ -103,15 +103,15 @@ def test_openai_client_property_returns_existing_client(self, openai_cog):
assert client == mock_client
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_success(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings, mock_openai_response
):
"""Test successful AI command execution."""
mock_get_settings.return_value = mock_bot_settings
- with patch.object(openai_cog, '_get_ai_response', return_value="AI response here"):
+ with patch.object(openai_cog, "_get_ai_response", return_value="AI response here"):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text="What is Python?")
mock_ctx.message.channel.typing.assert_called_once()
@@ -125,13 +125,13 @@ async def test_ai_command_success(
assert embed.author.icon_url == "https://example.com/avatar.png"
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_error(self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings):
"""Test AI command with OpenAI API error."""
mock_get_settings.return_value = mock_bot_settings
- with patch.object(openai_cog, '_get_ai_response', side_effect=Exception("API Error")):
+ with patch.object(openai_cog, "_get_ai_response", side_effect=Exception("API Error")):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text="What is Python?")
mock_ctx.message.channel.typing.assert_called_once()
@@ -147,7 +147,7 @@ async def test_ai_command_error(self, mock_send_embed, mock_get_settings, openai
openai_cog.bot.log.error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
async def test_get_ai_response_success(
self, mock_get_settings, openai_cog, mock_bot_settings, mock_openai_response
):
@@ -167,19 +167,19 @@ async def test_get_ai_response_success(
mock_client.chat.completions.create.assert_called_once()
call_args = mock_client.chat.completions.create.call_args
- assert call_args[1]['model'] == "gpt-3.5-turbo"
- assert call_args[1]['max_tokens'] == 1000
- assert call_args[1]['temperature'] == pytest.approx(0.7)
+ assert call_args[1]["model"] == "gpt-3.5-turbo"
+ assert call_args[1]["max_tokens"] == 1000
+ assert call_args[1]["temperature"] == pytest.approx(0.7)
# Verify message types and content
- messages = call_args[1]['messages']
+ messages = call_args[1]["messages"]
assert len(messages) == 2
- assert messages[0]['role'] == "system"
- assert messages[1]['role'] == "user"
- assert messages[1]['content'] == "What is Python?"
+ assert messages[0]["role"] == "system"
+ assert messages[1]["role"] == "user"
+ assert messages[1]["content"] == "What is Python?"
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
async def test_get_ai_response_with_leading_trailing_spaces(
self, mock_get_settings, openai_cog, mock_bot_settings, mock_openai_response
):
@@ -253,8 +253,8 @@ def test_create_ai_embed_no_bot_avatar(self, openai_cog, mock_ctx):
assert "UTC" in embed.footer.text
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_with_different_models(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_openai_response
):
@@ -273,11 +273,11 @@ async def test_ai_command_with_different_models(
# Verify correct model was used
call_args = mock_client.chat.completions.create.call_args
- assert call_args[1]['model'] == "gpt-4"
+ assert call_args[1]["model"] == "gpt-4"
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_with_long_question(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings
):
@@ -285,7 +285,7 @@ async def test_ai_command_with_long_question(
mock_get_settings.return_value = mock_bot_settings
long_question = "What is " + "very " * 1000 + "long question?"
- with patch.object(openai_cog, '_get_ai_response', return_value="Short answer"):
+ with patch.object(openai_cog, "_get_ai_response", return_value="Short answer"):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text=long_question)
mock_send_embed.assert_called_once()
@@ -293,8 +293,8 @@ async def test_ai_command_with_long_question(
assert embed.description == "Short answer"
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_with_special_characters(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings
):
@@ -302,7 +302,7 @@ async def test_ai_command_with_special_characters(
mock_get_settings.return_value = mock_bot_settings
special_question = "What is 2+2? 🤔 And émojis & spéciál chars?"
- with patch.object(openai_cog, '_get_ai_response', return_value="4! 😊"):
+ with patch.object(openai_cog, "_get_ai_response", return_value="4! 😊"):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text=special_question)
mock_send_embed.assert_called_once()
@@ -310,7 +310,7 @@ async def test_ai_command_with_special_characters(
assert embed.description == "4! 😊"
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
async def test_get_ai_response_system_message_content(
self, mock_get_settings, openai_cog, mock_bot_settings, mock_openai_response
):
@@ -324,13 +324,13 @@ async def test_get_ai_response_system_message_content(
await openai_cog._get_ai_response("Test message")
- messages = mock_client.chat.completions.create.call_args[1]['messages']
+ messages = mock_client.chat.completions.create.call_args[1]["messages"]
system_message = messages[0]
expected_content = "You are a helpful AI assistant. Provide clear, concise, and accurate responses."
- assert system_message['content'] == expected_content
+ assert system_message["content"] == expected_content
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
async def test_get_ai_response_api_parameters(
self, mock_get_settings, openai_cog, mock_bot_settings, mock_openai_response
):
@@ -345,11 +345,11 @@ async def test_get_ai_response_api_parameters(
await openai_cog._get_ai_response("Test message")
call_args = mock_client.chat.completions.create.call_args[1]
- assert call_args['max_tokens'] == 1000
- assert call_args['temperature'] == pytest.approx(0.7)
- assert call_args['model'] == "gpt-3.5-turbo"
+ assert call_args["max_tokens"] == 1000
+ assert call_args["temperature"] == pytest.approx(0.7)
+ assert call_args["model"] == "gpt-3.5-turbo"
- @patch('src.bot.cogs.open_ai.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.open_ai.bot_utils.get_current_date_time_str_long")
def test_create_ai_embed_footer(self, mock_get_datetime, openai_cog, mock_ctx):
"""Test that embed footer contains correct timestamp."""
mock_get_datetime.return_value = "2023-01-01 12:00:00"
@@ -374,11 +374,11 @@ async def test_setup_function(self, mock_bot):
def test_openai_cog_inheritance(self, openai_cog):
"""Test that OpenAi cog properly inherits from commands.Cog."""
assert isinstance(openai_cog, commands.Cog)
- assert hasattr(openai_cog, 'bot')
+ assert hasattr(openai_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_error_logging(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings
):
@@ -386,7 +386,7 @@ async def test_ai_command_error_logging(
mock_get_settings.return_value = mock_bot_settings
test_error = Exception("Test API Error")
- with patch.object(openai_cog, '_get_ai_response', side_effect=test_error):
+ with patch.object(openai_cog, "_get_ai_response", side_effect=test_error):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text="Test question")
# Verify error was logged with correct message
@@ -396,15 +396,15 @@ async def test_ai_command_error_logging(
assert "Test API Error" in log_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
- @patch('src.bot.cogs.open_ai.bot_utils.send_embed')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
+ @patch("src.bot.cogs.open_ai.bot_utils.send_embed")
async def test_ai_command_send_embed_parameters(
self, mock_send_embed, mock_get_settings, openai_cog, mock_ctx, mock_bot_settings
):
"""Test that send_embed is called with correct parameters."""
mock_get_settings.return_value = mock_bot_settings
- with patch.object(openai_cog, '_get_ai_response', return_value="Test response"):
+ with patch.object(openai_cog, "_get_ai_response", return_value="Test response"):
await openai_cog.ai.callback(openai_cog, mock_ctx, msg_text="Test question")
# Verify send_embed was called with ctx, embed, and False
@@ -415,7 +415,7 @@ async def test_ai_command_send_embed_parameters(
assert call_args[2] is False # dm parameter
@pytest.mark.asyncio
- @patch('src.bot.cogs.open_ai.get_bot_settings')
+ @patch("src.bot.cogs.open_ai.get_bot_settings")
async def test_get_ai_response_empty_response(self, mock_get_settings, openai_cog, mock_bot_settings):
"""Test _get_ai_response with empty response from OpenAI."""
mock_get_settings.return_value = mock_bot_settings
diff --git a/tests/unit/bot/cogs/test_owner.py b/tests/unit/bot/cogs/test_owner.py
index d141fc9a..ce10c4bf 100644
--- a/tests/unit/bot/cogs/test_owner.py
+++ b/tests/unit/bot/cogs/test_owner.py
@@ -8,7 +8,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.owner import Owner
from src.bot.constants import messages, variables
@@ -88,7 +88,7 @@ def test_init(self, mock_bot):
assert cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.bot_utils.invoke_subcommand')
+ @patch("src.bot.cogs.owner.bot_utils.invoke_subcommand")
async def test_owner_group_command(self, mock_invoke, owner_cog, mock_ctx):
"""Test owner group command."""
mock_invoke.return_value = "mock_command"
@@ -100,8 +100,8 @@ async def test_owner_group_command(self, mock_invoke, owner_cog, mock_ctx):
# Test prefix change command
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_success(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test successful prefix change."""
mock_dal = AsyncMock()
@@ -131,8 +131,8 @@ async def test_owner_change_prefix_invalid(self, owner_cog, mock_ctx):
assert ", ".join(variables.ALLOWED_PREFIXES) in error_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_with_activity_update(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test prefix change with bot activity update."""
mock_dal = AsyncMock()
@@ -142,13 +142,13 @@ async def test_owner_change_prefix_with_activity_update(self, mock_send_embed, m
# Verify activity was updated
owner_cog.bot.change_presence.assert_called_once()
- activity_call = owner_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = owner_cog.bot.change_presence.call_args[1]["activity"]
assert isinstance(activity_call, discord.Game)
assert activity_call.name == "Test Game | %help"
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_no_activity(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test prefix change when bot has no activity."""
owner_cog.bot.user.activity = None
@@ -167,8 +167,8 @@ async def test_owner_change_prefix_no_activity(self, mock_send_embed, mock_dal_c
mock_send_embed.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_non_playing_activity(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test prefix change when bot has non-playing activity."""
# Set the guild's bot member activity type to listening (not playing)
@@ -184,9 +184,9 @@ async def test_owner_change_prefix_non_playing_activity(self, mock_send_embed, m
# Test description update command
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.delete_message')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.delete_message")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_description_success(
self, mock_send_embed, mock_delete_msg, mock_dal_class, owner_cog, mock_ctx
):
@@ -211,8 +211,8 @@ async def test_owner_description_success(
# Test servers list command
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_success(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx, mock_server_data):
"""Test successful servers list."""
mock_dal = AsyncMock()
@@ -234,8 +234,8 @@ async def test_owner_servers_success(self, mock_send_embed, mock_dal_class, owne
assert mock_send_embed.call_args[0][2] is True
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_no_servers(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test servers list with no servers."""
mock_dal = AsyncMock()
@@ -254,8 +254,8 @@ async def test_owner_servers_no_servers(self, mock_send_embed, mock_dal_class, o
assert result == mock_send_embed.return_value
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_many_servers(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test servers list with more than 25 servers (pagination)."""
# Create 30 mock servers
@@ -285,8 +285,8 @@ async def test_owner_servers_many_servers(self, mock_send_embed, mock_dal_class,
assert embed.footer.text == "Total servers: 30"
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_no_bot_avatar(
self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx, mock_server_data
):
@@ -310,12 +310,12 @@ async def test_update_bot_activity_prefix_with_activity(self, owner_cog):
await owner_cog._update_bot_activity_prefix("$")
owner_cog.bot.change_presence.assert_called_once()
- activity_call = owner_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = owner_cog.bot.change_presence.call_args[1]["activity"]
assert isinstance(activity_call, discord.Game)
assert activity_call.name == "Test Game | $help"
# Check status and activity parameters
- assert owner_cog.bot.change_presence.call_args[1]['status'] == discord.Status.online
+ assert owner_cog.bot.change_presence.call_args[1]["status"] == discord.Status.online
@pytest.mark.asyncio
async def test_update_bot_activity_prefix_no_activity(self, owner_cog):
@@ -357,8 +357,8 @@ def test_create_owner_embed(self, owner_cog):
assert embed.color == discord.Color.gold()
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_all_valid_prefixes(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test prefix change with all valid prefixes."""
mock_dal = AsyncMock()
@@ -369,8 +369,8 @@ async def test_owner_change_prefix_all_valid_prefixes(self, mock_send_embed, moc
assert owner_cog.bot.command_prefix == prefix
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_description_with_special_characters(
self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx
):
@@ -380,29 +380,29 @@ async def test_owner_description_with_special_characters(
special_desc = "Bot with émojis 🤖 and spéciál chars & symbols!"
- with patch('src.bot.cogs.owner.bot_utils.delete_message'):
+ with patch("src.bot.cogs.owner.bot_utils.delete_message"):
await owner_cog.owner_description.callback(owner_cog, mock_ctx, desc=special_desc)
assert owner_cog.bot.description == special_desc
mock_dal.update_bot_description.assert_called_once_with(special_desc)
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_description_empty_string(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test description update with empty string."""
mock_dal = AsyncMock()
mock_dal_class.return_value = mock_dal
- with patch('src.bot.cogs.owner.bot_utils.delete_message'):
+ with patch("src.bot.cogs.owner.bot_utils.delete_message"):
await owner_cog.owner_description.callback(owner_cog, mock_ctx, desc="")
assert owner_cog.bot.description == ""
mock_dal.update_bot_description.assert_called_once_with("")
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_exactly_25_servers(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test servers list with exactly 25 servers (edge case)."""
# Create exactly 25 mock servers
@@ -438,7 +438,7 @@ async def test_update_bot_activity_prefix_complex_game_name(self, owner_cog):
await owner_cog._update_bot_activity_prefix("$")
# Should only take the first part before the first pipe
- activity_call = owner_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = owner_cog.bot.change_presence.call_args[1]["activity"]
assert activity_call.name == "Complex | $help"
@pytest.mark.asyncio
@@ -456,11 +456,11 @@ async def test_setup_function(self, mock_bot):
def test_owner_cog_inheritance(self, owner_cog):
"""Test that Owner cog properly inherits from commands.Cog."""
assert isinstance(owner_cog, commands.Cog)
- assert hasattr(owner_cog, 'bot')
+ assert hasattr(owner_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.ServersDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.ServersDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_servers_return_value_none_case(
self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx, mock_server_data
):
@@ -474,8 +474,8 @@ async def test_owner_servers_return_value_none_case(
assert result is None
@pytest.mark.asyncio
- @patch('src.bot.cogs.owner.BotConfigsDal')
- @patch('src.bot.cogs.owner.bot_utils.send_embed')
+ @patch("src.bot.cogs.owner.BotConfigsDal")
+ @patch("src.bot.cogs.owner.bot_utils.send_embed")
async def test_owner_change_prefix_case_sensitivity(self, mock_send_embed, mock_dal_class, owner_cog, mock_ctx):
"""Test that prefix validation is case-sensitive."""
mock_dal = AsyncMock()
@@ -498,5 +498,5 @@ async def test_update_bot_activity_prefix_with_no_pipe_in_activity(self, owner_c
await owner_cog._update_bot_activity_prefix("$")
- activity_call = owner_cog.bot.change_presence.call_args[1]['activity']
+ activity_call = owner_cog.bot.change_presence.call_args[1]["activity"]
assert activity_call.name == "SimpleGameName | $help"
diff --git a/tests/unit/bot/constants/test_settings.py b/tests/unit/bot/constants/test_settings.py
index 93083973..18dd15c6 100644
--- a/tests/unit/bot/constants/test_settings.py
+++ b/tests/unit/bot/constants/test_settings.py
@@ -14,14 +14,14 @@ def test_default_values_structure(self):
settings = BotSettings()
# Test that all expected fields exist
- assert hasattr(settings, 'prefix')
- assert hasattr(settings, 'token')
- assert hasattr(settings, 'openai_model')
- assert hasattr(settings, 'openai_api_key')
- assert hasattr(settings, 'bg_activity_timer')
- assert hasattr(settings, 'allowed_dm_commands')
- assert hasattr(settings, 'embed_color')
- assert hasattr(settings, 'admin_cooldown')
+ assert hasattr(settings, "prefix")
+ assert hasattr(settings, "token")
+ assert hasattr(settings, "openai_model")
+ assert hasattr(settings, "openai_api_key")
+ assert hasattr(settings, "bg_activity_timer")
+ assert hasattr(settings, "allowed_dm_commands")
+ assert hasattr(settings, "embed_color")
+ assert hasattr(settings, "admin_cooldown")
# Test types are correct (allowing for None due to Optional)
assert isinstance(settings.prefix, (str, type(None)))
@@ -178,10 +178,10 @@ def test_settings_with_actual_env_file(self):
settings = get_bot_settings()
# Just verify the structure is correct
- assert hasattr(settings, 'prefix')
- assert hasattr(settings, 'token')
- assert hasattr(settings, 'admin_cooldown')
- assert hasattr(settings, 'embed_color')
+ assert hasattr(settings, "prefix")
+ assert hasattr(settings, "token")
+ assert hasattr(settings, "admin_cooldown")
+ assert hasattr(settings, "embed_color")
# Verify types
assert isinstance(settings.prefix, (str, type(None)))
diff --git a/tests/unit/bot/events/test_bot_events.py b/tests/unit/bot/events/test_bot_events.py
index cbe7eaf1..ddf1efbc 100644
--- a/tests/unit/bot/events/test_bot_events.py
+++ b/tests/unit/bot/events/test_bot_events.py
@@ -7,7 +7,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_command import CommandLogger, OnCommand
from src.bot.cogs.events.on_disconnect import OnDisconnect
@@ -16,6 +16,7 @@
from src.bot.cogs.events.on_guild_channel_update import OnGuildChannelUpdate
from src.bot.cogs.events.on_guild_remove import GuildCleanupHandler, OnGuildRemove
from src.bot.cogs.events.on_presence_update import OnPresenceUpdate
+from src.bot.constants import messages
@pytest.fixture
@@ -30,7 +31,6 @@ def mock_bot():
bot.user = MagicMock()
bot.user.name = "TestBot"
bot.user.__str__ = MagicMock(return_value="TestBot#1234")
- bot.event = MagicMock(side_effect=lambda func: func)
bot.add_cog = AsyncMock(return_value=None)
return bot
@@ -190,10 +190,8 @@ async def test_on_command_event_calls_logger(self, mock_bot, mock_ctx):
"""Test on_command event handler calls the command logger."""
cog = OnCommand(mock_bot)
- # Access the event handler registered via bot.event
- on_command_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_command_event(mock_ctx)
+ # Call the listener method directly
+ await cog.on_command(mock_ctx)
# Verify the log was called (command_logger.log_command_execution was invoked)
mock_bot.log.info.assert_called_once()
@@ -206,10 +204,8 @@ async def test_on_command_event_exception(self, mock_bot, mock_ctx):
# Patch command_logger to raise an exception
cog.command_logger.log_command_execution = MagicMock(side_effect=RuntimeError("Logger error"))
- # Access the event handler registered via bot.event
- on_command_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_command_event(mock_ctx)
+ # Call the listener method directly
+ await cog.on_command(mock_ctx)
mock_bot.log.error.assert_called_once()
error_call = mock_bot.log.error.call_args[0][0]
@@ -221,7 +217,7 @@ def test_on_command_cog_inheritance(self, mock_bot):
cog = OnCommand(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -252,28 +248,24 @@ async def test_setup_function(self, mock_bot):
@pytest.mark.asyncio
async def test_on_disconnect_event_logs_warning(self, mock_bot):
"""Test on_disconnect event logs a warning message."""
- OnDisconnect(mock_bot)
-
- # Access the event handler registered via bot.event
- on_disconnect_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnDisconnect(mock_bot)
- await on_disconnect_event()
+ # Call the listener method directly
+ await cog.on_disconnect()
- # Since BOT_DISCONNECTED does not exist in messages, the else branch is used
- mock_bot.log.warning.assert_called_once_with(f"Bot {mock_bot.user} disconnected from Discord")
+ mock_bot.log.warning.assert_called_once_with(messages.bot_disconnected(mock_bot.user))
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_disconnect.messages')
+ @patch("src.bot.cogs.events.on_disconnect.messages")
async def test_on_disconnect_event_with_bot_disconnected_message(self, mock_messages, mock_bot):
- """Test on_disconnect event with BOT_DISCONNECTED message defined."""
- mock_messages.BOT_DISCONNECTED = "Bot {} has disconnected!"
-
- OnDisconnect(mock_bot)
+ """Test on_disconnect event calls bot_disconnected function."""
+ mock_messages.bot_disconnected.return_value = f"Bot {mock_bot.user} has disconnected!"
- on_disconnect_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnDisconnect(mock_bot)
- await on_disconnect_event()
+ await cog.on_disconnect()
+ mock_messages.bot_disconnected.assert_called_once_with(mock_bot.user)
mock_bot.log.warning.assert_called_once_with(f"Bot {mock_bot.user} has disconnected!")
@pytest.mark.asyncio
@@ -281,13 +273,11 @@ async def test_on_disconnect_event_exception(self, mock_bot):
"""Test on_disconnect event when exception occurs during logging."""
mock_bot.log.warning.side_effect = RuntimeError("Logging failure")
- OnDisconnect(mock_bot)
-
- on_disconnect_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnDisconnect(mock_bot)
# Should not raise; falls through to print fallback
- with patch('builtins.print') as mock_print:
- await on_disconnect_event()
+ with patch("builtins.print") as mock_print:
+ await cog.on_disconnect()
mock_print.assert_called_once()
print_call = mock_print.call_args[0][0]
assert "Bot disconnected - logging failed" in print_call
@@ -298,7 +288,7 @@ def test_on_disconnect_cog_inheritance(self, mock_bot):
cog = OnDisconnect(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -329,15 +319,13 @@ async def test_setup_function(self, mock_bot):
@pytest.mark.asyncio
async def test_on_guild_channel_create_event(self, mock_bot):
"""Test on_guild_channel_create event handler (currently a no-op)."""
- OnGuildChannelCreate(mock_bot)
-
- on_guild_channel_create_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildChannelCreate(mock_bot)
mock_channel = MagicMock()
mock_channel.name = "new-channel"
# Should not raise any exception
- await on_guild_channel_create_event(mock_channel)
+ await cog.on_guild_channel_create(mock_channel)
def test_on_guild_channel_create_cog_inheritance(self, mock_bot):
"""Test that OnGuildChannelCreate cog properly inherits from commands.Cog."""
@@ -345,7 +333,7 @@ def test_on_guild_channel_create_cog_inheritance(self, mock_bot):
cog = OnGuildChannelCreate(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -376,15 +364,13 @@ async def test_setup_function(self, mock_bot):
@pytest.mark.asyncio
async def test_on_guild_channel_delete_event(self, mock_bot):
"""Test on_guild_channel_delete event handler (currently a no-op)."""
- OnGuildChannelDelete(mock_bot)
-
- on_guild_channel_delete_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildChannelDelete(mock_bot)
mock_channel = MagicMock()
mock_channel.name = "deleted-channel"
# Should not raise any exception
- await on_guild_channel_delete_event(mock_channel)
+ await cog.on_guild_channel_delete(mock_channel)
def test_on_guild_channel_delete_cog_inheritance(self, mock_bot):
"""Test that OnGuildChannelDelete cog properly inherits from commands.Cog."""
@@ -392,7 +378,7 @@ def test_on_guild_channel_delete_cog_inheritance(self, mock_bot):
cog = OnGuildChannelDelete(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -423,9 +409,7 @@ async def test_setup_function(self, mock_bot):
@pytest.mark.asyncio
async def test_on_guild_channel_update_event(self, mock_bot):
"""Test on_guild_channel_update event handler (currently a no-op)."""
- OnGuildChannelUpdate(mock_bot)
-
- on_guild_channel_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildChannelUpdate(mock_bot)
mock_before = MagicMock()
mock_before.name = "old-channel"
@@ -433,7 +417,7 @@ async def test_on_guild_channel_update_event(self, mock_bot):
mock_after.name = "new-channel"
# Should not raise any exception
- await on_guild_channel_update_event(mock_before, mock_after)
+ await cog.on_guild_channel_update(mock_before, mock_after)
def test_on_guild_channel_update_cog_inheritance(self, mock_bot):
"""Test that OnGuildChannelUpdate cog properly inherits from commands.Cog."""
@@ -441,7 +425,7 @@ def test_on_guild_channel_update_cog_inheritance(self, mock_bot):
cog = OnGuildChannelUpdate(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -458,7 +442,7 @@ def test_init(self, mock_bot):
assert handler.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_remove.ServersDal")
async def test_cleanup_server_data_success(self, mock_dal_class, mock_bot, mock_guild):
"""Test successful cleanup of server data."""
mock_dal = AsyncMock()
@@ -476,7 +460,7 @@ async def test_cleanup_server_data_success(self, mock_dal_class, mock_bot, mock_
)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_remove.ServersDal")
async def test_cleanup_server_data_failure(self, mock_dal_class, mock_bot, mock_guild):
"""Test cleanup of server data when exception occurs."""
mock_dal = AsyncMock()
@@ -495,7 +479,7 @@ async def test_cleanup_server_data_failure(self, mock_dal_class, mock_bot, mock_
assert str(mock_guild.id) in error_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_remove.ServersDal")
async def test_cleanup_server_data_dal_init_failure(self, mock_dal_class, mock_bot, mock_guild):
"""Test cleanup of server data when DAL initialization fails."""
mock_dal_class.side_effect = RuntimeError("DAL init error")
@@ -536,7 +520,7 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_remove.ServersDal")
async def test_on_guild_remove_event_success(self, mock_dal_class, mock_bot, mock_guild):
"""Test on_guild_remove event handler with successful cleanup."""
mock_dal = AsyncMock()
@@ -544,9 +528,8 @@ async def test_on_guild_remove_event_success(self, mock_dal_class, mock_bot, moc
cog = OnGuildRemove(mock_bot)
- on_guild_remove_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_guild_remove_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_remove(mock_guild)
# Verify info log about removal
mock_bot.log.info.assert_any_call(f"Bot removed from guild: {mock_guild.name} (ID: {mock_guild.id})")
@@ -554,7 +537,7 @@ async def test_on_guild_remove_event_success(self, mock_dal_class, mock_bot, moc
mock_dal.delete_server.assert_called_once_with(mock_guild.id)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_remove.ServersDal")
async def test_on_guild_remove_event_cleanup_failure(self, mock_dal_class, mock_bot, mock_guild):
"""Test on_guild_remove event handler when cleanup fails."""
mock_dal = AsyncMock()
@@ -563,9 +546,8 @@ async def test_on_guild_remove_event_cleanup_failure(self, mock_dal_class, mock_
cog = OnGuildRemove(mock_bot)
- on_guild_remove_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_guild_remove_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_remove(mock_guild)
# Verify warning about incomplete cleanup
mock_bot.log.warning.assert_called_once_with(f"Database cleanup may be incomplete for guild: {mock_guild.name}")
@@ -578,9 +560,8 @@ async def test_on_guild_remove_event_general_exception(self, mock_bot, mock_guil
# Make the info log raise to trigger the outer exception handler
mock_bot.log.info.side_effect = RuntimeError("Unexpected error")
- on_guild_remove_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_guild_remove_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_remove(mock_guild)
mock_bot.log.error.assert_called_once()
error_call = mock_bot.log.error.call_args[0][0]
@@ -592,7 +573,7 @@ def test_on_guild_remove_cog_inheritance(self, mock_bot):
cog = OnGuildRemove(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
# =============================================================================
@@ -621,34 +602,32 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_presence_update.gw2_utils.check_gw2_game_activity')
+ @patch("src.bot.cogs.events.on_presence_update.gw2_utils.check_gw2_game_activity")
async def test_on_presence_update_non_bot_user(self, mock_check_gw2, mock_bot):
"""Test on_presence_update event for a non-bot user."""
- OnPresenceUpdate(mock_bot)
-
- on_presence_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnPresenceUpdate(mock_bot)
mock_before = MagicMock()
mock_after = MagicMock()
mock_after.bot = False
- await on_presence_update_event(mock_before, mock_after)
+ # Call the listener method directly
+ await cog.on_presence_update(mock_before, mock_after)
mock_check_gw2.assert_called_once_with(mock_bot, mock_before, mock_after)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_presence_update.gw2_utils.check_gw2_game_activity')
+ @patch("src.bot.cogs.events.on_presence_update.gw2_utils.check_gw2_game_activity")
async def test_on_presence_update_bot_user(self, mock_check_gw2, mock_bot):
"""Test on_presence_update event for a bot user (should be ignored)."""
- OnPresenceUpdate(mock_bot)
-
- on_presence_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnPresenceUpdate(mock_bot)
mock_before = MagicMock()
mock_after = MagicMock()
mock_after.bot = True
- await on_presence_update_event(mock_before, mock_after)
+ # Call the listener method directly
+ await cog.on_presence_update(mock_before, mock_after)
mock_check_gw2.assert_not_called()
@@ -658,4 +637,4 @@ def test_on_presence_update_cog_inheritance(self, mock_bot):
cog = OnPresenceUpdate(mock_bot)
assert isinstance(cog, commands.Cog)
- assert hasattr(cog, 'bot')
+ assert hasattr(cog, "bot")
diff --git a/tests/unit/bot/events/test_member_events.py b/tests/unit/bot/events/test_member_events.py
index 44cec306..091b06cc 100644
--- a/tests/unit/bot/events/test_member_events.py
+++ b/tests/unit/bot/events/test_member_events.py
@@ -6,7 +6,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_member_join import (
MemberJoinHandler,
@@ -35,7 +35,6 @@ def mock_bot():
bot.user.id = 99999
bot.user.avatar = MagicMock()
bot.user.avatar.url = "https://example.com/bot.png"
- bot.event = MagicMock(side_effect=lambda func: func)
bot.add_cog = AsyncMock()
return bot
@@ -70,7 +69,7 @@ def test_init(self, mock_bot):
builder = WelcomeMessageBuilder(mock_bot)
assert builder.bot == mock_bot
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
def test_create_join_embed_with_avatar(self, mock_datetime, mock_bot, mock_member):
"""Test create_join_embed when member has an avatar."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -86,7 +85,7 @@ def test_create_join_embed_with_avatar(self, mock_datetime, mock_bot, mock_membe
assert result.footer.text == "2023-06-15 10:30:00 UTC"
assert result.footer.icon_url == mock_bot.user.avatar.url
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
def test_create_join_embed_without_avatar(self, mock_datetime, mock_bot, mock_member):
"""Test create_join_embed when member has no avatar."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -101,7 +100,7 @@ def test_create_join_embed_without_avatar(self, mock_datetime, mock_bot, mock_me
# Thumbnail should not be set when avatar is None
assert result.thumbnail.url is None
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
def test_create_join_embed_with_exception(self, mock_datetime, mock_bot, mock_member):
"""Test create_join_embed when an exception occurs."""
mock_datetime.side_effect = Exception("DateTime error")
@@ -115,7 +114,7 @@ def test_create_join_embed_with_exception(self, mock_datetime, mock_bot, mock_me
assert "joined the server!" in result.description
mock_bot.log.error.assert_called_once()
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
def test_create_join_message_success(self, mock_datetime, mock_bot, mock_member):
"""Test create_join_message with successful execution."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -126,7 +125,7 @@ def test_create_join_message_success(self, mock_datetime, mock_bot, mock_member)
expected = f"TestUser {messages.JOINED_THE_SERVER}\n2023-06-15 10:30:00"
assert result == expected
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
def test_create_join_message_exception(self, mock_datetime, mock_bot, mock_member):
"""Test create_join_message when an exception occurs."""
mock_datetime.side_effect = Exception("DateTime error")
@@ -153,7 +152,7 @@ def test_init(self, mock_bot):
assert isinstance(handler.message_builder, WelcomeMessageBuilder)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.ServersDal')
+ @patch("src.bot.cogs.events.on_member_join.ServersDal")
async def test_process_member_join_no_config(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_join when no server config is found."""
mock_dal = AsyncMock()
@@ -168,7 +167,7 @@ async def test_process_member_join_no_config(self, mock_dal_class, mock_bot, moc
assert "No server config found" in mock_bot.log.warning.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.ServersDal')
+ @patch("src.bot.cogs.events.on_member_join.ServersDal")
async def test_process_member_join_msg_on_join_false(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_join when msg_on_join is False."""
mock_dal = AsyncMock()
@@ -183,9 +182,9 @@ async def test_process_member_join_msg_on_join_false(self, mock_dal_class, mock_
mock_bot.log.info.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_join.ServersDal')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_join.ServersDal")
async def test_process_member_join_msg_on_join_true(
self, mock_dal_class, mock_datetime, mock_send_msg, mock_bot, mock_member
):
@@ -204,7 +203,7 @@ async def test_process_member_join_msg_on_join_true(
assert "Welcome message sent" in mock_bot.log.info.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.ServersDal')
+ @patch("src.bot.cogs.events.on_member_join.ServersDal")
async def test_process_member_join_exception(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_join when an exception occurs."""
mock_dal = AsyncMock()
@@ -218,8 +217,8 @@ async def test_process_member_join_exception(self, mock_dal_class, mock_bot, moc
assert "Error processing member join" in mock_bot.log.error.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
async def test_send_welcome_message_success(self, mock_datetime, mock_send_msg, mock_bot, mock_member):
"""Test _send_welcome_message with successful execution."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -237,8 +236,8 @@ async def test_send_welcome_message_success(self, mock_datetime, mock_send_msg,
assert "Welcome message sent" in mock_bot.log.info.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_join.bot_utils.get_current_date_time_str_long")
async def test_send_welcome_message_exception(self, mock_datetime, mock_send_msg, mock_bot, mock_member):
"""Test _send_welcome_message when an exception occurs."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -264,8 +263,6 @@ def test_init(self, mock_bot):
cog = OnMemberJoin(mock_bot)
assert cog.bot == mock_bot
assert isinstance(cog.join_handler, MemberJoinHandler)
- # Verify event was registered
- mock_bot.event.assert_called_once()
@pytest.mark.asyncio
async def test_setup_function(self, mock_bot):
@@ -293,7 +290,7 @@ def test_init(self, mock_bot):
builder = FarewellMessageBuilder(mock_bot)
assert builder.bot == mock_bot
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
def test_create_leave_embed_with_avatar(self, mock_datetime, mock_bot, mock_member):
"""Test create_leave_embed when member has an avatar."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -309,7 +306,7 @@ def test_create_leave_embed_with_avatar(self, mock_datetime, mock_bot, mock_memb
assert result.footer.text == "2023-06-15 10:30:00 UTC"
assert result.footer.icon_url == mock_bot.user.avatar.url
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
def test_create_leave_embed_without_avatar(self, mock_datetime, mock_bot, mock_member):
"""Test create_leave_embed when member has no avatar."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -324,7 +321,7 @@ def test_create_leave_embed_without_avatar(self, mock_datetime, mock_bot, mock_m
# Thumbnail should not be set when avatar is None
assert result.thumbnail.url is None
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
def test_create_leave_embed_with_exception(self, mock_datetime, mock_bot, mock_member):
"""Test create_leave_embed when an exception occurs."""
mock_datetime.side_effect = Exception("DateTime error")
@@ -338,7 +335,7 @@ def test_create_leave_embed_with_exception(self, mock_datetime, mock_bot, mock_m
assert "left the server!" in result.description
mock_bot.log.error.assert_called_once()
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
def test_create_leave_message_success(self, mock_datetime, mock_bot, mock_member):
"""Test create_leave_message with successful execution."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -349,7 +346,7 @@ def test_create_leave_message_success(self, mock_datetime, mock_bot, mock_member
expected = f"TestUser {messages.LEFT_THE_SERVER}\n2023-06-15 10:30:00"
assert result == expected
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
def test_create_leave_message_exception(self, mock_datetime, mock_bot, mock_member):
"""Test create_leave_message when an exception occurs."""
mock_datetime.side_effect = Exception("DateTime error")
@@ -376,7 +373,7 @@ def test_init(self, mock_bot):
assert isinstance(handler.message_builder, FarewellMessageBuilder)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_member_remove.ServersDal")
async def test_process_member_leave_bot_is_member(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_leave when the bot itself is the member leaving."""
mock_member.id = mock_bot.user.id # Same ID as bot
@@ -388,7 +385,7 @@ async def test_process_member_leave_bot_is_member(self, mock_dal_class, mock_bot
mock_dal_class.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_member_remove.ServersDal")
async def test_process_member_leave_no_config(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_leave when no server config is found."""
mock_dal = AsyncMock()
@@ -403,7 +400,7 @@ async def test_process_member_leave_no_config(self, mock_dal_class, mock_bot, mo
assert "No server config found" in mock_bot.log.warning.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_member_remove.ServersDal")
async def test_process_member_leave_msg_on_leave_false(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_leave when msg_on_leave is False."""
mock_dal = AsyncMock()
@@ -418,9 +415,9 @@ async def test_process_member_leave_msg_on_leave_false(self, mock_dal_class, moc
mock_bot.log.info.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_remove.ServersDal")
async def test_process_member_leave_msg_on_leave_true(
self, mock_dal_class, mock_datetime, mock_send_msg, mock_bot, mock_member
):
@@ -439,7 +436,7 @@ async def test_process_member_leave_msg_on_leave_true(
assert "Farewell message sent" in mock_bot.log.info.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.ServersDal')
+ @patch("src.bot.cogs.events.on_member_remove.ServersDal")
async def test_process_member_leave_exception(self, mock_dal_class, mock_bot, mock_member):
"""Test process_member_leave when an exception occurs."""
mock_dal = AsyncMock()
@@ -453,8 +450,8 @@ async def test_process_member_leave_exception(self, mock_dal_class, mock_bot, mo
assert "Error processing member leave" in mock_bot.log.error.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
async def test_send_farewell_message_success(self, mock_datetime, mock_send_msg, mock_bot, mock_member):
"""Test _send_farewell_message with successful execution."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -472,8 +469,8 @@ async def test_send_farewell_message_success(self, mock_datetime, mock_send_msg,
assert "Farewell message sent" in mock_bot.log.info.call_args[0][0]
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
- @patch('src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_member_remove.bot_utils.get_current_date_time_str_long")
async def test_send_farewell_message_exception(self, mock_datetime, mock_send_msg, mock_bot, mock_member):
"""Test _send_farewell_message when an exception occurs."""
mock_datetime.return_value = "2023-06-15 10:30:00"
@@ -499,8 +496,6 @@ def test_init(self, mock_bot):
cog = OnMemberRemove(mock_bot)
assert cog.bot == mock_bot
assert isinstance(cog.leave_handler, MemberLeaveHandler)
- # Verify event was registered
- mock_bot.event.assert_called_once()
@pytest.mark.asyncio
async def test_setup_function(self, mock_bot):
@@ -527,8 +522,6 @@ def test_init(self, mock_bot):
"""Test OnUserUpdate cog initialization."""
cog = OnUserUpdate(mock_bot)
assert cog.bot == mock_bot
- # Verify event was registered
- mock_bot.event.assert_called_once()
@pytest.mark.asyncio
async def test_setup_function(self, mock_bot):
@@ -543,26 +536,26 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_embed')
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_embed")
async def test_on_user_update_bot_user(self, mock_get_embed, mock_bot):
"""Test on_user_update with a bot user (should return early)."""
before = MagicMock()
after = MagicMock()
after.bot = True
- OnUserUpdate(mock_bot)
- on_user_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnUserUpdate(mock_bot)
- await on_user_update_event(before, after)
+ # Call the listener method directly
+ await cog.on_user_update(before, after)
# Should not process bot users
mock_get_embed.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_user_update.ServersDal')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_user_update.ServersDal")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
async def test_on_user_update_avatar_change(
self, mock_send_msg, mock_dal_class, mock_datetime, mock_get_embed, mock_bot
):
@@ -595,10 +588,10 @@ async def test_on_user_update_avatar_change(
mock_dal_class.return_value = mock_dal
mock_dal.get_server.return_value = {"msg_on_member_update": True}
- OnUserUpdate(mock_bot)
- on_user_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnUserUpdate(mock_bot)
- await on_user_update_event(before, after)
+ # Call the listener method directly
+ await cog.on_user_update(before, after)
# Verify embed has avatar field
field_names = [f.name for f in embed.fields]
@@ -609,10 +602,10 @@ async def test_on_user_update_avatar_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_user_update.ServersDal')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_user_update.ServersDal")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
async def test_on_user_update_name_change(
self, mock_send_msg, mock_dal_class, mock_datetime, mock_get_embed, mock_bot
):
@@ -645,10 +638,10 @@ async def test_on_user_update_name_change(
mock_dal_class.return_value = mock_dal
mock_dal.get_server.return_value = {"msg_on_member_update": True}
- OnUserUpdate(mock_bot)
- on_user_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnUserUpdate(mock_bot)
- await on_user_update_event(before, after)
+ # Call the listener method directly
+ await cog.on_user_update(before, after)
# Verify embed has name fields
field_names = [f.name for f in embed.fields]
@@ -659,10 +652,10 @@ async def test_on_user_update_name_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_user_update.ServersDal')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_user_update.ServersDal")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
async def test_on_user_update_discriminator_change(
self, mock_send_msg, mock_dal_class, mock_datetime, mock_get_embed, mock_bot
):
@@ -695,10 +688,10 @@ async def test_on_user_update_discriminator_change(
mock_dal_class.return_value = mock_dal
mock_dal.get_server.return_value = {"msg_on_member_update": True}
- OnUserUpdate(mock_bot)
- on_user_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnUserUpdate(mock_bot)
- await on_user_update_event(before, after)
+ # Call the listener method directly
+ await cog.on_user_update(before, after)
# Verify embed has discriminator fields
field_names = [f.name for f in embed.fields]
@@ -709,10 +702,10 @@ async def test_on_user_update_discriminator_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_user_update.ServersDal')
- @patch('src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel', new_callable=AsyncMock)
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_user_update.ServersDal")
+ @patch("src.bot.cogs.events.on_user_update.bot_utils.send_msg_to_system_channel", new_callable=AsyncMock)
async def test_on_user_update_no_changes(
self, mock_send_msg, mock_dal_class, mock_datetime, mock_get_embed, mock_bot
):
@@ -736,10 +729,10 @@ async def test_on_user_update_no_changes(
after.name = "TestUser"
after.discriminator = "1234"
- OnUserUpdate(mock_bot)
- on_user_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnUserUpdate(mock_bot)
- await on_user_update_event(before, after)
+ # Call the listener method directly
+ await cog.on_user_update(before, after)
# Verify no fields were added
assert len(embed.fields) == 0
@@ -755,7 +748,7 @@ async def test_on_user_update_no_changes(
class TestOnMemberJoinEvent:
- """Test cases for the inner on_member_join event function registered in OnMemberJoin.__init__."""
+ """Test cases for the on_member_join listener method in OnMemberJoin."""
@pytest.fixture
def mock_bot(self):
@@ -770,7 +763,6 @@ def mock_bot(self):
bot.user.id = 99999
bot.user.avatar = MagicMock()
bot.user.avatar.url = "https://example.com/bot.png"
- bot.event = MagicMock(side_effect=lambda func: func)
bot.add_cog = AsyncMock()
return bot
@@ -794,11 +786,10 @@ def mock_member(self):
async def test_on_member_join_event_calls_handler(self, mock_bot, mock_member):
"""Test that the on_member_join event calls the join handler."""
cog = OnMemberJoin(mock_bot)
- # Get the registered event function
- on_member_join_event = mock_bot.event.call_args_list[0][0][0]
- with patch.object(cog.join_handler, 'process_member_join', new_callable=AsyncMock) as mock_process:
- await on_member_join_event(mock_member)
+ with patch.object(cog.join_handler, "process_member_join", new_callable=AsyncMock) as mock_process:
+ # Call the listener method directly
+ await cog.on_member_join(mock_member)
mock_process.assert_called_once_with(mock_member)
mock_bot.log.info.assert_called_once()
assert "Member joined" in mock_bot.log.info.call_args[0][0]
@@ -807,12 +798,12 @@ async def test_on_member_join_event_calls_handler(self, mock_bot, mock_member):
async def test_on_member_join_event_handles_exception(self, mock_bot, mock_member):
"""Test that on_member_join catches exceptions and logs critical error."""
cog = OnMemberJoin(mock_bot)
- on_member_join_event = mock_bot.event.call_args_list[0][0][0]
with patch.object(
- cog.join_handler, 'process_member_join', new_callable=AsyncMock, side_effect=RuntimeError("fail")
+ cog.join_handler, "process_member_join", new_callable=AsyncMock, side_effect=RuntimeError("fail")
):
- await on_member_join_event(mock_member)
+ # Call the listener method directly
+ await cog.on_member_join(mock_member)
mock_bot.log.error.assert_called_once()
assert "Critical error" in mock_bot.log.error.call_args[0][0]
@@ -823,7 +814,7 @@ async def test_on_member_join_event_handles_exception(self, mock_bot, mock_membe
class TestOnMemberRemoveEvent:
- """Test cases for the inner on_member_remove event function registered in OnMemberRemove.__init__."""
+ """Test cases for the on_member_remove listener method in OnMemberRemove."""
@pytest.fixture
def mock_bot(self):
@@ -838,7 +829,6 @@ def mock_bot(self):
bot.user.id = 99999
bot.user.avatar = MagicMock()
bot.user.avatar.url = "https://example.com/bot.png"
- bot.event = MagicMock(side_effect=lambda func: func)
bot.add_cog = AsyncMock()
return bot
@@ -862,11 +852,10 @@ def mock_member(self):
async def test_on_member_remove_event_calls_handler(self, mock_bot, mock_member):
"""Test that the on_member_remove event calls the leave handler."""
cog = OnMemberRemove(mock_bot)
- # Get the registered event function
- on_member_remove_event = mock_bot.event.call_args_list[0][0][0]
- with patch.object(cog.leave_handler, 'process_member_leave', new_callable=AsyncMock) as mock_process:
- await on_member_remove_event(mock_member)
+ with patch.object(cog.leave_handler, "process_member_leave", new_callable=AsyncMock) as mock_process:
+ # Call the listener method directly
+ await cog.on_member_remove(mock_member)
mock_process.assert_called_once_with(mock_member)
mock_bot.log.info.assert_called_once()
assert "Member left" in mock_bot.log.info.call_args[0][0]
@@ -875,11 +864,11 @@ async def test_on_member_remove_event_calls_handler(self, mock_bot, mock_member)
async def test_on_member_remove_event_handles_exception(self, mock_bot, mock_member):
"""Test that on_member_remove catches exceptions and logs critical error."""
cog = OnMemberRemove(mock_bot)
- on_member_remove_event = mock_bot.event.call_args_list[0][0][0]
with patch.object(
- cog.leave_handler, 'process_member_leave', new_callable=AsyncMock, side_effect=RuntimeError("fail")
+ cog.leave_handler, "process_member_leave", new_callable=AsyncMock, side_effect=RuntimeError("fail")
):
- await on_member_remove_event(mock_member)
+ # Call the listener method directly
+ await cog.on_member_remove(mock_member)
mock_bot.log.error.assert_called_once()
assert "Critical error" in mock_bot.log.error.call_args[0][0]
diff --git a/tests/unit/bot/events/test_on_command_error.py b/tests/unit/bot/events/test_on_command_error.py
index 1677d113..0a0a8a19 100644
--- a/tests/unit/bot/events/test_on_command_error.py
+++ b/tests/unit/bot/events/test_on_command_error.py
@@ -6,7 +6,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_command_error import ErrorContext, ErrorMessageBuilder, Errors
from src.bot.constants import messages, variables
@@ -202,10 +202,10 @@ def test_init(self, mock_bot):
"""Test Errors cog initialization."""
cog = Errors(mock_bot)
assert cog.bot == mock_bot
- assert hasattr(cog, 'message_builder')
+ assert hasattr(cog, "message_builder")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_send_error_message_no_log(self, mock_send_error, mock_ctx):
"""Test sending error message without logging."""
await Errors._send_error_message(mock_ctx, "Test error", False)
@@ -214,7 +214,7 @@ async def test_send_error_message_no_log(self, mock_send_error, mock_ctx):
mock_ctx.bot.log.error.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_send_error_message_with_log(self, mock_send_error, mock_ctx):
"""Test sending error message with logging."""
await Errors._send_error_message(mock_ctx, "Test error", True)
@@ -228,7 +228,7 @@ async def test_send_error_message_with_log(self, mock_send_error, mock_ctx):
assert "Test Server" in log_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_no_private_message(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling NoPrivateMessage error."""
context = ErrorContext(mock_ctx, Exception())
@@ -238,7 +238,7 @@ async def test_handle_no_private_message(self, mock_send_error, errors_cog, mock
mock_send_error.assert_called_once_with(mock_ctx, "Test error", True)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_command_not_found(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling CommandNotFound error."""
context = ErrorContext(mock_ctx, Exception())
@@ -249,7 +249,7 @@ async def test_handle_command_not_found(self, mock_send_error, errors_cog, mock_
mock_send_error.assert_called_once_with(mock_ctx, expected_msg, False)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_bad_argument_gw2_config_status(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling BadArgument error for GW2 config status."""
context = ErrorContext(mock_ctx, Exception())
@@ -262,8 +262,8 @@ async def test_handle_bad_argument_gw2_config_status(self, mock_send_error, erro
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
- @patch('src.bot.cogs.events.on_command_error.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.delete_message")
async def test_handle_command_on_cooldown_sensitive_command(
self, mock_delete, mock_send_error, errors_cog, mock_ctx
):
@@ -278,8 +278,8 @@ async def test_handle_command_on_cooldown_sensitive_command(
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
- @patch('src.bot.cogs.events.on_command_error.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.delete_message")
async def test_handle_command_on_cooldown_normal_command(self, mock_delete, mock_send_error, errors_cog, mock_ctx):
"""Test handling CommandOnCooldown for normal commands."""
context = ErrorContext(mock_ctx, Exception())
@@ -292,7 +292,7 @@ async def test_handle_command_on_cooldown_normal_command(self, mock_delete, mock
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_too_many_arguments(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling TooManyArguments error."""
context = ErrorContext(mock_ctx, Exception())
@@ -303,7 +303,7 @@ async def test_handle_too_many_arguments(self, mock_send_error, errors_cog, mock
mock_send_error.assert_called_once_with(mock_ctx, expected_msg, False)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_unknown_error(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling unknown error types."""
context = ErrorContext(mock_ctx, Exception())
@@ -328,7 +328,7 @@ async def test_setup_function(self, mock_bot):
def test_errors_cog_inheritance(self, errors_cog):
"""Test that Errors cog properly inherits from commands.Cog."""
assert isinstance(errors_cog, commands.Cog)
- assert hasattr(errors_cog, 'bot')
+ assert hasattr(errors_cog, "bot")
def test_error_context_with_complex_command(self, mock_ctx, mock_error):
"""Test ErrorContext with complex command structure."""
@@ -340,7 +340,7 @@ def test_error_context_with_complex_command(self, mock_ctx, mock_error):
assert context.help_command == "!help admin config profanity on"
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_command_error(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling CommandError."""
context = ErrorContext(mock_ctx, Exception())
@@ -492,7 +492,7 @@ class TestOnCommandErrorEventHandler:
"""Test cases for the on_command_error event handler (lines 133-151)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_command_not_found(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches CommandNotFound properly (lines 133-151)."""
cog = Errors(mock_bot)
@@ -509,7 +509,7 @@ async def test_on_command_error_command_not_found(self, mock_send_error, mock_bo
assert messages.COMMAND_NOT_FOUND in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_missing_required_argument(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches MissingRequiredArgument (lines 140, 175-176)."""
cog = Errors(mock_bot)
@@ -526,7 +526,7 @@ async def test_on_command_error_missing_required_argument(self, mock_send_error,
assert messages.MISSING_REQUIRED_ARGUMENT_HELP_MESSAGE in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_check_failure(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches CheckFailure (lines 141, 180-181)."""
cog = Errors(mock_bot)
@@ -540,7 +540,7 @@ async def test_on_command_error_check_failure(self, mock_send_error, mock_bot, m
assert messages.NOT_ADMIN_USE_COMMAND in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_bad_argument_gw2_server(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches BadArgument for GW2 server (lines 142, 188-190)."""
cog = Errors(mock_bot)
@@ -554,7 +554,7 @@ async def test_on_command_error_bad_argument_gw2_server(self, mock_send_error, m
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_bad_argument_else_branch(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches BadArgument else branch (lines 191-192)."""
cog = Errors(mock_bot)
@@ -569,7 +569,7 @@ async def test_on_command_error_bad_argument_else_branch(self, mock_send_error,
assert messages.UNKNOWN_OPTION in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_command_invoke_error(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches CommandInvokeError (lines 144, 204-205)."""
cog = Errors(mock_bot)
@@ -584,7 +584,7 @@ async def test_on_command_error_command_invoke_error(self, mock_send_error, mock
assert messages.COMMAND_INTERNAL_ERROR in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_forbidden(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches discord.Forbidden (lines 147, 234-235)."""
import discord
@@ -603,7 +603,7 @@ async def test_on_command_error_forbidden(self, mock_send_error, mock_bot, mock_
assert call_msg == messages.DM_CANNOT_EXECUTE_COMMAND
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_forbidden_privilege_low(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches discord.Forbidden with low privilege (lines 234-235)."""
import discord
@@ -622,7 +622,7 @@ async def test_on_command_error_forbidden_privilege_low(self, mock_send_error, m
assert call_msg == messages.PRIVILEGE_LOW
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_no_private_message(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches NoPrivateMessage (line 138)."""
cog = Errors(mock_bot)
@@ -634,7 +634,7 @@ async def test_on_command_error_no_private_message(self, mock_send_error, mock_b
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_command_error(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches CommandError (line 143)."""
cog = Errors(mock_bot)
@@ -648,7 +648,7 @@ async def test_on_command_error_command_error(self, mock_send_error, mock_bot, m
assert "CommandError:" in call_msg
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_on_command_error_unknown_error_type(self, mock_send_error, mock_bot, mock_ctx):
"""Test on_command_error dispatches unknown error type (line 150)."""
cog = Errors(mock_bot)
@@ -664,7 +664,7 @@ class TestSendErrorMessageGuildNone:
"""Test cases for _send_error_message when guild is None (lines 204-205 logging branch)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_command_error.bot_utils.send_error_msg")
async def test_send_error_message_with_log_no_guild(self, mock_send_error, mock_ctx):
"""Test sending error message with logging when guild is None."""
mock_ctx.guild = None
@@ -684,7 +684,7 @@ class TestHandleBadArgumentBranches:
"""Test cases for _handle_bad_argument different branches (lines 188-192)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_bad_argument_gw2_config_server(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling BadArgument for GW2 config server (lines 188-190)."""
context = ErrorContext(mock_ctx, Exception())
@@ -699,7 +699,7 @@ async def test_handle_bad_argument_gw2_config_server(self, mock_send_error, erro
mock_send_error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_bad_argument_else_branch(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling BadArgument else branch (lines 191-192)."""
context = ErrorContext(mock_ctx, Exception())
@@ -716,7 +716,7 @@ class TestHandleMissingArgument:
"""Test cases for _handle_missing_argument (lines 175-176)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_missing_argument(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling MissingRequiredArgument (lines 175-176)."""
context = ErrorContext(mock_ctx, Exception())
@@ -732,7 +732,7 @@ class TestHandleCheckFailure:
"""Test cases for _handle_check_failure (lines 180-181)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_check_failure_not_owner(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling CheckFailure with not owner (lines 180-181)."""
context = ErrorContext(mock_ctx, Exception())
@@ -744,7 +744,7 @@ async def test_handle_check_failure_not_owner(self, mock_send_error, errors_cog,
mock_send_error.assert_called_once_with(mock_ctx, expected_msg, True)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_check_failure_other(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling CheckFailure with generic error (lines 180-181)."""
context = ErrorContext(mock_ctx, Exception())
@@ -759,7 +759,7 @@ class TestHandleCommandInvokeError:
"""Test cases for _handle_command_invoke_error (lines 204-205)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_command_invoke_error(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling CommandInvokeError (lines 204-205)."""
context = ErrorContext(mock_ctx, Exception())
@@ -778,7 +778,7 @@ class TestHandleForbidden:
"""Test cases for _handle_forbidden (lines 234-235)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_forbidden_dm_channel(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling Forbidden for DM channel (lines 234-235)."""
context = ErrorContext(mock_ctx, Exception())
@@ -789,7 +789,7 @@ async def test_handle_forbidden_dm_channel(self, mock_send_error, errors_cog, mo
mock_send_error.assert_called_once_with(mock_ctx, messages.DM_CANNOT_EXECUTE_COMMAND, True)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_command_error.Errors._send_error_message')
+ @patch("src.bot.cogs.events.on_command_error.Errors._send_error_message")
async def test_handle_forbidden_privilege_low(self, mock_send_error, errors_cog, mock_ctx):
"""Test handling Forbidden for low privilege (lines 234-235)."""
context = ErrorContext(mock_ctx, Exception())
diff --git a/tests/unit/bot/events/test_on_connect.py b/tests/unit/bot/events/test_on_connect.py
index 43a8322e..1098db76 100644
--- a/tests/unit/bot/events/test_on_connect.py
+++ b/tests/unit/bot/events/test_on_connect.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_connect import ConnectionHandler, GuildSynchronizer, OnConnect
@@ -30,8 +30,6 @@ async def mock_fetch_guilds(limit=None):
bot.fetch_guilds = mock_fetch_guilds
# Ensure add_cog doesn't return a coroutine
bot.add_cog = AsyncMock(return_value=None)
- # Mock the event decorator to prevent coroutine issues
- bot.event = MagicMock(side_effect=lambda func: func)
return bot
@@ -83,7 +81,7 @@ def test_init(self, mock_bot):
assert synchronizer.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.ServersDal')
+ @patch("src.bot.cogs.events.on_connect.ServersDal")
async def test_get_database_server_ids_success(self, mock_dal_class, guild_synchronizer):
"""Test getting database server IDs successfully."""
# Setup mock
@@ -97,7 +95,7 @@ async def test_get_database_server_ids_success(self, mock_dal_class, guild_synch
mock_dal.get_server.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.ServersDal')
+ @patch("src.bot.cogs.events.on_connect.ServersDal")
async def test_get_database_server_ids_empty(self, mock_dal_class, guild_synchronizer):
"""Test getting database server IDs when empty."""
# Setup mock
@@ -110,7 +108,7 @@ async def test_get_database_server_ids_empty(self, mock_dal_class, guild_synchro
assert result == set()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.ServersDal')
+ @patch("src.bot.cogs.events.on_connect.ServersDal")
async def test_get_database_server_ids_error(self, mock_dal_class, guild_synchronizer):
"""Test getting database server IDs with error."""
# Setup mock to raise exception
@@ -160,7 +158,7 @@ async def error_fetch_guilds(limit=None):
guild_synchronizer.bot.log.error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.bot_utils.insert_server')
+ @patch("src.bot.cogs.events.on_connect.bot_utils.insert_server")
async def test_add_missing_guilds_success(self, mock_insert_server, guild_synchronizer, mock_guild):
"""Test adding missing guilds successfully."""
missing_guild_ids = {12345}
@@ -173,7 +171,7 @@ async def test_add_missing_guilds_success(self, mock_insert_server, guild_synchr
guild_synchronizer.bot.log.info.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.bot_utils.insert_server')
+ @patch("src.bot.cogs.events.on_connect.bot_utils.insert_server")
async def test_add_missing_guilds_guild_not_found(self, mock_insert_server, guild_synchronizer):
"""Test adding missing guilds when guild not found."""
missing_guild_ids = {12345}
@@ -186,7 +184,7 @@ async def test_add_missing_guilds_guild_not_found(self, mock_insert_server, guil
guild_synchronizer.bot.log.warning.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.bot_utils.insert_server')
+ @patch("src.bot.cogs.events.on_connect.bot_utils.insert_server")
async def test_add_missing_guilds_insert_error(self, mock_insert_server, guild_synchronizer, mock_guild):
"""Test adding missing guilds with insert error."""
missing_guild_ids = {12345}
@@ -297,10 +295,8 @@ async def test_on_connect_event_success(self, mock_bot):
cog = OnConnect(mock_bot)
cog.connection_handler.process_connection = AsyncMock()
- # Access the event handler directly
- on_connect_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_connect_event()
+ # Call the listener method directly
+ await cog.on_connect()
cog.connection_handler.process_connection.assert_called_once()
@@ -310,10 +306,8 @@ async def test_on_connect_event_error(self, mock_bot):
cog = OnConnect(mock_bot)
cog.connection_handler.process_connection = AsyncMock(side_effect=RuntimeError("Critical error"))
- # Access the event handler directly
- on_connect_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_connect_event()
+ # Call the listener method directly
+ await cog.on_connect()
mock_bot.log.error.assert_called_once()
error_call = mock_bot.log.error.call_args[0][0]
@@ -324,7 +318,7 @@ def test_on_connect_cog_inheritance(self, on_connect_cog):
from discord.ext import commands
assert isinstance(on_connect_cog, commands.Cog)
- assert hasattr(on_connect_cog, 'bot')
+ assert hasattr(on_connect_cog, "bot")
@pytest.mark.asyncio
async def test_guild_synchronizer_integration(self, mock_bot):
@@ -333,13 +327,13 @@ async def test_guild_synchronizer_integration(self, mock_bot):
cog = OnConnect(mock_bot)
# Verify that connection handler has a guild synchronizer
- assert hasattr(cog.connection_handler, 'guild_synchronizer')
+ assert hasattr(cog.connection_handler, "guild_synchronizer")
assert isinstance(cog.connection_handler.guild_synchronizer, GuildSynchronizer)
assert cog.connection_handler.guild_synchronizer.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_connect.ServersDal')
- @patch('src.bot.cogs.events.on_connect.bot_utils.insert_server')
+ @patch("src.bot.cogs.events.on_connect.ServersDal")
+ @patch("src.bot.cogs.events.on_connect.bot_utils.insert_server")
async def test_full_integration_sync_process(self, mock_insert_server, mock_dal_class, mock_bot, mock_guilds):
"""Test full integration of synchronization process."""
# Setup database mock
diff --git a/tests/unit/bot/events/test_on_guild_join.py b/tests/unit/bot/events/test_on_guild_join.py
index b8976a37..220a5b08 100644
--- a/tests/unit/bot/events/test_on_guild_join.py
+++ b/tests/unit/bot/events/test_on_guild_join.py
@@ -6,7 +6,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_guild_join import OnGuildJoin, WelcomeMessageBuilder
from src.bot.constants import messages, variables
@@ -28,8 +28,6 @@ def mock_bot():
bot.get_user = MagicMock()
# Ensure add_cog doesn't return a coroutine
bot.add_cog = AsyncMock(return_value=None)
- # Mock the event decorator to prevent coroutine issues
- bot.event = MagicMock(side_effect=lambda func: func)
return bot
@@ -77,7 +75,7 @@ def test_build_welcome_message(self, welcome_message_builder):
result = welcome_message_builder.build_welcome_message(bot_name, prefix, games_included)
# Should use the message template with proper formatting
- expected = messages.GUILD_JOIN_BOT_MESSAGE.format(bot_name, prefix, games_included, prefix, prefix)
+ expected = messages.guild_join_bot_message(bot_name, prefix, games_included)
assert result == expected
def test_build_welcome_embed_with_avatar(self, welcome_message_builder, mock_bot):
@@ -191,17 +189,15 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
- @patch('src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED', ['GW2', 'WoW'])
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
+ @patch("src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED", ["GW2", "WoW"])
async def test_on_guild_join_success(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test successful guild join handling."""
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
- await on_guild_join_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_join(mock_guild)
# Verify server was inserted
mock_insert_server.assert_called_once_with(mock_bot, mock_guild)
@@ -217,17 +213,15 @@ async def test_on_guild_join_success(self, mock_send_msg, mock_insert_server, mo
assert isinstance(call_args[3], str) # message text
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
- @patch('src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED', ['GW2'])
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
+ @patch("src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED", ["GW2"])
async def test_on_guild_join_with_games(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test guild join with specific games included."""
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
- await on_guild_join_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_join(mock_guild)
# Verify the welcome message includes games
call_args = mock_send_msg.call_args[0]
@@ -237,37 +231,31 @@ async def test_on_guild_join_with_games(self, mock_send_msg, mock_insert_server,
assert "GW2" in welcome_text
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
async def test_on_guild_join_insert_server_error(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test guild join when server insertion fails."""
mock_insert_server.side_effect = Exception("Database error")
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
# Should raise exception since we're not handling it
with pytest.raises(Exception, match="Database error"):
- await on_guild_join_event(mock_guild)
+ await cog.on_guild_join(mock_guild)
# Insert should still have been attempted
mock_insert_server.assert_called_once_with(mock_bot, mock_guild)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
async def test_on_guild_join_send_message_error(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test guild join when sending message fails."""
mock_send_msg.side_effect = Exception("Send error")
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
# Should raise exception since we're not handling it
with pytest.raises(Exception, match="Send error"):
- await on_guild_join_event(mock_guild)
+ await cog.on_guild_join(mock_guild)
# Both operations should have been attempted
mock_insert_server.assert_called_once_with(mock_bot, mock_guild)
@@ -278,19 +266,17 @@ def test_on_guild_join_cog_inheritance(self, on_guild_join_cog):
from discord.ext import commands
assert isinstance(on_guild_join_cog, commands.Cog)
- assert hasattr(on_guild_join_cog, 'bot')
+ assert hasattr(on_guild_join_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
async def test_on_guild_join_embed_properties(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test that the welcome embed has the correct properties."""
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
- await on_guild_join_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_join(mock_guild)
# Get the embed that was sent
call_args = mock_send_msg.call_args[0]
@@ -311,17 +297,15 @@ def test_welcome_message_builder_static_methods(self):
assert inspect.isfunction(WelcomeMessageBuilder._set_footer)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.insert_server')
- @patch('src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel')
- @patch('src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED', [])
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.insert_server")
+ @patch("src.bot.cogs.events.on_guild_join.bot_utils.send_msg_to_system_channel")
+ @patch("src.bot.cogs.events.on_guild_join.variables.GAMES_INCLUDED", [])
async def test_on_guild_join_no_games(self, mock_send_msg, mock_insert_server, mock_bot, mock_guild):
"""Test guild join with no games included."""
- OnGuildJoin(mock_bot)
-
- # Access the event handler directly
- on_guild_join_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildJoin(mock_bot)
- await on_guild_join_event(mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_join(mock_guild)
# Should still work with empty games list
mock_insert_server.assert_called_once_with(mock_bot, mock_guild)
diff --git a/tests/unit/bot/events/test_on_guild_update.py b/tests/unit/bot/events/test_on_guild_update.py
index c1b5ad1c..cd80c479 100644
--- a/tests/unit/bot/events/test_on_guild_update.py
+++ b/tests/unit/bot/events/test_on_guild_update.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_guild_update import OnGuildUpdate
from src.bot.constants import messages
@@ -24,8 +24,6 @@ def mock_bot():
bot.user.avatar.url = "https://example.com/bot_avatar.png"
# Ensure add_cog doesn't return a coroutine
bot.add_cog = AsyncMock(return_value=None)
- # Mock the event decorator to prevent coroutine issues
- bot.event = MagicMock(side_effect=lambda func: func)
return bot
@@ -95,10 +93,10 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel")
async def test_on_guild_update_icon_change(
self,
mock_send_msg,
@@ -127,10 +125,10 @@ async def test_on_guild_update_icon_change(
mock_guild_before.name = mock_guild.name # Same name
mock_guild_before.owner_id = mock_guild.owner_id # Same owner
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Verify embed setup
mock_embed.set_footer.assert_called_with(icon_url=mock_bot.user.avatar.url, text="2023-01-01 12:00:00 UTC")
@@ -143,10 +141,10 @@ async def test_on_guild_update_icon_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel")
async def test_on_guild_update_name_change(
self,
mock_send_msg,
@@ -175,10 +173,10 @@ async def test_on_guild_update_name_change(
mock_guild_before.icon.url = mock_guild.icon.url # Same icon
mock_guild_before.owner_id = mock_guild.owner_id # Same owner
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Verify name change handling
mock_embed.add_field.assert_any_call(name=messages.PREVIOUS_NAME, value="Old Test Server")
@@ -188,10 +186,10 @@ async def test_on_guild_update_name_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel")
async def test_on_guild_update_owner_change(
self,
mock_send_msg,
@@ -220,10 +218,10 @@ async def test_on_guild_update_owner_change(
mock_guild_before.name = mock_guild.name # Same name
mock_guild_before.icon.url = mock_guild.icon.url # Same icon
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Verify owner change handling
mock_embed.set_thumbnail.assert_called_with(url=mock_guild.icon.url)
@@ -234,9 +232,9 @@ async def test_on_guild_update_owner_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
async def test_on_guild_update_no_changes(
self, mock_dal_class, mock_datetime, mock_get_embed, mock_bot, mock_guild_before, mock_guild, mock_embed
):
@@ -257,19 +255,19 @@ async def test_on_guild_update_no_changes(
# Mock empty fields list
mock_embed.fields = []
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should not send a message if no changes
mock_dal.get_server.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel")
async def test_on_guild_update_disabled_notifications(
self,
mock_send_msg,
@@ -299,18 +297,18 @@ async def test_on_guild_update_disabled_notifications(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should not send message if notifications disabled
mock_send_msg.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
async def test_on_guild_update_no_server_config(
self,
mock_dal_class,
@@ -339,17 +337,17 @@ async def test_on_guild_update_no_server_config(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should get server config but not send message
mock_dal.get_server.assert_called_once_with(mock_guild.id)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
async def test_on_guild_update_bot_no_avatar(
self,
mock_datetime,
@@ -370,18 +368,18 @@ async def test_on_guild_update_bot_no_avatar(
mock_guild_before.icon.url = mock_guild.icon.url
mock_guild_before.owner_id = mock_guild.owner_id
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should set footer with None icon_url
mock_embed.set_footer.assert_called_with(icon_url=None, text="2023-01-01 12:00:00 UTC")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
async def test_on_guild_update_database_error(
self,
mock_dal_class,
@@ -410,10 +408,10 @@ async def test_on_guild_update_database_error(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should log error
mock_bot.log.error.assert_called()
@@ -421,16 +419,16 @@ async def test_on_guild_update_database_error(
assert "Failed to send guild update notification" in error_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
async def test_on_guild_update_general_error(self, mock_get_embed, mock_bot, mock_guild_before, mock_guild):
"""Test on_guild_update with general error."""
# Setup embed to raise exception
mock_get_embed.side_effect = Exception("General error")
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should log error
mock_bot.log.error.assert_called()
@@ -439,10 +437,10 @@ async def test_on_guild_update_general_error(self, mock_get_embed, mock_bot, moc
assert mock_guild.name in error_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_guild_update.ServersDal')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_guild_update.ServersDal")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.send_msg_to_system_channel")
async def test_on_guild_update_icon_url_change(
self,
mock_send_msg,
@@ -475,18 +473,18 @@ async def test_on_guild_update_icon_url_change(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should handle icon change
mock_embed.add_field.assert_called_with(name=messages.NEW_SERVER_ICON, value="")
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
async def test_on_guild_update_null_name_before(
self, mock_datetime, mock_get_embed, mock_bot, mock_guild_before, mock_guild, mock_embed
):
@@ -502,17 +500,17 @@ async def test_on_guild_update_null_name_before(
mock_guild_before.icon.url = mock_guild.icon.url
mock_guild_before.owner_id = mock_guild.owner_id
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should only add new name field (not previous)
mock_embed.add_field.assert_called_with(name=messages.NEW_SERVER_NAME, value="New Test Server")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_guild_update.bot_utils.get_current_date_time_str_long")
async def test_on_guild_update_null_owner_before(
self,
mock_datetime,
@@ -534,10 +532,10 @@ async def test_on_guild_update_null_owner_before(
mock_guild_before.name = mock_guild.name
mock_guild_before.icon.url = mock_guild.icon.url
- OnGuildUpdate(mock_bot)
- on_guild_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnGuildUpdate(mock_bot)
- await on_guild_update_event(mock_guild_before, mock_guild)
+ # Call the listener method directly
+ await cog.on_guild_update(mock_guild_before, mock_guild)
# Should only add new owner field (not previous)
mock_embed.add_field.assert_called_with(name=messages.NEW_SERVER_OWNER, value=str(mock_guild.owner))
diff --git a/tests/unit/bot/events/test_on_member_update.py b/tests/unit/bot/events/test_on_member_update.py
index 23ab803f..f783d73e 100644
--- a/tests/unit/bot/events/test_on_member_update.py
+++ b/tests/unit/bot/events/test_on_member_update.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_member_update import OnMemberUpdate
from src.bot.constants import messages
@@ -24,8 +24,6 @@ def mock_bot():
bot.user.avatar.url = "https://example.com/bot_avatar.png"
# Ensure add_cog doesn't return a coroutine
bot.add_cog = AsyncMock(return_value=None)
- # Mock the event decorator to prevent coroutine issues
- bot.event = MagicMock(side_effect=lambda func: func)
return bot
@@ -111,10 +109,10 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel")
async def test_on_member_update_bot_member(
self,
mock_send_msg,
@@ -127,22 +125,20 @@ async def test_on_member_update_bot_member(
):
"""Test on_member_update with bot member (should be skipped)."""
mock_member.bot = True
- OnMemberUpdate(mock_bot)
-
- # Access the event handler directly
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should not process bot members
mock_get_embed.assert_not_called()
mock_send_msg.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel")
async def test_on_member_update_nickname_change(
self,
mock_send_msg,
@@ -171,10 +167,10 @@ async def test_on_member_update_nickname_change(
# Mock embed fields to simulate having fields after add_field is called
mock_embed.fields = [MagicMock()] # Simulate one field added
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Verify embed setup
mock_embed.set_author.assert_called_with(name=mock_member.display_name, icon_url=mock_member.avatar.url)
@@ -188,10 +184,10 @@ async def test_on_member_update_nickname_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel")
async def test_on_member_update_role_change(
self,
mock_send_msg,
@@ -227,10 +223,10 @@ async def test_on_member_update_role_change(
# Mock embed fields to simulate having fields after add_field is called
mock_embed.fields = [MagicMock()] # Simulate one field added
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Verify role fields added
mock_embed.add_field.assert_any_call(name=messages.PREVIOUS_ROLES, value="Member")
@@ -240,9 +236,9 @@ async def test_on_member_update_role_change(
mock_send_msg.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
async def test_on_member_update_no_changes(
self,
mock_dal_class,
@@ -269,19 +265,19 @@ async def test_on_member_update_no_changes(
# Mock empty fields list
mock_embed.fields = []
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should not send message if no changes
mock_dal.get_server.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.send_msg_to_system_channel")
async def test_on_member_update_disabled_notifications(
self,
mock_send_msg,
@@ -310,18 +306,18 @@ async def test_on_member_update_disabled_notifications(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should not send message if notifications disabled
mock_send_msg.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
async def test_on_member_update_no_server_config(
self,
mock_dal_class,
@@ -349,17 +345,17 @@ async def test_on_member_update_no_server_config(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should get server config but not send message
mock_dal.get_server.assert_called_once_with(mock_member.guild.id)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
async def test_on_member_update_no_avatar(
self,
mock_datetime,
@@ -379,17 +375,17 @@ async def test_on_member_update_no_avatar(
mock_member.nick = "NewNick"
mock_member_before.roles = mock_member.roles
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should set author without icon_url
mock_embed.set_author.assert_called_with(name=mock_member.display_name)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
async def test_on_member_update_bot_no_avatar(
self,
mock_datetime,
@@ -409,18 +405,18 @@ async def test_on_member_update_bot_no_avatar(
mock_member.nick = "NewNick"
mock_member_before.roles = mock_member.roles
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should set footer with None icon_url
mock_embed.set_footer.assert_called_with(icon_url=None, text="2023-01-01 12:00:00 UTC")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
- @patch('src.bot.cogs.events.on_member_update.ServersDal')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
+ @patch("src.bot.cogs.events.on_member_update.ServersDal")
async def test_on_member_update_database_error(
self,
mock_dal_class,
@@ -448,10 +444,10 @@ async def test_on_member_update_database_error(
# Mock fields being added
mock_embed.fields = [MagicMock()]
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should log error
mock_bot.log.error.assert_called()
@@ -459,16 +455,16 @@ async def test_on_member_update_database_error(
assert "Failed to send member update notification" in error_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
async def test_on_member_update_general_error(self, mock_get_embed, mock_bot, mock_member_before, mock_member):
"""Test on_member_update with general error."""
# Setup embed to raise exception
mock_get_embed.side_effect = Exception("General error")
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should log error
mock_bot.log.error.assert_called()
@@ -477,8 +473,8 @@ async def test_on_member_update_general_error(self, mock_get_embed, mock_bot, mo
assert str(mock_member) in error_call
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
async def test_on_member_update_null_nick_to_nick(
self,
mock_datetime,
@@ -497,17 +493,17 @@ async def test_on_member_update_null_nick_to_nick(
mock_member.nick = "NewNick"
mock_member_before.roles = mock_member.roles
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should only add new nickname field (not previous)
mock_embed.add_field.assert_called_with(name=messages.NEW_NICKNAME, value="NewNick")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_embed')
- @patch('src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long')
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_embed")
+ @patch("src.bot.cogs.events.on_member_update.bot_utils.get_current_date_time_str_long")
async def test_on_member_update_null_roles(
self,
mock_datetime,
@@ -528,10 +524,10 @@ async def test_on_member_update_null_roles(
mock_member.roles = [role1]
mock_member_before.nick = mock_member.nick
- OnMemberUpdate(mock_bot)
- on_member_update_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnMemberUpdate(mock_bot)
- await on_member_update_event(mock_member_before, mock_member)
+ # Call the listener method directly
+ await cog.on_member_update(mock_member_before, mock_member)
# Should only add new roles field (not previous)
mock_embed.add_field.assert_called_with(name=messages.NEW_ROLES, value="Member")
diff --git a/tests/unit/bot/events/test_on_message.py b/tests/unit/bot/events/test_on_message.py
index 55f91153..20e163f1 100644
--- a/tests/unit/bot/events/test_on_message.py
+++ b/tests/unit/bot/events/test_on_message.py
@@ -7,7 +7,7 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_message import (
CustomReactionHandler,
@@ -218,7 +218,7 @@ async def test_check_and_censor_no_profanity(self, mock_bot, mock_ctx):
mock_bot.profanity.contains_profanity.assert_called_once_with("Hello world")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_check_and_censor_with_profanity(self, mock_delete, mock_bot, mock_ctx):
"""Test check_and_censor with profanity."""
mock_bot.profanity.contains_profanity.return_value = True
@@ -354,7 +354,7 @@ async def test_check_exclusive_users_allowed(self, mock_bot, mock_ctx):
assert result is True
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_message.bot_utils.send_error_msg")
async def test_check_exclusive_users_not_allowed(self, mock_send_error, mock_bot, mock_ctx):
"""Test exclusive users check when user is not allowed."""
mock_bot.settings["bot"]["ExclusiveUsers"] = [99999]
@@ -372,10 +372,10 @@ def test_init(self, mock_bot):
"""Test DMMessageHandler initialization."""
handler = DMMessageHandler(mock_bot)
assert handler.bot == mock_bot
- assert hasattr(handler, 'reaction_handler')
+ assert hasattr(handler, "reaction_handler")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.is_bot_owner')
+ @patch("src.bot.cogs.events.on_message.bot_utils.is_bot_owner")
async def test_handle_dm_non_command_owner(self, mock_is_owner, mock_bot, mock_ctx):
"""Test DM non-command handling for bot owner."""
mock_is_owner.return_value = True
@@ -393,7 +393,7 @@ async def test_handle_dm_non_command_owner(self, mock_is_owner, mock_bot, mock_c
mock_ctx.author.send.assert_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.is_bot_owner')
+ @patch("src.bot.cogs.events.on_message.bot_utils.is_bot_owner")
async def test_handle_dm_non_command_not_owner(self, mock_is_owner, mock_bot, mock_ctx):
"""Test DM non-command handling for non-owner."""
mock_is_owner.return_value = False
@@ -435,11 +435,11 @@ def test_init(self, mock_bot):
"""Test ServerMessageHandler initialization."""
handler = ServerMessageHandler(mock_bot)
assert handler.bot == mock_bot
- assert hasattr(handler, 'profanity_filter')
- assert hasattr(handler, 'reaction_handler')
+ assert hasattr(handler, "profanity_filter")
+ assert hasattr(handler, "reaction_handler")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
async def test_process_no_configs(self, mock_dal_class, mock_bot, mock_ctx):
"""Test server message processing with no configs."""
mock_dal = AsyncMock()
@@ -454,8 +454,8 @@ async def test_process_no_configs(self, mock_dal_class, mock_bot, mock_ctx):
mock_bot.process_commands.assert_called_once_with(mock_ctx.message)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_process_invisible_member_blocked(self, mock_delete, mock_dal_class, mock_bot, mock_ctx):
"""Test server message processing with invisible member blocked."""
mock_dal = AsyncMock()
@@ -474,7 +474,7 @@ async def test_process_invisible_member_blocked(self, mock_delete, mock_dal_clas
mock_delete.assert_called_once_with(mock_ctx)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
async def test_handle_server_non_command_profanity(self, mock_dal_class, mock_bot, mock_ctx):
"""Test handling server non-command with profanity filter."""
configs = {"profanity_filter": True, "bot_word_reactions": False}
@@ -486,7 +486,7 @@ async def test_handle_server_non_command_profanity(self, mock_dal_class, mock_bo
handler.profanity_filter.check_and_censor.assert_called_once_with(mock_ctx)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_try_custom_command_found(self, mock_dal_class, mock_bot, mock_ctx):
"""Test trying custom command when found."""
mock_dal = AsyncMock()
@@ -502,7 +502,7 @@ async def test_try_custom_command_found(self, mock_dal_class, mock_bot, mock_ctx
mock_ctx.message.channel.send.assert_called_once_with("Custom command response")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_try_custom_command_not_found(self, mock_dal_class, mock_bot, mock_ctx):
"""Test trying custom command when not found."""
mock_dal = AsyncMock()
@@ -523,8 +523,8 @@ def test_init(self, mock_bot):
"""Test OnMessage cog initialization."""
cog = OnMessage(mock_bot)
assert cog.bot == mock_bot
- assert hasattr(cog, 'dm_handler')
- assert hasattr(cog, 'server_handler')
+ assert hasattr(cog, "dm_handler")
+ assert hasattr(cog, "server_handler")
@pytest.mark.asyncio
async def test_setup_function(self, mock_bot):
@@ -541,7 +541,7 @@ async def test_setup_function(self, mock_bot):
def test_on_message_cog_inheritance(self, on_message_cog):
"""Test that OnMessage cog properly inherits from commands.Cog."""
assert isinstance(on_message_cog, commands.Cog)
- assert hasattr(on_message_cog, 'bot')
+ assert hasattr(on_message_cog, "bot")
def test_message_context_dm_detection(self, mock_dm_ctx):
"""Test MessageContext DM detection."""
diff --git a/tests/unit/bot/events/test_on_message_extra.py b/tests/unit/bot/events/test_on_message_extra.py
index 6df7dd70..b3dfe275 100644
--- a/tests/unit/bot/events/test_on_message_extra.py
+++ b/tests/unit/bot/events/test_on_message_extra.py
@@ -9,7 +9,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_message import (
DMMessageHandler,
@@ -105,7 +105,7 @@ class TestProfanityFilterHTTPException:
"""Tests for ProfanityFilter._censor_message HTTPException fallback (lines 83-84)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_censor_message_embed_http_exception_fallback(self, mock_delete, mock_bot, mock_ctx):
"""When embed send raises HTTPException, fallback to text mention (lines 83-84)."""
mock_bot.profanity.contains_profanity.return_value = True
@@ -141,7 +141,7 @@ class TestDMMessageHandlerProcess:
"""Tests for DMMessageHandler.process routing (lines 198-201)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.is_bot_owner')
+ @patch("src.bot.cogs.events.on_message.bot_utils.is_bot_owner")
async def test_process_not_command_routes_to_non_command(self, mock_is_owner, mock_bot, mock_dm_ctx):
"""When is_command=False, routes to _handle_dm_non_command (lines 198-199)."""
mock_is_owner.return_value = False
@@ -181,7 +181,7 @@ async def test_reaction_handler_returns_true_early_return(self, mock_bot, mock_d
mock_dm_ctx.message.author.send.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.is_bot_owner')
+ @patch("src.bot.cogs.events.on_message.bot_utils.is_bot_owner")
async def test_is_bot_owner_sends_owner_help(self, mock_is_owner, mock_bot, mock_dm_ctx):
"""When user is bot owner, sends owner help (lines 209-210)."""
mock_is_owner.return_value = True
@@ -199,7 +199,7 @@ async def test_is_bot_owner_sends_owner_help(self, mock_is_owner, mock_bot, mock
assert mock_dm_ctx.message.author.send.call_count >= 1
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.is_bot_owner')
+ @patch("src.bot.cogs.events.on_message.bot_utils.is_bot_owner")
async def test_not_bot_owner_sends_dm_not_allowed(self, mock_is_owner, mock_bot, mock_dm_ctx):
"""When user is not bot owner, sends dm not allowed (lines 211-212)."""
mock_is_owner.return_value = False
@@ -210,7 +210,7 @@ async def test_not_bot_owner_sends_dm_not_allowed(self, mock_is_owner, mock_bot,
mock_dm_ctx.message.author.send.assert_called_once()
embed_sent = (
- mock_dm_ctx.message.author.send.call_args[1].get('embed') or mock_dm_ctx.message.author.send.call_args[0][0]
+ mock_dm_ctx.message.author.send.call_args[1].get("embed") or mock_dm_ctx.message.author.send.call_args[0][0]
if mock_dm_ctx.message.author.send.call_args[0]
else None
)
@@ -221,7 +221,7 @@ class TestDMMessageHandlerCommand:
"""Tests for DMMessageHandler._handle_dm_command (lines 214-230)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ExclusiveUsersChecker.check_exclusive_users')
+ @patch("src.bot.cogs.events.on_message.ExclusiveUsersChecker.check_exclusive_users")
async def test_exclusive_users_check_fails_returns(self, mock_check, mock_bot, mock_dm_ctx):
"""When exclusive users check fails, returns early (line 217)."""
mock_check.return_value = False
@@ -244,7 +244,7 @@ async def test_allowed_commands_none_sends_no_dm_commands(self, mock_bot, mock_d
mock_dm_ctx.message.author.send.assert_called_once()
embed_arg = (
- mock_dm_ctx.message.author.send.call_args[1].get('embed') or mock_dm_ctx.message.author.send.call_args[0][0]
+ mock_dm_ctx.message.author.send.call_args[1].get("embed") or mock_dm_ctx.message.author.send.call_args[0][0]
)
assert isinstance(embed_arg, discord.Embed)
mock_bot.process_commands.assert_not_called()
@@ -284,7 +284,7 @@ async def test_send_no_dm_commands_allowed(self, mock_bot, mock_dm_ctx):
await handler._send_no_dm_commands_allowed(mock_dm_ctx)
mock_dm_ctx.message.author.send.assert_called_once()
- embed_sent = mock_dm_ctx.message.author.send.call_args[1]['embed']
+ embed_sent = mock_dm_ctx.message.author.send.call_args[1]["embed"]
assert embed_sent.color == discord.Color.red()
assert messages.DM_COMMAND_NOT_ALLOWED in embed_sent.description
@@ -297,7 +297,7 @@ async def test_send_command_not_allowed(self, mock_bot, mock_dm_ctx):
await handler._send_command_not_allowed(mock_dm_ctx, allowed_commands)
mock_dm_ctx.message.author.send.assert_called_once()
- embed_sent = mock_dm_ctx.message.author.send.call_args[1]['embed']
+ embed_sent = mock_dm_ctx.message.author.send.call_args[1]["embed"]
assert embed_sent.color == discord.Color.red()
assert messages.DM_COMMAND_NOT_ALLOWED in embed_sent.description
assert len(embed_sent.fields) == 1
@@ -311,7 +311,7 @@ async def test_send_dm_not_allowed(self, mock_bot, mock_dm_ctx):
await handler._send_dm_not_allowed(mock_dm_ctx)
mock_dm_ctx.message.author.send.assert_called_once()
- embed_sent = mock_dm_ctx.message.author.send.call_args[1]['embed']
+ embed_sent = mock_dm_ctx.message.author.send.call_args[1]["embed"]
assert embed_sent.color == discord.Color.red()
assert messages.NO_DM_MESSAGES in embed_sent.description
@@ -335,7 +335,7 @@ async def test_send_owner_help(self, mock_bot, mock_dm_ctx):
# Second call: box(owner_command.help)
assert mock_dm_ctx.message.author.send.call_count == 2
first_call = mock_dm_ctx.message.author.send.call_args_list[0]
- embed_sent = first_call[1]['embed']
+ embed_sent = first_call[1]["embed"]
assert embed_sent.color == discord.Color.green()
assert messages.OWNER_DM_BOT_MESSAGE in embed_sent.description
second_call = mock_dm_ctx.message.author.send.call_args_list[1]
@@ -346,7 +346,7 @@ class TestServerMessageHandlerProcess:
"""Tests for ServerMessageHandler.process (lines 281-301, specifically 298-301)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
async def test_no_configs_not_command_logs_warning_no_process(self, mock_dal_class, mock_bot, mock_ctx):
"""When no configs and not a command, logs warning and returns (lines 288-291)."""
mock_dal = AsyncMock()
@@ -360,7 +360,7 @@ async def test_no_configs_not_command_logs_warning_no_process(self, mock_dal_cla
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
async def test_no_configs_is_command_processes_commands(self, mock_dal_class, mock_bot, mock_ctx):
"""When no configs but is_command, processes commands (lines 289-290, 298-301 related)."""
mock_dal = AsyncMock()
@@ -374,7 +374,7 @@ async def test_no_configs_is_command_processes_commands(self, mock_dal_class, mo
mock_bot.process_commands.assert_called_once_with(mock_ctx.message)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
async def test_not_command_routes_to_non_command(self, mock_dal_class, mock_bot, mock_ctx):
"""When not a command, routes to _handle_server_non_command (lines 298-299)."""
mock_dal = AsyncMock()
@@ -392,8 +392,8 @@ async def test_not_command_routes_to_non_command(self, mock_dal_class, mock_bot,
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_is_command_routes_to_command_handler(self, mock_cmd_dal_class, mock_dal_class, mock_bot, mock_ctx):
"""When is a command, routes to _handle_server_command (lines 300-301)."""
mock_dal = AsyncMock()
@@ -419,7 +419,7 @@ class TestServerMessageHandlerInvisibleMember:
"""Tests for ServerMessageHandler._handle_invisible_member (lines 303-322)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_invisible_member_dm_succeeds(self, mock_delete, mock_bot, mock_ctx):
"""When DM send succeeds, invisible member is notified via DM (lines 316-317)."""
handler = ServerMessageHandler(mock_bot)
@@ -428,11 +428,11 @@ async def test_invisible_member_dm_succeeds(self, mock_delete, mock_bot, mock_ct
mock_delete.assert_called_once_with(mock_ctx)
mock_ctx.message.author.send.assert_called_once()
- embed_sent = mock_ctx.message.author.send.call_args[1]['embed']
+ embed_sent = mock_ctx.message.author.send.call_args[1]["embed"]
assert embed_sent.color == discord.Color.red()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_invisible_member_dm_http_exception_channel_send(self, mock_delete, mock_bot, mock_ctx):
"""When DM raises HTTPException, tries channel send (lines 318-319)."""
handler = ServerMessageHandler(mock_bot)
@@ -444,11 +444,11 @@ async def test_invisible_member_dm_http_exception_channel_send(self, mock_delete
mock_ctx.message.author.send.assert_called_once()
# Falls back to ctx.send with embed
mock_ctx.send.assert_called_once()
- embed_sent = mock_ctx.send.call_args[1]['embed']
+ embed_sent = mock_ctx.send.call_args[1]["embed"]
assert embed_sent.color == discord.Color.red()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_invisible_member_both_exceptions_fallback_mention(self, mock_delete, mock_bot, mock_ctx):
"""When both DM and channel embed raise HTTPException, fallback to mention (lines 320-322)."""
handler = ServerMessageHandler(mock_bot)
@@ -458,7 +458,7 @@ async def test_invisible_member_both_exceptions_fallback_mention(self, mock_dele
async def send_side_effect(*args, **kwargs):
call_count[0] += 1
- if call_count[0] == 1 and 'embed' in kwargs:
+ if call_count[0] == 1 and "embed" in kwargs:
raise discord.HTTPException(MagicMock(), "Channel embed failed")
return MagicMock()
@@ -542,7 +542,7 @@ async def test_double_prefix_returns_early(self, mock_bot, mock_ctx):
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ExclusiveUsersChecker.check_exclusive_users')
+ @patch("src.bot.cogs.events.on_message.ExclusiveUsersChecker.check_exclusive_users")
async def test_exclusive_users_fails_returns(self, mock_check, mock_bot, mock_ctx):
"""When exclusive users check fails, returns early (lines 341-342)."""
mock_check.return_value = False
@@ -555,7 +555,7 @@ async def test_exclusive_users_fails_returns(self, mock_check, mock_bot, mock_ct
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_custom_command_found_returns(self, mock_dal_class, mock_bot, mock_ctx):
"""When custom command is found, returns True (lines 345-347)."""
mock_dal = AsyncMock()
@@ -572,7 +572,7 @@ async def test_custom_command_found_returns(self, mock_dal_class, mock_bot, mock
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_no_custom_command_processes_regular(self, mock_dal_class, mock_bot, mock_ctx):
"""When no custom command found, processes regular commands (line 350)."""
mock_dal = AsyncMock()
@@ -597,7 +597,7 @@ async def test_non_alpha_second_char_double_prefix(self, mock_bot, mock_ctx):
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_alpha_second_char_not_double_prefix(self, mock_dal_class, mock_bot, mock_ctx):
"""Alpha character after prefix is NOT double prefix (line 338)."""
mock_dal = AsyncMock()
@@ -620,8 +620,8 @@ def test_on_message_cog_registers_event(self, mock_bot):
cog = OnMessage(mock_bot)
assert cog.bot == mock_bot
- assert hasattr(cog, 'dm_handler')
- assert hasattr(cog, 'server_handler')
+ assert hasattr(cog, "dm_handler")
+ assert hasattr(cog, "server_handler")
mock_bot.event.assert_called_once()
@pytest.mark.asyncio
@@ -735,8 +735,8 @@ class TestServerMessageHandlerBlockInvisible:
"""Additional tests for block invisible member flow (lines 294-296)."""
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
- @patch('src.bot.cogs.events.on_message.bot_utils.delete_message')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
+ @patch("src.bot.cogs.events.on_message.bot_utils.delete_message")
async def test_block_invisible_enabled_member_invisible(self, mock_delete, mock_dal_class, mock_bot, mock_ctx):
"""When block invisible is True and member is invisible, handles invisible (lines 294-296)."""
mock_dal = AsyncMock()
@@ -756,8 +756,8 @@ async def test_block_invisible_enabled_member_invisible(self, mock_delete, mock_
mock_bot.process_commands.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.ServersDal')
- @patch('src.bot.cogs.events.on_message.CustomCommandsDal')
+ @patch("src.bot.cogs.events.on_message.ServersDal")
+ @patch("src.bot.cogs.events.on_message.CustomCommandsDal")
async def test_block_invisible_enabled_member_online(self, mock_cmd_dal_class, mock_dal_class, mock_bot, mock_ctx):
"""When block invisible is True but member is online, proceeds normally."""
mock_dal = AsyncMock()
@@ -801,7 +801,7 @@ async def test_exclusive_users_single_id_match(self, mock_bot, mock_ctx):
assert result is True
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_message.bot_utils.send_error_msg')
+ @patch("src.bot.cogs.events.on_message.bot_utils.send_error_msg")
async def test_exclusive_users_single_id_no_match(self, mock_send_error, mock_bot, mock_ctx):
"""When ExclusiveUsers is a single int not matching user, returns False."""
mock_bot.settings["bot"]["ExclusiveUsers"] = 11111
diff --git a/tests/unit/bot/events/test_on_ready.py b/tests/unit/bot/events/test_on_ready.py
index e6b59f98..8e62497f 100644
--- a/tests/unit/bot/events/test_on_ready.py
+++ b/tests/unit/bot/events/test_on_ready.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.cogs.events.on_ready import OnReady, StartupInfoDisplay
from src.bot.constants import messages, variables
@@ -24,8 +24,6 @@ def mock_bot():
bot.command_prefix = "!"
# Ensure add_cog doesn't return a coroutine
bot.add_cog = AsyncMock(return_value=None)
- # Mock the event decorator to prevent coroutine issues
- bot.event = MagicMock(side_effect=lambda func: func)
return bot
@@ -44,13 +42,13 @@ def startup_info_display():
@pytest.fixture
def mock_bot_stats():
"""Create mock bot statistics."""
- return {'servers': 5, 'users': 150, 'channels': 45}
+ return {"servers": 5, "users": 150, "channels": 45}
class TestStartupInfoDisplay:
"""Test cases for StartupInfoDisplay class."""
- @patch('builtins.print')
+ @patch("builtins.print")
def test_print_startup_banner(self, mock_print, startup_info_display):
"""Test printing startup banner."""
test_version = "2.0.21"
@@ -64,8 +62,8 @@ def test_print_startup_banner(self, mock_print, startup_info_display):
assert "=" * 20 in printed_text
assert f"Discord Bot v{test_version}" in printed_text
- @patch('builtins.print')
- @patch('discord.__version__', '2.3.2')
+ @patch("builtins.print")
+ @patch("discord.__version__", "2.3.2")
def test_print_version_info(self, mock_print, startup_info_display):
"""Test printing version information."""
startup_info_display.print_version_info()
@@ -81,7 +79,7 @@ def test_print_version_info(self, mock_print, startup_info_display):
# Check Discord version
assert calls[1] == "Discord API v2.3.2"
- @patch('builtins.print')
+ @patch("builtins.print")
def test_print_bot_info(self, mock_print, startup_info_display, mock_bot):
"""Test printing bot information."""
startup_info_display.print_bot_info(mock_bot)
@@ -95,7 +93,7 @@ def test_print_bot_info(self, mock_print, startup_info_display, mock_bot):
assert "(id:123456789)" in calls[1]
assert calls[2] == "Prefix: !"
- @patch('builtins.print')
+ @patch("builtins.print")
def test_print_bot_stats(self, mock_print, startup_info_display, mock_bot_stats):
"""Test printing bot statistics."""
startup_info_display.print_bot_stats(mock_bot_stats)
@@ -108,8 +106,8 @@ def test_print_bot_stats(self, mock_print, startup_info_display, mock_bot_stats)
assert calls[1] == "Users: 150"
assert calls[2] == "Channels: 45"
- @patch('builtins.print')
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_current_date_time_str_long')
+ @patch("builtins.print")
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_current_date_time_str_long")
def test_print_timestamp(self, mock_datetime, mock_print, startup_info_display):
"""Test printing timestamp."""
mock_datetime.return_value = "2023-01-01 12:00:00"
@@ -146,7 +144,7 @@ async def test_setup_function(self, mock_bot):
assert added_cog.bot == mock_bot
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_bot_stats')
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_bot_stats")
async def test_on_ready_event_success(self, mock_get_bot_stats, mock_bot, mock_bot_stats):
"""Test on_ready event handler success."""
mock_get_bot_stats.return_value = mock_bot_stats
@@ -160,10 +158,8 @@ async def test_on_ready_event_success(self, mock_get_bot_stats, mock_bot, mock_b
cog.info_display.print_bot_stats = MagicMock()
cog.info_display.print_timestamp = MagicMock()
- # Access the event handler directly
- on_ready_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_ready_event()
+ # Call the listener method directly
+ await cog.on_ready()
# Verify all display methods were called
cog.info_display.print_startup_banner.assert_called_once_with(variables.VERSION)
@@ -176,36 +172,34 @@ async def test_on_ready_event_success(self, mock_get_bot_stats, mock_bot, mock_b
mock_get_bot_stats.assert_called_once_with(mock_bot)
# Verify log message
- mock_bot.log.info.assert_called_once_with(messages.BOT_ONLINE.format(mock_bot.user))
+ mock_bot.log.info.assert_called_once_with(messages.bot_online(mock_bot.user))
def test_on_ready_cog_inheritance(self, on_ready_cog):
"""Test that OnReady cog properly inherits from commands.Cog."""
from discord.ext import commands
assert isinstance(on_ready_cog, commands.Cog)
- assert hasattr(on_ready_cog, 'bot')
+ assert hasattr(on_ready_cog, "bot")
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_bot_stats')
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_bot_stats")
async def test_on_ready_event_with_different_stats(self, mock_get_bot_stats, mock_bot):
"""Test on_ready event with different bot statistics."""
- different_stats = {'servers': 10, 'users': 500, 'channels': 120}
+ different_stats = {"servers": 10, "users": 500, "channels": 120}
mock_get_bot_stats.return_value = different_stats
cog = OnReady(mock_bot)
cog.info_display.print_bot_stats = MagicMock()
- # Access the event handler directly
- on_ready_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_ready_event()
+ # Call the listener method directly
+ await cog.on_ready()
# Verify stats were passed correctly
cog.info_display.print_bot_stats.assert_called_once_with(different_stats)
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_ready.variables.VERSION', '3.0.0')
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_bot_stats')
+ @patch("src.bot.cogs.events.on_ready.variables.VERSION", "3.0.0")
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_bot_stats")
async def test_on_ready_event_with_different_version(self, mock_get_bot_stats, mock_bot, mock_bot_stats):
"""Test on_ready event with different version."""
mock_get_bot_stats.return_value = mock_bot_stats
@@ -213,13 +207,11 @@ async def test_on_ready_event_with_different_version(self, mock_get_bot_stats, m
cog = OnReady(mock_bot)
cog.info_display.print_startup_banner = MagicMock()
- # Access the event handler directly
- on_ready_event = mock_bot.event.call_args_list[0][0][0]
-
- await on_ready_event()
+ # Call the listener method directly
+ await cog.on_ready()
# Verify version was passed correctly
- cog.info_display.print_startup_banner.assert_called_once_with('3.0.0')
+ cog.info_display.print_startup_banner.assert_called_once_with("3.0.0")
def test_startup_info_display_static_methods(self):
"""Test that all StartupInfoDisplay methods are static."""
@@ -231,7 +223,7 @@ def test_startup_info_display_static_methods(self):
assert inspect.isfunction(StartupInfoDisplay.print_bot_stats)
assert inspect.isfunction(StartupInfoDisplay.print_timestamp)
- @patch('builtins.print')
+ @patch("builtins.print")
def test_startup_info_display_integration(self, mock_print, mock_bot, mock_bot_stats):
"""Test integration of all StartupInfoDisplay methods."""
display = StartupInfoDisplay()
@@ -246,10 +238,10 @@ def test_startup_info_display_integration(self, mock_print, mock_bot, mock_bot_s
# Should have printed multiple lines
assert mock_print.call_count >= 10 # At least 10 print calls
- @patch('builtins.print')
+ @patch("builtins.print")
def test_print_bot_stats_empty_stats(self, mock_print, startup_info_display):
"""Test printing bot statistics with empty stats."""
- empty_stats = {'servers': 0, 'users': 0, 'channels': 0}
+ empty_stats = {"servers": 0, "users": 0, "channels": 0}
startup_info_display.print_bot_stats(empty_stats)
@@ -258,7 +250,7 @@ def test_print_bot_stats_empty_stats(self, mock_print, startup_info_display):
assert calls[1] == "Users: 0"
assert calls[2] == "Channels: 0"
- @patch('builtins.print')
+ @patch("builtins.print")
def test_print_bot_info_complex_bot_name(self, mock_print, startup_info_display):
"""Test printing bot info with complex bot name."""
complex_bot = MagicMock()
@@ -275,7 +267,7 @@ def test_print_bot_info_complex_bot_name(self, mock_print, startup_info_display)
assert calls[2] == "Prefix: $$"
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_bot_stats')
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_bot_stats")
async def test_on_ready_event_bot_stats_error(self, mock_get_bot_stats, mock_bot):
"""Test on_ready event when bot stats retrieval fails."""
# This test verifies the event still completes even if bot_stats fails
@@ -290,11 +282,9 @@ async def test_on_ready_event_bot_stats_error(self, mock_get_bot_stats, mock_bot
cog.info_display.print_bot_stats = MagicMock()
cog.info_display.print_timestamp = MagicMock()
- # Access the event handler directly
- on_ready_event = mock_bot.event.call_args_list[0][0][0]
-
+ # Call the listener method directly
# Should handle exception gracefully and not raise
- await on_ready_event()
+ await cog.on_ready()
# Startup banner should still have been called
cog.info_display.print_startup_banner.assert_called_once()
@@ -306,19 +296,17 @@ async def test_on_ready_event_bot_stats_error(self, mock_get_bot_stats, mock_bot
cog.info_display.print_bot_stats.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.cogs.events.on_ready.messages.BOT_ONLINE', 'Bot {} is now online!')
- @patch('src.bot.cogs.events.on_ready.bot_utils.get_bot_stats')
- async def test_on_ready_event_custom_message(self, mock_get_bot_stats, mock_bot, mock_bot_stats):
+ @patch("src.bot.cogs.events.on_ready.messages.bot_online", return_value="Bot TestBot is now online!")
+ @patch("src.bot.cogs.events.on_ready.bot_utils.get_bot_stats")
+ async def test_on_ready_event_custom_message(self, mock_get_bot_stats, mock_bot_online, mock_bot, mock_bot_stats):
"""Test on_ready event with custom bot online message."""
mock_get_bot_stats.return_value = mock_bot_stats
- OnReady(mock_bot)
-
- # Access the event handler directly
- on_ready_event = mock_bot.event.call_args_list[0][0][0]
+ cog = OnReady(mock_bot)
- await on_ready_event()
+ # Call the listener method directly
+ await cog.on_ready()
# Verify custom log message format
- expected_message = f'Bot {mock_bot.user} is now online!'
- mock_bot.log.info.assert_called_once_with(expected_message)
+ mock_bot_online.assert_called_once_with(mock_bot.user)
+ mock_bot.log.info.assert_called_once_with("Bot TestBot is now online!")
diff --git a/tests/unit/bot/test_discord_bot.py b/tests/unit/bot/test_discord_bot.py
index 7263b7b1..94f0c5ef 100644
--- a/tests/unit/bot/test_discord_bot.py
+++ b/tests/unit/bot/test_discord_bot.py
@@ -3,7 +3,7 @@
import sys
from unittest.mock import Mock
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
import discord
import pytest
@@ -15,10 +15,10 @@
class TestBotInit:
"""Test cases for Bot.__init__."""
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
def _create_bot(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity, **overrides):
"""Helper to create a Bot instance with all patches applied."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
@@ -87,10 +87,10 @@ def test_bot_init_loads_profanity(self):
mock_profanity.load_censor_words.assert_called_once()
assert bot.profanity is mock_profanity
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
def test_bot_init_calls_load_settings(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
"""Verify _load_settings is called during __init__."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
@@ -102,7 +102,7 @@ def test_bot_init_calls_load_settings(self, mock_bot_utils, mock_get_bot, mock_g
mock_gw2_settings = MagicMock()
mock_get_gw2.return_value = mock_gw2_settings
- with patch.object(Bot, '_load_settings') as mock_load:
+ with patch.object(Bot, "_load_settings") as mock_load:
bot = Bot(
command_prefix="!",
intents=discord.Intents.default(),
@@ -116,10 +116,10 @@ def test_bot_init_calls_load_settings(self, mock_bot_utils, mock_get_bot, mock_g
class TestLoadSettings:
"""Test cases for Bot._load_settings."""
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
def test_load_settings_populates_bot_settings(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
"""Verify _load_settings populates self.settings['bot'] and self.settings['gw2']."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
@@ -150,17 +150,17 @@ def test_load_settings_populates_bot_settings(self, mock_bot_utils, mock_get_bot
)
assert bot.settings["bot"]["BGActivityTimer"] == 300
- assert bot.settings["bot"]["AllowedDMCommands"] == "owner, about"
- assert bot.settings["bot"]["BotReactionWords"] == "stupid, retard"
+ assert bot.settings["bot"]["AllowedDMCommands"] == ["owner", "about"]
+ assert bot.settings["bot"]["BotReactionWords"] == ["stupid", "retard"]
assert bot.settings["bot"]["EmbedColor"] == embed_color
assert bot.settings["bot"]["EmbedOwnerColor"] == owner_color
assert bot.settings["bot"]["ExclusiveUsers"] == "user1"
assert bot.settings["gw2"]["EmbedColor"] == gw2_color
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
def test_load_settings_error_raises(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
"""Verify exception propagates and log.error is called when _load_settings fails."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
@@ -181,15 +181,131 @@ def test_load_settings_error_raises(self, mock_bot_utils, mock_get_bot, mock_get
error_msg = log.error.call_args[0][0]
assert messages.BOT_LOAD_SETTINGS_FAILED in error_msg
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
+ def test_allowed_dm_commands_parsed_as_list(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
+ """Verify comma-separated allowed_dm_commands string is parsed into a list."""
+ mock_bot_utils.get_current_date_time.return_value = MagicMock()
+ mock_bot_utils.get_color_settings.return_value = discord.Color.green()
+
+ mock_bot_settings = MagicMock()
+ mock_bot_settings.allowed_dm_commands = "owner, about, gw2"
+ mock_bot_settings.bot_reaction_words = ""
+ mock_get_bot.return_value = mock_bot_settings
+
+ mock_gw2_settings = MagicMock()
+ mock_get_gw2.return_value = mock_gw2_settings
+
+ bot = Bot(
+ command_prefix="!",
+ intents=discord.Intents.default(),
+ aiosession=MagicMock(),
+ db_session=MagicMock(),
+ log=MagicMock(),
+ )
+
+ result = bot.settings["bot"]["AllowedDMCommands"]
+ assert isinstance(result, list)
+ assert result == ["owner", "about", "gw2"]
+
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
+ def test_allowed_dm_commands_empty_returns_none(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
+ """Verify empty allowed_dm_commands string returns None."""
+ mock_bot_utils.get_current_date_time.return_value = MagicMock()
+ mock_bot_utils.get_color_settings.return_value = discord.Color.green()
+
+ mock_bot_settings = MagicMock()
+ mock_bot_settings.allowed_dm_commands = ""
+ mock_bot_settings.bot_reaction_words = ""
+ mock_get_bot.return_value = mock_bot_settings
+
+ mock_gw2_settings = MagicMock()
+ mock_get_gw2.return_value = mock_gw2_settings
+
+ bot = Bot(
+ command_prefix="!",
+ intents=discord.Intents.default(),
+ aiosession=MagicMock(),
+ db_session=MagicMock(),
+ log=MagicMock(),
+ )
+
+ assert bot.settings["bot"]["AllowedDMCommands"] is None
+
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
+ def test_bot_reaction_words_parsed_as_list(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
+ """Verify comma-separated bot_reaction_words string is parsed into a list."""
+ mock_bot_utils.get_current_date_time.return_value = MagicMock()
+ mock_bot_utils.get_color_settings.return_value = discord.Color.green()
+
+ mock_bot_settings = MagicMock()
+ mock_bot_settings.allowed_dm_commands = "owner"
+ mock_bot_settings.bot_reaction_words = "stupid, retard, idiot"
+ mock_get_bot.return_value = mock_bot_settings
+
+ mock_gw2_settings = MagicMock()
+ mock_get_gw2.return_value = mock_gw2_settings
+
+ bot = Bot(
+ command_prefix="!",
+ intents=discord.Intents.default(),
+ aiosession=MagicMock(),
+ db_session=MagicMock(),
+ log=MagicMock(),
+ )
+
+ result = bot.settings["bot"]["BotReactionWords"]
+ assert isinstance(result, list)
+ assert result == ["stupid", "retard", "idiot"]
+
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
+ def test_bot_reaction_words_empty_returns_empty_list(
+ self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity
+ ):
+ """Verify empty bot_reaction_words string returns empty list."""
+ mock_bot_utils.get_current_date_time.return_value = MagicMock()
+ mock_bot_utils.get_color_settings.return_value = discord.Color.green()
+
+ mock_bot_settings = MagicMock()
+ mock_bot_settings.allowed_dm_commands = "owner"
+ mock_bot_settings.bot_reaction_words = ""
+ mock_get_bot.return_value = mock_bot_settings
+
+ mock_gw2_settings = MagicMock()
+ mock_get_gw2.return_value = mock_gw2_settings
+
+ bot = Bot(
+ command_prefix="!",
+ intents=discord.Intents.default(),
+ aiosession=MagicMock(),
+ db_session=MagicMock(),
+ log=MagicMock(),
+ )
+
+ result = bot.settings["bot"]["BotReactionWords"]
+ assert isinstance(result, list)
+ assert result == []
+
class TestSetupHook:
"""Test cases for Bot.setup_hook."""
@pytest.mark.asyncio
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
async def test_setup_hook_loads_cogs(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
"""Verify setup_hook calls bot_utils.load_cogs and logs success."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
@@ -217,10 +333,10 @@ async def test_setup_hook_loads_cogs(self, mock_bot_utils, mock_get_bot, mock_ge
log.info.assert_any_call(messages.BOT_LOADED_ALL_COGS_SUCCESS)
@pytest.mark.asyncio
- @patch('src.bot.discord_bot.profanity')
- @patch('src.bot.discord_bot.get_gw2_settings')
- @patch('src.bot.discord_bot.get_bot_settings')
- @patch('src.bot.discord_bot.bot_utils')
+ @patch("src.bot.discord_bot.profanity")
+ @patch("src.bot.discord_bot.get_gw2_settings")
+ @patch("src.bot.discord_bot.get_bot_settings")
+ @patch("src.bot.discord_bot.bot_utils")
async def test_setup_hook_failure_logs_and_raises(self, mock_bot_utils, mock_get_bot, mock_get_gw2, mock_profanity):
"""Verify setup_hook logs error and re-raises when load_cogs fails."""
mock_bot_utils.get_current_date_time.return_value = MagicMock()
diff --git a/tests/unit/bot/test_main.py b/tests/unit/bot/test_main.py
index 7204117a..2bbdd966 100644
--- a/tests/unit/bot/test_main.py
+++ b/tests/unit/bot/test_main.py
@@ -3,7 +3,7 @@
import sys
from unittest.mock import Mock
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
import discord
import pytest
@@ -21,7 +21,7 @@ async def test_get_command_prefix_success(self):
mock_db_session = MagicMock()
mock_log = MagicMock()
- with patch('src.__main__.BotConfigsDal') as mock_dal_class:
+ with patch("src.__main__.BotConfigsDal") as mock_dal_class:
mock_dal = AsyncMock()
mock_dal.get_bot_prefix.return_value = "!!"
mock_dal_class.return_value = mock_dal
@@ -38,7 +38,7 @@ async def test_get_command_prefix_none_returns_default(self):
mock_db_session = MagicMock()
mock_log = MagicMock()
- with patch('src.__main__.BotConfigsDal') as mock_dal_class:
+ with patch("src.__main__.BotConfigsDal") as mock_dal_class:
mock_dal = AsyncMock()
mock_dal.get_bot_prefix.return_value = None
mock_dal_class.return_value = mock_dal
@@ -53,7 +53,7 @@ async def test_get_command_prefix_exception_returns_default(self):
mock_db_session = MagicMock()
mock_log = MagicMock()
- with patch('src.__main__.BotConfigsDal') as mock_dal_class:
+ with patch("src.__main__.BotConfigsDal") as mock_dal_class:
mock_dal_class.side_effect = RuntimeError("db connection failed")
result = await _get_command_prefix(mock_db_session, mock_log)
@@ -67,14 +67,14 @@ async def test_get_command_prefix_exception_returns_default(self):
class TestCreateBotActivity:
"""Test cases for _create_bot_activity."""
- @patch('src.__main__.get_bot_settings')
+ @patch("src.__main__.get_bot_settings")
def test_create_bot_activity_no_exclusive_users(self, mock_get_bot_settings):
"""Verify a Game activity is returned with a game from GAMES_INCLUDED when no exclusive users."""
mock_settings = MagicMock()
mock_settings.exclusive_users = ""
mock_get_bot_settings.return_value = mock_settings
- with patch('src.__main__.random.SystemRandom') as mock_sys_random_class:
+ with patch("src.__main__.random.SystemRandom") as mock_sys_random_class:
mock_rng = MagicMock()
mock_rng.choice.return_value = "Guild Wars 2"
mock_sys_random_class.return_value = mock_rng
@@ -86,7 +86,7 @@ def test_create_bot_activity_no_exclusive_users(self, mock_get_bot_settings):
assert "!help" in result.name
mock_rng.choice.assert_called_once_with(variables.GAMES_INCLUDED)
- @patch('src.__main__.get_bot_settings')
+ @patch("src.__main__.get_bot_settings")
def test_create_bot_activity_exclusive_users(self, mock_get_bot_settings):
"""Verify 'PRIVATE BOT' activity is returned when exclusive_users is non-empty."""
mock_settings = MagicMock()
@@ -99,7 +99,7 @@ def test_create_bot_activity_exclusive_users(self, mock_get_bot_settings):
assert "PRIVATE BOT" in result.name
assert "!help" in result.name
- @patch('src.__main__.get_bot_settings')
+ @patch("src.__main__.get_bot_settings")
def test_create_bot_activity_custom_prefix(self, mock_get_bot_settings):
"""Verify the activity includes the custom prefix in the help command."""
mock_settings = MagicMock()
@@ -115,11 +115,11 @@ def test_create_bot_activity_custom_prefix(self, mock_get_bot_settings):
class TestRunBot:
"""Test cases for run_bot."""
- @patch('src.__main__.main', new_callable=MagicMock)
- @patch('src.__main__.sys.exit')
- @patch('src.__main__.print')
- @patch('src.__main__.time.sleep')
- @patch('src.__main__.asyncio.run')
+ @patch("src.__main__.main", new_callable=MagicMock)
+ @patch("src.__main__.sys.exit")
+ @patch("src.__main__.print")
+ @patch("src.__main__.time.sleep")
+ @patch("src.__main__.asyncio.run")
def test_run_bot_keyboard_interrupt(self, mock_asyncio_run, mock_sleep, mock_print, mock_exit, mock_main):
"""Verify KeyboardInterrupt is caught and CTRLC message is printed."""
mock_asyncio_run.side_effect = KeyboardInterrupt
@@ -133,11 +133,11 @@ def test_run_bot_keyboard_interrupt(self, mock_asyncio_run, mock_sleep, mock_pri
assert any(messages.BOT_STOPPED_CTRTC == c for c in calls)
mock_exit.assert_not_called()
- @patch('src.__main__.main', new_callable=MagicMock)
- @patch('src.__main__.sys.exit')
- @patch('src.__main__.print')
- @patch('src.__main__.time.sleep')
- @patch('src.__main__.asyncio.run')
+ @patch("src.__main__.main", new_callable=MagicMock)
+ @patch("src.__main__.sys.exit")
+ @patch("src.__main__.print")
+ @patch("src.__main__.time.sleep")
+ @patch("src.__main__.asyncio.run")
def test_run_bot_exception(self, mock_asyncio_run, mock_sleep, mock_print, mock_exit, mock_main):
"""Verify generic exceptions cause sys.exit(1)."""
mock_asyncio_run.side_effect = RuntimeError("unexpected crash")
@@ -150,11 +150,11 @@ def test_run_bot_exception(self, mock_asyncio_run, mock_sleep, mock_print, mock_
calls = [call[0][0] for call in mock_print.call_args_list]
assert any(messages.BOT_CRASHED in c for c in calls)
- @patch('src.__main__.main', new_callable=MagicMock)
- @patch('src.__main__.sys.exit')
- @patch('src.__main__.print')
- @patch('src.__main__.time.sleep')
- @patch('src.__main__.asyncio.run')
+ @patch("src.__main__.main", new_callable=MagicMock)
+ @patch("src.__main__.sys.exit")
+ @patch("src.__main__.print")
+ @patch("src.__main__.time.sleep")
+ @patch("src.__main__.asyncio.run")
def test_run_bot_prints_starting_message(self, mock_asyncio_run, mock_sleep, mock_print, mock_exit, mock_main):
"""Verify run_bot prints the starting message and sleeps before running."""
mock_asyncio_run.return_value = None
@@ -163,6 +163,6 @@ def test_run_bot_prints_starting_message(self, mock_asyncio_run, mock_sleep, moc
# First print should be the starting message
first_print = mock_print.call_args_list[0][0][0]
- expected_start_msg = messages.BOT_STARTING.format(variables.TIME_BEFORE_START)
+ expected_start_msg = messages.bot_starting(variables.TIME_BEFORE_START)
assert first_print == expected_start_msg
mock_sleep.assert_called_once_with(variables.TIME_BEFORE_START)
diff --git a/tests/unit/bot/tools/test_background_tasks.py b/tests/unit/bot/tools/test_background_tasks.py
index 5a7271e8..1c2fc5db 100644
--- a/tests/unit/bot/tools/test_background_tasks.py
+++ b/tests/unit/bot/tools/test_background_tasks.py
@@ -25,7 +25,7 @@ def test_init(self, mock_bot):
bg_tasks = BackGroundTasks(mock_bot)
assert bg_tasks.bot is mock_bot
- assert hasattr(bg_tasks, 'random')
+ assert hasattr(bg_tasks, "random")
assert bg_tasks.random is not None
@pytest.mark.asyncio
@@ -44,7 +44,7 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
# Mock asyncio.sleep to prevent actual waiting
- with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
await bg_tasks.change_presence_task(5)
# Verify bot.wait_until_ready was called
@@ -55,18 +55,18 @@ def mock_is_closed():
# Verify the call arguments
call_args = mock_bot.change_presence.call_args
- assert call_args[1]['status'] == discord.Status.online
- assert isinstance(call_args[1]['activity'], discord.Game)
+ assert call_args[1]["status"] == discord.Status.online
+ assert isinstance(call_args[1]["activity"], discord.Game)
# Verify activity description format
- activity_name = call_args[1]['activity'].name
- assert ' | !help' in activity_name
+ activity_name = call_args[1]["activity"].name
+ assert " | !help" in activity_name
# Activity name should contain some game and the help command
# Verify logging
mock_bot.log.info.assert_called_once()
log_message = mock_bot.log.info.call_args[0][0]
- assert 'Background task (5s) - Changing activity:' in log_message
+ assert "Background task (5s) - Changing activity:" in log_message
# Verify sleep was called with correct interval
mock_sleep.assert_called_once_with(5)
@@ -86,7 +86,7 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
await bg_tasks.change_presence_task(10)
# Verify multiple calls
@@ -113,18 +113,18 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock):
+ with patch("asyncio.sleep", new_callable=AsyncMock):
await bg_tasks.change_presence_task(1)
# Collect all activity names
activity_names = []
for call in mock_bot.change_presence.call_args_list:
- activity_name = call[1]['activity'].name
+ activity_name = call[1]["activity"].name
activity_names.append(activity_name)
# Verify all names contain the help command
for name in activity_names:
- assert ' | !help' in name
+ assert " | !help" in name
@pytest.mark.asyncio
async def test_change_presence_task_exception_handling(self, mock_bot):
@@ -144,7 +144,7 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
await bg_tasks.change_presence_task(3)
# Verify error was logged
@@ -166,7 +166,7 @@ async def test_change_presence_task_wait_until_ready_exception(self, mock_bot):
bg_tasks = BackGroundTasks(mock_bot)
# Should raise exception since wait_until_ready fails
- with patch('asyncio.sleep', new_callable=AsyncMock):
+ with patch("asyncio.sleep", new_callable=AsyncMock):
with pytest.raises(Exception, match="Ready error"):
await bg_tasks.change_presence_task(1)
@@ -192,11 +192,11 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock):
+ with patch("asyncio.sleep", new_callable=AsyncMock):
await bg_tasks.change_presence_task(1)
# Verify activity contains the first prefix
- activity_name = mock_bot.change_presence.call_args[1]['activity'].name
+ activity_name = mock_bot.change_presence.call_args[1]["activity"].name
expected_suffix = f" | {prefixes[0]}help"
assert activity_name.endswith(expected_suffix)
@@ -220,7 +220,7 @@ def mock_is_closed():
# Mock the random choice to raise IndexError
bg_tasks.random.choice = MagicMock(side_effect=IndexError("list index out of range"))
- with patch('asyncio.sleep', new_callable=AsyncMock):
+ with patch("asyncio.sleep", new_callable=AsyncMock):
# Should not raise IndexError, should be caught and logged as an error
await bg_tasks.change_presence_task(1)
@@ -247,7 +247,7 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
await bg_tasks.change_presence_task(2)
# Verify errors were logged for each attempt
@@ -289,8 +289,8 @@ def test_random_instance_creation(self):
# Should have different random instances
assert bg_tasks1.random is not bg_tasks2.random
- assert type(bg_tasks1.random).__name__ == 'SystemRandom'
- assert type(bg_tasks2.random).__name__ == 'SystemRandom'
+ assert type(bg_tasks1.random).__name__ == "SystemRandom"
+ assert type(bg_tasks2.random).__name__ == "SystemRandom"
def test_random_choice_usage(self):
"""Test that SystemRandom.choice is used correctly."""
@@ -298,13 +298,13 @@ def test_random_choice_usage(self):
bg_tasks = BackGroundTasks(mock_bot)
# Mock the random choice method
- bg_tasks.random.choice = MagicMock(return_value='SelectedGame')
+ bg_tasks.random.choice = MagicMock(return_value="SelectedGame")
# Test the choice method directly with a test list
- test_games = ['Game1', 'Game2', 'Game3']
+ test_games = ["Game1", "Game2", "Game3"]
result = bg_tasks.random.choice(test_games)
- assert result == 'SelectedGame'
+ assert result == "SelectedGame"
bg_tasks.random.choice.assert_called_once_with(test_games)
@@ -335,7 +335,7 @@ def mock_is_closed():
bg_tasks = BackGroundTasks(mock_bot)
- with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
+ with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
await bg_tasks.change_presence_task(5)
# Comprehensive verification
@@ -348,10 +348,10 @@ def mock_is_closed():
# Verify activity structure
for call in mock_bot.change_presence.call_args_list:
- activity = call[1]['activity']
+ activity = call[1]["activity"]
assert isinstance(activity, discord.Game)
- assert ' | !help' in activity.name
- assert call[1]['status'] == discord.Status.online
+ assert " | !help" in activity.name
+ assert call[1]["status"] == discord.Status.online
# Verify logging messages
for call in mock_bot.log.info.call_args_list:
diff --git a/tests/unit/bot/tools/test_bot_utils.py b/tests/unit/bot/tools/test_bot_utils.py
index f4e9dabf..c9721fd3 100644
--- a/tests/unit/bot/tools/test_bot_utils.py
+++ b/tests/unit/bot/tools/test_bot_utils.py
@@ -25,29 +25,29 @@ def test_colors_enum_completeness(self):
"""Test that all expected colors are defined."""
colors = bot_utils.Colors
expected_colors = [
- 'black',
- 'teal',
- 'dark_teal',
- 'green',
- 'dark_green',
- 'blue',
- 'dark_blue',
- 'purple',
- 'dark_purple',
- 'magenta',
- 'dark_magenta',
- 'gold',
- 'dark_gold',
- 'orange',
- 'dark_orange',
- 'red',
- 'dark_red',
- 'lighter_grey',
- 'dark_grey',
- 'light_grey',
- 'darker_grey',
- 'blurple',
- 'greyple',
+ "black",
+ "teal",
+ "dark_teal",
+ "green",
+ "dark_green",
+ "blue",
+ "dark_blue",
+ "purple",
+ "dark_purple",
+ "magenta",
+ "dark_magenta",
+ "gold",
+ "dark_gold",
+ "orange",
+ "dark_orange",
+ "red",
+ "dark_red",
+ "lighter_grey",
+ "dark_grey",
+ "light_grey",
+ "darker_grey",
+ "blurple",
+ "greyple",
]
for color_name in expected_colors:
@@ -75,8 +75,8 @@ def mock_server(self):
return guild
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.ServersDal')
- @patch('src.gw2.tools.gw2_utils.insert_gw2_server_configs')
+ @patch("src.bot.tools.bot_utils.ServersDal")
+ @patch("src.gw2.tools.gw2_utils.insert_gw2_server_configs")
async def test_insert_server_success(self, mock_gw2_insert, mock_servers_dal, mock_bot, mock_server):
"""Test successful server insertion."""
mock_dal = AsyncMock()
@@ -94,7 +94,7 @@ async def test_insert_server_success(self, mock_gw2_insert, mock_servers_dal, mo
# Verify GW2 configs were inserted
mock_gw2_insert.assert_called_once_with(mock_bot, mock_server)
- @patch('src.bot.tools.bot_utils.BackGroundTasks')
+ @patch("src.bot.tools.bot_utils.BackGroundTasks")
def test_init_background_tasks_enabled(self, mock_bg_tasks_class, mock_bot):
"""Test background task initialization when enabled."""
mock_bot.settings = {"bot": {"BGActivityTimer": 30}}
@@ -144,12 +144,12 @@ def mock_bot(self):
async def test_load_cogs_success(self, mock_bot):
"""Test successful cog loading."""
with patch(
- 'src.bot.tools.bot_utils.variables.ALL_COGS', ['src/bot/cogs/test_cog.py', 'src/bot/events/test_event.py']
+ "src.bot.tools.bot_utils.variables.ALL_COGS", ["src/bot/cogs/test_cog.py", "src/bot/events/test_event.py"]
):
await bot_utils.load_cogs(mock_bot)
# Verify extensions were loaded
- expected_extensions = ['src.bot.cogs.test_cog', 'src.bot.events.test_event']
+ expected_extensions = ["src.bot.cogs.test_cog", "src.bot.events.test_event"]
assert mock_bot.load_extension.call_count == 2
actual_calls = [call[0][0] for call in mock_bot.load_extension.call_args_list]
@@ -162,8 +162,8 @@ async def test_load_cogs_success(self, mock_bot):
async def test_load_cogs_with_failure(self, mock_bot):
"""Test cog loading with some failures."""
with patch(
- 'src.bot.tools.bot_utils.variables.ALL_COGS',
- ['src/bot/cogs/working_cog.py', 'src/bot/cogs/broken_cog.py'],
+ "src.bot.tools.bot_utils.variables.ALL_COGS",
+ ["src/bot/cogs/working_cog.py", "src/bot/cogs/broken_cog.py"],
):
# Make second cog fail to load
mock_bot.load_extension.side_effect = [None, Exception("Failed to load")]
@@ -196,7 +196,7 @@ def mock_ctx(self):
return ctx
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_help_msg')
+ @patch("src.bot.tools.bot_utils.send_help_msg")
async def test_invoke_subcommand_with_subcommand(self, mock_send_help, mock_ctx):
"""Test invoke_subcommand when subcommand exists."""
mock_subcommand = MagicMock()
@@ -210,7 +210,7 @@ async def test_invoke_subcommand_with_subcommand(self, mock_send_help, mock_ctx)
mock_ctx.message.channel.typing.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_help_msg')
+ @patch("src.bot.tools.bot_utils.send_help_msg")
async def test_invoke_subcommand_without_subcommand(self, mock_send_help, mock_ctx):
"""Test invoke_subcommand when no subcommand exists."""
mock_ctx.invoked_subcommand = None
@@ -223,7 +223,7 @@ async def test_invoke_subcommand_without_subcommand(self, mock_send_help, mock_c
mock_send_help.assert_called_once_with(mock_ctx, mock_ctx.command)
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_help_msg')
+ @patch("src.bot.tools.bot_utils.send_help_msg")
async def test_invoke_subcommand_no_command(self, mock_send_help, mock_ctx):
"""Test invoke_subcommand when no command exists."""
mock_ctx.invoked_subcommand = None
@@ -302,7 +302,7 @@ def mock_ctx(self):
return ctx
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_embed')
+ @patch("src.bot.tools.bot_utils.send_embed")
async def test_send_msg_basic(self, mock_send_embed, mock_ctx):
"""Test basic send_msg functionality."""
await bot_utils.send_msg(mock_ctx, "Test message")
@@ -317,7 +317,7 @@ async def test_send_msg_basic(self, mock_send_embed, mock_ctx):
assert dm_arg is False
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_embed')
+ @patch("src.bot.tools.bot_utils.send_embed")
async def test_send_msg_dm(self, mock_send_embed, mock_ctx):
"""Test send_msg with DM enabled."""
await bot_utils.send_msg(mock_ctx, "DM message", dm=True)
@@ -327,7 +327,7 @@ async def test_send_msg_dm(self, mock_send_embed, mock_ctx):
assert dm_arg is True
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_embed')
+ @patch("src.bot.tools.bot_utils.send_embed")
async def test_send_warning_msg(self, mock_send_embed, mock_ctx):
"""Test send_warning_msg functionality."""
await bot_utils.send_warning_msg(mock_ctx, "Warning message")
@@ -337,7 +337,7 @@ async def test_send_warning_msg(self, mock_send_embed, mock_ctx):
assert embed_arg.color == discord.Color.orange()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_embed')
+ @patch("src.bot.tools.bot_utils.send_embed")
async def test_send_info_msg(self, mock_send_embed, mock_ctx):
"""Test send_info_msg functionality."""
await bot_utils.send_info_msg(mock_ctx, "Info message")
@@ -347,7 +347,7 @@ async def test_send_info_msg(self, mock_send_embed, mock_ctx):
assert embed_arg.color == discord.Color.blue()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_embed')
+ @patch("src.bot.tools.bot_utils.send_embed")
async def test_send_error_msg(self, mock_send_embed, mock_ctx):
"""Test send_error_msg functionality."""
await bot_utils.send_error_msg(mock_ctx, "Error message")
@@ -455,7 +455,7 @@ def test_is_private_message_guild(self):
class TestDateTimeUtilities:
"""Test date and time utility functions."""
- @patch('src.bot.tools.bot_utils.datetime')
+ @patch("src.bot.tools.bot_utils.datetime")
def test_get_current_date_time(self, mock_datetime):
"""Test get_current_date_time function."""
mock_now = MagicMock()
@@ -466,8 +466,8 @@ def test_get_current_date_time(self, mock_datetime):
mock_datetime.now.assert_called_once_with(UTC)
assert result == mock_now
- @patch('src.bot.tools.bot_utils.get_current_date_time')
- @patch('src.bot.tools.bot_utils.convert_datetime_to_str_long')
+ @patch("src.bot.tools.bot_utils.get_current_date_time")
+ @patch("src.bot.tools.bot_utils.convert_datetime_to_str_long")
def test_get_current_date_time_str_long(self, mock_convert, mock_get_current):
"""Test get_current_date_time_str_long function."""
mock_datetime = MagicMock()
@@ -480,7 +480,7 @@ def test_get_current_date_time_str_long(self, mock_convert, mock_get_current):
mock_convert.assert_called_once_with(mock_datetime)
assert result == "formatted_date"
- @patch('src.bot.tools.bot_utils.variables.DATE_TIME_FORMATTER_STR', '%Y-%m-%d %H:%M:%S')
+ @patch("src.bot.tools.bot_utils.variables.DATE_TIME_FORMATTER_STR", "%Y-%m-%d %H:%M:%S")
def test_convert_datetime_to_str_long(self):
"""Test convert_datetime_to_str_long function."""
test_date = datetime(2023, 1, 1, 12, 30, 45, tzinfo=UTC)
@@ -489,8 +489,8 @@ def test_convert_datetime_to_str_long(self):
assert result == "2023-01-01 12:30:45"
- @patch('src.bot.tools.bot_utils.variables.DATE_FORMATTER', '%Y-%m-%d')
- @patch('src.bot.tools.bot_utils.variables.TIME_FORMATTER', '%H:%M:%S')
+ @patch("src.bot.tools.bot_utils.variables.DATE_FORMATTER", "%Y-%m-%d")
+ @patch("src.bot.tools.bot_utils.variables.TIME_FORMATTER", "%H:%M:%S")
def test_convert_datetime_to_str_short(self):
"""Test convert_datetime_to_str_short function."""
test_date = datetime(2023, 1, 1, 12, 30, 45, tzinfo=UTC)
@@ -499,8 +499,8 @@ def test_convert_datetime_to_str_short(self):
assert result == "2023-01-01 12:30:45"
- @patch('src.bot.tools.bot_utils.variables.DATE_FORMATTER', '%Y-%m-%d')
- @patch('src.bot.tools.bot_utils.variables.TIME_FORMATTER', '%H:%M:%S')
+ @patch("src.bot.tools.bot_utils.variables.DATE_FORMATTER", "%Y-%m-%d")
+ @patch("src.bot.tools.bot_utils.variables.TIME_FORMATTER", "%H:%M:%S")
def test_convert_str_to_datetime_short(self):
"""Test convert_str_to_datetime_short function."""
date_string = "2023-01-01 12:30:45"
@@ -614,7 +614,7 @@ class TestSystemChannelUtilities:
"""Test system channel related utilities."""
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.get_server_system_channel')
+ @patch("src.bot.tools.bot_utils.get_server_system_channel")
async def test_send_msg_to_system_channel_success(self, mock_get_channel):
"""Test send_msg_to_system_channel with successful send."""
mock_log = MagicMock()
@@ -630,7 +630,7 @@ async def test_send_msg_to_system_channel_success(self, mock_get_channel):
mock_log.error.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.get_server_system_channel')
+ @patch("src.bot.tools.bot_utils.get_server_system_channel")
async def test_send_msg_to_system_channel_no_channel(self, mock_get_channel):
"""Test send_msg_to_system_channel when no channel found."""
mock_log = MagicMock()
@@ -644,7 +644,7 @@ async def test_send_msg_to_system_channel_no_channel(self, mock_get_channel):
mock_log.error.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.get_server_system_channel')
+ @patch("src.bot.tools.bot_utils.get_server_system_channel")
async def test_send_msg_to_system_channel_with_fallback(self, mock_get_channel):
"""Test send_msg_to_system_channel with fallback to plain message."""
mock_log = MagicMock()
@@ -694,11 +694,11 @@ def test_get_color_settings_invalid_color(self):
assert result is None
- @patch('random.SystemRandom')
+ @patch("random.SystemRandom")
def test_get_color_settings_random_consistency(self, mock_system_random):
"""Test that random color generation is consistent."""
mock_random = MagicMock()
- mock_random.choice.side_effect = ['A', 'B', 'C', 'D', 'E', 'F']
+ mock_random.choice.side_effect = ["A", "B", "C", "D", "E", "F"]
mock_system_random.return_value = mock_random
result = bot_utils.get_color_settings("random")
@@ -753,7 +753,7 @@ def test_get_bot_stats_no_start_time(self):
bot.guilds = []
bot.start_time = None
- with patch('src.bot.tools.bot_utils.get_current_date_time') as mock_get_time:
+ with patch("src.bot.tools.bot_utils.get_current_date_time") as mock_get_time:
mock_current_time = MagicMock()
mock_get_time.return_value = mock_current_time
diff --git a/tests/unit/bot/tools/test_bot_utils_extra.py b/tests/unit/bot/tools/test_bot_utils_extra.py
index c83b9edd..1deaa7c3 100644
--- a/tests/unit/bot/tools/test_bot_utils_extra.py
+++ b/tests/unit/bot/tools/test_bot_utils_extra.py
@@ -5,7 +5,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.bot.tools import bot_utils
@@ -153,7 +153,7 @@ async def test_send_embed_dm_true(self, mock_ctx):
# Should send notification embed to channel
mock_ctx.send.assert_called_once()
notification_call = mock_ctx.send.call_args
- notification_embed = notification_call[1]['embed']
+ notification_embed = notification_call[1]["embed"]
assert isinstance(notification_embed, discord.Embed)
assert notification_embed.color == discord.Color.green()
assert "Response sent to your DM" in notification_embed.description
@@ -166,7 +166,7 @@ async def test_send_embed_dm_true_notification_author_with_avatar(self, mock_ctx
await bot_utils.send_embed(mock_ctx, embed, dm=True)
notification_call = mock_ctx.send.call_args
- notification_embed = notification_call[1]['embed']
+ notification_embed = notification_call[1]["embed"]
assert notification_embed.author.name == "TestUser"
assert notification_embed.author.icon_url == "https://example.com/avatar.png"
@@ -179,7 +179,7 @@ async def test_send_embed_dm_true_notification_author_no_avatar(self, mock_ctx):
await bot_utils.send_embed(mock_ctx, embed, dm=True)
notification_call = mock_ctx.send.call_args
- notification_embed = notification_call[1]['embed']
+ notification_embed = notification_call[1]["embed"]
assert notification_embed.author.icon_url == "https://example.com/default.png"
@pytest.mark.asyncio
@@ -193,7 +193,7 @@ async def test_send_embed_normal_channel_send(self, mock_ctx):
mock_ctx.author.send.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_error_msg')
+ @patch("src.bot.tools.bot_utils.send_error_msg")
async def test_send_embed_discord_forbidden_exception(self, mock_send_error, mock_ctx):
"""Test send_embed handles discord.Forbidden exception."""
embed = discord.Embed(description="Test", color=discord.Color.green())
@@ -207,7 +207,7 @@ async def test_send_embed_discord_forbidden_exception(self, mock_send_error, moc
assert call_args[0] == mock_ctx
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_error_msg')
+ @patch("src.bot.tools.bot_utils.send_error_msg")
async def test_send_embed_discord_http_exception(self, mock_send_error, mock_ctx):
"""Test send_embed handles discord.HTTPException."""
embed = discord.Embed(description="Test", color=discord.Color.green())
@@ -252,7 +252,7 @@ def mock_ctx(self):
return ctx
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_warning_true_success(self, mock_send_msg, mock_ctx):
"""Test delete_message with warning=True sends privacy message after successful delete."""
await bot_utils.delete_message(mock_ctx, warning=True)
@@ -269,7 +269,7 @@ async def test_delete_message_warning_true_success(self, mock_send_msg, mock_ctx
assert call_args[3] is None # color=None (no error)
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_warning_true_delete_fails(self, mock_send_msg, mock_ctx):
"""Test delete_message with warning=True sends error message when delete fails."""
mock_ctx.message.delete.side_effect = discord.Forbidden(MagicMock(), "No permission")
@@ -286,7 +286,7 @@ async def test_delete_message_warning_true_delete_fails(self, mock_send_msg, moc
assert call_args[3] == discord.Color.red() # color=red for error
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_warning_false_success(self, mock_send_msg, mock_ctx):
"""Test delete_message with warning=False does not send message after successful delete."""
await bot_utils.delete_message(mock_ctx, warning=False)
@@ -295,7 +295,7 @@ async def test_delete_message_warning_false_success(self, mock_send_msg, mock_ct
mock_send_msg.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_warning_false_delete_fails(self, mock_send_msg, mock_ctx):
"""Test delete_message with warning=False does not send message even when delete fails."""
mock_ctx.message.delete.side_effect = Exception("Delete failed")
@@ -306,7 +306,7 @@ async def test_delete_message_warning_false_delete_fails(self, mock_send_msg, mo
mock_ctx.bot.log.error.assert_called_once()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_private_message_no_action(self, mock_send_msg):
"""Test delete_message does nothing for private messages."""
ctx = MagicMock()
@@ -320,7 +320,7 @@ async def test_delete_message_private_message_no_action(self, mock_send_msg):
mock_send_msg.assert_not_called()
@pytest.mark.asyncio
- @patch('src.bot.tools.bot_utils.send_msg')
+ @patch("src.bot.tools.bot_utils.send_msg")
async def test_delete_message_warning_true_logs_error_on_failure(self, mock_send_msg, mock_ctx):
"""Test delete_message logs error when delete fails."""
mock_ctx.message.delete.side_effect = Exception("Permission denied")
diff --git a/tests/unit/bot/tools/test_checks.py b/tests/unit/bot/tools/test_checks.py
index b6ca5459..945f458e 100644
--- a/tests/unit/bot/tools/test_checks.py
+++ b/tests/unit/bot/tools/test_checks.py
@@ -70,10 +70,10 @@ def dummy_command(ctx):
return "success"
# Verify the decorator added command checks
- assert hasattr(dummy_command, '__commands_checks__')
+ assert hasattr(dummy_command, "__commands_checks__")
assert len(dummy_command.__commands_checks__) > 0
- @patch('src.bot.tools.checks.bot_utils.is_member_admin')
+ @patch("src.bot.tools.checks.bot_utils.is_member_admin")
def test_check_is_admin_predicate_failure(self, mock_is_admin):
"""Test the predicate function when a user is not admin."""
mock_is_admin.return_value = False
@@ -82,7 +82,7 @@ def test_check_is_admin_predicate_failure(self, mock_is_admin):
# Extract the predicate function from the decorator
# commands.check stores the predicate in the decorator's first argument
- if hasattr(decorator, '__wrapped__'):
+ if hasattr(decorator, "__wrapped__"):
predicate = decorator.__wrapped__
else:
# Get the predicate from the closure
@@ -102,8 +102,8 @@ def test_check_is_admin_predicate_failure(self, mock_is_admin):
# If we can't access the predicate, just verify the decorator exists
assert callable(decorator)
- @patch('src.bot.tools.checks.commands.check')
- @patch('src.bot.tools.checks.bot_utils.is_member_admin')
+ @patch("src.bot.tools.checks.commands.check")
+ @patch("src.bot.tools.checks.bot_utils.is_member_admin")
def test_check_is_admin_uses_commands_check(self, mock_is_admin, mock_commands_check):
"""Test that check_is_admin uses commands.check."""
mock_commands_check.return_value = lambda f: f # Return identity function
@@ -125,7 +125,7 @@ def test_command(ctx):
return "command executed"
# Verify the decorator was applied
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) > 0
# The check should be a function
@@ -154,10 +154,10 @@ def dummy_command(ctx):
return "success"
# Verify the decorator added command checks
- assert hasattr(dummy_command, '__commands_checks__')
+ assert hasattr(dummy_command, "__commands_checks__")
assert len(dummy_command.__commands_checks__) > 0
- @patch('src.bot.tools.checks.bot_utils.is_bot_owner')
+ @patch("src.bot.tools.checks.bot_utils.is_bot_owner")
def test_check_is_bot_owner_predicate_failure(self, mock_is_owner):
"""Test the predicate function when user is not bot owner."""
mock_is_owner.return_value = False
@@ -165,7 +165,7 @@ def test_check_is_bot_owner_predicate_failure(self, mock_is_owner):
decorator = Checks.check_is_bot_owner()
# Extract the predicate function from the decorator
- if hasattr(decorator, '__wrapped__'):
+ if hasattr(decorator, "__wrapped__"):
predicate = decorator.__wrapped__
else:
# Get the predicate from the closure
@@ -185,8 +185,8 @@ def test_check_is_bot_owner_predicate_failure(self, mock_is_owner):
# If we can't access the predicate, just verify the decorator exists
assert callable(decorator)
- @patch('src.bot.tools.checks.commands.check')
- @patch('src.bot.tools.checks.bot_utils.is_bot_owner')
+ @patch("src.bot.tools.checks.commands.check")
+ @patch("src.bot.tools.checks.bot_utils.is_bot_owner")
def test_check_is_bot_owner_uses_commands_check(self, mock_is_owner, mock_commands_check):
"""Test that check_is_bot_owner uses commands.check."""
mock_commands_check.return_value = lambda f: f # Return identity function
@@ -208,7 +208,7 @@ def test_command(ctx):
return "command executed"
# Verify the decorator was applied
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) > 0
# The check should be a function
@@ -237,7 +237,7 @@ def test_command(ctx):
return "command executed"
# Should have both checks
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) == 2
def test_combined_decorators_both_pass(self):
@@ -249,7 +249,7 @@ def test_command(ctx):
return "success"
# Should have both checks
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) == 2
def test_combined_decorators_admin_fails(self):
@@ -261,7 +261,7 @@ def test_command(ctx):
return "command"
# Should have both checks
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) == 2
def test_combined_decorators_owner_fails(self):
@@ -273,7 +273,7 @@ def test_command(ctx):
return "command"
# Should have both checks
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) == 2
@@ -320,7 +320,7 @@ def test_command(ctx):
return "success"
# Verify decorator was applied
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
def test_owner_check_with_none_context(self):
"""Test owner check decorator can be created."""
@@ -332,7 +332,7 @@ def test_command(ctx):
return "success"
# Verify decorator was applied
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
def test_decorator_preserves_function_metadata(self):
"""Test that decorators preserve original function metadata."""
@@ -346,7 +346,7 @@ def test_command(ctx):
assert "test_command" in test_command.__name__ or "wrapper" in test_command.__name__
# Should have check attributes
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
def test_multiple_applications_of_same_decorator(self):
"""Test applying the same decorator multiple times."""
@@ -358,7 +358,7 @@ def test_command(ctx):
return "success"
# Should have multiple identical checks
- assert hasattr(test_command, '__commands_checks__')
+ assert hasattr(test_command, "__commands_checks__")
assert len(test_command.__commands_checks__) == 2
@@ -410,7 +410,7 @@ def test_static_method_accessibility(self):
class TestCheckPredicateRaisesCheckFailure:
"""Tests that directly invoke the predicate to cover the raise CheckFailure lines (23 and 41)."""
- @patch('src.bot.tools.checks.bot_utils.is_member_admin', return_value=False)
+ @patch("src.bot.tools.checks.bot_utils.is_member_admin", return_value=False)
def test_admin_predicate_raises_check_failure(self, mock_is_admin):
"""Test that admin predicate raises CheckFailure when user is not admin (line 23)."""
@@ -430,7 +430,7 @@ async def dummy(ctx):
assert "User is not an administrator" in str(exc_info.value)
mock_is_admin.assert_called_once_with(ctx.message.author)
- @patch('src.bot.tools.checks.bot_utils.is_member_admin', return_value=True)
+ @patch("src.bot.tools.checks.bot_utils.is_member_admin", return_value=True)
def test_admin_predicate_returns_true_when_admin(self, mock_is_admin):
"""Test that admin predicate returns True when user is admin (line 23)."""
@@ -447,7 +447,7 @@ async def dummy(ctx):
assert result is True
mock_is_admin.assert_called_once_with(ctx.message.author)
- @patch('src.bot.tools.checks.bot_utils.is_bot_owner', return_value=False)
+ @patch("src.bot.tools.checks.bot_utils.is_bot_owner", return_value=False)
def test_owner_predicate_raises_check_failure(self, mock_is_owner):
"""Test that owner predicate raises CheckFailure when user is not owner (line 41)."""
@@ -466,7 +466,7 @@ async def dummy(ctx):
assert "User is not the bot owner" in str(exc_info.value)
mock_is_owner.assert_called_once_with(ctx, ctx.message.author)
- @patch('src.bot.tools.checks.bot_utils.is_bot_owner', return_value=True)
+ @patch("src.bot.tools.checks.bot_utils.is_bot_owner", return_value=True)
def test_owner_predicate_returns_true_when_owner(self, mock_is_owner):
"""Test that owner predicate returns True when user is bot owner (line 41)."""
diff --git a/tests/unit/bot/tools/test_cooldowns.py b/tests/unit/bot/tools/test_cooldowns.py
index 113fc6bf..32f17f73 100644
--- a/tests/unit/bot/tools/test_cooldowns.py
+++ b/tests/unit/bot/tools/test_cooldowns.py
@@ -9,13 +9,13 @@ class TestCoolDownsEnum:
def test_cooldowns_enum_exists(self):
"""Test that CoolDowns enum is properly defined."""
- assert hasattr(CoolDowns, 'Admin')
- assert hasattr(CoolDowns, 'Config')
- assert hasattr(CoolDowns, 'CustomCommand')
- assert hasattr(CoolDowns, 'DiceRolls')
- assert hasattr(CoolDowns, 'Misc')
- assert hasattr(CoolDowns, 'OpenAI')
- assert hasattr(CoolDowns, 'Owner')
+ assert hasattr(CoolDowns, "Admin")
+ assert hasattr(CoolDowns, "Config")
+ assert hasattr(CoolDowns, "CustomCommand")
+ assert hasattr(CoolDowns, "DiceRolls")
+ assert hasattr(CoolDowns, "Misc")
+ assert hasattr(CoolDowns, "OpenAI")
+ assert hasattr(CoolDowns, "Owner")
def test_cooldowns_enum_values_are_integers(self):
"""Test that all cooldown values are integers."""
@@ -29,7 +29,7 @@ def test_cooldowns_enum_members_count(self):
assert len(actual_members) >= 1 # At least Admin should exist
# Check that Admin exists (which we know works)
- assert 'Admin' in actual_members
+ assert "Admin" in actual_members
class TestCoolDownsDebugMode:
@@ -112,7 +112,7 @@ def test_configuration_file_loading(self):
"""Test that configuration loading works."""
# Since the configuration is loaded at import time, we can't mock it easily
# Just verify that the CoolDowns enum was successfully created
- assert hasattr(CoolDowns, 'Admin')
+ assert hasattr(CoolDowns, "Admin")
assert isinstance(CoolDowns.Admin.value, int)
def test_missing_configuration_values(self):
@@ -171,7 +171,7 @@ def test_cooldowns_iteration(self):
# Should have at least Admin
assert len(cooldown_names) >= 1
- assert 'Admin' in cooldown_names
+ assert "Admin" in cooldown_names
# All values should be positive integers
for value in cooldown_values:
@@ -180,10 +180,10 @@ def test_cooldowns_iteration(self):
def test_cooldowns_lookup_by_name(self):
"""Test looking up cooldowns by name."""
- admin = CoolDowns['Admin']
+ admin = CoolDowns["Admin"]
assert admin == CoolDowns.Admin
- config = CoolDowns['Config']
+ config = CoolDowns["Config"]
assert config == CoolDowns.Config
# Test all cooldowns can be looked up
@@ -269,7 +269,7 @@ class TestCoolDownsErrorHandling:
def test_nonexistent_cooldown_access(self):
"""Test accessing non-existent cooldown raises appropriate error."""
with pytest.raises(KeyError):
- _ = CoolDowns['NonExistentCooldown']
+ _ = CoolDowns["NonExistentCooldown"]
def test_cooldown_attribute_error(self):
"""Test that accessing non-existent attributes raises appropriate error."""
diff --git a/tests/unit/bot/tools/test_custom_help.py b/tests/unit/bot/tools/test_custom_help.py
index 8234fbde..08dfd974 100644
--- a/tests/unit/bot/tools/test_custom_help.py
+++ b/tests/unit/bot/tools/test_custom_help.py
@@ -1,4 +1,4 @@
-"""Comprehensive tests for CustomHelpCommand class."""
+"""Comprehensive tests for CustomHelpCommand and HelpPaginatorView classes."""
import discord
import pytest
@@ -6,9 +6,193 @@
from discord.ext import commands
from unittest.mock import AsyncMock, MagicMock, Mock
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
-from src.bot.tools.custom_help import CustomHelpCommand
+from src.bot.tools.custom_help import CustomHelpCommand, HelpPaginatorView
+
+
+class TestHelpPaginatorView:
+ """Test HelpPaginatorView pagination logic."""
+
+ @pytest.mark.asyncio
+ async def test_initial_state_first_page(self):
+ """Test view starts on page 0 with previous disabled and next enabled."""
+ view = HelpPaginatorView(["Page A", "Page B", "Page C"], author_id=123)
+
+ assert view.current_page == 0
+ assert view.previous_button.disabled is True
+ assert view.next_button.disabled is False
+ assert view.page_indicator.label == "1/3"
+ assert view.page_indicator.disabled is True
+
+ @pytest.mark.asyncio
+ async def test_initial_state_two_pages(self):
+ """Test view with two pages has correct initial state."""
+ view = HelpPaginatorView(["Page A", "Page B"], author_id=456)
+
+ assert view.current_page == 0
+ assert view.previous_button.disabled is True
+ assert view.next_button.disabled is False
+ assert view.page_indicator.label == "1/2"
+
+ @pytest.mark.asyncio
+ async def test_format_page_first(self):
+ """Test _format_page returns page header + content for first page."""
+ view = HelpPaginatorView(["```\nContent A\n```", "```\nContent B\n```"], author_id=1)
+
+ result = view._format_page()
+
+ assert result == "**Page 1/2**\n```\nContent A\n```"
+
+ @pytest.mark.asyncio
+ async def test_format_page_second(self):
+ """Test _format_page returns correct content after navigating."""
+ view = HelpPaginatorView(["Page A", "Page B", "Page C"], author_id=1)
+ view.current_page = 1
+ view._update_buttons()
+
+ result = view._format_page()
+
+ assert result == "**Page 2/3**\nPage B"
+
+ @pytest.mark.asyncio
+ async def test_update_buttons_middle_page(self):
+ """Test buttons state on middle page: both enabled."""
+ view = HelpPaginatorView(["A", "B", "C"], author_id=1)
+ view.current_page = 1
+ view._update_buttons()
+
+ assert view.previous_button.disabled is False
+ assert view.next_button.disabled is False
+ assert view.page_indicator.label == "2/3"
+
+ @pytest.mark.asyncio
+ async def test_update_buttons_last_page(self):
+ """Test buttons state on last page: next disabled."""
+ view = HelpPaginatorView(["A", "B", "C"], author_id=1)
+ view.current_page = 2
+ view._update_buttons()
+
+ assert view.previous_button.disabled is False
+ assert view.next_button.disabled is True
+ assert view.page_indicator.label == "3/3"
+
+ @pytest.mark.asyncio
+ async def test_next_button_advances_page(self):
+ """Test clicking next button advances current_page."""
+ view = HelpPaginatorView(["A", "B", "C"], author_id=42)
+ interaction = MagicMock()
+ interaction.user.id = 42
+ interaction.response = AsyncMock()
+
+ await view.next_button.callback(interaction)
+
+ assert view.current_page == 1
+ interaction.response.edit_message.assert_called_once()
+ call_kwargs = interaction.response.edit_message.call_args[1]
+ assert "**Page 2/3**" in call_kwargs["content"]
+
+ @pytest.mark.asyncio
+ async def test_previous_button_goes_back(self):
+ """Test clicking previous button goes back a page."""
+ view = HelpPaginatorView(["A", "B", "C"], author_id=42)
+ view.current_page = 2
+ view._update_buttons()
+ interaction = MagicMock()
+ interaction.user.id = 42
+ interaction.response = AsyncMock()
+
+ await view.previous_button.callback(interaction)
+
+ assert view.current_page == 1
+ interaction.response.edit_message.assert_called_once()
+ call_kwargs = interaction.response.edit_message.call_args[1]
+ assert "**Page 2/3**" in call_kwargs["content"]
+
+ @pytest.mark.asyncio
+ async def test_next_button_rejects_non_author(self):
+ """Test non-author clicking next gets ephemeral rejection."""
+ view = HelpPaginatorView(["A", "B"], author_id=42)
+ interaction = MagicMock()
+ interaction.user.id = 999
+ interaction.response = AsyncMock()
+
+ await view.next_button.callback(interaction)
+
+ assert view.current_page == 0 # unchanged
+ interaction.response.send_message.assert_called_once_with(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+
+ @pytest.mark.asyncio
+ async def test_previous_button_rejects_non_author(self):
+ """Test non-author clicking previous gets ephemeral rejection."""
+ view = HelpPaginatorView(["A", "B"], author_id=42)
+ view.current_page = 1
+ view._update_buttons()
+ interaction = MagicMock()
+ interaction.user.id = 999
+ interaction.response = AsyncMock()
+
+ await view.previous_button.callback(interaction)
+
+ assert view.current_page == 1 # unchanged
+ interaction.response.send_message.assert_called_once_with(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+
+ @pytest.mark.asyncio
+ async def test_page_indicator_defers(self):
+ """Test page indicator button just defers (non-interactive)."""
+ view = HelpPaginatorView(["A", "B"], author_id=1)
+ interaction = MagicMock()
+ interaction.response = AsyncMock()
+
+ await view.page_indicator.callback(interaction)
+
+ interaction.response.defer.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_on_timeout_disables_all_buttons(self):
+ """Test on_timeout disables all children and edits message."""
+ view = HelpPaginatorView(["A", "B"], author_id=1)
+ view.message = AsyncMock()
+
+ await view.on_timeout()
+
+ for item in view.children:
+ assert item.disabled is True
+ view.message.edit.assert_called_once_with(view=view)
+
+ @pytest.mark.asyncio
+ async def test_on_timeout_no_message(self):
+ """Test on_timeout with no message reference does not raise."""
+ view = HelpPaginatorView(["A", "B"], author_id=1)
+ view.message = None
+
+ await view.on_timeout()
+
+ for item in view.children:
+ assert item.disabled is True
+
+ @pytest.mark.asyncio
+ async def test_timeout_is_300(self):
+ """Test view timeout is 300 seconds."""
+ view = HelpPaginatorView(["A", "B"], author_id=1)
+ assert view.timeout == 300
+
+ @pytest.mark.asyncio
+ async def test_author_id_stored(self):
+ """Test author_id is stored correctly."""
+ view = HelpPaginatorView(["A"], author_id=12345)
+ assert view.author_id == 12345
+
+ @pytest.mark.asyncio
+ async def test_pages_stored(self):
+ """Test pages list is stored correctly."""
+ pages = ["Page 1", "Page 2"]
+ view = HelpPaginatorView(pages, author_id=1)
+ assert view.pages is pages
class TestCustomHelpCommandInit:
@@ -20,25 +204,25 @@ def test_init_default_options(self):
assert cmd.paginator is not None
assert isinstance(cmd.paginator, commands.Paginator)
- assert cmd.paginator.prefix == '```'
- assert cmd.paginator.suffix == '```'
+ assert cmd.paginator.prefix == "```"
+ assert cmd.paginator.suffix == "```"
assert cmd.paginator.max_size == 2000
def test_init_custom_paginator(self):
"""Test initialization with custom paginator preserves it."""
- custom_paginator = commands.Paginator(prefix='---', suffix='---', max_size=1000)
+ custom_paginator = commands.Paginator(prefix="---", suffix="---", max_size=1000)
cmd = CustomHelpCommand(paginator=custom_paginator)
assert cmd.paginator is custom_paginator
- assert cmd.paginator.prefix == '---'
- assert cmd.paginator.suffix == '---'
+ assert cmd.paginator.prefix == "---"
+ assert cmd.paginator.suffix == "---"
assert cmd.paginator.max_size == 1000
def test_init_custom_options_passed_to_parent(self):
"""Test that other options are passed to the parent class."""
- cmd = CustomHelpCommand(no_category='Uncategorized', sort_commands=False)
+ cmd = CustomHelpCommand(no_category="Uncategorized", sort_commands=False)
- assert cmd.no_category == 'Uncategorized'
+ assert cmd.no_category == "Uncategorized"
assert cmd.sort_commands is False
def test_init_dm_help_option(self):
@@ -99,7 +283,7 @@ async def test_send_pages_dm_help_true_not_in_dm(self, help_command):
# Should send notification embed to channel
help_command.context.send.assert_called_once()
call_kwargs = help_command.context.send.call_args[1]
- embed = call_kwargs['embed']
+ embed = call_kwargs["embed"]
assert isinstance(embed, discord.Embed)
assert embed.color == discord.Color.green()
@@ -117,7 +301,7 @@ async def test_send_pages_dm_help_true_notification_embed_description(self, help
await help_command.send_pages()
call_kwargs = help_command.context.send.call_args[1]
- embed = call_kwargs['embed']
+ embed = call_kwargs["embed"]
assert "Response sent to your DM" in embed.description
@pytest.mark.asyncio
@@ -137,7 +321,7 @@ async def test_send_pages_dm_help_true_author_with_avatar(self, help_command):
await help_command.send_pages()
call_kwargs = help_command.context.send.call_args[1]
- embed = call_kwargs['embed']
+ embed = call_kwargs["embed"]
assert embed.author.name == "TestUser"
assert embed.author.icon_url == "https://cdn.example.com/avatar.png"
@@ -160,7 +344,7 @@ async def test_send_pages_dm_help_true_author_no_avatar(self, help_command):
await help_command.send_pages()
call_kwargs = help_command.context.send.call_args[1]
- embed = call_kwargs['embed']
+ embed = call_kwargs["embed"]
assert embed.author.icon_url == "https://cdn.example.com/default.png"
@pytest.mark.asyncio
@@ -205,12 +389,13 @@ def help_command(self):
cmd = CustomHelpCommand()
cmd.context = MagicMock()
cmd.context.author = MagicMock()
+ cmd.context.author.id = 42
cmd.context.author.send = AsyncMock()
return cmd
@pytest.mark.asyncio
async def test_send_pages_to_dm_single_page(self, help_command):
- """Test _send_pages_to_dm with a single page does not add page header."""
+ """Test _send_pages_to_dm with a single page does not add view."""
help_command.paginator = MagicMock()
help_command.paginator.pages = ["```\nHelp content here\n```"]
@@ -219,40 +404,46 @@ async def test_send_pages_to_dm_single_page(self, help_command):
help_command.context.author.send.assert_called_once_with("```\nHelp content here\n```")
@pytest.mark.asyncio
- async def test_send_pages_to_dm_multiple_pages(self, help_command):
- """Test _send_pages_to_dm with multiple pages adds page headers."""
+ async def test_send_pages_to_dm_multiple_pages_sends_single_message(self, help_command):
+ """Test _send_pages_to_dm with multiple pages sends one message with view."""
help_command.paginator = MagicMock()
help_command.paginator.pages = ["```\nPage 1 content\n```", "```\nPage 2 content\n```"]
await help_command._send_pages_to_dm()
- assert help_command.context.author.send.call_count == 2
-
- first_call_content = help_command.context.author.send.call_args_list[0][0][0]
- assert first_call_content == "**Page 1/2**\n```\nPage 1 content\n```"
-
- second_call_content = help_command.context.author.send.call_args_list[1][0][0]
- assert second_call_content == "**Page 2/2**\n```\nPage 2 content\n```"
+ # Should send exactly one message (not two)
+ help_command.context.author.send.assert_called_once()
+ call_kwargs = help_command.context.author.send.call_args[1]
+ assert "**Page 1/2**" in call_kwargs["content"]
+ assert isinstance(call_kwargs["view"], HelpPaginatorView)
@pytest.mark.asyncio
- async def test_send_pages_to_dm_three_pages(self, help_command):
- """Test _send_pages_to_dm with three pages for complete pagination."""
+ async def test_send_pages_to_dm_multiple_pages_view_has_correct_pages(self, help_command):
+ """Test that the view receives all pages."""
+ pages = ["Page A", "Page B", "Page C"]
help_command.paginator = MagicMock()
- help_command.paginator.pages = ["Page A", "Page B", "Page C"]
+ help_command.paginator.pages = pages
await help_command._send_pages_to_dm()
- assert help_command.context.author.send.call_count == 3
+ call_kwargs = help_command.context.author.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.pages is pages
+ assert view.author_id == 42
- expected_contents = [
- "**Page 1/3**\nPage A",
- "**Page 2/3**\nPage B",
- "**Page 3/3**\nPage C",
- ]
+ @pytest.mark.asyncio
+ async def test_send_pages_to_dm_multiple_pages_sets_message_ref(self, help_command):
+ """Test that view.message is set to the sent message."""
+ help_command.paginator = MagicMock()
+ help_command.paginator.pages = ["A", "B"]
+ mock_msg = AsyncMock()
+ help_command.context.author.send.return_value = mock_msg
+
+ await help_command._send_pages_to_dm()
- for i, expected in enumerate(expected_contents):
- actual = help_command.context.author.send.call_args_list[i][0][0]
- assert actual == expected
+ call_kwargs = help_command.context.author.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.message is mock_msg
class TestSendPagesToDestination:
@@ -263,11 +454,13 @@ def help_command(self):
"""Create a CustomHelpCommand instance."""
cmd = CustomHelpCommand()
cmd.context = MagicMock()
+ cmd.context.author = MagicMock()
+ cmd.context.author.id = 42
return cmd
@pytest.mark.asyncio
async def test_send_pages_to_destination_single_page(self, help_command):
- """Test _send_pages_to_destination with a single page does not add page header."""
+ """Test _send_pages_to_destination with a single page does not add view."""
help_command.paginator = MagicMock()
help_command.paginator.pages = ["```\nSingle page content\n```"]
mock_destination = AsyncMock()
@@ -277,42 +470,49 @@ async def test_send_pages_to_destination_single_page(self, help_command):
mock_destination.send.assert_called_once_with("```\nSingle page content\n```")
@pytest.mark.asyncio
- async def test_send_pages_to_destination_multiple_pages(self, help_command):
- """Test _send_pages_to_destination with multiple pages adds page headers."""
+ async def test_send_pages_to_destination_multiple_pages_sends_single_message(self, help_command):
+ """Test _send_pages_to_destination with multiple pages sends one message with view."""
help_command.paginator = MagicMock()
help_command.paginator.pages = ["```\nFirst page\n```", "```\nSecond page\n```"]
mock_destination = AsyncMock()
await help_command._send_pages_to_destination(mock_destination)
- assert mock_destination.send.call_count == 2
-
- first_call_content = mock_destination.send.call_args_list[0][0][0]
- assert first_call_content == "**Page 1/2**\n```\nFirst page\n```"
-
- second_call_content = mock_destination.send.call_args_list[1][0][0]
- assert second_call_content == "**Page 2/2**\n```\nSecond page\n```"
+ # Should send exactly one message (not two)
+ mock_destination.send.assert_called_once()
+ call_kwargs = mock_destination.send.call_args[1]
+ assert "**Page 1/2**" in call_kwargs["content"]
+ assert isinstance(call_kwargs["view"], HelpPaginatorView)
@pytest.mark.asyncio
- async def test_send_pages_to_destination_three_pages(self, help_command):
- """Test _send_pages_to_destination with three pages for complete pagination."""
+ async def test_send_pages_to_destination_multiple_pages_view_has_correct_pages(self, help_command):
+ """Test that the view receives all pages."""
+ pages = ["Content A", "Content B", "Content C"]
help_command.paginator = MagicMock()
- help_command.paginator.pages = ["Content A", "Content B", "Content C"]
+ help_command.paginator.pages = pages
mock_destination = AsyncMock()
await help_command._send_pages_to_destination(mock_destination)
- assert mock_destination.send.call_count == 3
+ call_kwargs = mock_destination.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.pages is pages
+ assert view.author_id == 42
- expected_contents = [
- "**Page 1/3**\nContent A",
- "**Page 2/3**\nContent B",
- "**Page 3/3**\nContent C",
- ]
+ @pytest.mark.asyncio
+ async def test_send_pages_to_destination_multiple_pages_sets_message_ref(self, help_command):
+ """Test that view.message is set to the sent message."""
+ help_command.paginator = MagicMock()
+ help_command.paginator.pages = ["A", "B"]
+ mock_destination = AsyncMock()
+ mock_msg = AsyncMock()
+ mock_destination.send.return_value = mock_msg
- for i, expected in enumerate(expected_contents):
- actual = mock_destination.send.call_args_list[i][0][0]
- assert actual == expected
+ await help_command._send_pages_to_destination(mock_destination)
+
+ call_kwargs = mock_destination.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.message is mock_msg
@pytest.mark.asyncio
async def test_send_pages_to_destination_sends_to_correct_destination(self, help_command):
@@ -327,3 +527,186 @@ async def test_send_pages_to_destination_sends_to_correct_destination(self, help
destination_a.send.assert_called_once()
destination_b.send.assert_not_called()
+
+
+class TestSendBotHelp:
+ """Test send_bot_help method with subcommand pages."""
+
+ @pytest.mark.asyncio
+ async def test_send_bot_help_adds_subcommand_pages_for_groups(self):
+ """Verify groups get separate pages listing their subcommands."""
+ cmd = CustomHelpCommand()
+ cmd.context = MagicMock()
+ cmd.context.author = MagicMock()
+ cmd.context.author.send = AsyncMock()
+ cmd.dm_help = False
+
+ # Create a mock bot with a group command that has subcommands
+ bot = MagicMock()
+ bot.description = ""
+ cmd.context.bot = bot
+
+ # Create a cog
+ mock_cog = MagicMock()
+ mock_cog.qualified_name = "Config"
+
+ # Create subcommands
+ sub1 = MagicMock(spec=commands.Command)
+ sub1.name = "botgame"
+ sub1.short_doc = "Change bot game."
+ sub1.cog = mock_cog
+ sub1.hidden = False
+ sub1.checks = []
+ sub1.enabled = True
+
+ sub2 = MagicMock(spec=commands.Command)
+ sub2.name = "config"
+ sub2.short_doc = "Bot configuration."
+ sub2.cog = mock_cog
+ sub2.hidden = False
+ sub2.checks = []
+ sub2.enabled = True
+
+ # Create a group command with subcommands
+ group_cmd = MagicMock(spec=commands.Group)
+ group_cmd.name = "admin"
+ group_cmd.short_doc = "Admin commands."
+ group_cmd.cog = mock_cog
+ group_cmd.hidden = False
+ group_cmd.checks = []
+ group_cmd.enabled = True
+ group_cmd.qualified_name = "admin"
+ group_cmd.commands = [sub1, sub2]
+
+ # Create a regular (non-group) command
+ regular_cmd = MagicMock(spec=commands.Command)
+ regular_cmd.name = "roll"
+ regular_cmd.short_doc = "Roll a die."
+ regular_cmd.cog = mock_cog
+ regular_cmd.hidden = False
+ regular_cmd.checks = []
+ regular_cmd.enabled = True
+
+ bot.commands = [group_cmd, regular_cmd]
+
+ # Mock send_pages to capture paginator state
+ sent_pages = []
+
+ async def capture_send_pages():
+ sent_pages.extend(cmd.paginator.pages)
+
+ cmd.send_pages = capture_send_pages
+
+ # Mock filter_commands to return commands as-is
+ async def mock_filter(cmds, *, sort=True, key=None):
+ return sorted(cmds, key=lambda c: c.name) if sort else list(cmds)
+
+ cmd.filter_commands = mock_filter
+
+ mapping = {mock_cog: [group_cmd, regular_cmd], None: []}
+ await cmd.send_bot_help(mapping)
+
+ # Should have at least 2 pages: overview + admin subcommands
+ assert len(sent_pages) >= 2
+
+ # First page should have the overview with command names
+ assert "admin" in sent_pages[0]
+ assert "roll" in sent_pages[0]
+
+ # Second page should have the group subcommands
+ subcommand_page = sent_pages[1]
+ assert "admin subcommands:" in subcommand_page
+ assert "botgame" in subcommand_page
+ assert "config" in subcommand_page
+
+ @pytest.mark.asyncio
+ async def test_send_bot_help_no_subcommand_page_for_empty_groups(self):
+ """Verify groups with no subcommands don't get extra pages."""
+ cmd = CustomHelpCommand()
+ cmd.context = MagicMock()
+ cmd.context.author = MagicMock()
+ cmd.context.author.send = AsyncMock()
+ cmd.dm_help = False
+
+ bot = MagicMock()
+ bot.description = ""
+ cmd.context.bot = bot
+
+ mock_cog = MagicMock()
+ mock_cog.qualified_name = "Config"
+
+ # Group with no subcommands
+ group_cmd = MagicMock(spec=commands.Group)
+ group_cmd.name = "admin"
+ group_cmd.short_doc = "Admin commands."
+ group_cmd.cog = mock_cog
+ group_cmd.hidden = False
+ group_cmd.checks = []
+ group_cmd.enabled = True
+ group_cmd.qualified_name = "admin"
+ group_cmd.commands = []
+
+ bot.commands = [group_cmd]
+
+ sent_pages = []
+
+ async def capture_send_pages():
+ sent_pages.extend(cmd.paginator.pages)
+
+ cmd.send_pages = capture_send_pages
+
+ async def mock_filter(cmds, *, sort=True, key=None):
+ return sorted(cmds, key=lambda c: c.name) if sort else list(cmds)
+
+ cmd.filter_commands = mock_filter
+
+ mapping = {mock_cog: [group_cmd], None: []}
+ await cmd.send_bot_help(mapping)
+
+ # Should have only the overview page, no subcommand pages
+ assert len(sent_pages) == 1
+
+ @pytest.mark.asyncio
+ async def test_send_bot_help_regular_commands_no_extra_pages(self):
+ """Verify non-group commands don't generate extra pages."""
+ cmd = CustomHelpCommand()
+ cmd.context = MagicMock()
+ cmd.context.author = MagicMock()
+ cmd.context.author.send = AsyncMock()
+ cmd.dm_help = False
+
+ bot = MagicMock()
+ bot.description = ""
+ cmd.context.bot = bot
+
+ mock_cog = MagicMock()
+ mock_cog.qualified_name = "General"
+
+ regular_cmd = MagicMock(spec=commands.Command)
+ regular_cmd.name = "ping"
+ regular_cmd.short_doc = "Pong!"
+ regular_cmd.cog = mock_cog
+ regular_cmd.hidden = False
+ regular_cmd.checks = []
+ regular_cmd.enabled = True
+
+ bot.commands = [regular_cmd]
+
+ sent_pages = []
+
+ async def capture_send_pages():
+ sent_pages.extend(cmd.paginator.pages)
+
+ cmd.send_pages = capture_send_pages
+
+ async def mock_filter(cmds, *, sort=True, key=None):
+ return sorted(cmds, key=lambda c: c.name) if sort else list(cmds)
+
+ cmd.filter_commands = mock_filter
+
+ mapping = {mock_cog: [regular_cmd], None: []}
+ await cmd.send_bot_help(mapping)
+
+ # Only overview page
+ assert len(sent_pages) == 1
+ assert "ping" in sent_pages[0]
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index 82ff9eb9..c0c1e8a8 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -17,7 +17,7 @@
# Mock problematic imports before any test modules are loaded
def setup_mocks():
"""Setup mocks for problematic dependencies."""
- sys.modules['ddcDatabases'] = Mock()
+ sys.modules["ddcDatabases"] = Mock()
def auto_import_modules():
diff --git a/tests/unit/database/test_dal.py b/tests/unit/database/test_dal.py
index 31b574b8..64e59686 100644
--- a/tests/unit/database/test_dal.py
+++ b/tests/unit/database/test_dal.py
@@ -4,7 +4,7 @@
import sys
from unittest.mock import AsyncMock, MagicMock, Mock, patch
-sys.modules['ddcDatabases'] = Mock()
+sys.modules["ddcDatabases"] = Mock()
from src.database.dal.bot.bot_configs_dal import BotConfigsDal
from src.database.dal.bot.custom_commands_dal import CustomCommandsDal
@@ -28,7 +28,7 @@ class TestBotConfigsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.bot_configs_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.bot_configs_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = BotConfigsDal(db_session, log)
@@ -39,7 +39,7 @@ def test_init(self):
"""Test BotConfigsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.bot_configs_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.bot_configs_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = BotConfigsDal(db_session, log)
@@ -117,7 +117,7 @@ class TestCustomCommandsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.custom_commands_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.custom_commands_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = CustomCommandsDal(db_session, log)
@@ -128,7 +128,7 @@ def test_init(self):
"""Test CustomCommandsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.custom_commands_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.custom_commands_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = CustomCommandsDal(db_session, log)
@@ -233,7 +233,7 @@ class TestDiceRollsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.dice_rolls_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.dice_rolls_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = DiceRollsDal(db_session, log)
@@ -244,7 +244,7 @@ def test_init(self):
"""Test DiceRollsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.dice_rolls_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.dice_rolls_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = DiceRollsDal(db_session, log)
@@ -397,7 +397,7 @@ class TestProfanityFilterDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.profanity_filters_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.profanity_filters_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = ProfanityFilterDal(db_session, log)
@@ -408,7 +408,7 @@ def test_init(self):
"""Test ProfanityFilterDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.profanity_filters_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.profanity_filters_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = ProfanityFilterDal(db_session, log)
@@ -496,7 +496,7 @@ class TestServersDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.servers_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.servers_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = ServersDal(db_session, log)
@@ -507,7 +507,7 @@ def test_init(self):
"""Test ServersDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.bot.servers_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.bot.servers_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = ServersDal(db_session, log)
@@ -697,7 +697,7 @@ class TestGw2ConfigsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_configs_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_configs_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2ConfigsDal(db_session, log)
@@ -708,7 +708,7 @@ def test_init(self):
"""Test Gw2ConfigsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_configs_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_configs_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2ConfigsDal(db_session, log)
@@ -779,7 +779,7 @@ class TestGw2KeyDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_key_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_key_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2KeyDal(db_session, log)
@@ -790,7 +790,7 @@ def test_init(self):
"""Test Gw2KeyDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_key_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_key_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2KeyDal(db_session, log)
@@ -914,7 +914,7 @@ class TestGw2SessionCharsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_session_chars_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_session_chars_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2SessionCharsDal(db_session, log)
@@ -925,7 +925,7 @@ def test_init(self):
"""Test Gw2SessionCharsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_session_chars_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_session_chars_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2SessionCharsDal(db_session, log)
@@ -946,6 +946,8 @@ async def test_insert_session_char(self, mock_dal):
"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)
@@ -975,6 +977,8 @@ async def test_insert_session_char_single_character(self, mock_dal):
"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)
@@ -1055,7 +1059,7 @@ class TestGw2SessionsDal:
def mock_dal(self):
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_sessions_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_sessions_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2SessionsDal(db_session, log)
@@ -1066,7 +1070,7 @@ def test_init(self):
"""Test Gw2SessionsDal initialization."""
db_session = MagicMock()
log = MagicMock()
- with patch('src.database.dal.gw2.gw2_sessions_dal.DBUtilsAsync') as mock_db_utils_class:
+ with patch("src.database.dal.gw2.gw2_sessions_dal.DBUtilsAsync") as mock_db_utils_class:
mock_db_utils = AsyncMock()
mock_db_utils_class.return_value = mock_db_utils
dal = Gw2SessionsDal(db_session, log)
diff --git a/tests/unit/gw2/cogs/test_account.py b/tests/unit/gw2/cogs/test_account.py
index 5a6d3074..1eed5141 100644
--- a/tests/unit/gw2/cogs/test_account.py
+++ b/tests/unit/gw2/cogs/test_account.py
@@ -97,11 +97,11 @@ def sample_world_data(self):
@pytest.mark.asyncio
async def test_account_command_no_api_key(self, mock_ctx):
"""Test account command when user has no API key."""
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.account.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error:
await account(mock_ctx)
mock_error.assert_called_once()
@@ -111,18 +111,18 @@ async def test_account_command_no_api_key(self, mock_ctx):
@pytest.mark.asyncio
async def test_account_command_invalid_api_key(self, mock_ctx, sample_api_key_data):
"""Test account command with invalid API key."""
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_key_error = APIInvalidKey(mock_ctx.bot, "Invalid API key")
# Make the error have an args attribute like a real exception
invalid_key_error.args = ("error", "Invalid API key message")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_key_error)
- with patch('src.gw2.cogs.account.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error:
await account(mock_ctx)
mock_error.assert_called_once()
@@ -136,15 +136,15 @@ async def test_account_command_insufficient_permissions(self, mock_ctx, sample_a
{"key": "test-api-key-12345", "permissions": "characters"} # Missing 'account' permission
]
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=insufficient_permissions_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
- with patch('src.gw2.cogs.account.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error:
await account(mock_ctx)
mock_error.assert_called_once()
@@ -156,11 +156,11 @@ async def test_account_command_successful_basic(
self, mock_ctx, sample_api_key_data, sample_account_data, sample_world_data
):
"""Test successful account command with basic information."""
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -170,7 +170,7 @@ async def test_account_command_successful_basic(
]
)
- with patch('src.gw2.cogs.account.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send:
await account(mock_ctx)
mock_send.assert_called_once()
@@ -184,11 +184,11 @@ async def test_account_command_with_characters_permission(self, mock_ctx, sample
characters_data = ["Character1", "Character2", "Character3"]
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=characters_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -199,7 +199,7 @@ async def test_account_command_with_characters_permission(self, mock_ctx, sample
]
)
- with patch('src.gw2.cogs.account.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send:
await account(mock_ctx)
mock_send.assert_called_once()
@@ -213,11 +213,11 @@ async def test_account_command_with_progression_permission(self, mock_ctx, sampl
achievements_data = [{"id": 1, "current": 10}, {"id": 2, "current": 5}]
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=progression_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -228,13 +228,13 @@ async def test_account_command_with_progression_permission(self, mock_ctx, sampl
]
)
- with patch('src.gw2.cogs.account.gw2_utils.calculate_user_achiev_points') as mock_calc:
+ with patch("src.gw2.cogs.account.gw2_utils.calculate_user_achiev_points") as mock_calc:
mock_calc.return_value = 15000
- with patch('src.gw2.cogs.account.gw2_utils.get_wvw_rank_title') as mock_wvw_title:
+ with patch("src.gw2.cogs.account.gw2_utils.get_wvw_rank_title") as mock_wvw_title:
mock_wvw_title.return_value = "Gold General"
- with patch('src.gw2.cogs.account.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send:
await account(mock_ctx)
mock_send.assert_called_once()
@@ -248,11 +248,11 @@ async def test_account_command_with_pvp_permission(self, mock_ctx, sample_accoun
pvp_stats_data = {"pvp_rank": 45, "pvp_rank_rollovers": 5}
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=pvp_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -263,10 +263,10 @@ async def test_account_command_with_pvp_permission(self, mock_ctx, sample_accoun
]
)
- with patch('src.gw2.cogs.account.gw2_utils.get_pvp_rank_title') as mock_pvp_title:
+ with patch("src.gw2.cogs.account.gw2_utils.get_pvp_rank_title") as mock_pvp_title:
mock_pvp_title.return_value = "Tiger"
- with patch('src.gw2.cogs.account.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send:
await account(mock_ctx)
mock_send.assert_called_once()
@@ -282,11 +282,11 @@ async def test_account_command_with_guilds(self, mock_ctx, sample_account_data,
guild_data_2 = {"id": "guild-id-2", "name": "Test Guild Two", "tag": "TG2"}
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=full_permissions_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -298,7 +298,7 @@ async def test_account_command_with_guilds(self, mock_ctx, sample_account_data,
]
)
- with patch('src.gw2.cogs.account.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.account.bot_utils.send_embed") as mock_send:
await account(mock_ctx)
mock_send.assert_called_once()
@@ -308,16 +308,16 @@ async def test_account_command_with_guilds(self, mock_ctx, sample_account_data,
@pytest.mark.asyncio
async def test_account_command_api_error_during_execution(self, mock_ctx, sample_api_key_data, sample_account_data):
"""Test account command when API error occurs during execution."""
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(side_effect=Exception("API Error"))
- with patch('src.gw2.cogs.account.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error:
await account(mock_ctx)
mock_error.assert_called_once()
@@ -328,11 +328,11 @@ async def test_account_command_guild_api_error(self, mock_ctx, sample_account_da
"""Test account command when guild API call fails."""
full_permissions_data = [{"key": "test-api-key-12345", "permissions": "account,guilds"}]
- with patch('src.gw2.cogs.account.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.account.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=full_permissions_data)
- with patch('src.gw2.cogs.account.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.account.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(return_value=sample_account_data)
mock_client_instance.call_api = AsyncMock(
@@ -343,7 +343,7 @@ async def test_account_command_guild_api_error(self, mock_ctx, sample_account_da
]
)
- with patch('src.gw2.cogs.account.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.account.bot_utils.send_error_msg") as mock_error:
await account(mock_ctx)
mock_error.assert_called_once()
@@ -450,7 +450,7 @@ async def typing_side_effect():
mock_ctx.message.channel.typing = AsyncMock(side_effect=typing_side_effect)
- with patch('src.gw2.cogs.account.asyncio.sleep', new_callable=AsyncMock):
+ with patch("src.gw2.cogs.account.asyncio.sleep", new_callable=AsyncMock):
await _keep_typing_alive(mock_ctx, stop_event)
assert call_count >= 1
@@ -599,15 +599,14 @@ async def test_account_command_full_success_path(self, mock_ctx, sample_account_
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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)
mock_client_instance = mock_client.return_value
@@ -654,11 +653,10 @@ async def test_account_command_exception_handler(self, mock_ctx):
mock_ctx.send = AsyncMock(side_effect=RuntimeError("send failed"))
with (
- 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.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,
):
-
mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data)
mock_client_instance = mock_client.return_value
@@ -683,14 +681,13 @@ async def test_account_command_exception_handler_with_active_typing_task(self, m
mock_ctx.send.return_value = progress_msg
with (
- 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,
+ 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)
mock_client_instance = mock_client.return_value
@@ -724,22 +721,21 @@ async def test_account_command_all_permissions(self, mock_ctx, sample_account_da
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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',
+ "src.gw2.cogs.account.gw2_utils.calculate_user_achiev_points",
new_callable=AsyncMock,
return_value=15000,
) as mock_achiev,
- patch('src.gw2.cogs.account.gw2_utils.get_wvw_rank_title', return_value="Gold General") as mock_wvw,
- patch('src.gw2.cogs.account.gw2_utils.get_pvp_rank_title', return_value="Tiger") as mock_pvp,
+ patch("src.gw2.cogs.account.gw2_utils.get_wvw_rank_title", return_value="Gold General") as mock_wvw,
+ patch("src.gw2.cogs.account.gw2_utils.get_pvp_rank_title", return_value="Tiger") as mock_pvp,
):
-
mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data)
mock_client_instance = mock_client.return_value
@@ -792,15 +788,14 @@ async def test_account_command_optional_task_failure_is_skipped(
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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)
mock_client_instance = mock_client.return_value
@@ -860,15 +855,14 @@ async def test_account_command_guild_handling(self, mock_ctx, sample_world_data)
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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)
mock_client_instance = mock_client.return_value
@@ -935,20 +929,19 @@ async def test_account_command_guild_fetch_exception_skipped(self, mock_ctx, sam
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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',
+ "src.gw2.cogs.account._fetch_guild_info_standalone",
new_callable=AsyncMock,
return_value=(None, "guild-id-1"),
),
):
-
mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=api_key_data)
mock_client_instance = mock_client.return_value
@@ -997,15 +990,14 @@ async def test_account_command_commander_no(self, mock_ctx, sample_world_data):
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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)
mock_client_instance = mock_client.return_value
@@ -1050,15 +1042,14 @@ async def test_account_command_access_normalization(self, mock_ctx, sample_world
mock_ctx.send.return_value = progress_msg
with (
- 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_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.Gw2KeyDal") as mock_dal,
+ 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)
mock_client_instance = mock_client.return_value
diff --git a/tests/unit/gw2/cogs/test_characters.py b/tests/unit/gw2/cogs/test_characters.py
index e82c6682..f700f953 100644
--- a/tests/unit/gw2/cogs/test_characters.py
+++ b/tests/unit/gw2/cogs/test_characters.py
@@ -88,10 +88,10 @@ def sample_character_data(self):
@pytest.mark.asyncio
async def test_characters_no_api_key_sends_error(self, mock_ctx):
"""Test characters command when user has no API key."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.characters.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await characters(mock_ctx)
mock_error.assert_called_once()
@@ -101,15 +101,15 @@ async def test_characters_no_api_key_sends_error(self, mock_ctx):
@pytest.mark.asyncio
async def test_characters_invalid_api_key_sends_error_with_help(self, mock_ctx, sample_api_key_data):
"""Test characters command with invalid API key sends error with help info."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_error = APIInvalidKey(mock_ctx.bot, "Invalid key")
invalid_error.args = ("error", "This API Key is INVALID")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_error)
- with patch('src.gw2.cogs.characters.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await characters(mock_ctx)
mock_error.assert_called_once()
@@ -123,15 +123,15 @@ async def test_characters_missing_characters_permission_sends_error(self, mock_c
no_chars_permission_data = [
{"key": "test-api-key-12345", "permissions": "account,progression"} # Missing 'characters'
]
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=no_chars_permission_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "progression"]}
)
- with patch('src.gw2.cogs.characters.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await characters(mock_ctx)
mock_error.assert_called_once()
@@ -144,15 +144,15 @@ async def test_characters_missing_account_permission_sends_error(self, mock_ctx)
no_account_permission_data = [
{"key": "test-api-key-12345", "permissions": "characters,progression"} # Missing 'account'
]
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=no_account_permission_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["characters", "progression"]}
)
- with patch('src.gw2.cogs.characters.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.characters.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await characters(mock_ctx)
mock_error.assert_called_once()
@@ -164,10 +164,10 @@ async def test_characters_successful_with_character_data(
self, mock_ctx, sample_api_key_data, sample_account_data, sample_character_data
):
"""Test successful characters command with character data creates embed with fields."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters", "progression"]}
@@ -196,8 +196,8 @@ async def test_characters_successful_with_character_data(
},
]
)
- 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:
+ 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()
@@ -222,10 +222,10 @@ async def test_characters_successful_character_age_calculation(
self, mock_ctx, sample_api_key_data, sample_account_data
):
"""Test that character age is correctly calculated in days."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -246,8 +246,8 @@ async def test_characters_successful_character_age_calculation(
},
]
)
- 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:
+ 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()
@@ -258,10 +258,10 @@ async def test_characters_successful_character_age_calculation(
@pytest.mark.asyncio
async def test_characters_successful_created_date_parsed(self, mock_ctx, sample_api_key_data, sample_account_data):
"""Test that character created date is parsed correctly (only date portion before T)."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -281,8 +281,8 @@ async def test_characters_successful_created_date_parsed(self, mock_ctx, sample_
},
]
)
- 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:
+ 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()
@@ -293,16 +293,16 @@ async def test_characters_successful_created_date_parsed(self, mock_ctx, sample_
@pytest.mark.asyncio
async def test_characters_api_exception_during_execution(self, mock_ctx, sample_api_key_data):
"""Test characters command when API exception occurs during execution."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
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:
+ 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()
@@ -310,20 +310,20 @@ async def test_characters_api_exception_during_execution(self, mock_ctx, sample_
@pytest.mark.asyncio
async def test_characters_triggers_typing_indicator(self, mock_ctx, sample_api_key_data):
"""Test that characters command triggers typing indicator."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.characters.bot_utils.send_error_msg'):
+ with patch("src.gw2.cogs.characters.bot_utils.send_error_msg"):
await characters(mock_ctx)
mock_ctx.message.channel.typing.assert_called()
@pytest.mark.asyncio
async def test_characters_embed_has_thumbnail_and_author(self, mock_ctx, sample_api_key_data, sample_account_data):
"""Test that the characters embed has proper thumbnail and author set."""
- with patch('src.gw2.cogs.characters.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.characters.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.characters.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.characters.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -343,8 +343,8 @@ async def test_characters_embed_has_thumbnail_and_author(self, mock_ctx, sample_
},
]
)
- 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:
+ 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()
diff --git a/tests/unit/gw2/cogs/test_config.py b/tests/unit/gw2/cogs/test_config.py
index 7d1b3ff2..b3404dfb 100644
--- a/tests/unit/gw2/cogs/test_config.py
+++ b/tests/unit/gw2/cogs/test_config.py
@@ -53,7 +53,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_config_group_invokes_subcommand(self, mock_ctx):
"""Test that config group command calls invoke_subcommand."""
- with patch('src.gw2.cogs.config.bot_utils.invoke_subcommand') as mock_invoke:
+ with patch("src.gw2.cogs.config.bot_utils.invoke_subcommand") as mock_invoke:
mock_invoke.return_value = None
await config(mock_ctx)
mock_invoke.assert_called_once_with(mock_ctx, "gw2 config")
@@ -89,12 +89,12 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_config_list_creates_embed_with_current_config_session_on(self, mock_ctx):
"""Test config_list creates embed with session ON."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text') as mock_green:
+ with patch("src.gw2.cogs.config.chat_formatting.green_text") as mock_green:
mock_green.return_value = "```css\nON\n```"
- with patch('src.gw2.cogs.config.chat_formatting.red_text') as mock_red:
+ with patch("src.gw2.cogs.config.chat_formatting.red_text") as mock_red:
mock_red.return_value = "```diff\n-OFF\n```"
await config_list(mock_ctx)
mock_ctx.author.send.assert_called_once()
@@ -108,12 +108,12 @@ async def test_config_list_creates_embed_with_current_config_session_on(self, mo
@pytest.mark.asyncio
async def test_config_list_creates_embed_with_current_config_session_off(self, mock_ctx):
"""Test config_list creates embed with session OFF."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": False}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text') as mock_green:
+ with patch("src.gw2.cogs.config.chat_formatting.green_text") as mock_green:
mock_green.return_value = "```css\nON\n```"
- with patch('src.gw2.cogs.config.chat_formatting.red_text') as mock_red:
+ with patch("src.gw2.cogs.config.chat_formatting.red_text") as mock_red:
mock_red.return_value = "```diff\n-OFF\n```"
await config_list(mock_ctx)
mock_ctx.author.send.assert_called_once()
@@ -125,11 +125,11 @@ async def test_config_list_creates_embed_with_current_config_session_off(self, m
@pytest.mark.asyncio
async def test_config_list_creates_interactive_view(self, mock_ctx):
"""Test config_list creates interactive view with buttons."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_list(mock_ctx)
mock_ctx.author.send.assert_called_once()
call_kwargs = mock_ctx.author.send.call_args[1]
@@ -139,11 +139,11 @@ async def test_config_list_creates_interactive_view(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_list_sends_dm_and_notification_in_channel(self, mock_ctx):
"""Test config_list sends to DM and notification in channel."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_list(mock_ctx)
# DM sent
mock_ctx.author.send.assert_called_once()
@@ -156,11 +156,11 @@ async def test_config_list_sends_dm_and_notification_in_channel(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_list_button_style_success_when_session_on(self, mock_ctx):
"""Test config_list sets button style to success when session is ON."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_list(mock_ctx)
call_kwargs = mock_ctx.author.send.call_args[1]
view = call_kwargs["view"]
@@ -169,11 +169,11 @@ async def test_config_list_button_style_success_when_session_on(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_list_button_style_danger_when_session_off(self, mock_ctx):
"""Test config_list sets button style to danger when session is OFF."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": False}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_list(mock_ctx)
call_kwargs = mock_ctx.author.send.call_args[1]
view = call_kwargs["view"]
@@ -183,11 +183,11 @@ async def test_config_list_button_style_danger_when_session_off(self, mock_ctx):
async def test_config_list_no_guild_icon(self, mock_ctx):
"""Test config_list when guild has no icon."""
mock_ctx.guild.icon = None
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_list(mock_ctx)
call_kwargs = mock_ctx.author.send.call_args[1]
embed = call_kwargs["embed"]
@@ -221,10 +221,10 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_config_session_on_activates(self, mock_ctx):
"""Test config session 'on' activates session with green color."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.config.bot_utils.send_embed") as mock_send:
await config_session(mock_ctx, "on")
mock_instance.update_gw2_session_config.assert_called_once_with(99999, True, 12345)
mock_send.assert_called_once()
@@ -235,10 +235,10 @@ async def test_config_session_on_activates(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_session_ON_uppercase_activates(self, mock_ctx):
"""Test config session 'ON' (uppercase) activates session with green color."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.config.bot_utils.send_embed") as mock_send:
await config_session(mock_ctx, "ON")
mock_instance.update_gw2_session_config.assert_called_once_with(99999, True, 12345)
mock_send.assert_called_once()
@@ -248,10 +248,10 @@ async def test_config_session_ON_uppercase_activates(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_session_off_deactivates(self, mock_ctx):
"""Test config session 'off' deactivates session with red color."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.config.bot_utils.send_embed") as mock_send:
await config_session(mock_ctx, "off")
mock_instance.update_gw2_session_config.assert_called_once_with(99999, False, 12345)
mock_send.assert_called_once()
@@ -262,10 +262,10 @@ async def test_config_session_off_deactivates(self, mock_ctx):
@pytest.mark.asyncio
async def test_config_session_OFF_uppercase_deactivates(self, mock_ctx):
"""Test config session 'OFF' (uppercase) deactivates session with red color."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.config.bot_utils.send_embed") as mock_send:
await config_session(mock_ctx, "OFF")
mock_instance.update_gw2_session_config.assert_called_once_with(99999, False, 12345)
mock_send.assert_called_once()
@@ -287,10 +287,10 @@ async def test_config_session_invalid_mixed_case_raises_bad_argument(self, mock_
@pytest.mark.asyncio
async def test_config_session_triggers_typing_indicator(self, mock_ctx):
"""Test that config session triggers typing indicator."""
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.bot_utils.send_embed'):
+ with patch("src.gw2.cogs.config.bot_utils.send_embed"):
await config_session(mock_ctx, "on")
mock_ctx.message.channel.typing.assert_called_once()
@@ -326,7 +326,7 @@ def server_config(self):
@pytest.fixture
def config_view(self, mock_ctx, server_config):
"""Create a GW2ConfigView instance."""
- with patch('discord.ui.view.View.__init__', return_value=None):
+ with patch("discord.ui.view.View.__init__", return_value=None):
view = GW2ConfigView(mock_ctx, server_config)
# Manually set up what View.__init__ would set
view._children = []
@@ -345,7 +345,7 @@ def config_view(self, mock_ctx, server_config):
@pytest.mark.asyncio
async def test_config_view_initialization(self, mock_ctx, server_config):
"""Test GW2ConfigView initialization."""
- with patch('discord.ui.view.View.__init__', return_value=None):
+ with patch("discord.ui.view.View.__init__", return_value=None):
view = GW2ConfigView(mock_ctx, server_config)
assert view.ctx == mock_ctx
assert view.server_config == server_config
@@ -400,18 +400,20 @@ async def test_handle_update_successful_update(self, config_view, mock_ctx, serv
button = MagicMock()
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_view._handle_update(interaction, button, "session", "Session Tracking")
# Verify defer was called
interaction.response.defer.assert_called_once()
# Verify database update
mock_instance.update_gw2_session_config.assert_called_once_with(
- 99999, False, 12345 # Toggle from True to False
+ 99999,
+ False,
+ 12345, # Toggle from True to False
)
# Verify config was toggled
assert config_view.server_config["session"] is False
@@ -431,7 +433,7 @@ async def test_handle_update_discord_http_error(self, config_view, mock_ctx):
button = MagicMock()
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_response = MagicMock()
mock_response.status = 500
@@ -457,7 +459,7 @@ async def test_handle_update_other_exception(self, config_view, mock_ctx):
button = MagicMock()
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock(side_effect=Exception("Database connection error"))
await config_view._handle_update(interaction, button, "session", "Session Tracking")
@@ -482,7 +484,7 @@ async def test_handle_update_exception_with_edit_failure(self, config_view, mock
button = MagicMock()
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock(side_effect=Exception("DB error"))
# Should not raise - the inner exception is caught
@@ -495,7 +497,7 @@ async def test_restore_buttons(self, config_view):
"""Test _restore_buttons restores button states."""
# Disable all buttons first
for item in config_view.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
item.disabled = True
config_view.server_config["session"] = True
@@ -503,7 +505,7 @@ async def test_restore_buttons(self, config_view):
# Check buttons are re-enabled
for item in config_view.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
assert item.disabled is False
# Check toggle_session style is success (session is True)
@@ -521,9 +523,9 @@ async def test_restore_buttons_session_off(self, config_view):
async def test_create_updated_embed(self, config_view):
"""Test _create_updated_embed creates proper embed."""
config_view.server_config["session"] = True
- with patch('src.gw2.cogs.config.chat_formatting.green_text') as mock_green:
+ with patch("src.gw2.cogs.config.chat_formatting.green_text") as mock_green:
mock_green.return_value = "```css\nON\n```"
- with patch('src.gw2.cogs.config.chat_formatting.red_text') as mock_red:
+ with patch("src.gw2.cogs.config.chat_formatting.red_text") as mock_red:
mock_red.return_value = "```diff\n-OFF\n```"
embed = await config_view._create_updated_embed()
assert embed.color.value == 0x00FF00
@@ -535,9 +537,9 @@ async def test_create_updated_embed(self, config_view):
async def test_create_updated_embed_session_off(self, config_view):
"""Test _create_updated_embed with session OFF."""
config_view.server_config["session"] = False
- with patch('src.gw2.cogs.config.chat_formatting.green_text') as mock_green:
+ with patch("src.gw2.cogs.config.chat_formatting.green_text") as mock_green:
mock_green.return_value = "```css\nON\n```"
- with patch('src.gw2.cogs.config.chat_formatting.red_text') as mock_red:
+ with patch("src.gw2.cogs.config.chat_formatting.red_text") as mock_red:
mock_red.return_value = "```diff\n-OFF\n```"
embed = await config_view._create_updated_embed()
assert "OFF" in embed.fields[0].value
@@ -547,15 +549,15 @@ async def test_create_updated_embed_no_guild_icon(self, config_view, mock_ctx):
"""Test _create_updated_embed when guild has no icon."""
mock_ctx.guild.icon = None
config_view.server_config["session"] = True
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
embed = await config_view._create_updated_embed()
assert embed.thumbnail.url is None
@pytest.mark.asyncio
async def test_toggle_session_button_callback(self, mock_ctx, server_config):
"""Test toggle_session button callback calls _handle_update with correct args."""
- with patch('discord.ui.view.View.__init__', return_value=None):
+ with patch("discord.ui.view.View.__init__", return_value=None):
view = GW2ConfigView(mock_ctx, server_config)
view._children = []
view._View__timeout = 300
@@ -568,7 +570,7 @@ async def test_toggle_session_button_callback(self, mock_ctx, server_config):
button = MagicMock()
- with patch.object(view, '_handle_update', new_callable=AsyncMock) as mock_handle:
+ with patch.object(view, "_handle_update", new_callable=AsyncMock) as mock_handle:
# Call the actual toggle_session function directly (it's a decorated method)
await GW2ConfigView.toggle_session(view, interaction, button)
mock_handle.assert_called_once_with(
@@ -643,16 +645,16 @@ async def capture_disabled_state(*args, **kwargs):
# Capture button states when edit is first called (processing state)
if not buttons_disabled_during_processing:
for item in config_view.children:
- if hasattr(item, 'disabled'):
+ if hasattr(item, "disabled"):
buttons_disabled_during_processing.append(item.disabled)
interaction.edit_original_response = AsyncMock(side_effect=capture_disabled_state)
- with patch('src.gw2.cogs.config.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.cogs.config.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.update_gw2_session_config = AsyncMock()
- with patch('src.gw2.cogs.config.chat_formatting.green_text', return_value="ON"):
- with patch('src.gw2.cogs.config.chat_formatting.red_text', return_value="OFF"):
+ with patch("src.gw2.cogs.config.chat_formatting.green_text", return_value="ON"):
+ with patch("src.gw2.cogs.config.chat_formatting.red_text", return_value="OFF"):
await config_view._handle_update(interaction, button, "session", "Session Tracking")
# Verify all buttons were disabled during processing
diff --git a/tests/unit/gw2/cogs/test_key.py b/tests/unit/gw2/cogs/test_key.py
index d8a9d238..793486d6 100644
--- a/tests/unit/gw2/cogs/test_key.py
+++ b/tests/unit/gw2/cogs/test_key.py
@@ -52,7 +52,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_key_group_invokes_subcommand(self, mock_ctx):
"""Test that key group command calls invoke_subcommand."""
- with patch('src.gw2.cogs.key.bot_utils.invoke_subcommand') as mock_invoke:
+ with patch("src.gw2.cogs.key.bot_utils.invoke_subcommand") as mock_invoke:
mock_invoke.return_value = None
await key(mock_ctx)
mock_invoke.assert_called_once_with(mock_ctx, "gw2 key")
@@ -101,13 +101,13 @@ def mock_ctx(self):
async def test_add_deletes_message_for_privacy(self, mock_ctx):
"""Test that add command deletes the user's message for privacy."""
api_key = "test-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message') as mock_delete:
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message") as mock_delete:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_error = APIInvalidKey(mock_ctx.bot, "Invalid key")
invalid_error.args = ("error", "Invalid API key")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_error)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await add(mock_ctx, api_key)
mock_delete.assert_called_once_with(mock_ctx, warning=True)
@@ -116,13 +116,13 @@ async def test_add_deletes_message_for_privacy(self, mock_ctx):
async def test_add_invalid_api_key_sends_error(self, mock_ctx):
"""Test add command with invalid API key sends error message."""
api_key = "invalid-key"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_error = APIInvalidKey(mock_ctx.bot, "Invalid key")
invalid_error.args = ("error", "This API Key is INVALID")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_error)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await add(mock_ctx, api_key)
mock_error.assert_called_once()
@@ -134,14 +134,14 @@ async def test_add_invalid_api_key_sends_error(self, mock_ctx):
async def test_add_account_info_api_fails(self, mock_ctx):
"""Test add command when account info API call fails."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
)
mock_client_instance.call_api = AsyncMock(side_effect=Exception("Account API error"))
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
await add(mock_ctx, api_key)
mock_error.assert_called_once()
mock_ctx.bot.log.error.assert_called_once()
@@ -150,8 +150,8 @@ async def test_add_account_info_api_fails(self, mock_ctx):
async def test_add_server_name_api_fails(self, mock_ctx):
"""Test add command when server name API call fails."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -162,7 +162,7 @@ async def test_add_server_name_api_fails(self, mock_ctx):
Exception("Server API error"), # world call
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await add(mock_ctx, api_key)
mock_error.assert_called_once()
mock_ctx.bot.log.error.assert_called_once()
@@ -172,8 +172,8 @@ async def test_add_server_name_api_fails(self, mock_ctx):
async def test_add_user_already_has_key_shows_error_with_options(self, mock_ctx):
"""Test add command when user already has a key registered."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -184,12 +184,12 @@ async def test_add_user_already_has_key_shows_error_with_options(self, mock_ctx)
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(
return_value=[{"name": "OldKey", "gw2_acc_name": "TestUser.1234"}]
)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await add(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -203,8 +203,8 @@ async def test_add_user_already_has_key_shows_error_with_options(self, mock_ctx)
async def test_add_key_already_in_use_by_someone_else(self, mock_ctx):
"""Test add command when the API key is already in use by another user."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -215,11 +215,11 @@ async def test_add_key_already_in_use_by_someone_else(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
mock_instance.get_api_key = AsyncMock(return_value=[{"user_id": 99999, "key": api_key}])
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await add(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -230,8 +230,8 @@ async def test_add_key_already_in_use_by_someone_else(self, mock_ctx):
async def test_add_successful_insert(self, mock_ctx):
"""Test add command with successful key insertion."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -242,12 +242,12 @@ async def test_add_successful_insert(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
mock_instance.get_api_key = AsyncMock(return_value=None)
mock_instance.insert_api_key = AsyncMock()
- with patch('src.gw2.cogs.key.bot_utils.send_msg') as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.send_msg") as mock_send:
result = await add(mock_ctx, api_key)
mock_instance.insert_api_key.assert_called_once()
insert_args = mock_instance.insert_api_key.call_args[0][0]
@@ -267,8 +267,8 @@ async def test_add_successful_insert(self, mock_ctx):
async def test_add_insert_raises_exception(self, mock_ctx):
"""Test add command when database insert raises an exception."""
api_key = "valid-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters"]}
@@ -279,12 +279,12 @@ async def test_add_insert_raises_exception(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
mock_instance.get_api_key = AsyncMock(return_value=None)
mock_instance.insert_api_key = AsyncMock(side_effect=Exception("DB insert error"))
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await add(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -329,11 +329,11 @@ def mock_ctx(self):
async def test_update_deletes_message_for_privacy(self, mock_ctx):
"""Test that update command deletes the user's message for privacy."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message') as mock_delete:
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message") as mock_delete:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg'):
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg"):
await update(mock_ctx, api_key)
mock_delete.assert_called_once_with(mock_ctx, warning=True)
@@ -341,11 +341,11 @@ async def test_update_deletes_message_for_privacy(self, mock_ctx):
async def test_update_no_existing_key_sends_error(self, mock_ctx):
"""Test update command when user has no existing key."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await update(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -357,16 +357,16 @@ async def test_update_no_existing_key_sends_error(self, mock_ctx):
async def test_update_invalid_api_key_sends_error(self, mock_ctx):
"""Test update command with invalid new API key."""
api_key = "invalid-new-key"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_error = APIInvalidKey(mock_ctx.bot, "Invalid key")
invalid_error.args = ("error", "This API Key is INVALID")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_error)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await update(mock_ctx, api_key)
mock_error.assert_called_once()
@@ -377,17 +377,17 @@ async def test_update_invalid_api_key_sends_error(self, mock_ctx):
async def test_update_account_info_api_fails(self, mock_ctx):
"""Test update command when account info API call fails."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account", "characters"]}
)
mock_client_instance.call_api = AsyncMock(side_effect=Exception("Account API error"))
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
await update(mock_ctx, api_key)
mock_error.assert_called_once()
mock_ctx.bot.log.error.assert_called_once()
@@ -396,11 +396,11 @@ async def test_update_account_info_api_fails(self, mock_ctx):
async def test_update_server_name_api_fails(self, mock_ctx):
"""Test update command when server name API call fails."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account", "characters"]}
@@ -411,7 +411,7 @@ async def test_update_server_name_api_fails(self, mock_ctx):
Exception("Server API error"),
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await update(mock_ctx, api_key)
mock_error.assert_called_once()
mock_ctx.bot.log.error.assert_called_once()
@@ -421,14 +421,14 @@ async def test_update_server_name_api_fails(self, mock_ctx):
async def test_update_key_in_use_by_different_user(self, mock_ctx):
"""Test update command when the new API key is in use by another user."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
mock_instance.get_api_key = AsyncMock(
return_value=[{"user_id": 99999, "key": api_key}] # different user
)
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account", "characters"]}
@@ -439,7 +439,7 @@ async def test_update_key_in_use_by_different_user(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await update(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -450,13 +450,13 @@ async def test_update_key_in_use_by_different_user(self, mock_ctx):
async def test_update_successful(self, mock_ctx):
"""Test update command with successful key update."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
mock_instance.get_api_key = AsyncMock(return_value=None)
mock_instance.update_api_key = AsyncMock()
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account", "characters"]}
@@ -467,7 +467,7 @@ async def test_update_successful(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_msg') as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.send_msg") as mock_send:
result = await update(mock_ctx, api_key)
mock_instance.update_api_key.assert_called_once()
update_args = mock_instance.update_api_key.call_args[0][0]
@@ -488,14 +488,14 @@ async def test_update_successful(self, mock_ctx):
async def test_update_same_user_key_allowed(self, mock_ctx):
"""Test update command when API key is already owned by same user (re-using own key)."""
api_key = "same-user-api-key"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
# Key is found but belongs to same user
mock_instance.get_api_key = AsyncMock(return_value=[{"user_id": 12345, "key": api_key}]) # same user
mock_instance.update_api_key = AsyncMock()
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account"]}
@@ -506,7 +506,7 @@ async def test_update_same_user_key_allowed(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_msg') as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.send_msg") as mock_send:
result = await update(mock_ctx, api_key)
mock_instance.update_api_key.assert_called_once()
mock_send.assert_called_once()
@@ -516,13 +516,13 @@ async def test_update_same_user_key_allowed(self, mock_ctx):
async def test_update_raises_exception_on_db_update(self, mock_ctx):
"""Test update command when database update raises an exception."""
api_key = "new-api-key-12345"
- with patch('src.gw2.cogs.key.bot_utils.delete_message'):
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.bot_utils.delete_message"):
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"name": "OldKey", "key": "old-key-12345"}])
mock_instance.get_api_key = AsyncMock(return_value=None)
mock_instance.update_api_key = AsyncMock(side_effect=Exception("DB update error"))
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "NewKey", "permissions": ["account"]}
@@ -533,7 +533,7 @@ async def test_update_raises_exception_on_db_update(self, mock_ctx):
{"name": "Anvil Rock"},
]
)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
result = await update(mock_ctx, api_key)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -563,10 +563,10 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_remove_no_api_key_sends_error(self, mock_ctx):
"""Test remove command when user has no API key."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
mock_error.return_value = None
await remove(mock_ctx)
mock_error.assert_called_once()
@@ -576,11 +576,11 @@ async def test_remove_no_api_key_sends_error(self, mock_ctx):
@pytest.mark.asyncio
async def test_remove_has_api_key_deletes_and_confirms(self, mock_ctx):
"""Test remove command when user has an API key."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-api-key", "name": "TestKey"}])
mock_instance.delete_user_api_key = AsyncMock()
- with patch('src.gw2.cogs.key.bot_utils.send_msg') as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.send_msg") as mock_send:
result = await remove(mock_ctx)
mock_instance.delete_user_api_key.assert_called_once_with(12345)
mock_send.assert_called_once()
@@ -616,10 +616,10 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_info_no_api_key_sends_error(self, mock_ctx):
"""Test info command when user has no API key."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
await info(mock_ctx)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
@@ -629,7 +629,7 @@ async def test_info_no_api_key_sends_error(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_valid_api_key_shows_embed(self, mock_ctx):
"""Test info command with a valid API key shows info embed."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(
return_value=[
@@ -642,13 +642,13 @@ async def test_info_valid_api_key_shows_embed(self, mock_ctx):
}
]
)
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(
return_value={"name": "TestKey", "permissions": ["account", "characters", "progression"]}
)
- with patch('src.gw2.cogs.key.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.key.bot_utils.get_current_date_time_str_long') as mock_time:
+ with patch("src.gw2.cogs.key.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.get_current_date_time_str_long") as mock_time:
mock_time.return_value = "2025-01-01 12:00:00"
await info(mock_ctx)
mock_send.assert_called_once()
@@ -661,7 +661,7 @@ async def test_info_valid_api_key_shows_embed(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_invalid_api_key_on_check_shows_no_valid(self, mock_ctx):
"""Test info command when API key is invalid on check shows NO valid with invalid name."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(
return_value=[
@@ -674,13 +674,13 @@ async def test_info_invalid_api_key_on_check_shows_no_valid(self, mock_ctx):
}
]
)
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
invalid_error = APIInvalidKey(mock_ctx.bot, "Invalid key")
invalid_error.args = ("error", "Invalid key")
mock_client_instance.check_api_key = AsyncMock(return_value=invalid_error)
- with patch('src.gw2.cogs.key.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.key.bot_utils.get_current_date_time_str_long') as mock_time:
+ with patch("src.gw2.cogs.key.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.key.bot_utils.get_current_date_time_str_long") as mock_time:
mock_time.return_value = "2025-01-01 12:00:00"
await info(mock_ctx)
mock_send.assert_called_once()
@@ -697,7 +697,7 @@ async def test_info_invalid_api_key_on_check_shows_no_valid(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_exception_during_check(self, mock_ctx):
"""Test info command when an exception occurs during API key check."""
- with patch('src.gw2.cogs.key.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.key.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(
return_value=[
@@ -710,10 +710,10 @@ async def test_info_exception_during_check(self, mock_ctx):
}
]
)
- with patch('src.gw2.cogs.key.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.key.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.check_api_key = AsyncMock(side_effect=Exception("Connection error"))
- with patch('src.gw2.cogs.key.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.key.bot_utils.send_error_msg") as mock_error:
await info(mock_ctx)
mock_error.assert_called_once()
mock_ctx.bot.log.error.assert_called_once()
diff --git a/tests/unit/gw2/cogs/test_misc.py b/tests/unit/gw2/cogs/test_misc.py
index 6f49cc08..0702f41e 100644
--- a/tests/unit/gw2/cogs/test_misc.py
+++ b/tests/unit/gw2/cogs/test_misc.py
@@ -50,7 +50,7 @@ def test_gw2_misc_inheritance(self, gw2_misc_cog):
def test_gw2_misc_docstring(self, gw2_misc_cog):
"""Test that GW2Misc has proper docstring."""
assert GW2Misc.__doc__ is not None
- assert "GW2" in GW2Misc.__doc__
+ assert "Guild Wars 2" in GW2Misc.__doc__
class TestWikiCommand:
@@ -94,7 +94,7 @@ def _make_search_result_html(self, results):
divs += (
f'
\n'
+ f"\n"
)
return f"{divs}"
@@ -117,7 +117,7 @@ async def test_wiki_search_exactly_300_chars_allowed(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_error_msg', new_callable=AsyncMock) as mock_error:
+ with patch("src.gw2.cogs.misc.bot_utils.send_error_msg", new_callable=AsyncMock) as mock_error:
await wiki(mock_ctx, search=search)
# Should not send LONG_SEARCH message
from src.gw2.constants import gw2_messages
@@ -133,7 +133,7 @@ async def test_wiki_no_results_found(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_error_msg', new_callable=AsyncMock) as mock_error:
+ with patch("src.gw2.cogs.misc.bot_utils.send_error_msg", new_callable=AsyncMock) as mock_error:
await wiki(mock_ctx, search="nonexistent")
mock_error.assert_called_once()
from src.gw2.constants import gw2_messages
@@ -159,7 +159,7 @@ async def test_wiki_results_with_matching_keywords(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="sword")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -183,7 +183,7 @@ async def test_wiki_duplicate_keyword_title_skipped(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -203,7 +203,7 @@ async def test_wiki_history_page_skipped(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -225,7 +225,7 @@ async def test_wiki_posts_limited_to_around_25(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="sword")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -244,7 +244,7 @@ async def test_wiki_index_error_handled(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
# Should not raise - IndexError is caught
await wiki(mock_ctx, search="test item")
mock_send.assert_called_once()
@@ -261,7 +261,7 @@ async def test_wiki_no_matching_keywords(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -279,7 +279,7 @@ async def test_wiki_url_with_parenthesis_escaped(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -298,7 +298,7 @@ async def test_wiki_sets_thumbnail(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
embed = mock_send.call_args[0][1]
from src.gw2.constants import gw2_variables
@@ -316,7 +316,7 @@ async def test_wiki_search_with_spaces_converted_to_plus(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="dawn weapon")
# Verify that the URL passed to aiosession.get contains + instead of spaces
called_url = mock_ctx.bot.aiosession.get.call_args[0][0]
@@ -333,7 +333,7 @@ async def test_wiki_embed_title_set(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await wiki(mock_ctx, search="eternity")
embed = mock_send.call_args[0][1]
from src.gw2.constants import gw2_messages
@@ -383,7 +383,7 @@ def _make_info_html(self, skill_name, description=None, image_alt=None, srcset=N
"""
blockquote = ""
if description:
- blockquote = f'
\n\n{description}
'
+ blockquote = f"
\n\n{description}
"
img = ""
if image_alt:
@@ -405,7 +405,7 @@ async def test_info_non_200_status(self, mock_ctx):
mock_response.status = 404
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_error_msg', new_callable=AsyncMock) as mock_error:
+ with patch("src.gw2.cogs.misc.bot_utils.send_error_msg", new_callable=AsyncMock) as mock_error:
await info(mock_ctx, skill="nonexistent")
mock_error.assert_called_once()
from src.gw2.constants import gw2_messages
@@ -427,7 +427,7 @@ async def test_info_skill_with_description_and_icon(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -452,10 +452,10 @@ async def test_info_skill_with_trading_post_data(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
tp_html = (
- ''
+ ""
'
1g
'
'
0g 90s
'
- ''
+ ""
)
mock_tp_response = AsyncMock()
mock_tp_response.status = 200
@@ -469,8 +469,8 @@ async def test_info_skill_with_trading_post_data(self, mock_ctx):
]
)
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
- with patch('src.gw2.cogs.misc.gw2_utils.format_gold', side_effect=["10g 0s 0c", "9g 0s 0c"]):
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.gw2_utils.format_gold", side_effect=["10g 0s 0c", "9g 0s 0c"]):
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -483,14 +483,14 @@ async def test_info_skill_with_trading_post_data(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_skill_without_description(self, mock_ctx):
"""Test info command with skill that has no description (no blockquote)."""
- html = '' '' ''
+ html = ''
mock_response = AsyncMock()
mock_response.status = 200
mock_response.url = "https://wiki.guildwars2.com/wiki/Eternity"
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -503,10 +503,10 @@ async def test_info_skill_without_description(self, mock_ctx):
async def test_info_no_image_match_found(self, mock_ctx):
"""Test info command when no matching image is found."""
html = (
- ''
- '
\n\nSome description.
'
+ ""
+ "
\n\nSome description.
"
''
- ''
+ ""
)
mock_response = AsyncMock()
mock_response.status = 200
@@ -514,7 +514,7 @@ async def test_info_no_image_match_found(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -524,19 +524,14 @@ async def test_info_no_image_match_found(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_image_key_error_on_srcset(self, mock_ctx):
"""Test info command handles KeyError when image has no srcset."""
- html = (
- ''
- '
\n\nSome description.
'
- ''
- ''
- )
+ html = '
\n\nSome description.
'
mock_response = AsyncMock()
mock_response.status = 200
mock_response.url = "https://wiki.guildwars2.com/wiki/Eternity"
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
# Should not raise KeyError
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
@@ -548,10 +543,10 @@ async def test_info_image_key_error_on_srcset(self, mock_ctx):
async def test_info_no_trading_post_item(self, mock_ctx):
"""Test info command when no trading post item is found (item_id is None)."""
html = (
- ''
- '
\n\nA skill description.
'
+ ""
+ "
\n\nA skill description.
"
''
- ''
+ ""
)
mock_response = AsyncMock()
mock_response.status = 200
@@ -559,7 +554,7 @@ async def test_info_no_trading_post_item(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -592,7 +587,7 @@ async def test_info_tp_request_non_200(self, mock_ctx):
]
)
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -614,7 +609,7 @@ async def test_info_skill_name_extracted_from_url(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Sunrise")
embed = mock_send.call_args[0][1]
assert embed.title == "Sunrise"
@@ -634,7 +629,7 @@ async def test_info_skill_name_with_underscores_converted(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="bolt of damask")
embed = mock_send.call_args[0][1]
assert embed.title == "Bolt of Damask"
@@ -652,7 +647,7 @@ async def test_info_skill_with_spaces_replaced_by_underscore(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="dawn weapon")
called_url = mock_ctx.bot.aiosession.get.call_args[0][0]
assert "Dawn_Weapon" in called_url or "dawn_weapon" in called_url
@@ -670,7 +665,7 @@ async def test_info_embed_author_set(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
embed = mock_send.call_args[0][1]
assert embed.author.name == "TestUser"
@@ -689,7 +684,7 @@ async def test_info_embed_url_set(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
embed = mock_send.call_args[0][1]
assert embed.url == "https://wiki.guildwars2.com/wiki/Eternity"
@@ -697,14 +692,14 @@ async def test_info_embed_url_set(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_description_strips_question_mark(self, mock_ctx):
"""Test info command removes question marks from description."""
- html = '' '
\n\nSome description?
' ''
+ html = "
\n\nSome description?
"
mock_response = AsyncMock()
mock_response.status = 200
mock_response.url = "https://wiki.guildwars2.com/wiki/Eternity"
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
embed = mock_send.call_args[0][1]
assert "?" not in embed.description
@@ -712,16 +707,14 @@ async def test_info_description_strips_question_mark(self, mock_ctx):
@pytest.mark.asyncio
async def test_info_description_splits_on_em_dash(self, mock_ctx):
"""Test info command splits description on em dash."""
- html = (
- '' '
\n\nSome description text\u2014Attribution here
' ''
- )
+ html = "
\n\nSome description text\u2014Attribution here
"
mock_response = AsyncMock()
mock_response.status = 200
mock_response.url = "https://wiki.guildwars2.com/wiki/Eternity"
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="Eternity")
embed = mock_send.call_args[0][1]
# Description should only contain text before the em dash
@@ -743,7 +736,7 @@ async def test_info_of_and_the_lowercased_in_skill(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
await info(mock_ctx, skill="blade of the void")
called_url = mock_ctx.bot.aiosession.get.call_args[0][0]
# URL should use "of" and "the" (lowercase), not "Of" and "The"
@@ -762,7 +755,7 @@ async def test_info_returns_none_on_success(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock):
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock):
result = await info(mock_ctx, skill="Eternity")
assert result is None
@@ -779,7 +772,7 @@ async def test_info_calls_typing(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock):
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock):
await info(mock_ctx, skill="Eternity")
mock_ctx.message.channel.typing.assert_called_once()
@@ -799,10 +792,10 @@ async def test_info_tp_url_format(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
tp_html = (
- ''
+ ""
'
5g
'
'
4g 50s
'
- ''
+ ""
)
mock_tp_response = AsyncMock()
mock_tp_response.status = 200
@@ -815,8 +808,8 @@ async def test_info_tp_url_format(self, mock_ctx):
]
)
- with patch('src.gw2.cogs.misc.bot_utils.send_embed', new_callable=AsyncMock) as mock_send:
- with patch('src.gw2.cogs.misc.gw2_utils.format_gold', side_effect=["5g 0s 0c", "4g 50s 0c"]):
+ with patch("src.gw2.cogs.misc.bot_utils.send_embed", new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.misc.gw2_utils.format_gold", side_effect=["5g 0s 0c", "4g 50s 0c"]):
await info(mock_ctx, skill="bolt of damask")
embed = mock_send.call_args[0][1]
# TP URL should use hyphens instead of underscores
@@ -899,16 +892,16 @@ async def test_wiki_no_search_results_sends_no_results_error(self, mock_ctx):
mock_response.text = AsyncMock(return_value=html)
mock_ctx.bot.aiosession.get = MagicMock(
return_value=type(
- 'AsyncCM',
+ "AsyncCM",
(),
{
- '__aenter__': AsyncMock(return_value=mock_response),
- '__aexit__': AsyncMock(return_value=False),
+ "__aenter__": AsyncMock(return_value=mock_response),
+ "__aexit__": AsyncMock(return_value=False),
},
)()
)
- with patch('src.gw2.cogs.misc.bot_utils.send_error_msg', new_callable=AsyncMock) as mock_error:
+ with patch("src.gw2.cogs.misc.bot_utils.send_error_msg", new_callable=AsyncMock) as mock_error:
await wiki(mock_ctx, search="xyznonexistent123")
mock_error.assert_called_once()
from src.gw2.constants import gw2_messages
diff --git a/tests/unit/gw2/cogs/test_sessions.py b/tests/unit/gw2/cogs/test_sessions.py
index 4f474cc2..b28f2a83 100644
--- a/tests/unit/gw2/cogs/test_sessions.py
+++ b/tests/unit/gw2/cogs/test_sessions.py
@@ -135,11 +135,11 @@ def sample_time_passed(self):
@pytest.mark.asyncio
async def test_session_no_api_key(self, mock_ctx):
"""Test session command when user has no API key."""
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error:
await session(mock_ctx)
mock_error.assert_called_once()
@@ -149,15 +149,15 @@ async def test_session_no_api_key(self, mock_ctx):
@pytest.mark.asyncio
async def test_session_not_active_in_config(self, mock_ctx, sample_api_key_data):
"""Test session command when session is not active in server config."""
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": False}])
- with patch('src.gw2.cogs.sessions.bot_utils.send_warning_msg') as mock_warning:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_warning_msg") as mock_warning:
await session(mock_ctx)
mock_warning.assert_called_once()
@@ -165,15 +165,15 @@ async def test_session_not_active_in_config(self, mock_ctx, sample_api_key_data)
@pytest.mark.asyncio
async def test_session_not_active_empty_config(self, mock_ctx, sample_api_key_data):
"""Test session command when server config is empty."""
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[])
- with patch('src.gw2.cogs.sessions.bot_utils.send_warning_msg') as mock_warning:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_warning_msg") as mock_warning:
await session(mock_ctx)
mock_warning.assert_called_once()
@@ -189,15 +189,15 @@ async def test_session_missing_all_permissions(self, mock_ctx):
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_no_perms)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error:
await session(mock_ctx)
mock_error.assert_called_once()
@@ -215,19 +215,19 @@ async def test_session_has_some_permissions_not_all_missing(self, mock_ctx):
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_some_perms)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error:
await session(mock_ctx)
# Should not error on permissions since at least one is present
@@ -237,19 +237,19 @@ async def test_session_has_some_permissions_not_all_missing(self, mock_ctx):
@pytest.mark.asyncio
async def test_session_no_session_found(self, mock_ctx, sample_api_key_data):
"""Test session command when no session records are found."""
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error:
await session(mock_ctx)
mock_error.assert_called_once()
@@ -267,19 +267,19 @@ async def test_session_end_date_is_none(self, mock_ctx, sample_api_key_data):
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_error_msg") as mock_error:
await session(mock_ctx)
mock_error.assert_called_once()
@@ -303,25 +303,25 @@ async def test_session_time_passed_less_than_one_minute(self, mock_ctx, sample_a
time_obj.minutes = 0
time_obj.seconds = 30
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = time_obj
- with patch('src.gw2.cogs.sessions.gw2_utils.send_msg') as mock_send_msg:
+ with patch("src.gw2.cogs.sessions.gw2_utils.send_msg") as mock_send_msg:
await session(mock_ctx)
mock_send_msg.assert_called_once()
@@ -373,34 +373,34 @@ async def test_session_gold_gained(self, mock_ctx, sample_api_key_data, sample_t
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.gw2_utils.format_gold') as mock_format:
+ with patch("src.gw2.cogs.sessions.gw2_utils.format_gold") as mock_format:
mock_format.return_value = "5g 00s 00c"
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -458,34 +458,34 @@ async def test_session_gold_lost(self, mock_ctx, sample_api_key_data, sample_tim
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.gw2_utils.format_gold') as mock_format:
+ with patch("src.gw2.cogs.sessions.gw2_utils.format_gold") as mock_format:
mock_format.return_value = "5g 00s 00c"
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -540,35 +540,35 @@ async def test_session_gold_lost_with_leading_dash(self, mock_ctx, sample_api_ke
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.gw2_utils.format_gold') as mock_format:
+ with patch("src.gw2.cogs.sessions.gw2_utils.format_gold") as mock_format:
# Formatted gold already starts with dash
mock_format.return_value = "-5g 00s 00c"
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -634,32 +634,32 @@ async def test_session_characters_with_deaths(self, mock_ctx, sample_api_key_dat
"char2": {"name": "TestChar2", "profession": "Ranger", "deaths": 5}, # No change
}
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=chars_start)
mock_chars_instance.get_all_end_characters = AsyncMock(return_value=chars_end)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -718,31 +718,31 @@ async def test_session_karma_gained(self, mock_ctx, sample_api_key_data, sample_
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -795,31 +795,31 @@ async def test_session_karma_lost(self, mock_ctx, sample_api_key_data, sample_ti
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -872,31 +872,31 @@ async def test_session_laurels_gained(self, mock_ctx, sample_api_key_data, sampl
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -951,31 +951,31 @@ async def test_session_wvw_rank_change(self, mock_ctx, sample_api_key_data, samp
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -989,34 +989,34 @@ async def test_session_wvw_rank_change(self, mock_ctx, sample_api_key_data, samp
@pytest.mark.asyncio
async def test_session_all_wvw_stats(self, mock_ctx, sample_api_key_data, sample_session_data, sample_time_passed):
"""Test session command with all WvW stats changed (yaks, players, keeps, towers, camps, castles)."""
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=sample_session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.gw2_utils.format_gold') as mock_format:
+ with patch("src.gw2.cogs.sessions.gw2_utils.format_gold") as mock_format:
mock_format.return_value = "5g 00s 00c"
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -1076,31 +1076,31 @@ async def test_session_wvw_tickets_gained(self, mock_ctx, sample_api_key_data, s
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1155,31 +1155,31 @@ async def test_session_wvw_tickets_lost(self, mock_ctx, sample_api_key_data, sam
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1234,31 +1234,31 @@ async def test_session_proof_heroics_gained(self, mock_ctx, sample_api_key_data,
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1313,31 +1313,31 @@ async def test_session_badges_honor_gained(self, mock_ctx, sample_api_key_data,
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1392,31 +1392,31 @@ async def test_session_guild_commendations_gained(self, mock_ctx, sample_api_key
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1476,34 +1476,34 @@ async def test_session_still_playing_gw2(self, mock_ctx, sample_api_key_data, sa
mock_ctx.message.author.activity.name = "Guild Wars 2"
mock_ctx.channel = MagicMock(spec=discord.TextChannel) # Not a DMChannel
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
with patch(
- 'src.gw2.cogs.sessions.gw2_utils.end_session', new_callable=AsyncMock
+ "src.gw2.cogs.sessions.gw2_utils.end_session", new_callable=AsyncMock
) as mock_end_session:
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -1560,34 +1560,34 @@ async def test_session_not_playing_gw2_no_activity(self, mock_ctx, sample_api_ke
mock_ctx.message.author.activity = None
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
with patch(
- 'src.gw2.cogs.sessions.gw2_utils.end_session', new_callable=AsyncMock
+ "src.gw2.cogs.sessions.gw2_utils.end_session", new_callable=AsyncMock
) as mock_end_session:
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -1647,34 +1647,34 @@ async def test_session_dm_channel_no_still_playing(self, mock_ctx, sample_api_ke
mock_ctx.message.author.activity = MagicMock()
mock_ctx.message.author.activity.name = "Guild Wars 2"
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
with patch(
- 'src.gw2.cogs.sessions.gw2_utils.end_session', new_callable=AsyncMock
+ "src.gw2.cogs.sessions.gw2_utils.end_session", new_callable=AsyncMock
) as mock_end_session:
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline',
+ "src.gw2.cogs.sessions.chat_formatting.inline",
side_effect=lambda x: f"`{x}`",
):
await session(mock_ctx)
@@ -1728,31 +1728,31 @@ async def test_session_successful_embed_basic_fields(self, mock_ctx, sample_api_
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1816,31 +1816,31 @@ async def test_session_time_passed_exactly_one_minute(self, mock_ctx, sample_api
time_obj.seconds = 0
time_obj.timedelta = "0:01:00"
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = time_obj
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
@@ -1892,31 +1892,31 @@ async def test_session_laurels_lost(self, mock_ctx, sample_api_key_data, sample_
}
]
- with patch('src.gw2.cogs.sessions.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.sessions.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=sample_api_key_data)
- with patch('src.gw2.cogs.sessions.Gw2ConfigsDal') as mock_configs:
+ with patch("src.gw2.cogs.sessions.Gw2ConfigsDal") as mock_configs:
mock_configs_instance = mock_configs.return_value
mock_configs_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.cogs.sessions.Gw2SessionsDal') as mock_sessions_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionsDal") as mock_sessions_dal:
mock_sessions_instance = mock_sessions_dal.return_value
mock_sessions_instance.get_user_last_session = AsyncMock(return_value=session_data)
- with patch('src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short') as mock_convert:
+ with patch("src.gw2.cogs.sessions.bot_utils.convert_str_to_datetime_short") as mock_convert:
mock_convert.side_effect = lambda x: x
- with patch('src.gw2.cogs.sessions.gw2_utils.get_time_passed') as mock_time:
+ with patch("src.gw2.cogs.sessions.gw2_utils.get_time_passed") as mock_time:
mock_time.return_value = sample_time_passed
- with patch('src.gw2.cogs.sessions.Gw2SessionCharsDal') as mock_chars_dal:
+ with patch("src.gw2.cogs.sessions.Gw2SessionCharsDal") as mock_chars_dal:
mock_chars_instance = mock_chars_dal.return_value
mock_chars_instance.get_all_start_characters = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.sessions.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.sessions.bot_utils.send_embed") as mock_send:
with patch(
- 'src.gw2.cogs.sessions.chat_formatting.inline', side_effect=lambda x: f"`{x}`"
+ "src.gw2.cogs.sessions.chat_formatting.inline", side_effect=lambda x: f"`{x}`"
):
await session(mock_ctx)
diff --git a/tests/unit/gw2/cogs/test_worlds.py b/tests/unit/gw2/cogs/test_worlds.py
index d616bd30..169703c1 100644
--- a/tests/unit/gw2/cogs/test_worlds.py
+++ b/tests/unit/gw2/cogs/test_worlds.py
@@ -1,9 +1,16 @@
"""Comprehensive tests for GW2 Worlds cog."""
-import asyncio
import discord
import pytest
-from src.gw2.cogs.worlds import GW2Worlds, _send_paginated_worlds_embed, setup, worlds, worlds_eu, worlds_na
+from src.gw2.cogs.worlds import (
+ EmbedPaginatorView,
+ GW2Worlds,
+ _send_paginated_worlds_embed,
+ setup,
+ worlds,
+ worlds_eu,
+ worlds_na,
+)
from unittest.mock import AsyncMock, MagicMock, patch
@@ -60,7 +67,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_worlds_calls_invoke_subcommand(self, mock_ctx):
"""Test that worlds group command calls invoke_subcommand."""
- with patch('src.gw2.cogs.worlds.bot_utils.invoke_subcommand', new_callable=AsyncMock) as mock_invoke:
+ with patch("src.gw2.cogs.worlds.bot_utils.invoke_subcommand", new_callable=AsyncMock) as mock_invoke:
await worlds(mock_ctx)
mock_invoke.assert_called_once_with(mock_ctx, "gw2 worlds")
@@ -78,7 +85,6 @@ def mock_ctx(self):
ctx.bot.settings = {"gw2": {"EmbedColor": 0x00FF00}}
ctx.bot.user = MagicMock()
ctx.bot.user.mention = "<@bot>"
- ctx.bot.wait_for = AsyncMock()
ctx.message = MagicMock()
ctx.message.author = MagicMock()
ctx.message.author.id = 12345
@@ -98,7 +104,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(False, None))
result = await worlds_na(mock_ctx)
assert result is None
@@ -113,12 +119,12 @@ 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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -137,12 +143,12 @@ async def test_worlds_na_skips_eu_worlds(self, mock_ctx):
matches_data_na = {"id": "1-2"}
matches_data_eu = {"id": "2-1"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[matches_data_na, matches_data_eu])
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
embed = mock_send.call_args[0][1]
# Only NA world should be added
@@ -158,12 +164,12 @@ 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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[Exception("API timeout"), matches_data])
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
mock_ctx.bot.log.warning.assert_called_once()
warning_msg = mock_ctx.bot.log.warning.call_args[0][0]
@@ -182,12 +188,12 @@ async def test_worlds_na_failed_worlds_adds_footer(self, mock_ctx):
{"id": 1002, "name": "Borlis Pass", "population": "Medium"},
]
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[Exception("Error1"), Exception("Error2")])
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
embed = mock_send.call_args[0][1]
assert embed.footer is not None
@@ -204,12 +210,12 @@ async def test_worlds_na_failed_worlds_footer_truncates_at_3(self, mock_ctx):
{"id": 1004, "name": "World4", "population": "VeryHigh"},
]
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=Exception("Error"))
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
embed = mock_send.call_args[0][1]
assert "..." in embed.footer.text
@@ -220,12 +226,12 @@ async def test_worlds_na_calls_send_paginated(self, mock_ctx):
worlds_ids = [{"id": 1001, "name": "Anvil Rock", "population": "High"}]
matches_data = {"id": "1-1"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
mock_send.assert_called_once_with(mock_ctx, mock_send.call_args[0][1])
@@ -235,12 +241,12 @@ async def test_worlds_na_no_failed_worlds_no_footer(self, mock_ctx):
worlds_ids = [{"id": 1001, "name": "Anvil Rock", "population": "High"}]
matches_data = {"id": "1-2"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
embed = mock_send.call_args[0][1]
assert embed.footer.text is None
@@ -259,7 +265,6 @@ def mock_ctx(self):
ctx.bot.settings = {"gw2": {"EmbedColor": 0x00FF00}}
ctx.bot.user = MagicMock()
ctx.bot.user.mention = "<@bot>"
- ctx.bot.wait_for = AsyncMock()
ctx.message = MagicMock()
ctx.message.author = MagicMock()
ctx.message.author.id = 12345
@@ -279,7 +284,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(False, None))
result = await worlds_eu(mock_ctx)
assert result is None
@@ -294,12 +299,12 @@ 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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
mock_send.assert_called_once()
embed = mock_send.call_args[0][1]
@@ -318,12 +323,12 @@ async def test_worlds_eu_skips_na_worlds(self, mock_ctx):
matches_data_na = {"id": "1-2"}
matches_data_eu = {"id": "2-1"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[matches_data_na, matches_data_eu])
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
embed = mock_send.call_args[0][1]
# Only EU world should be added
@@ -339,12 +344,12 @@ 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:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[Exception("API timeout"), matches_data])
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
mock_ctx.bot.log.warning.assert_called_once()
warning_msg = mock_ctx.bot.log.warning.call_args[0][0]
@@ -362,12 +367,12 @@ async def test_worlds_eu_failed_worlds_adds_footer(self, mock_ctx):
{"id": 2003, "name": "Gandara", "population": "VeryHigh"},
]
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=Exception("Error"))
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
embed = mock_send.call_args[0][1]
assert embed.footer is not None
@@ -384,12 +389,12 @@ async def test_worlds_eu_failed_worlds_footer_truncates_at_3(self, mock_ctx):
{"id": 2005, "name": "World4", "population": "VeryHigh"},
]
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=Exception("Error"))
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
embed = mock_send.call_args[0][1]
assert "..." in embed.footer.text
@@ -400,17 +405,192 @@ async def test_worlds_eu_tier_number_replaces_2_prefix(self, mock_ctx):
worlds_ids = [{"id": 2002, "name": "Desolation", "population": "Full"}]
matches_data = {"id": "2-4"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
embed = mock_send.call_args[0][1]
assert "T4" in embed.fields[0].value
+class TestEmbedPaginatorView:
+ """Test cases for the EmbedPaginatorView class."""
+
+ def _make_embed_pages(self, count):
+ """Helper to create a list of embed pages."""
+ pages = []
+ for i in range(count):
+ embed = discord.Embed(description=f"Page {i + 1}", color=0x00FF00)
+ embed.set_footer(text=f"Page {i + 1}/{count}")
+ pages.append(embed)
+ return pages
+
+ @pytest.mark.asyncio
+ async def test_initial_state(self):
+ """Test view starts on page 0 with correct button states."""
+ pages = self._make_embed_pages(3)
+ view = EmbedPaginatorView(pages, author_id=123)
+
+ assert view.current_page == 0
+ assert view.previous_button.disabled is True
+ assert view.next_button.disabled is False
+ assert view.page_indicator.label == "1/3"
+ assert view.page_indicator.disabled is True
+
+ @pytest.mark.asyncio
+ async def test_update_buttons_middle_page(self):
+ """Test buttons state on middle page: both enabled."""
+ pages = self._make_embed_pages(3)
+ view = EmbedPaginatorView(pages, author_id=1)
+ view.current_page = 1
+ view._update_buttons()
+
+ assert view.previous_button.disabled is False
+ assert view.next_button.disabled is False
+ assert view.page_indicator.label == "2/3"
+
+ @pytest.mark.asyncio
+ async def test_update_buttons_last_page(self):
+ """Test buttons state on last page: next disabled."""
+ pages = self._make_embed_pages(3)
+ view = EmbedPaginatorView(pages, author_id=1)
+ view.current_page = 2
+ view._update_buttons()
+
+ assert view.previous_button.disabled is False
+ assert view.next_button.disabled is True
+ assert view.page_indicator.label == "3/3"
+
+ @pytest.mark.asyncio
+ async def test_next_button_advances_page(self):
+ """Test clicking next button advances current_page and edits embed."""
+ pages = self._make_embed_pages(3)
+ view = EmbedPaginatorView(pages, author_id=42)
+ interaction = MagicMock()
+ interaction.user.id = 42
+ interaction.response = AsyncMock()
+
+ await view.next_button.callback(interaction)
+
+ assert view.current_page == 1
+ interaction.response.edit_message.assert_called_once()
+ call_kwargs = interaction.response.edit_message.call_args[1]
+ assert call_kwargs["embed"] is pages[1]
+
+ @pytest.mark.asyncio
+ async def test_previous_button_goes_back(self):
+ """Test clicking previous button goes back a page."""
+ pages = self._make_embed_pages(3)
+ view = EmbedPaginatorView(pages, author_id=42)
+ view.current_page = 2
+ view._update_buttons()
+ interaction = MagicMock()
+ interaction.user.id = 42
+ interaction.response = AsyncMock()
+
+ await view.previous_button.callback(interaction)
+
+ assert view.current_page == 1
+ interaction.response.edit_message.assert_called_once()
+ call_kwargs = interaction.response.edit_message.call_args[1]
+ assert call_kwargs["embed"] is pages[1]
+
+ @pytest.mark.asyncio
+ async def test_next_button_rejects_non_author(self):
+ """Test non-author clicking next gets ephemeral rejection."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=42)
+ interaction = MagicMock()
+ interaction.user.id = 999
+ interaction.response = AsyncMock()
+
+ await view.next_button.callback(interaction)
+
+ assert view.current_page == 0 # unchanged
+ interaction.response.send_message.assert_called_once_with(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+
+ @pytest.mark.asyncio
+ async def test_previous_button_rejects_non_author(self):
+ """Test non-author clicking previous gets ephemeral rejection."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=42)
+ view.current_page = 1
+ view._update_buttons()
+ interaction = MagicMock()
+ interaction.user.id = 999
+ interaction.response = AsyncMock()
+
+ await view.previous_button.callback(interaction)
+
+ assert view.current_page == 1 # unchanged
+ interaction.response.send_message.assert_called_once_with(
+ "Only the command invoker can use these buttons.", ephemeral=True
+ )
+
+ @pytest.mark.asyncio
+ async def test_page_indicator_defers(self):
+ """Test page indicator button just defers (non-interactive)."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=1)
+ interaction = MagicMock()
+ interaction.response = AsyncMock()
+
+ await view.page_indicator.callback(interaction)
+
+ interaction.response.defer.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_on_timeout_disables_all_buttons(self):
+ """Test on_timeout disables all children and edits message."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=1)
+ view.message = AsyncMock()
+
+ await view.on_timeout()
+
+ for item in view.children:
+ assert item.disabled is True
+ view.message.edit.assert_called_once_with(view=view)
+
+ @pytest.mark.asyncio
+ async def test_on_timeout_no_message(self):
+ """Test on_timeout with no message reference does not raise."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=1)
+ view.message = None
+
+ await view.on_timeout()
+
+ for item in view.children:
+ assert item.disabled is True
+
+ @pytest.mark.asyncio
+ async def test_timeout_is_300(self):
+ """Test view timeout is 300 seconds."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=1)
+ assert view.timeout == 300
+
+ @pytest.mark.asyncio
+ async def test_pages_stored(self):
+ """Test pages list is stored correctly."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=1)
+ assert view.pages is pages
+
+ @pytest.mark.asyncio
+ async def test_author_id_stored(self):
+ """Test author_id is stored correctly."""
+ pages = self._make_embed_pages(2)
+ view = EmbedPaginatorView(pages, author_id=12345)
+ assert view.author_id == 12345
+
+
class TestSendPaginatedWorldsEmbed:
"""Test cases for the _send_paginated_worlds_embed function."""
@@ -424,7 +604,6 @@ def mock_ctx(self):
ctx.bot.settings = {"gw2": {"EmbedColor": 0x00FF00}}
ctx.bot.user = MagicMock()
ctx.bot.user.mention = "<@bot>"
- ctx.bot.wait_for = AsyncMock()
ctx.message = MagicMock()
ctx.message.author = MagicMock()
ctx.message.author.id = 12345
@@ -467,457 +646,100 @@ async def test_sends_single_embed_exactly_25_fields(self, mock_ctx):
@pytest.mark.asyncio
async def test_paginates_when_more_than_25_fields(self, mock_ctx):
- """Test that embed with >25 fields is paginated."""
+ """Test that embed with >25 fields sends first page with view."""
embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should send the first page
- assert mock_ctx.send.called
+ mock_ctx.send.assert_called_once()
+ call_kwargs = mock_ctx.send.call_args[1]
+ assert isinstance(call_kwargs["view"], EmbedPaginatorView)
+ sent_embed = call_kwargs["embed"]
+ assert len(sent_embed.fields) == 25
@pytest.mark.asyncio
- async def test_dm_channel_different_footer_text(self, mock_ctx):
- """Test that DM channel gets different footer text."""
+ async def test_paginated_footer_shows_page_numbers(self, mock_ctx):
+ """Test that paginated embeds have correct page footer."""
embed = self._make_embed_with_fields(30)
- mock_ctx.channel = MagicMock(spec=discord.DMChannel)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- sent_embed = mock_ctx.send.call_args[1]["embed"]
- assert "reactions won't disappear in DMs" in sent_embed.footer.text
+ call_kwargs = mock_ctx.send.call_args[1]
+ sent_embed = call_kwargs["embed"]
+ assert "Page 1/2" in sent_embed.footer.text
@pytest.mark.asyncio
- async def test_non_dm_channel_simple_footer(self, mock_ctx):
- """Test that non-DM channel gets simple page footer."""
+ async def test_paginated_view_has_second_page(self, mock_ctx):
+ """Test that the view contains both pages with correct fields."""
embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.id = 111
- mock_message.clear_reactions = AsyncMock()
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- sent_embed = mock_ctx.send.call_args[1]["embed"]
- assert "Page 1/2" in sent_embed.footer.text
- assert "reactions won't disappear" not in sent_embed.footer.text
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ assert len(view.pages) == 2
+ assert len(view.pages[0].fields) == 25
+ assert len(view.pages[1].fields) == 5
+ assert "Page 2/2" in view.pages[1].footer.text
@pytest.mark.asyncio
- async def test_single_page_after_split_sends_without_reactions(self, mock_ctx):
- """Test that a single page after splitting sends without reactions."""
- # 25 fields exactly fits one page after split logic
+ async def test_single_page_after_split_sends_without_view(self, mock_ctx):
+ """Test that a single page after splitting sends without view."""
embed = self._make_embed_with_fields(25)
await _send_paginated_worlds_embed(mock_ctx, embed)
mock_ctx.send.assert_called_once()
-
- @pytest.mark.asyncio
- async def test_multiple_pages_adds_reactions(self, mock_ctx):
- """Test that multiple pages adds left and right arrow reactions."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.id = 111
- mock_message.clear_reactions = AsyncMock()
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Check that both reactions were added
- calls = mock_message.add_reaction.call_args_list
- emojis = [call[0][0] for call in calls]
- assert "\u2b05\ufe0f" in emojis # left arrow
- assert "\u27a1\ufe0f" in emojis # right arrow
-
- @pytest.mark.asyncio
- async def test_reaction_add_fails_sends_first_page(self, mock_ctx):
- """Test that when reaction add fails, first page is sent without pagination."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock(side_effect=discord.HTTPException(MagicMock(), "error"))
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should send twice: once for failed pagination, once for fallback
- assert mock_ctx.send.call_count == 2
-
- @pytest.mark.asyncio
- async def test_right_arrow_next_page(self, mock_ctx):
- """Test that right arrow reaction navigates to next page."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- # First call returns right arrow reaction, second call times out
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction, mock_user), asyncio.TimeoutError])
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should have edited the message to show page 2
- mock_message.edit.assert_called_once()
- edited_embed = mock_message.edit.call_args[1]["embed"]
- assert "Page 2/2" in edited_embed.footer.text
-
- @pytest.mark.asyncio
- async def test_left_arrow_previous_page(self, mock_ctx):
- """Test that left arrow reaction navigates to previous page."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- # Navigate right first, then left
- mock_reaction_right = MagicMock()
- mock_reaction_right.emoji = "\u27a1\ufe0f"
- mock_reaction_right.message = mock_message
-
- mock_reaction_left = MagicMock()
- mock_reaction_left.emoji = "\u2b05\ufe0f"
- mock_reaction_left.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(
- side_effect=[
- (mock_reaction_right, mock_user),
- (mock_reaction_left, mock_user),
- asyncio.TimeoutError,
- ]
- )
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should have edited the message twice (once right, once left)
- assert mock_message.edit.call_count == 2
- # Last edit should be back to page 1
- last_edited_embed = mock_message.edit.call_args_list[1][1]["embed"]
- assert "Page 1/2" in last_edited_embed.footer.text
-
- @pytest.mark.asyncio
- async def test_left_arrow_on_first_page_does_nothing(self, mock_ctx):
- """Test that left arrow on first page does not change page."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- mock_reaction_left = MagicMock()
- mock_reaction_left.emoji = "\u2b05\ufe0f"
- mock_reaction_left.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction_left, mock_user), asyncio.TimeoutError])
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should not edit message since already on first page
- mock_message.edit.assert_not_called()
-
- @pytest.mark.asyncio
- async def test_right_arrow_on_last_page_does_nothing(self, mock_ctx):
- """Test that right arrow on last page does not change page."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- mock_reaction_right = MagicMock()
- mock_reaction_right.emoji = "\u27a1\ufe0f"
- mock_reaction_right.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- # Go right twice (only first should work since only 2 pages)
- mock_ctx.bot.wait_for = AsyncMock(
- side_effect=[
- (mock_reaction_right, mock_user),
- (mock_reaction_right, mock_user),
- asyncio.TimeoutError,
- ]
- )
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- # Only one edit (the first right arrow)
- assert mock_message.edit.call_count == 1
-
- @pytest.mark.asyncio
- async def test_not_in_dm_removes_user_reaction(self, mock_ctx):
- """Test that in non-DM channel, user reaction is removed."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- # Non-DM channel (TextChannel)
- mock_ctx.channel = MagicMock(spec=discord.TextChannel)
-
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction, mock_user), asyncio.TimeoutError])
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- mock_message.remove_reaction.assert_called_once_with(mock_reaction.emoji, mock_user)
-
- @pytest.mark.asyncio
- async def test_in_dm_skips_reaction_removal(self, mock_ctx):
- """Test that in DM channel, user reaction is NOT removed."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.channel = MagicMock(spec=discord.DMChannel)
-
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction, mock_user), asyncio.TimeoutError])
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- mock_message.remove_reaction.assert_not_called()
-
- @pytest.mark.asyncio
- async def test_timeout_error_silently_passes(self, mock_ctx):
- """Test that TimeoutError is handled silently."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
- # Should not raise
- await _send_paginated_worlds_embed(mock_ctx, embed)
-
- @pytest.mark.asyncio
- async def test_not_in_dm_after_timeout_clears_reactions(self, mock_ctx):
- """Test that reactions are cleared after timeout in non-DM channel."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.channel = MagicMock(spec=discord.TextChannel)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- mock_message.clear_reactions.assert_called_once()
-
- @pytest.mark.asyncio
- async def test_in_dm_after_timeout_does_not_clear_reactions(self, mock_ctx):
- """Test that reactions are NOT cleared after timeout in DM channel."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.channel = MagicMock(spec=discord.DMChannel)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
- await _send_paginated_worlds_embed(mock_ctx, embed)
- mock_message.clear_reactions.assert_not_called()
-
- @pytest.mark.asyncio
- async def test_forbidden_on_clear_reactions_silently_passes(self, mock_ctx):
- """Test that Forbidden error on clear_reactions is silently handled."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock(side_effect=discord.Forbidden(MagicMock(), "Missing permissions"))
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.channel = MagicMock(spec=discord.TextChannel)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
- # Should not raise
- await _send_paginated_worlds_embed(mock_ctx, embed)
-
- @pytest.mark.asyncio
- async def test_remove_reaction_forbidden_silently_passes(self, mock_ctx):
- """Test that Forbidden on remove_reaction is silently handled."""
- embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock(side_effect=discord.Forbidden(MagicMock(), "Missing permissions"))
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.channel = MagicMock(spec=discord.TextChannel)
-
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
-
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction, mock_user), asyncio.TimeoutError])
-
- # Should not raise
- await _send_paginated_worlds_embed(mock_ctx, embed)
+ call_kwargs = mock_ctx.send.call_args[1]
+ assert "view" not in call_kwargs
@pytest.mark.asyncio
async def test_embed_description_preserved_in_pages(self, mock_ctx):
"""Test that embed description is preserved in paginated pages."""
embed = self._make_embed_with_fields(30, description="~~~~~ NA Servers ~~~~~")
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- sent_embed = mock_ctx.send.call_args[1]["embed"]
+ call_kwargs = mock_ctx.send.call_args[1]
+ sent_embed = call_kwargs["embed"]
assert sent_embed.description == "~~~~~ NA Servers ~~~~~"
@pytest.mark.asyncio
async def test_color_applied_to_all_pages(self, mock_ctx):
"""Test that color is applied to paginated pages."""
embed = self._make_embed_with_fields(30)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(side_effect=[(mock_reaction, mock_user), asyncio.TimeoutError])
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- # First page
- first_page = mock_ctx.send.call_args[1]["embed"]
- assert first_page.color.value == 0x00FF00
- # Second page
- second_page = mock_message.edit.call_args[1]["embed"]
- assert second_page.color.value == 0x00FF00
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ for page in view.pages:
+ assert page.color.value == 0x00FF00
@pytest.mark.asyncio
async def test_fields_split_correctly_across_pages(self, mock_ctx):
"""Test that fields are correctly distributed across pages."""
embed = self._make_embed_with_fields(55)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.edit = AsyncMock()
- mock_message.remove_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
-
- mock_reaction = MagicMock()
- mock_reaction.emoji = "\u27a1\ufe0f"
- mock_reaction.message = mock_message
- mock_user = MagicMock()
- mock_user.bot = False
-
- mock_ctx.bot.wait_for = AsyncMock(
- side_effect=[
- (mock_reaction, mock_user),
- (mock_reaction, mock_user),
- asyncio.TimeoutError,
- ]
- )
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- # First page should have 25 fields
- first_page = mock_ctx.send.call_args[1]["embed"]
- assert len(first_page.fields) == 25
- # Page 1/3
- assert "Page 1/3" in first_page.footer.text
-
-
-class TestWorldsSetup:
- """Test cases for worlds cog setup."""
-
- @pytest.mark.asyncio
- async def test_setup_function_exists(self):
- """Test that setup function exists and is callable."""
- assert callable(setup)
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ assert len(view.pages) == 3
+ assert len(view.pages[0].fields) == 25
+ assert len(view.pages[1].fields) == 25
+ assert len(view.pages[2].fields) == 5
+ assert "Page 1/3" in view.pages[0].footer.text
@pytest.mark.asyncio
- async def test_setup_removes_existing_gw2_command(self):
- """Test that setup removes existing gw2 command."""
- mock_bot = MagicMock()
- mock_bot.remove_command = MagicMock()
- mock_bot.add_cog = AsyncMock()
-
- await setup(mock_bot)
- mock_bot.remove_command.assert_called_once_with("gw2")
+ async def test_view_message_reference_is_set(self, mock_ctx):
+ """Test that view.message is set to the sent message."""
+ embed = self._make_embed_with_fields(30)
+ mock_msg = AsyncMock()
+ mock_ctx.send.return_value = mock_msg
+ await _send_paginated_worlds_embed(mock_ctx, embed)
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.message is mock_msg
@pytest.mark.asyncio
- async def test_setup_adds_cog(self):
- """Test that setup adds the GW2Worlds cog."""
- mock_bot = MagicMock()
- mock_bot.remove_command = MagicMock()
- mock_bot.add_cog = AsyncMock()
-
- await setup(mock_bot)
- mock_bot.add_cog.assert_called_once()
- cog_instance = mock_bot.add_cog.call_args[0][0]
- assert isinstance(cog_instance, GW2Worlds)
+ async def test_view_author_id_matches_ctx_author(self, mock_ctx):
+ """Test that view.author_id matches ctx.author.id."""
+ embed = self._make_embed_with_fields(30)
+ await _send_paginated_worlds_embed(mock_ctx, embed)
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ assert view.author_id == 12345
class TestSendPaginatedWorldsEmbedEdgeCases:
- """Additional edge case tests for _send_paginated_worlds_embed (lines 154-155, 177)."""
+ """Additional edge case tests for _send_paginated_worlds_embed."""
@pytest.fixture
def mock_ctx(self):
@@ -929,7 +751,6 @@ def mock_ctx(self):
ctx.bot.settings = {"gw2": {"EmbedColor": 0x00FF00}}
ctx.bot.user = MagicMock()
ctx.bot.user.mention = "<@bot>"
- ctx.bot.wait_for = AsyncMock()
ctx.message = MagicMock()
ctx.message.author = MagicMock()
ctx.message.author.id = 12345
@@ -952,9 +773,7 @@ def _make_embed_with_fields(self, num_fields, description="Test"):
@pytest.mark.asyncio
async def test_single_page_after_split_sends_directly(self, mock_ctx):
- """Test that when fields split into exactly one page, it sends without reactions (lines 153-155)."""
- # 26 fields -> split gives: page1=25, page2=1 -> 2 pages
- # But 25 fields -> split gives 1 page only since len<=25
+ """Test that when fields fit in one page, it sends without view."""
embed = self._make_embed_with_fields(25)
await _send_paginated_worlds_embed(mock_ctx, embed)
mock_ctx.send.assert_called_once()
@@ -963,36 +782,31 @@ async def test_single_page_after_split_sends_directly(self, mock_ctx):
@pytest.mark.asyncio
async def test_paginated_26_fields_creates_two_pages(self, mock_ctx):
- """Test pagination with 26 fields creates exactly 2 pages (lines 153-155)."""
+ """Test pagination with 26 fields creates exactly 2 pages."""
embed = self._make_embed_with_fields(26)
- mock_message = MagicMock()
- mock_message.add_reaction = AsyncMock()
- mock_message.clear_reactions = AsyncMock()
- mock_message.id = 111
- mock_ctx.send = AsyncMock(return_value=mock_message)
- mock_ctx.bot.wait_for = AsyncMock(side_effect=asyncio.TimeoutError)
-
await _send_paginated_worlds_embed(mock_ctx, embed)
- # Should send first page
- sent_embed = mock_ctx.send.call_args[1]["embed"]
- assert len(sent_embed.fields) == 25
- assert "Page 1/2" in sent_embed.footer.text
+ call_kwargs = mock_ctx.send.call_args[1]
+ view = call_kwargs["view"]
+ assert len(view.pages) == 2
+ assert len(view.pages[0].fields) == 25
+ assert len(view.pages[1].fields) == 1
+ assert "Page 1/2" in view.pages[0].footer.text
@pytest.mark.asyncio
async def test_worlds_na_exact_2001_boundary(self, mock_ctx):
- """Test worlds_na does not include world ID exactly at 2001 (line 177 equivalence)."""
+ """Test worlds_na does not include world ID exactly at 2001."""
worlds_ids = [
{"id": 2001, "name": "Boundary World", "population": "High"},
]
# wid=2001 is NOT < 2001, so NA should NOT add it
matches_data = {"id": "2-1"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_na(mock_ctx)
embed = mock_send.call_args[0][1]
# 2001 is NOT < 2001, so should not be added to NA embed
@@ -1000,20 +814,51 @@ async def test_worlds_na_exact_2001_boundary(self, mock_ctx):
@pytest.mark.asyncio
async def test_worlds_eu_exact_2001_boundary(self, mock_ctx):
- """Test worlds_eu does not include world ID exactly at 2001 (line 177)."""
+ """Test worlds_eu does not include world ID exactly at 2001."""
worlds_ids = [
{"id": 2001, "name": "Boundary World", "population": "High"},
]
# wid=2001 is NOT > 2001, so EU should NOT add it
matches_data = {"id": "2-1"}
- with patch('src.gw2.cogs.worlds.gw2_utils') as mock_utils:
+ with patch("src.gw2.cogs.worlds.gw2_utils") as mock_utils:
mock_utils.get_worlds_ids = AsyncMock(return_value=(True, worlds_ids))
- with patch('src.gw2.cogs.worlds.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.worlds.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=matches_data)
- with patch('src.gw2.cogs.worlds._send_paginated_worlds_embed', new_callable=AsyncMock) as mock_send:
+ with patch("src.gw2.cogs.worlds._send_paginated_worlds_embed", new_callable=AsyncMock) as mock_send:
await worlds_eu(mock_ctx)
embed = mock_send.call_args[0][1]
# 2001 is NOT > 2001, so should not be added to EU embed
assert len(embed.fields) == 0
+
+
+class TestWorldsSetup:
+ """Test cases for worlds cog setup."""
+
+ @pytest.mark.asyncio
+ async def test_setup_function_exists(self):
+ """Test that setup function exists and is callable."""
+ assert callable(setup)
+
+ @pytest.mark.asyncio
+ async def test_setup_removes_existing_gw2_command(self):
+ """Test that setup removes existing gw2 command."""
+ mock_bot = MagicMock()
+ mock_bot.remove_command = MagicMock()
+ mock_bot.add_cog = AsyncMock()
+
+ await setup(mock_bot)
+ mock_bot.remove_command.assert_called_once_with("gw2")
+
+ @pytest.mark.asyncio
+ async def test_setup_adds_cog(self):
+ """Test that setup adds the GW2Worlds cog."""
+ mock_bot = MagicMock()
+ mock_bot.remove_command = MagicMock()
+ mock_bot.add_cog = AsyncMock()
+
+ await setup(mock_bot)
+ mock_bot.add_cog.assert_called_once()
+ cog_instance = mock_bot.add_cog.call_args[0][0]
+ assert isinstance(cog_instance, GW2Worlds)
diff --git a/tests/unit/gw2/cogs/test_wvw.py b/tests/unit/gw2/cogs/test_wvw.py
index 8416e09c..dfd189f1 100644
--- a/tests/unit/gw2/cogs/test_wvw.py
+++ b/tests/unit/gw2/cogs/test_wvw.py
@@ -7,6 +7,7 @@
_get_kdr_embed_values,
_get_map_names_embed_values,
_get_match_embed_values,
+ _resolve_tier,
setup,
)
from src.gw2.tools.gw2_exceptions import APIKeyError
@@ -149,12 +150,12 @@ async def test_info_no_world_no_api_key(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
- with patch('src.gw2.cogs.wvw.Gw2Client'):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
await cog.info.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
@@ -169,11 +170,11 @@ async def test_info_no_world_api_key_exists(self, mock_bot, mock_ctx, sample_mat
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -183,7 +184,7 @@ async def test_info_no_world_api_key_exists(self, mock_bot, mock_ctx, sample_mat
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world=None)
mock_send.assert_called_once()
@@ -195,15 +196,15 @@ async def test_info_no_world_api_key_error(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "Invalid key"))
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.info.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
@@ -218,16 +219,16 @@ async def test_info_no_world_generic_exception(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("Something went wrong")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.info.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once_with(mock_ctx, error)
@@ -241,10 +242,10 @@ async def test_info_world_given_calls_get_world_id(
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -253,7 +254,7 @@ async def test_info_world_given_calls_get_world_id(
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
mock_get_wid.assert_called_once_with(mock_ctx.bot, "Anvil Rock")
@@ -265,11 +266,11 @@ async def test_info_wid_is_none(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = None
- with patch('src.gw2.cogs.wvw.Gw2Client'):
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.info.callback(cog, mock_ctx, world="InvalidWorld")
mock_error.assert_called_once()
@@ -283,15 +284,15 @@ async def test_info_api_call_fails(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("API failure")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
mock_error.assert_called_once_with(mock_ctx, error)
@@ -312,10 +313,10 @@ async def test_info_world_color_not_found(self, mock_bot, mock_ctx, sample_world
},
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001 # Not in any of the all_worlds lists
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -324,7 +325,7 @@ async def test_info_world_color_not_found(self, mock_bot, mock_ctx, sample_world
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
mock_error.assert_called_once()
@@ -337,10 +338,10 @@ async def test_info_na_tier(self, mock_bot, mock_ctx, sample_matches_data, sampl
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001 # NA world
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -349,7 +350,7 @@ async def test_info_na_tier(self, mock_bot, mock_ctx, sample_matches_data, sampl
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -387,10 +388,10 @@ async def test_info_eu_tier(self, mock_bot, mock_ctx, sample_worldinfo_data):
"population": "Full",
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 2001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -399,7 +400,7 @@ async def test_info_eu_tier(self, mock_bot, mock_ctx, sample_worldinfo_data):
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Desolation")
embed = mock_send.call_args[0][1]
@@ -411,10 +412,10 @@ async def test_info_red_world_color(self, mock_bot, mock_ctx, sample_matches_dat
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001 # In red all_worlds
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -423,7 +424,7 @@ async def test_info_red_world_color(self, mock_bot, mock_ctx, sample_matches_dat
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -435,10 +436,10 @@ async def test_info_green_world_color(self, mock_bot, mock_ctx, sample_matches_d
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1003 # In green all_worlds
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -447,7 +448,7 @@ async def test_info_green_world_color(self, mock_bot, mock_ctx, sample_matches_d
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Some World")
embed = mock_send.call_args[0][1]
@@ -459,10 +460,10 @@ async def test_info_blue_world_color(self, mock_bot, mock_ctx, sample_matches_da
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1005 # In blue all_worlds
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -471,7 +472,7 @@ async def test_info_blue_world_color(self, mock_bot, mock_ctx, sample_matches_da
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.info.callback(cog, mock_ctx, world="Some World")
embed = mock_send.call_args[0][1]
@@ -489,10 +490,10 @@ async def test_info_population_veryhigh(self, mock_bot, mock_ctx, sample_matches
"population": "VeryHigh",
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -501,8 +502,8 @@ async def test_info_population_veryhigh(self, mock_bot, mock_ctx, sample_matches
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -535,10 +536,10 @@ async def test_info_kills_zero_kd(self, mock_bot, mock_ctx, sample_worldinfo_dat
],
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -547,8 +548,8 @@ async def test_info_kills_zero_kd(self, mock_bot, mock_ctx, sample_worldinfo_dat
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -580,10 +581,10 @@ async def test_info_deaths_zero_kd(self, mock_bot, mock_ctx, sample_worldinfo_da
],
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -592,8 +593,8 @@ async def test_info_deaths_zero_kd(self, mock_bot, mock_ctx, sample_worldinfo_da
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -606,10 +607,10 @@ async def test_info_normal_kd_calculation(self, mock_bot, mock_ctx, sample_match
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001 # red world
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -618,8 +619,8 @@ async def test_info_normal_kd_calculation(self, mock_bot, mock_ctx, sample_match
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -633,10 +634,10 @@ async def test_info_successful_embed_sent(self, mock_bot, mock_ctx, sample_match
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(
side_effect=[
@@ -645,8 +646,8 @@ async def test_info_successful_embed_sent(self, mock_bot, mock_ctx, sample_match
]
)
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
mock_send.assert_called_once()
@@ -757,12 +758,12 @@ async def test_match_no_world_no_api_key(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
- with patch('src.gw2.cogs.wvw.Gw2Client'):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
await cog.match.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
@@ -777,20 +778,20 @@ async def test_match_no_world_api_key_error(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "Invalid key"))
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.match.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
- assert "You dont have an API key registered" in error_msg
+ assert "Missing World Name" in error_msg
@pytest.mark.asyncio
async def test_match_no_world_generic_exception(self, mock_bot, mock_ctx):
@@ -800,16 +801,16 @@ async def test_match_no_world_generic_exception(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("Something went wrong")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.match.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once_with(mock_ctx, error)
@@ -821,17 +822,17 @@ async def test_match_world_given_uses_get_world_id(self, mock_bot, mock_ctx, sam
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)", "World2 (Medium)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.match.callback(cog, mock_ctx, world="Anvil Rock")
mock_get_wid.assert_called_once_with(mock_ctx.bot, "Anvil Rock")
@@ -842,11 +843,11 @@ async def test_match_wid_none(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = None
- with patch('src.gw2.cogs.wvw.Gw2Client'):
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.match.callback(cog, mock_ctx, world="InvalidWorld")
mock_error.assert_called_once()
@@ -860,17 +861,17 @@ async def test_match_na_tier(self, mock_bot, mock_ctx, sample_matches_data):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.match.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -921,17 +922,17 @@ async def test_match_eu_tier(self, mock_bot, mock_ctx):
],
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 2001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=eu_matches)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.match.callback(cog, mock_ctx, world="Desolation")
embed = mock_send.call_args[0][1]
@@ -943,15 +944,15 @@ async def test_match_exception_during_fetch(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("API Error")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.match.callback(cog, mock_ctx, world="Anvil Rock")
mock_error.assert_called_once_with(mock_ctx, error)
@@ -963,17 +964,17 @@ async def test_match_successful_embed(self, mock_bot, mock_ctx, sample_matches_d
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)", "World2 (Medium)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.match.callback(cog, mock_ctx, world="Anvil Rock")
mock_send.assert_called_once()
@@ -1069,12 +1070,12 @@ async def test_kdr_no_world_no_api_key(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=None)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
- with patch('src.gw2.cogs.wvw.Gw2Client'):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
await cog.kdr.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
@@ -1089,20 +1090,20 @@ async def test_kdr_no_world_api_key_error(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "Invalid key"))
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.kdr.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
error_msg = mock_error.call_args[0][1]
- assert "You dont have an API key registered" in error_msg
+ assert "Invalid world name" in error_msg
@pytest.mark.asyncio
async def test_kdr_no_world_generic_exception(self, mock_bot, mock_ctx):
@@ -1112,16 +1113,16 @@ async def test_kdr_no_world_generic_exception(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key-12345"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("Something went wrong")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.kdr.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once_with(mock_ctx, error)
@@ -1133,17 +1134,17 @@ async def test_kdr_world_given(self, mock_bot, mock_ctx, sample_matches_data):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.kdr.callback(cog, mock_ctx, world="Anvil Rock")
mock_get_wid.assert_called_once_with(mock_ctx.bot, "Anvil Rock")
@@ -1154,11 +1155,11 @@ async def test_kdr_wid_none(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = None
- with patch('src.gw2.cogs.wvw.Gw2Client'):
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.Gw2Client"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.kdr.callback(cog, mock_ctx, world="InvalidWorld")
mock_error.assert_called_once()
@@ -1172,17 +1173,17 @@ async def test_kdr_na_tier_title(self, mock_bot, mock_ctx, sample_matches_data):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.kdr.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -1233,17 +1234,17 @@ async def test_kdr_eu_tier_title(self, mock_bot, mock_ctx):
],
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 2001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=eu_matches)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.kdr.callback(cog, mock_ctx, world="Desolation")
embed = mock_send.call_args[0][1]
@@ -1256,15 +1257,15 @@ async def test_kdr_exception_during_fetch(self, mock_bot, mock_ctx):
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
error = Exception("API Error")
mock_client_instance.call_api = AsyncMock(side_effect=error)
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
await cog.kdr.callback(cog, mock_ctx, world="Anvil Rock")
mock_error.assert_called_once_with(mock_ctx, error)
@@ -1276,17 +1277,17 @@ async def test_kdr_successful_embed(self, mock_bot, mock_ctx, sample_matches_dat
cog = GW2WvW(mock_bot)
cog.bot = mock_ctx.bot
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(return_value=sample_matches_data)
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
await cog.kdr.callback(cog, mock_ctx, world="Anvil Rock")
mock_send.assert_called_once()
@@ -1327,7 +1328,7 @@ def sample_matches(self):
@pytest.mark.asyncio
async def test_get_map_names_green(self, mock_ctx, sample_matches):
"""Test _get_map_names_embed_values for green color."""
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)", "World2 (Medium)", "World3 (Low)"]
result = await _get_map_names_embed_values(mock_ctx, "green", sample_matches)
@@ -1341,7 +1342,7 @@ async def test_get_map_names_green(self, mock_ctx, sample_matches):
@pytest.mark.asyncio
async def test_get_map_names_primary_server_first(self, mock_ctx, sample_matches):
"""Test that primary server ID is first in the list."""
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)"]
await _get_map_names_embed_values(mock_ctx, "green", sample_matches)
@@ -1359,7 +1360,7 @@ async def test_get_map_names_no_duplicates(self, mock_ctx):
"worlds": {"red": 1001},
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_name_population', new_callable=AsyncMock) as mock_pop:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_name_population", new_callable=AsyncMock) as mock_pop:
mock_pop.return_value = ["World1 (High)", "World2 (Medium)"]
await _get_map_names_embed_values(mock_ctx, "red", matches)
@@ -1787,7 +1788,7 @@ async def test_wvw_group_calls_invoke_subcommand(self, mock_bot, mock_ctx):
"""Test that wvw group command calls invoke_subcommand (line 27)."""
cog = GW2WvW(mock_bot)
- with patch('src.gw2.cogs.wvw.bot_utils.invoke_subcommand', new_callable=AsyncMock) as mock_invoke:
+ with patch("src.gw2.cogs.wvw.bot_utils.invoke_subcommand", new_callable=AsyncMock) as mock_invoke:
await cog.wvw.callback(cog, mock_ctx)
mock_invoke.assert_called_once_with(mock_ctx, "gw2 wvw")
@@ -1846,15 +1847,15 @@ async def test_info_unknown_world_color_uses_default(self, mock_bot, mock_ctx):
"maps": [{"objectives": [{"owner": "Yellow", "points_tick": 5}]}],
}
- with patch('src.gw2.cogs.wvw.gw2_utils.get_world_id', new_callable=AsyncMock) as mock_get_wid:
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock) as mock_get_wid:
mock_get_wid.return_value = 1001
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=[matches_data, sample_worldinfo])
- with patch('src.gw2.cogs.wvw.bot_utils.send_embed') as mock_send:
- with patch('src.gw2.cogs.wvw.chat_formatting.inline', side_effect=lambda x: f"`{x}`"):
+ with patch("src.gw2.cogs.wvw.bot_utils.send_embed") as mock_send:
+ with patch("src.gw2.cogs.wvw.chat_formatting.inline", side_effect=lambda x: f"`{x}`"):
await cog.info.callback(cog, mock_ctx, world="Anvil Rock")
embed = mock_send.call_args[0][1]
@@ -1901,22 +1902,21 @@ async def test_match_api_key_error_on_account_call(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
# Raise APIKeyError on the account call (results = await gw2_api.call_api("account", api_key))
mock_client_instance.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "Invalid key"))
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
result = await cog.match.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
- from src.gw2.constants import gw2_messages
-
- assert mock_error.call_args[0][1] == gw2_messages.NO_API_KEY
+ error_msg = mock_error.call_args[0][1]
+ assert "Missing World Name" in error_msg
class TestKdrAPIKeyError:
@@ -1959,18 +1959,143 @@ async def test_kdr_api_key_error_on_account_call(self, mock_bot, mock_ctx):
api_key_data = [{"key": "test-api-key"}]
- with patch('src.gw2.cogs.wvw.Gw2KeyDal') as mock_dal:
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_api_key_by_user = AsyncMock(return_value=api_key_data)
- with patch('src.gw2.cogs.wvw.Gw2Client') as mock_client:
+ with patch("src.gw2.cogs.wvw.Gw2Client") as mock_client:
mock_client_instance = mock_client.return_value
mock_client_instance.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "Invalid key"))
- with patch('src.gw2.cogs.wvw.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
result = await cog.kdr.callback(cog, mock_ctx, world=None)
mock_error.assert_called_once()
- from src.gw2.constants import gw2_messages
+ error_msg = mock_error.call_args[0][1]
+ assert "Invalid world name" in error_msg
+
+
+class TestResolveTier:
+ """Test cases for the _resolve_tier helper function."""
+
+ def test_na_tier(self):
+ """Test NA tier resolution from match ID."""
+ matches = {"id": "1-3"}
+ result = _resolve_tier(matches)
+ assert "North America Tier" in result
+ assert "3" in result
+
+ def test_eu_tier(self):
+ """Test EU tier resolution from match ID."""
+ matches = {"id": "2-5"}
+ result = _resolve_tier(matches)
+ assert "Europe Tier" in result
+ assert "5" in result
+
+ def test_na_tier_1(self):
+ """Test NA tier 1."""
+ matches = {"id": "1-1"}
+ result = _resolve_tier(matches)
+ assert result == "North America Tier 1"
+
+ def test_eu_tier_1(self):
+ """Test EU tier 1."""
+ matches = {"id": "2-1"}
+ result = _resolve_tier(matches)
+ assert result == "Europe Tier 1"
+
+
+class TestResolveWvwWorldId:
+ """Test cases for the _resolve_wvw_world_id method."""
+
+ @pytest.fixture
+ def mock_bot(self):
+ bot = MagicMock()
+ bot.db_session = MagicMock()
+ bot.log = MagicMock()
+ bot.settings = {"gw2": {"EmbedColor": 0x00FF00}}
+ return bot
+
+ @pytest.fixture
+ def mock_ctx(self):
+ ctx = MagicMock()
+ ctx.bot = MagicMock()
+ ctx.bot.db_session = MagicMock()
+ ctx.bot.log = MagicMock()
+ ctx.message = MagicMock()
+ ctx.message.author = MagicMock()
+ ctx.message.author.id = 12345
+ ctx.message.channel = MagicMock()
+ ctx.message.channel.typing = AsyncMock()
+ ctx.prefix = "!"
+ return ctx
+
+ @pytest.mark.asyncio
+ async def test_with_world_name_delegates_to_get_world_id(self, mock_bot, mock_ctx):
+ """Test that providing a world name uses get_world_id."""
+ cog = GW2WvW(mock_bot)
+ cog.bot = mock_ctx.bot
+ gw2_api = MagicMock()
+
+ with patch("src.gw2.cogs.wvw.gw2_utils.get_world_id", new_callable=AsyncMock, return_value=1001):
+ result = await cog._resolve_wvw_world_id(mock_ctx, gw2_api, "Anvil Rock", "error msg")
+ assert result == 1001
+
+ @pytest.mark.asyncio
+ async def test_prefers_wvw_team_id_over_world(self, mock_bot, mock_ctx):
+ """Test that wvw.team_id is preferred over legacy world field."""
+ cog = GW2WvW(mock_bot)
+ cog.bot = mock_ctx.bot
+ gw2_api = MagicMock()
+ gw2_api.call_api = AsyncMock(return_value={"world": 1001, "wvw": {"team_id": 11005}})
+
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
+ mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-key"}])
+
+ result = await cog._resolve_wvw_world_id(mock_ctx, gw2_api, None, "error msg")
+ assert result == 11005
+
+ @pytest.mark.asyncio
+ async def test_falls_back_to_world_when_no_team_id(self, mock_bot, mock_ctx):
+ """Test fallback to world when wvw.team_id is absent."""
+ cog = GW2WvW(mock_bot)
+ cog.bot = mock_ctx.bot
+ gw2_api = MagicMock()
+ gw2_api.call_api = AsyncMock(return_value={"world": 1001})
+
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
+ mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-key"}])
+
+ result = await cog._resolve_wvw_world_id(mock_ctx, gw2_api, None, "error msg")
+ assert result == 1001
+
+ @pytest.mark.asyncio
+ async def test_no_api_key_sends_error(self, mock_bot, mock_ctx):
+ """Test that missing API key sends error and returns None."""
+ cog = GW2WvW(mock_bot)
+ cog.bot = mock_ctx.bot
+ gw2_api = MagicMock()
+
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
+ mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=None)
+
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
+ result = await cog._resolve_wvw_world_id(mock_ctx, gw2_api, None, "no key msg")
+ assert result is None
+ mock_error.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_api_key_error_sends_error(self, mock_bot, mock_ctx):
+ """Test that APIKeyError sends error message."""
+ cog = GW2WvW(mock_bot)
+ cog.bot = mock_ctx.bot
+ gw2_api = MagicMock()
+ gw2_api.call_api = AsyncMock(side_effect=APIKeyError(mock_ctx.bot, "bad key"))
+
+ with patch("src.gw2.cogs.wvw.Gw2KeyDal") as mock_dal:
+ mock_dal.return_value.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-key"}])
- assert mock_error.call_args[0][1] == gw2_messages.NO_API_KEY
+ with patch("src.gw2.cogs.wvw.bot_utils.send_error_msg") as mock_error:
+ result = await cog._resolve_wvw_world_id(mock_ctx, gw2_api, None, "error msg")
+ assert result is None
+ mock_error.assert_called_once()
diff --git a/tests/unit/gw2/constants/test_gw2_settings.py b/tests/unit/gw2/constants/test_gw2_settings.py
index 4cf3cec3..c33f7d12 100644
--- a/tests/unit/gw2/constants/test_gw2_settings.py
+++ b/tests/unit/gw2/constants/test_gw2_settings.py
@@ -162,15 +162,15 @@ def test_all_cooldown_fields_exist(self):
settings = Gw2Settings()
cooldown_fields = [
- 'account_cooldown',
- 'api_keys_cooldown',
- 'characters_cooldown',
- 'config_cooldown',
- 'daily_cooldown',
- 'misc_cooldown',
- 'session_cooldown',
- 'worlds_cooldown',
- 'wvw_cooldown',
+ "account_cooldown",
+ "api_keys_cooldown",
+ "characters_cooldown",
+ "config_cooldown",
+ "daily_cooldown",
+ "misc_cooldown",
+ "session_cooldown",
+ "worlds_cooldown",
+ "wvw_cooldown",
]
for field in cooldown_fields:
@@ -207,10 +207,10 @@ def test_settings_with_actual_env_file(self):
settings = get_gw2_settings()
# Just verify the structure is correct
- assert hasattr(settings, 'embed_color')
- assert hasattr(settings, 'api_version')
- assert hasattr(settings, 'account_cooldown')
- assert hasattr(settings, 'session_cooldown')
+ assert hasattr(settings, "embed_color")
+ assert hasattr(settings, "api_version")
+ assert hasattr(settings, "account_cooldown")
+ assert hasattr(settings, "session_cooldown")
# Verify types
assert isinstance(settings.embed_color, (str, type(None)))
diff --git a/tests/unit/gw2/constants/test_gw2_teams.py b/tests/unit/gw2/constants/test_gw2_teams.py
new file mode 100644
index 00000000..d853eb68
--- /dev/null
+++ b/tests/unit/gw2/constants/test_gw2_teams.py
@@ -0,0 +1,86 @@
+"""Tests for GW2 World Restructuring team constants."""
+
+from src.gw2.constants.gw2_teams import WR_TEAM_NAMES, get_team_name, is_wr_team_id
+
+
+class TestIsWrTeamId:
+ """Test cases for is_wr_team_id function."""
+
+ def test_na_team_id(self):
+ assert is_wr_team_id(11001) is True
+
+ def test_na_team_id_last(self):
+ assert is_wr_team_id(11012) is True
+
+ def test_eu_team_id(self):
+ assert is_wr_team_id(12001) is True
+
+ def test_eu_team_id_last(self):
+ assert is_wr_team_id(12015) is True
+
+ def test_legacy_na_world_id(self):
+ assert is_wr_team_id(1001) is False
+
+ def test_legacy_eu_world_id(self):
+ assert is_wr_team_id(2001) is False
+
+ def test_zero(self):
+ assert is_wr_team_id(0) is False
+
+ def test_boundary_below(self):
+ assert is_wr_team_id(11000) is False
+
+ def test_boundary_above(self):
+ assert is_wr_team_id(13000) is False
+
+ def test_mid_range_gap(self):
+ """ID between NA and EU ranges is still considered WR."""
+ assert is_wr_team_id(11500) is True
+
+
+class TestGetTeamName:
+ """Test cases for get_team_name function."""
+
+ def test_na_team(self):
+ assert get_team_name(11001) == "Team 1 (NA)"
+
+ def test_eu_team(self):
+ assert get_team_name(12001) == "Team 1 (EU)"
+
+ def test_last_na_team(self):
+ assert get_team_name(11012) == "Team 12 (NA)"
+
+ def test_last_eu_team(self):
+ assert get_team_name(12015) == "Team 15 (EU)"
+
+ def test_unknown_team_id(self):
+ assert get_team_name(99999) is None
+
+ def test_legacy_world_id(self):
+ assert get_team_name(1001) is None
+
+ def test_zero(self):
+ assert get_team_name(0) is None
+
+
+class TestWrTeamNames:
+ """Test cases for WR_TEAM_NAMES dict."""
+
+ def test_has_12_na_teams(self):
+ na_teams = {k: v for k, v in WR_TEAM_NAMES.items() if 11001 <= k <= 11999}
+ assert len(na_teams) == 12
+
+ def test_has_15_eu_teams(self):
+ eu_teams = {k: v for k, v in WR_TEAM_NAMES.items() if 12001 <= k <= 12999}
+ assert len(eu_teams) == 15
+
+ def test_total_teams(self):
+ assert len(WR_TEAM_NAMES) == 27
+
+ def test_all_na_names_contain_na(self):
+ for team_id in range(11001, 11013):
+ assert "(NA)" in WR_TEAM_NAMES[team_id]
+
+ def test_all_eu_names_contain_eu(self):
+ for team_id in range(12001, 12016):
+ assert "(EU)" in WR_TEAM_NAMES[team_id]
diff --git a/tests/unit/gw2/tools/test_gw2_client.py b/tests/unit/gw2/tools/test_gw2_client.py
index d75f9589..78294dcd 100644
--- a/tests/unit/gw2/tools/test_gw2_client.py
+++ b/tests/unit/gw2/tools/test_gw2_client.py
@@ -296,7 +296,9 @@ async def test_call_api_with_key(self, gw2_client):
headers = (
call_kwargs[1]["headers"]
if "headers" in call_kwargs[1]
- else call_kwargs[0][1] if len(call_kwargs[0]) > 1 else None
+ else call_kwargs[0][1]
+ if len(call_kwargs[0]) > 1
+ else None
)
# The headers parameter is passed as keyword arg
if headers:
@@ -870,7 +872,7 @@ async def test_call_api_non_200_206_returns_none_after_error_handler(self, gw2_c
gw2_client.bot.aiosession.get = MagicMock(return_value=AsyncContextManager(mock_response))
# Patch _handle_api_error to NOT raise, so execution falls through to return None
- with patch.object(gw2_client, '_handle_api_error', new_callable=AsyncMock) as mock_handler:
+ with patch.object(gw2_client, "_handle_api_error", new_callable=AsyncMock) as mock_handler:
result = await gw2_client.call_api("account")
mock_handler.assert_called_once()
diff --git a/tests/unit/gw2/tools/test_gw2_cooldowns.py b/tests/unit/gw2/tools/test_gw2_cooldowns.py
index 879f1efb..015f634c 100644
--- a/tests/unit/gw2/tools/test_gw2_cooldowns.py
+++ b/tests/unit/gw2/tools/test_gw2_cooldowns.py
@@ -9,7 +9,7 @@ class TestGW2CoolDowns:
def test_cooldown_enum_values_exist(self):
"""Test that all expected cooldown values exist."""
- expected_cooldowns = ['Account', 'ApiKeys', 'Characters', 'Config', 'Daily', 'Misc', 'Session', 'Worlds', 'Wvw']
+ expected_cooldowns = ["Account", "ApiKeys", "Characters", "Config", "Daily", "Misc", "Session", "Worlds", "Wvw"]
for cooldown_name in expected_cooldowns:
assert hasattr(GW2CoolDowns, cooldown_name)
@@ -103,5 +103,5 @@ def test_cooldown_enum_iteration(self):
assert isinstance(cooldown.value[0], int)
# Should have all expected cooldowns
- expected_names = {'Account', 'ApiKeys', 'Characters', 'Config', 'Daily', 'Misc', 'Session', 'Worlds', 'Wvw'}
+ expected_names = {"Account", "ApiKeys", "Characters", "Config", "Daily", "Misc", "Session", "Worlds", "Wvw"}
assert cooldown_names == expected_names
diff --git a/tests/unit/gw2/tools/test_gw2_utils.py b/tests/unit/gw2/tools/test_gw2_utils.py
index c8d5dda6..e4274c2e 100644
--- a/tests/unit/gw2/tools/test_gw2_utils.py
+++ b/tests/unit/gw2/tools/test_gw2_utils.py
@@ -54,7 +54,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_send_msg_default_settings(self, mock_ctx):
"""Test send_msg with default settings."""
- with patch('src.gw2.tools.gw2_utils.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.send_embed") as mock_send:
await send_msg(mock_ctx, "Test message")
mock_send.assert_called_once()
@@ -69,7 +69,7 @@ async def test_send_msg_default_settings(self, mock_ctx):
@pytest.mark.asyncio
async def test_send_msg_with_dm(self, mock_ctx):
"""Test send_msg with DM option."""
- with patch('src.gw2.tools.gw2_utils.bot_utils.send_embed') as mock_send:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.send_embed") as mock_send:
await send_msg(mock_ctx, "DM message", dm=True)
mock_send.assert_called_once()
@@ -99,7 +99,7 @@ def mock_server(self):
@pytest.mark.asyncio
async def test_insert_configs_when_not_exists(self, mock_bot, mock_server):
"""Test inserting configs when they don't exist."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=None)
mock_instance.insert_gw2_server_configs = AsyncMock(return_value=None)
@@ -112,7 +112,7 @@ async def test_insert_configs_when_not_exists(self, mock_bot, mock_server):
@pytest.mark.asyncio
async def test_skip_insert_when_configs_exist(self, mock_bot, mock_server):
"""Test skipping insert when configs already exist."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value={"existing": "config"})
mock_instance.insert_gw2_server_configs = AsyncMock(return_value=None)
@@ -282,14 +282,14 @@ def sample_account_data(self):
@pytest.mark.asyncio
async def test_calculate_achievement_points(self, mock_ctx, sample_user_achievements, sample_account_data):
"""Test calculating achievement points."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client'):
- with patch('src.gw2.tools.gw2_utils._fetch_achievement_data_in_batches') as mock_fetch:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client"):
+ with patch("src.gw2.tools.gw2_utils._fetch_achievement_data_in_batches") as mock_fetch:
mock_fetch.return_value = [
{"id": 1, "tiers": [{"count": 5, "points": 10}, {"count": 10, "points": 20}]},
{"id": 2, "tiers": [{"count": 3, "points": 5}]},
]
- with patch('src.gw2.tools.gw2_utils._calculate_earned_points') as mock_calc:
+ with patch("src.gw2.tools.gw2_utils._calculate_earned_points") as mock_calc:
mock_calc.return_value = 75
result = await calculate_user_achiev_points(mock_ctx, sample_user_achievements, sample_account_data)
@@ -416,7 +416,7 @@ def mock_bot(self):
@pytest.mark.asyncio
async def test_get_world_id_exact_match(self, mock_bot):
"""Test successful world ID retrieval with exact match."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(
return_value=[{"id": 1001, "name": "Anvil Rock"}, {"id": 1002, "name": "Borlis Pass"}]
@@ -428,7 +428,7 @@ async def test_get_world_id_exact_match(self, mock_bot):
@pytest.mark.asyncio
async def test_get_world_id_case_insensitive(self, mock_bot):
"""Test world ID retrieval with case-insensitive match."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(
return_value=[
@@ -442,7 +442,7 @@ async def test_get_world_id_case_insensitive(self, mock_bot):
@pytest.mark.asyncio
async def test_get_world_id_partial_match(self, mock_bot):
"""Test world ID retrieval with partial match (line 196)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(
return_value=[
@@ -459,7 +459,7 @@ async def test_get_world_id_partial_match(self, mock_bot):
@pytest.mark.asyncio
async def test_get_world_id_not_found(self, mock_bot):
"""Test world ID not found."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=[{"id": 1001, "name": "Anvil Rock"}])
@@ -477,7 +477,7 @@ async def test_get_world_id_api_error(self, mock_bot):
"""Test get_world_id with API error."""
mock_bot.log.error = MagicMock()
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=Exception("API Error"))
@@ -499,24 +499,25 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_successful_retrieval(self, mock_ctx):
- """Test successful world name population retrieval (lines 206-213)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ """Test successful world name population retrieval for legacy IDs."""
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(
return_value=[
- {"name": "Anvil Rock", "population": "High"},
- {"name": "Borlis Pass", "population": "Medium"},
+ {"id": 1001, "name": "Anvil Rock", "population": "High"},
+ {"id": 1002, "name": "Borlis Pass", "population": "Medium"},
]
)
result = await get_world_name_population(mock_ctx, "1001,1002")
assert result == ["Anvil Rock", "Borlis Pass"]
+ mock_client.call_api.assert_called_once_with("worlds?ids=1001,1002")
@pytest.mark.asyncio
async def test_empty_results(self, mock_ctx):
"""Test when API returns empty results (line 211)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=[])
@@ -527,7 +528,7 @@ async def test_empty_results(self, mock_ctx):
@pytest.mark.asyncio
async def test_none_results(self, mock_ctx):
"""Test when API returns None."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=None)
@@ -538,7 +539,7 @@ async def test_none_results(self, mock_ctx):
@pytest.mark.asyncio
async def test_exception_returns_none(self, mock_ctx):
"""Test that exception returns None (lines 215-217)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=Exception("API Error"))
@@ -561,7 +562,7 @@ def mock_bot(self):
@pytest.mark.asyncio
async def test_successful_retrieval(self, mock_bot):
"""Test successful world name retrieval (lines 222-225)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value={"name": "Anvil Rock", "id": 1001})
@@ -572,7 +573,7 @@ async def test_successful_retrieval(self, mock_bot):
@pytest.mark.asyncio
async def test_no_result_returns_none(self, mock_bot):
"""Test that empty result returns None (line 225)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=None)
@@ -583,7 +584,7 @@ async def test_no_result_returns_none(self, mock_bot):
@pytest.mark.asyncio
async def test_result_without_name_key(self, mock_bot):
"""Test result dict without name key."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value={"id": 1001})
@@ -594,7 +595,7 @@ async def test_result_without_name_key(self, mock_bot):
@pytest.mark.asyncio
async def test_exception_returns_none(self, mock_bot):
"""Test that exception returns None (lines 227-229)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=Exception("API Error"))
@@ -628,7 +629,7 @@ async def test_gw2_activity_detected_triggers_handler(self, mock_bot):
before.activities = []
after.activities = [gw2_activity]
- with patch('src.gw2.tools.gw2_utils._handle_gw2_activity_change') as mock_handle:
+ with patch("src.gw2.tools.gw2_utils._handle_gw2_activity_change") as mock_handle:
mock_handle.return_value = None
await check_gw2_game_activity(mock_bot, before, after)
mock_handle.assert_called_once_with(mock_bot, after, gw2_activity)
@@ -646,7 +647,7 @@ async def test_no_gw2_activity_does_nothing(self, mock_bot):
before.activities = []
after.activities = [other_activity]
- with patch('src.gw2.tools.gw2_utils._handle_gw2_activity_change') as mock_handle:
+ with patch("src.gw2.tools.gw2_utils._handle_gw2_activity_change") as mock_handle:
await check_gw2_game_activity(mock_bot, before, after)
mock_handle.assert_not_called()
@@ -663,7 +664,7 @@ async def test_custom_activity_ignored(self, mock_bot):
before.activities = [custom_activity]
after.activities = [custom_activity]
- with patch('src.gw2.tools.gw2_utils._handle_gw2_activity_change') as mock_handle:
+ with patch("src.gw2.tools.gw2_utils._handle_gw2_activity_change") as mock_handle:
await check_gw2_game_activity(mock_bot, before, after)
mock_handle.assert_not_called()
@@ -691,7 +692,7 @@ def mock_member(self):
@pytest.mark.asyncio
async def test_no_server_configs_returns(self, mock_bot, mock_member):
"""Test that no server configs returns early (line 281)."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=None)
@@ -699,13 +700,13 @@ async def test_no_server_configs_returns(self, mock_bot, mock_member):
await _handle_gw2_activity_change(mock_bot, mock_member, after_activity)
# Should not proceed to Gw2KeyDal
- with patch('src.gw2.tools.gw2_utils.Gw2KeyDal') as mock_key_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2KeyDal") as mock_key_dal:
mock_key_dal.assert_not_called()
@pytest.mark.asyncio
async def test_session_not_active_returns(self, mock_bot, mock_member):
"""Test that inactive session returns early (line 281)."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.get_gw2_server_configs = AsyncMock(return_value=[{"session": False}])
@@ -715,11 +716,11 @@ async def test_session_not_active_returns(self, mock_bot, mock_member):
@pytest.mark.asyncio
async def test_no_api_key_returns(self, mock_bot, mock_member):
"""Test that no API key returns early (lines 287-288)."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_configs = mock_dal.return_value
mock_configs.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.tools.gw2_utils.Gw2KeyDal') as mock_key_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2KeyDal") as mock_key_dal:
mock_key_instance = mock_key_dal.return_value
mock_key_instance.get_api_key_by_user = AsyncMock(return_value=None)
@@ -729,15 +730,15 @@ async def test_no_api_key_returns(self, mock_bot, mock_member):
@pytest.mark.asyncio
async def test_after_activity_not_none_starts_session(self, mock_bot, mock_member):
"""Test that non-None after_activity starts a session (lines 292-293)."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_configs = mock_dal.return_value
mock_configs.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.tools.gw2_utils.Gw2KeyDal') as mock_key_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2KeyDal") as mock_key_dal:
mock_key_instance = mock_key_dal.return_value
mock_key_instance.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-api-key-123"}])
- with patch('src.gw2.tools.gw2_utils.start_session') as mock_start:
+ with patch("src.gw2.tools.gw2_utils.start_session") as mock_start:
mock_start.return_value = None
after_activity = MagicMock() # Not None
@@ -748,15 +749,15 @@ async def test_after_activity_not_none_starts_session(self, mock_bot, mock_membe
@pytest.mark.asyncio
async def test_after_activity_none_ends_session(self, mock_bot, mock_member):
"""Test that None after_activity ends a session (lines 294-295)."""
- with patch('src.gw2.tools.gw2_utils.Gw2ConfigsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2ConfigsDal") as mock_dal:
mock_configs = mock_dal.return_value
mock_configs.get_gw2_server_configs = AsyncMock(return_value=[{"session": True}])
- with patch('src.gw2.tools.gw2_utils.Gw2KeyDal') as mock_key_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2KeyDal") as mock_key_dal:
mock_key_instance = mock_key_dal.return_value
mock_key_instance.get_api_key_by_user = AsyncMock(return_value=[{"key": "test-api-key-123"}])
- with patch('src.gw2.tools.gw2_utils.end_session') as mock_end:
+ with patch("src.gw2.tools.gw2_utils.end_session") as mock_end:
mock_end.return_value = None
await _handle_gw2_activity_change(mock_bot, mock_member, None)
@@ -785,13 +786,13 @@ def mock_member(self):
@pytest.mark.asyncio
async def test_get_user_stats_returns_none(self, mock_bot, mock_member):
"""Test that None user stats returns early (lines 300-302)."""
- with patch('src.gw2.tools.gw2_utils.get_user_stats') as mock_stats:
+ with patch("src.gw2.tools.gw2_utils.get_user_stats") as mock_stats:
mock_stats.return_value = None
await start_session(mock_bot, mock_member, "api-key")
# Should not proceed to session dal
- with patch('src.gw2.tools.gw2_utils.Gw2SessionsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2SessionsDal") as mock_dal:
mock_dal.assert_not_called()
@pytest.mark.asyncio
@@ -799,20 +800,20 @@ async def test_successful_start_session(self, mock_bot, mock_member):
"""Test successful session start (lines 304-309)."""
session_data = {"acc_name": "TestUser.1234", "wvw_rank": 50, "gold": 1000}
- with patch('src.gw2.tools.gw2_utils.get_user_stats') as mock_stats:
+ with patch("src.gw2.tools.gw2_utils.get_user_stats") as mock_stats:
mock_stats.return_value = session_data.copy()
- with patch('src.gw2.tools.gw2_utils.bot_utils.convert_datetime_to_str_short') as mock_convert:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.convert_datetime_to_str_short") as mock_convert:
mock_convert.return_value = "2023-01-01"
- with patch('src.gw2.tools.gw2_utils.bot_utils.get_current_date_time') as mock_time:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.get_current_date_time") as mock_time:
mock_time.return_value = datetime(2023, 1, 1, 12, 0, 0)
- with patch('src.gw2.tools.gw2_utils.Gw2SessionsDal') as mock_session_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2SessionsDal") as mock_session_dal:
mock_instance = mock_session_dal.return_value
mock_instance.insert_start_session = AsyncMock(return_value=42)
- with patch('src.gw2.tools.gw2_utils.insert_session_char') as mock_insert_char:
+ with patch("src.gw2.tools.gw2_utils.insert_session_char") as mock_insert_char:
mock_insert_char.return_value = None
await start_session(mock_bot, mock_member, "api-key")
@@ -846,7 +847,7 @@ def mock_member(self):
@pytest.mark.asyncio
async def test_get_user_stats_returns_none(self, mock_bot, mock_member):
"""Test that None user stats returns early (lines 314-316)."""
- with patch('src.gw2.tools.gw2_utils.get_user_stats') as mock_stats:
+ with patch("src.gw2.tools.gw2_utils.get_user_stats") as mock_stats:
mock_stats.return_value = None
await end_session(mock_bot, mock_member, "api-key")
@@ -856,20 +857,20 @@ async def test_successful_end_session(self, mock_bot, mock_member):
"""Test successful session end (lines 318-323)."""
session_data = {"acc_name": "TestUser.1234", "wvw_rank": 50, "gold": 1000}
- with patch('src.gw2.tools.gw2_utils.get_user_stats') as mock_stats:
+ with patch("src.gw2.tools.gw2_utils.get_user_stats") as mock_stats:
mock_stats.return_value = session_data.copy()
- with patch('src.gw2.tools.gw2_utils.bot_utils.convert_datetime_to_str_short') as mock_convert:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.convert_datetime_to_str_short") as mock_convert:
mock_convert.return_value = "2023-01-01"
- with patch('src.gw2.tools.gw2_utils.bot_utils.get_current_date_time') as mock_time:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.get_current_date_time") as mock_time:
mock_time.return_value = datetime(2023, 1, 1, 12, 0, 0)
- with patch('src.gw2.tools.gw2_utils.Gw2SessionsDal') as mock_session_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2SessionsDal") as mock_session_dal:
mock_instance = mock_session_dal.return_value
mock_instance.update_end_session = AsyncMock(return_value=42)
- with patch('src.gw2.tools.gw2_utils.insert_session_char') as mock_insert_char:
+ with patch("src.gw2.tools.gw2_utils.insert_session_char") as mock_insert_char:
mock_insert_char.return_value = None
await end_session(mock_bot, mock_member, "api-key")
@@ -896,7 +897,7 @@ def mock_bot(self):
@pytest.mark.asyncio
async def test_api_exception_returns_none(self, mock_bot):
"""Test that API exception returns None (lines 336-338)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=Exception("API Error"))
@@ -907,7 +908,7 @@ async def test_api_exception_returns_none(self, mock_bot):
@pytest.mark.asyncio
async def test_successful_stats_retrieval(self, mock_bot):
- """Test successful user stats retrieval (lines 328-344)."""
+ """Test successful user stats retrieval with legacy wvw_rank."""
account_data = {"name": "TestUser.1234", "wvw_rank": 50}
wallet_data = [
{"id": 1, "value": 50000}, # gold
@@ -919,7 +920,7 @@ async def test_successful_stats_retrieval(self, mock_bot):
{"id": 291, "current": 42}, # camps
]
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=[account_data, wallet_data, achievements_data])
@@ -934,6 +935,22 @@ async def test_successful_stats_retrieval(self, mock_bot):
assert result["players"] == 150
assert result["camps"] == 42
+ @pytest.mark.asyncio
+ async def test_successful_stats_retrieval_new_wvw_format(self, mock_bot):
+ """Test user stats retrieval with new wvw.rank format."""
+ account_data = {"name": "TestUser.1234", "wvw": {"rank": 200}}
+ wallet_data = []
+ achievements_data = []
+
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
+ mock_client = mock_client_class.return_value
+ mock_client.call_api = AsyncMock(side_effect=[account_data, wallet_data, achievements_data])
+
+ result = await get_user_stats(mock_bot, "api-key")
+
+ assert result is not None
+ assert result["wvw_rank"] == 200
+
@pytest.mark.asyncio
async def test_stats_with_all_wallet_items(self, mock_bot):
"""Test stats with all wallet items populated."""
@@ -950,7 +967,7 @@ async def test_stats_with_all_wallet_items(self, mock_bot):
]
achievements_data = []
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=[account_data, wallet_data, achievements_data])
@@ -1155,12 +1172,12 @@ 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)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ 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}]
mock_client.call_api = AsyncMock(return_value=characters_data)
- with patch('src.gw2.tools.gw2_utils.Gw2SessionCharsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2SessionCharsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.insert_session_char = AsyncMock()
@@ -1182,11 +1199,11 @@ async def test_successful_insert(self, mock_bot, mock_member):
@pytest.mark.asyncio
async def test_insert_end_session_type(self, mock_bot, mock_member):
"""Test insert with end session type."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=[])
- with patch('src.gw2.tools.gw2_utils.Gw2SessionCharsDal') as mock_dal:
+ with patch("src.gw2.tools.gw2_utils.Gw2SessionCharsDal") as mock_dal:
mock_instance = mock_dal.return_value
mock_instance.insert_session_char = AsyncMock()
@@ -1200,7 +1217,7 @@ async def test_insert_end_session_type(self, mock_bot, mock_member):
@pytest.mark.asyncio
async def test_exception_logs_error(self, mock_bot, mock_member):
"""Test that exception is caught and logged (lines 430-431)."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=Exception("API Error"))
@@ -1510,7 +1527,7 @@ def mock_ctx_dm(self):
@pytest.mark.asyncio
async def test_delete_api_key_in_guild(self, mock_ctx_guild):
"""Test deleting API key message in guild."""
- with patch('src.gw2.tools.gw2_utils.send_msg') as mock_send:
+ with patch("src.gw2.tools.gw2_utils.send_msg") as mock_send:
await delete_api_key(mock_ctx_guild, message=True)
mock_ctx_guild.message.delete.assert_called_once()
@@ -1528,14 +1545,14 @@ async def test_delete_api_key_http_exception(self, mock_ctx_guild):
side_effect=discord.HTTPException(response=MagicMock(), message="Forbidden")
)
- with patch('src.gw2.tools.gw2_utils.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.send_error_msg") as mock_error:
await delete_api_key(mock_ctx_guild, message=True)
mock_error.assert_called_once()
@pytest.mark.asyncio
async def test_delete_api_key_no_message_flag(self, mock_ctx_guild):
"""Test delete without message flag."""
- with patch('src.gw2.tools.gw2_utils.send_msg') as mock_send:
+ with patch("src.gw2.tools.gw2_utils.send_msg") as mock_send:
await delete_api_key(mock_ctx_guild, message=False)
mock_ctx_guild.message.delete.assert_called_once()
@@ -1581,7 +1598,7 @@ def mock_ctx(self):
@pytest.mark.asyncio
async def test_get_worlds_ids_success(self, mock_ctx):
"""Test successful world IDs retrieval."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(return_value=[{"id": 1001, "name": "Anvil Rock"}])
@@ -1594,11 +1611,11 @@ async def test_get_worlds_ids_success(self, mock_ctx):
@pytest.mark.asyncio
async def test_get_worlds_ids_api_error(self, mock_ctx):
"""Test get_worlds_ids with API error."""
- with patch('src.gw2.tools.gw2_utils.Gw2Client') as mock_client_class:
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
mock_client = mock_client_class.return_value
mock_client.call_api = AsyncMock(side_effect=APIConnectionError(mock_ctx.bot, "API Error"))
- with patch('src.gw2.tools.gw2_utils.bot_utils.send_error_msg') as mock_error:
+ with patch("src.gw2.tools.gw2_utils.bot_utils.send_error_msg") as mock_error:
success, results = await get_worlds_ids(mock_ctx)
assert success is False
@@ -1679,7 +1696,7 @@ class TestCreateInitialUserStats:
"""Test cases for _create_initial_user_stats function."""
def test_creates_correct_structure(self):
- """Test that initial stats structure is correct."""
+ """Test that initial stats structure is correct with legacy wvw_rank."""
account_data = {"name": "TestUser.1234", "wvw_rank": 75}
result = _create_initial_user_stats(account_data)
@@ -1701,3 +1718,80 @@ def test_creates_correct_structure(self):
assert result["castles"] == 0
assert result["towers"] == 0
assert result["keeps"] == 0
+
+ def test_new_wvw_rank_format(self):
+ """Test that wvw.rank (new API format) is preferred over wvw_rank."""
+ account_data = {"name": "TestUser.1234", "wvw": {"rank": 200}, "wvw_rank": 75}
+
+ result = _create_initial_user_stats(account_data)
+
+ assert result["wvw_rank"] == 200
+
+ def test_fallback_to_legacy_wvw_rank(self):
+ """Test fallback to wvw_rank when wvw.rank is absent."""
+ account_data = {"name": "TestUser.1234", "wvw_rank": 75}
+
+ result = _create_initial_user_stats(account_data)
+
+ assert result["wvw_rank"] == 75
+
+ def test_no_wvw_rank_defaults_to_zero(self):
+ """Test that missing both wvw.rank and wvw_rank defaults to 0."""
+ account_data = {"name": "TestUser.1234"}
+
+ result = _create_initial_user_stats(account_data)
+
+ assert result["wvw_rank"] == 0
+
+ def test_wvw_rank_zero_in_new_format_falls_back(self):
+ """Test that wvw.rank=0 (falsy) falls back to wvw_rank."""
+ account_data = {"name": "TestUser.1234", "wvw": {"rank": 0}, "wvw_rank": 50}
+
+ result = _create_initial_user_stats(account_data)
+
+ # 0 is falsy, so it falls back to wvw_rank
+ assert result["wvw_rank"] == 50
+
+
+class TestGetWorldNamePopulationWithWR:
+ """Test cases for get_world_name_population with WR team IDs."""
+
+ @pytest.fixture
+ def mock_ctx(self):
+ """Create a mock command context."""
+ ctx = MagicMock()
+ ctx.bot = MagicMock()
+ ctx.bot.log = MagicMock()
+ return ctx
+
+ @pytest.mark.asyncio
+ async def test_wr_team_ids_only(self, mock_ctx):
+ """Test resolving only WR team IDs (no API call needed)."""
+ result = await get_world_name_population(mock_ctx, "11001,12001")
+
+ assert result is not None
+ assert len(result) == 2
+ assert "Team 1 (NA)" in result
+ assert "Team 1 (EU)" in result
+
+ @pytest.mark.asyncio
+ async def test_mixed_legacy_and_wr_ids(self, mock_ctx):
+ """Test resolving a mix of legacy world IDs and WR team IDs."""
+ with patch("src.gw2.tools.gw2_utils.Gw2Client") as mock_client_class:
+ mock_client = mock_client_class.return_value
+ mock_client.call_api = AsyncMock(return_value=[{"id": 1001, "name": "Anvil Rock", "population": "High"}])
+
+ result = await get_world_name_population(mock_ctx, "1001,11005")
+
+ assert result is not None
+ assert len(result) == 2
+ assert result[0] == "Anvil Rock"
+ assert result[1] == "Team 5 (NA)"
+
+ @pytest.mark.asyncio
+ async def test_unknown_wr_team_id(self, mock_ctx):
+ """Test resolving an unknown WR team ID uses fallback name."""
+ result = await get_world_name_population(mock_ctx, "11999")
+
+ assert result is not None
+ assert result[0] == "Team 11999"
diff --git a/uv.lock b/uv.lock
index d1255877..06fc7d4c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -204,28 +204,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/dd/0b074d89e903cc771721cde2c4bf3d8c9d114b5bd791af5c62bcf5fb9459/better_profanity-0.7.0-py3-none-any.whl", hash = "sha256:bd4c529ea6aa2db1aaa50524be1ed14d0fe5c664f1fd88c8bc388c7e9f9f00e8", size = 46104, upload-time = "2020-11-02T10:49:56.066Z" },
]
-[[package]]
-name = "black"
-version = "26.1.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "click" },
- { name = "mypy-extensions" },
- { name = "packaging" },
- { name = "pathspec" },
- { name = "platformdirs" },
- { name = "pytokens" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" },
- { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" },
- { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" },
- { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" },
- { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" },
- { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" },
-]
-
[[package]]
name = "certifi"
version = "2026.1.4"
@@ -387,7 +365,7 @@ wheels = [
[[package]]
name = "discordbot"
-version = "3.0.1"
+version = "3.0.2"
source = { virtual = "." }
dependencies = [
{ name = "alembic" },
@@ -403,7 +381,6 @@ dependencies = [
[package.dev-dependencies]
dev = [
- { name = "black" },
{ name = "poethepoet" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
@@ -419,18 +396,17 @@ requires-dist = [
{ name = "ddcdatabases", extras = ["postgres"], specifier = ">=3.0.10" },
{ name = "discord-py", specifier = ">=2.6.4" },
{ name = "gtts", specifier = ">=2.5.4" },
- { name = "openai", specifier = ">=2.20.0" },
+ { name = "openai", specifier = ">=2.21.0" },
{ name = "pynacl", specifier = ">=1.6.2" },
{ name = "pythonlogs", specifier = ">=6.0.2" },
]
[package.metadata.requires-dev]
dev = [
- { name = "black", specifier = ">=26.1.0" },
{ name = "poethepoet", specifier = ">=0.41.0" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "pytest-cov", specifier = ">=7.0.0" },
- { name = "ruff", specifier = ">=0.15.0" },
+ { name = "ruff", specifier = ">=0.15.1" },
{ name = "testcontainers", extras = ["postgres"], specifier = ">=4.14.1" },
]
@@ -711,18 +687,9 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
]
-[[package]]
-name = "mypy-extensions"
-version = "1.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
-]
-
[[package]]
name = "openai"
-version = "2.20.0"
+version = "2.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -734,9 +701,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6e/5a/f495777c02625bfa18212b6e3b73f1893094f2bf660976eb4bc6f43a1ca2/openai-2.20.0.tar.gz", hash = "sha256:2654a689208cd0bf1098bb9462e8d722af5cbe961e6bba54e6f19fb843d88db1", size = 642355, upload-time = "2026-02-10T19:02:54.145Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/a0/cf4297aa51bbc21e83ef0ac018947fa06aea8f2364aad7c96cbf148590e6/openai-2.20.0-py3-none-any.whl", hash = "sha256:38d989c4b1075cd1f76abc68364059d822327cf1a932531d429795f4fc18be99", size = 1098479, upload-time = "2026-02-10T19:02:52.157Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" },
]
[[package]]
@@ -757,24 +724,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" },
]
-[[package]]
-name = "pathspec"
-version = "1.0.4"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
-]
-
-[[package]]
-name = "platformdirs"
-version = "4.6.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/20/e5/474d0a8508029286b905622e6929470fb84337cfa08f9d09fbb624515249/platformdirs-4.6.0.tar.gz", hash = "sha256:4a13c2db1071e5846c3b3e04e5b095c0de36b2a24be9a3bc0145ca66fce4e328", size = 23433, upload-time = "2026-02-12T14:36:21.288Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/da/10/1b0dcf51427326f70e50d98df21b18c228117a743a1fc515a42f8dc7d342/platformdirs-4.6.0-py3-none-any.whl", hash = "sha256:dd7f808d828e1764a22ebff09e60f175ee3c41876606a6132a688d809c7c9c73", size = 19549, upload-time = "2026-02-12T14:36:19.743Z" },
-]
-
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -936,16 +885,16 @@ wheels = [
[[package]]
name = "pydantic-settings"
-version = "2.12.0"
+version = "2.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/a1/ae859ffac5a3338a66b74c5e29e244fd3a3cc483c89feaf9f56c39898d75/pydantic_settings-2.13.0.tar.gz", hash = "sha256:95d875514610e8595672800a5c40b073e99e4aae467fa7c8f9c263061ea2e1fe", size = 222450, upload-time = "2026-02-15T12:11:23.476Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1a/dd1b9d7e627486cf8e7523d09b70010e05a4bc41414f4ae6ce184cf0afb6/pydantic_settings-2.13.0-py3-none-any.whl", hash = "sha256:d67b576fff39cd086b595441bf9c75d4193ca9c0ed643b90360694d0f1240246", size = 58429, upload-time = "2026-02-15T12:11:22.133Z" },
]
[[package]]
@@ -1055,25 +1004,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/60/fdafb69e7f6b99455c1f74d835484844d297a9f1027cbb6a76a11f5ef9b7/pythonlogs-6.0.2-py3-none-any.whl", hash = "sha256:ff227f60bdc7aa091ade76e14f41b3b706f3ef1e2260373e5e50f610fda773c4", size = 25299, upload-time = "2026-02-09T17:24:32.649Z" },
]
-[[package]]
-name = "pytokens"
-version = "0.4.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
- { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
- { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
- { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
- { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
- { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
- { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
- { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
- { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
- { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
- { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
-]
-
[[package]]
name = "pywin32"
version = "311"
@@ -1127,27 +1057,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.15.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" },
- { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" },
- { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" },
- { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" },
- { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" },
- { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" },
- { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" },
- { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" },
- { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" },
- { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" },
- { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" },
- { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" },
- { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" },
- { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" },
- { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" },
- { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" },
- { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" },
+version = "0.15.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
+ { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
+ { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
]
[[package]]