Files
GuardDen/tests/test_file_config.py

294 lines
10 KiB
Python

"""Tests for file-based configuration system."""
import asyncio
import tempfile
import yaml
from pathlib import Path
from unittest.mock import patch
import pytest
from guardden.services.file_config import FileConfigurationManager, ConfigurationError
class TestFileConfigurationManager:
"""Tests for the file-based configuration manager."""
@pytest.fixture
def temp_config_dir(self):
"""Create a temporary configuration directory."""
with tempfile.TemporaryDirectory() as temp_dir:
yield temp_dir
@pytest.fixture
async def config_manager(self, temp_config_dir):
"""Create a configuration manager with temporary directory."""
manager = FileConfigurationManager(temp_config_dir)
await manager.initialize()
yield manager
await manager.shutdown()
def test_directory_creation(self, temp_config_dir):
"""Test that required directories are created."""
manager = FileConfigurationManager(temp_config_dir)
expected_dirs = [
"guilds",
"wordlists",
"schemas",
"templates",
"backups"
]
for dir_name in expected_dirs:
assert (Path(temp_config_dir) / dir_name).exists()
@pytest.mark.asyncio
async def test_guild_config_creation(self, config_manager, temp_config_dir):
"""Test creating a new guild configuration."""
guild_id = 123456789
name = "Test Guild"
owner_id = 987654321
file_path = await config_manager.create_guild_config(guild_id, name, owner_id)
assert file_path.exists()
assert file_path.name == f"guild-{guild_id}.yml"
# Verify content
with open(file_path, 'r') as f:
content = yaml.safe_load(f)
assert content["guild_id"] == guild_id
assert content["name"] == name
assert content["owner_id"] == owner_id
assert "settings" in content
@pytest.mark.asyncio
async def test_duplicate_guild_config_creation(self, config_manager):
"""Test that creating duplicate guild configs raises error."""
guild_id = 123456789
name = "Test Guild"
# Create first config
await config_manager.create_guild_config(guild_id, name)
# Attempt to create duplicate should raise error
with pytest.raises(ConfigurationError):
await config_manager.create_guild_config(guild_id, name)
@pytest.mark.asyncio
async def test_guild_config_loading(self, config_manager, temp_config_dir):
"""Test loading guild configuration from file."""
guild_id = 123456789
# Create config file manually
config_data = {
"guild_id": guild_id,
"name": "Test Guild",
"premium": False,
"settings": {
"general": {
"prefix": "!",
"locale": "en"
},
"ai_moderation": {
"enabled": True,
"sensitivity": 80,
"nsfw_only_filtering": True
}
}
}
guild_dir = Path(temp_config_dir) / "guilds"
guild_file = guild_dir / f"guild-{guild_id}.yml"
with open(guild_file, 'w') as f:
yaml.dump(config_data, f)
# Load config
await config_manager._load_guild_config(guild_file)
# Verify loaded config
config = config_manager.get_guild_config(guild_id)
assert config is not None
assert config.guild_id == guild_id
assert config.name == "Test Guild"
assert config.settings["ai_moderation"]["nsfw_only_filtering"] is True
@pytest.mark.asyncio
async def test_invalid_config_validation(self, config_manager, temp_config_dir):
"""Test that invalid configurations are rejected."""
guild_id = 123456789
# Create invalid config (missing required fields)
invalid_config = {
"name": "Test Guild",
# Missing guild_id
"settings": {}
}
guild_dir = Path(temp_config_dir) / "guilds"
guild_file = guild_dir / f"guild-{guild_id}.yml"
with open(guild_file, 'w') as f:
yaml.dump(invalid_config, f)
# Should return None for invalid config
result = await config_manager._load_guild_config(guild_file)
assert result is None
def test_configuration_validation(self, config_manager):
"""Test configuration validation against schema."""
valid_config = {
"guild_id": 123456789,
"name": "Test Guild",
"settings": {
"general": {"prefix": "!", "locale": "en"},
"channels": {"log_channel_id": None},
"roles": {"mod_role_ids": []},
"moderation": {"automod_enabled": True},
"automod": {"message_rate_limit": 5},
"ai_moderation": {"enabled": True},
"verification": {"enabled": False}
}
}
# Should return no errors for valid config
errors = config_manager.validate_config(valid_config)
assert len(errors) == 0
# Invalid config should return errors
invalid_config = {
"guild_id": "not-a-number", # Should be integer
"name": "Test Guild"
# Missing required settings
}
errors = config_manager.validate_config(invalid_config)
assert len(errors) > 0
@pytest.mark.asyncio
async def test_wordlist_config_loading(self, config_manager, temp_config_dir):
"""Test loading wordlist configurations."""
wordlist_dir = Path(temp_config_dir) / "wordlists"
# Create banned words config
banned_words_config = {
"global_patterns": [
{
"pattern": "badword",
"action": "delete",
"is_regex": False,
"category": "profanity"
}
]
}
with open(wordlist_dir / "banned-words.yml", 'w') as f:
yaml.dump(banned_words_config, f)
# Create allowlist config
allowlist_config = {
"global_allowlist": [
{
"domain": "discord.com",
"reason": "Official Discord domain"
}
]
}
with open(wordlist_dir / "domain-allowlists.yml", 'w') as f:
yaml.dump(allowlist_config, f)
# Load configs
await config_manager._load_wordlist_configs()
# Verify loaded configs
wordlist_config = config_manager.get_wordlist_config()
assert wordlist_config is not None
assert "global_patterns" in wordlist_config
assert len(wordlist_config["global_patterns"]) == 1
allowlist = config_manager.get_allowlist_config()
assert allowlist is not None
assert "global_allowlist" in allowlist
assert len(allowlist["global_allowlist"]) == 1
@pytest.mark.asyncio
async def test_config_backup(self, config_manager, temp_config_dir):
"""Test configuration backup functionality."""
guild_id = 123456789
# Create a guild config
await config_manager.create_guild_config(guild_id, "Test Guild")
# Create backup
backup_path = await config_manager.backup_config(guild_id)
assert backup_path.exists()
assert "backup" in backup_path.parent.name
assert f"guild-{guild_id}" in backup_path.name
# Verify backup content matches original
original_config = config_manager.get_guild_config(guild_id)
with open(backup_path, 'r') as f:
backup_content = yaml.safe_load(f)
assert backup_content["guild_id"] == original_config.guild_id
assert backup_content["name"] == original_config.name
@pytest.mark.asyncio
async def test_config_change_callbacks(self, config_manager, temp_config_dir):
"""Test that configuration change callbacks are triggered."""
callback_called = False
callback_guild_id = None
def test_callback(guild_id, config):
nonlocal callback_called, callback_guild_id
callback_called = True
callback_guild_id = guild_id
# Register callback
config_manager.register_change_callback(test_callback)
# Create config (should trigger callback)
guild_id = 123456789
await config_manager.create_guild_config(guild_id, "Test Guild")
# Wait a moment for callback to be called
await asyncio.sleep(0.1)
assert callback_called
assert callback_guild_id == guild_id
def test_all_guild_configs_retrieval(self, config_manager, temp_config_dir):
"""Test retrieving all guild configurations."""
# Initially should be empty
all_configs = config_manager.get_all_guild_configs()
assert len(all_configs) == 0
@pytest.mark.asyncio
async def test_nsfw_only_filtering_in_config(self, config_manager):
"""Test that NSFW-only filtering setting is properly handled."""
guild_id = 123456789
# Create config with NSFW-only filtering enabled
file_path = await config_manager.create_guild_config(guild_id, "NSFW Test Guild")
# Load and modify config to enable NSFW-only filtering
with open(file_path, 'r') as f:
config_data = yaml.safe_load(f)
config_data["settings"]["ai_moderation"]["nsfw_only_filtering"] = True
with open(file_path, 'w') as f:
yaml.dump(config_data, f)
# Reload config
await config_manager._load_guild_config(file_path)
# Verify NSFW-only filtering is enabled
config = config_manager.get_guild_config(guild_id)
assert config is not None
assert config.settings["ai_moderation"]["nsfw_only_filtering"] is True