update
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 4m49s
CI/CD Pipeline / Security Scanning (push) Successful in 15s
CI/CD Pipeline / Tests (3.11) (push) Successful in 9m41s
CI/CD Pipeline / Tests (3.12) (push) Successful in 9m36s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
Dependency Updates / Update Dependencies (push) Successful in 29s

This commit is contained in:
2026-01-17 21:57:04 +01:00
parent 831eed8dbc
commit abef368a68
19 changed files with 677 additions and 757 deletions

View File

@@ -7,11 +7,11 @@ import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
from typing import AsyncGenerator
from unittest.mock import AsyncMock, MagicMock
import pytest
from sqlalchemy import create_engine
from sqlalchemy import create_engine, event, text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
@@ -23,7 +23,7 @@ if str(SRC_DIR) not in sys.path:
# Import after path setup
from guardden.config import Settings
from guardden.models.base import Base
from guardden.models.guild import Guild, GuildSettings, BannedWord
from guardden.models.guild import BannedWord, Guild, GuildSettings
from guardden.models.moderation import ModerationLog, Strike, UserNote
from guardden.services.database import Database
@@ -52,6 +52,7 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> bool | None:
# Basic Test Fixtures
# ==============================================================================
@pytest.fixture
def sample_guild_id() -> int:
"""Return a sample Discord guild ID."""
@@ -80,11 +81,12 @@ def sample_owner_id() -> int:
# Configuration Fixtures
# ==============================================================================
@pytest.fixture
def test_settings() -> Settings:
"""Return test configuration settings."""
return Settings(
discord_token="test_token_12345678901234567890",
discord_token="a" * 60,
discord_prefix="!test",
database_url="sqlite+aiosqlite:///test.db",
database_pool_min=1,
@@ -101,6 +103,7 @@ def test_settings() -> Settings:
# Database Fixtures
# ==============================================================================
@pytest.fixture
async def test_database(test_settings: Settings) -> AsyncGenerator[Database, None]:
"""Create a test database with in-memory SQLite."""
@@ -111,19 +114,26 @@ async def test_database(test_settings: Settings) -> AsyncGenerator[Database, Non
poolclass=StaticPool,
echo=False,
)
@event.listens_for(engine.sync_engine, "connect")
def _enable_sqlite_foreign_keys(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
# Create all tables
async with engine.begin() as conn:
await conn.execute(text("PRAGMA foreign_keys=ON"))
await conn.run_sync(Base.metadata.create_all)
database = Database(test_settings)
database._engine = engine
database._session_factory = async_sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
yield database
await engine.dispose()
@@ -138,10 +148,9 @@ async def db_session(test_database: Database) -> AsyncGenerator[AsyncSession, No
# Model Fixtures
# ==============================================================================
@pytest.fixture
async def test_guild(
db_session: AsyncSession, sample_guild_id: int, sample_owner_id: int
) -> Guild:
async def test_guild(db_session: AsyncSession, sample_guild_id: int, sample_owner_id: int) -> Guild:
"""Create a test guild with settings."""
guild = Guild(
id=sample_guild_id,
@@ -150,7 +159,7 @@ async def test_guild(
premium=False,
)
db_session.add(guild)
# Create associated settings
settings = GuildSettings(
guild_id=sample_guild_id,
@@ -160,7 +169,7 @@ async def test_guild(
verification_enabled=False,
)
db_session.add(settings)
await db_session.commit()
await db_session.refresh(guild)
return guild
@@ -187,10 +196,7 @@ async def test_banned_word(
@pytest.fixture
async def test_moderation_log(
db_session: AsyncSession,
test_guild: Guild,
sample_user_id: int,
sample_moderator_id: int
db_session: AsyncSession, test_guild: Guild, sample_user_id: int, sample_moderator_id: int
) -> ModerationLog:
"""Create a test moderation log entry."""
mod_log = ModerationLog(
@@ -211,10 +217,7 @@ async def test_moderation_log(
@pytest.fixture
async def test_strike(
db_session: AsyncSession,
test_guild: Guild,
sample_user_id: int,
sample_moderator_id: int
db_session: AsyncSession, test_guild: Guild, sample_user_id: int, sample_moderator_id: int
) -> Strike:
"""Create a test strike."""
strike = Strike(
@@ -236,6 +239,7 @@ async def test_strike(
# Discord Mock Fixtures
# ==============================================================================
@pytest.fixture
def mock_discord_user(sample_user_id: int) -> MagicMock:
"""Create a mock Discord user."""
@@ -261,7 +265,7 @@ def mock_discord_member(mock_discord_user: MagicMock) -> MagicMock:
member.avatar = mock_discord_user.avatar
member.bot = mock_discord_user.bot
member.send = mock_discord_user.send
# Member-specific attributes
member.guild = MagicMock()
member.top_role = MagicMock()
@@ -271,7 +275,7 @@ def mock_discord_member(mock_discord_user: MagicMock) -> MagicMock:
member.kick = AsyncMock()
member.ban = AsyncMock()
member.timeout = AsyncMock()
return member
@@ -284,14 +288,14 @@ def mock_discord_guild(sample_guild_id: int, sample_owner_id: int) -> MagicMock:
guild.owner_id = sample_owner_id
guild.member_count = 100
guild.premium_tier = 0
# Methods
guild.get_member = MagicMock(return_value=None)
guild.get_channel = MagicMock(return_value=None)
guild.leave = AsyncMock()
guild.ban = AsyncMock()
guild.unban = AsyncMock()
return guild
@@ -327,9 +331,7 @@ def mock_discord_message(
@pytest.fixture
def mock_discord_context(
mock_discord_member: MagicMock,
mock_discord_guild: MagicMock,
mock_discord_channel: MagicMock
mock_discord_member: MagicMock, mock_discord_guild: MagicMock, mock_discord_channel: MagicMock
) -> MagicMock:
"""Create a mock Discord command context."""
ctx = MagicMock()
@@ -345,6 +347,7 @@ def mock_discord_context(
# Bot and Service Fixtures
# ==============================================================================
@pytest.fixture
def mock_bot(test_database: Database) -> MagicMock:
"""Create a mock GuardDen bot."""
@@ -363,6 +366,7 @@ def mock_bot(test_database: Database) -> MagicMock:
# Test Environment Setup
# ==============================================================================
@pytest.fixture(autouse=True)
def setup_test_environment() -> None:
"""Set up test environment variables."""