quick 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) Failing after 4m58s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m0s
CI/CD Pipeline / Build Docker Image (push) Has been skipped

This commit is contained in:
2026-01-24 19:14:33 +01:00
parent 574a07d127
commit 824dd681f7
6 changed files with 360 additions and 8 deletions

View File

@@ -21,7 +21,7 @@ if str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))
# Import after path setup
from guardden.config import Settings
from guardden.config import GuildDefaults, Settings
from guardden.models.base import Base
from guardden.models.guild import BannedWord, Guild, GuildSettings
from guardden.models.moderation import ModerationLog, Strike, UserNote
@@ -99,6 +99,31 @@ def test_settings() -> Settings:
)
@pytest.fixture
def settings_with_custom_defaults() -> Settings:
"""Return test settings with custom guild defaults."""
custom_defaults = GuildDefaults(
prefix="?",
ai_sensitivity=50,
automod_enabled=False,
verification_enabled=True,
verification_type="captcha",
)
return Settings(
discord_token="a" * 60,
discord_prefix="!test",
database_url="sqlite+aiosqlite:///test.db",
database_pool_min=1,
database_pool_max=1,
ai_provider="none",
log_level="DEBUG",
allowed_guilds=[],
owner_ids=[],
data_dir=Path("/tmp/guardden_test"),
guild_default=custom_defaults,
)
# ==============================================================================
# Database Fixtures
# ==============================================================================

View File

@@ -3,7 +3,7 @@
import pytest
from pydantic import ValidationError
from guardden.config import Settings, _parse_id_list, _validate_discord_id
from guardden.config import GuildDefaults, Settings, _parse_id_list, _validate_discord_id
from guardden.services.automod import normalize_domain
@@ -244,3 +244,143 @@ class TestSecurityImprovements:
os.environ["GUARDDEN_OWNER_IDS"] = original_owners
else:
os.environ.pop("GUARDDEN_OWNER_IDS", None)
class TestGuildDefaultsValidation:
"""Test GuildDefaults model validation."""
def test_default_values(self):
"""Test default factory creates valid GuildDefaults."""
defaults = GuildDefaults()
assert defaults.prefix == "!"
assert defaults.locale == "en"
assert defaults.automod_enabled is True
assert defaults.ai_sensitivity == 80
assert defaults.ai_confidence_threshold == 0.7
assert defaults.verification_type == "button"
def test_ai_sensitivity_valid_range(self):
"""Test ai_sensitivity accepts values 0-100."""
assert GuildDefaults(ai_sensitivity=0).ai_sensitivity == 0
assert GuildDefaults(ai_sensitivity=50).ai_sensitivity == 50
assert GuildDefaults(ai_sensitivity=100).ai_sensitivity == 100
def test_ai_sensitivity_invalid_range(self):
"""Test ai_sensitivity rejects values outside 0-100."""
with pytest.raises(ValidationError):
GuildDefaults(ai_sensitivity=-1)
with pytest.raises(ValidationError):
GuildDefaults(ai_sensitivity=101)
def test_ai_confidence_threshold_valid_range(self):
"""Test ai_confidence_threshold accepts values 0.0-1.0."""
assert GuildDefaults(ai_confidence_threshold=0.0).ai_confidence_threshold == 0.0
assert GuildDefaults(ai_confidence_threshold=0.5).ai_confidence_threshold == 0.5
assert GuildDefaults(ai_confidence_threshold=1.0).ai_confidence_threshold == 1.0
def test_ai_confidence_threshold_invalid_range(self):
"""Test ai_confidence_threshold rejects values outside 0.0-1.0."""
with pytest.raises(ValidationError):
GuildDefaults(ai_confidence_threshold=-0.1)
with pytest.raises(ValidationError):
GuildDefaults(ai_confidence_threshold=1.1)
def test_verification_type_valid_values(self):
"""Test verification_type only accepts valid types."""
valid_types = ["button", "captcha", "math", "emoji"]
for vtype in valid_types:
assert GuildDefaults(verification_type=vtype).verification_type == vtype
def test_verification_type_invalid_values(self):
"""Test verification_type rejects invalid types."""
with pytest.raises(ValidationError):
GuildDefaults(verification_type="invalid")
with pytest.raises(ValidationError):
GuildDefaults(verification_type="")
def test_positive_rate_limits(self):
"""Test rate limit fields must be positive."""
# Valid positive values
defaults = GuildDefaults(
message_rate_limit=1,
message_rate_window=1,
duplicate_threshold=1,
mention_limit=1,
mention_rate_limit=1,
mention_rate_window=1,
)
assert defaults.message_rate_limit == 1
# Invalid zero or negative values
with pytest.raises(ValidationError):
GuildDefaults(message_rate_limit=0)
with pytest.raises(ValidationError):
GuildDefaults(message_rate_window=-1)
def test_prefix_length_constraints(self):
"""Test prefix has length constraints."""
# Valid prefixes
assert GuildDefaults(prefix="!").prefix == "!"
assert GuildDefaults(prefix="??").prefix == "??"
assert GuildDefaults(prefix="!" * 10).prefix == "!" * 10
# Invalid: empty prefix
with pytest.raises(ValidationError):
GuildDefaults(prefix="")
# Invalid: too long
with pytest.raises(ValidationError):
GuildDefaults(prefix="!" * 11)
class TestSettingsGuildDefaults:
"""Test Settings.guild_default field."""
def test_guild_default_factory(self):
"""Test guild_default uses factory default."""
settings = Settings(discord_token="a" * 60)
assert settings.guild_default is not None
assert isinstance(settings.guild_default, GuildDefaults)
assert settings.guild_default.prefix == "!"
def test_guild_default_custom_values(self):
"""Test guild_default can be set with custom values."""
custom_defaults = GuildDefaults(
prefix="?",
ai_sensitivity=50,
verification_enabled=True,
)
settings = Settings(discord_token="a" * 60, guild_default=custom_defaults)
assert settings.guild_default.prefix == "?"
assert settings.guild_default.ai_sensitivity == 50
assert settings.guild_default.verification_enabled is True
def test_strike_actions_default(self):
"""Test strike_actions has correct default structure."""
defaults = GuildDefaults()
assert defaults.strike_actions == {
"1": {"action": "warn"},
"3": {"action": "timeout", "duration": 3600},
"5": {"action": "kick"},
"7": {"action": "ban"},
}
def test_strike_actions_custom(self):
"""Test strike_actions can be customized."""
custom_actions = {
"1": {"action": "warn"},
"5": {"action": "ban"},
}
defaults = GuildDefaults(strike_actions=custom_actions)
assert defaults.strike_actions == custom_actions
def test_scam_allowlist_default(self):
"""Test scam_allowlist defaults to empty list."""
defaults = GuildDefaults()
assert defaults.scam_allowlist == []
def test_scam_allowlist_custom(self):
"""Test scam_allowlist can be customized."""
custom_list = ["discord.com", "github.com"]
defaults = GuildDefaults(scam_allowlist=custom_list)
assert defaults.scam_allowlist == custom_list

View File

@@ -1,6 +1,7 @@
"""Tests for database integration and models."""
from datetime import datetime, timezone
from unittest.mock import MagicMock
import pytest
from sqlalchemy import select
@@ -8,6 +9,7 @@ from sqlalchemy import select
from guardden.models.guild import BannedWord, Guild, GuildSettings
from guardden.models.moderation import ModerationLog, Strike, UserNote
from guardden.services.database import Database
from guardden.services.guild_config import GuildConfigService
class TestDatabaseModels:
@@ -312,3 +314,106 @@ class TestDatabaseSecurity:
db_session.add(banned_word)
await db_session.commit()
await db_session.rollback()
class TestGuildConfigServiceWithDefaults:
"""Test GuildConfigService.create_guild() with settings defaults."""
async def test_create_guild_uses_settings_defaults(
self, test_database, settings_with_custom_defaults, sample_guild_id, sample_owner_id
):
"""Test create_guild applies settings.guild_default values."""
service = GuildConfigService(test_database, settings=settings_with_custom_defaults)
# Create mock Discord guild
mock_guild = MagicMock()
mock_guild.id = sample_guild_id
mock_guild.name = "Test Guild"
mock_guild.owner_id = sample_owner_id
# Create guild
db_guild = await service.create_guild(mock_guild)
# Verify guild was created
assert db_guild.id == sample_guild_id
assert db_guild.name == "Test Guild"
# Get settings and verify defaults were applied
guild_settings = await service.get_config(sample_guild_id)
assert guild_settings is not None
assert guild_settings.prefix == "?" # Custom default
assert guild_settings.ai_sensitivity == 50 # Custom default
assert guild_settings.automod_enabled is False # Custom default
assert guild_settings.verification_enabled is True # Custom default
assert guild_settings.verification_type == "captcha" # Custom default
async def test_create_guild_without_settings(
self, test_database, sample_guild_id, sample_owner_id
):
"""Test create_guild works when settings is None."""
service = GuildConfigService(test_database, settings=None)
# Create mock Discord guild
mock_guild = MagicMock()
mock_guild.id = sample_guild_id
mock_guild.name = "Test Guild"
mock_guild.owner_id = sample_owner_id
# Create guild
db_guild = await service.create_guild(mock_guild)
# Verify guild was created
assert db_guild.id == sample_guild_id
# Get settings and verify hardcoded defaults were used
guild_settings = await service.get_config(sample_guild_id)
assert guild_settings is not None
assert guild_settings.prefix == "!" # Hardcoded default
assert guild_settings.ai_sensitivity == 80 # Hardcoded default
assert guild_settings.automod_enabled is True # Hardcoded default
async def test_create_guild_existing_guild_unchanged(
self, test_database, settings_with_custom_defaults, sample_guild_id, sample_owner_id
):
"""Test create_guild returns existing guild without changes."""
service = GuildConfigService(test_database, settings=settings_with_custom_defaults)
# Create mock Discord guild
mock_guild = MagicMock()
mock_guild.id = sample_guild_id
mock_guild.name = "Test Guild"
mock_guild.owner_id = sample_owner_id
# Create guild first time
first_guild = await service.create_guild(mock_guild)
assert first_guild.id == sample_guild_id
# Try to create again with different name
mock_guild.name = "Different Name"
second_guild = await service.create_guild(mock_guild)
# Should return existing guild
assert second_guild.id == first_guild.id
assert second_guild.name == "Test Guild" # Original name
async def test_create_guild_with_standard_settings(
self, test_database, test_settings, sample_guild_id, sample_owner_id
):
"""Test create_guild with standard test_settings fixture."""
service = GuildConfigService(test_database, settings=test_settings)
# Create mock Discord guild
mock_guild = MagicMock()
mock_guild.id = sample_guild_id
mock_guild.name = "Test Guild"
mock_guild.owner_id = sample_owner_id
# Create guild
await service.create_guild(mock_guild)
# Get settings and verify standard defaults
guild_settings = await service.get_config(sample_guild_id)
assert guild_settings is not None
# Standard settings use default GuildDefaults
assert guild_settings.prefix == "!"
assert guild_settings.ai_sensitivity == 80