294 lines
10 KiB
Python
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 |