feat: Multi-guild support implementation
- Added multi-guild support with comma-separated AllowedGuildIDs config - Per-guild embed tracking with isolated state - Per-server action locking to prevent race conditions across guilds - Cross-guild embed updates after power actions - Debug logging for embed update triggers - Fixed resource usage field formatting (brackets now properly closed) - Updated config.ini.sample, generate_config.py for backward compatibility - Updated README.md with multi-guild documentation - Updated tests for multi-guild support
This commit is contained in:
1 parent
e68b248033
commit
bec06bd909
10 files changed
+802
-408
No files matched your search
+33
-18
@@ -14,6 +14,7 @@ import pytest
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
from datetime import datetime
|
||||
import configparser
|
||||
@@ -52,7 +53,8 @@ def mock_config():
|
||||
config.client_api_key = 'ptlc_test_client_key_123'
|
||||
config.application_api_key = 'ptla_test_app_key_456'
|
||||
config.discord_token = 'test_discord_token'
|
||||
config.allowed_guild_id = 123456789
|
||||
config.allowed_guild_ids = [123456789, 987654321]
|
||||
config.allowed_guild_id = 123456789 # Backward compat
|
||||
config.required_role = "Game Server User"
|
||||
config.update_interval = 10
|
||||
config.embed_storage_path = Path("./embed/embed_locations.json")
|
||||
@@ -177,8 +179,8 @@ def mock_bot(mock_config, mock_panel_api):
|
||||
bot = PterodactylBot(mock_config, command_prefix="!", intents=intents)
|
||||
bot.api = mock_panel_api
|
||||
bot.server_cache = {}
|
||||
bot.embed_manager.embed_locations = {}
|
||||
bot.previous_states = {}
|
||||
# Don't override embed_locations - let it use the defaultdict from EmbedManager
|
||||
bot.previous_states = defaultdict(dict)
|
||||
return bot
|
||||
|
||||
|
||||
@@ -561,13 +563,15 @@ class TestServerStatusView:
|
||||
server_id='abc123',
|
||||
server_name='Test Server',
|
||||
api=mock_panel_api,
|
||||
server_data=sample_server_data
|
||||
server_data=sample_server_data,
|
||||
guild_id=123456789
|
||||
)
|
||||
|
||||
assert view.server_id == 'abc123'
|
||||
assert view.server_name == 'Test Server'
|
||||
assert view.api is mock_panel_api
|
||||
assert view.timeout is None # Persistent view
|
||||
assert view.guild_id == 123456789
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interaction_check_authorized(self, mock_config, mock_panel_api, sample_server_data, mock_discord_interaction):
|
||||
@@ -576,7 +580,8 @@ class TestServerStatusView:
|
||||
server_id='abc123',
|
||||
server_name='Test Server',
|
||||
api=mock_panel_api,
|
||||
server_data=sample_server_data
|
||||
server_data=sample_server_data,
|
||||
guild_id=123456789
|
||||
)
|
||||
|
||||
result = await view.interaction_check(mock_discord_interaction)
|
||||
@@ -590,7 +595,8 @@ class TestServerStatusView:
|
||||
server_id='abc123',
|
||||
server_name='Test Server',
|
||||
api=mock_panel_api,
|
||||
server_data=sample_server_data
|
||||
server_data=sample_server_data,
|
||||
guild_id=123456789
|
||||
)
|
||||
|
||||
mock_discord_interaction.guild_id = 999999999
|
||||
@@ -607,7 +613,8 @@ class TestServerStatusView:
|
||||
server_id='abc123',
|
||||
server_name='Test Server',
|
||||
api=mock_panel_api,
|
||||
server_data=sample_server_data
|
||||
server_data=sample_server_data,
|
||||
guild_id=123456789
|
||||
)
|
||||
|
||||
# Remove required role
|
||||
@@ -638,7 +645,8 @@ class TestPterodactylBot:
|
||||
assert bot.metrics_manager is not None
|
||||
assert bot.embed_manager is not None
|
||||
assert bot.server_cache == {}
|
||||
assert bot.previous_states == {}
|
||||
# previous_states is now a defaultdict
|
||||
assert isinstance(bot.previous_states, dict)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_track_new_embed(self, mock_bot, mock_discord_interaction):
|
||||
@@ -647,25 +655,29 @@ class TestPterodactylBot:
|
||||
mock_message.channel.id = 987654321
|
||||
mock_message.id = 123456789
|
||||
|
||||
await mock_bot.embed_manager.track_embed('abc123', mock_message)
|
||||
await mock_bot.embed_manager.track_embed(123456789, 'abc123', mock_message)
|
||||
|
||||
assert 'abc123' in mock_bot.embed_manager.embed_locations
|
||||
assert mock_bot.embed_manager.embed_locations['abc123']['channel_id'] == '987654321'
|
||||
assert mock_bot.embed_manager.embed_locations['abc123']['message_id'] == '123456789'
|
||||
assert 123456789 in mock_bot.embed_manager.embed_locations
|
||||
assert 'abc123' in mock_bot.embed_manager.embed_locations[123456789]
|
||||
assert mock_bot.embed_manager.embed_locations[123456789]['abc123']['channel_id'] == '987654321'
|
||||
assert mock_bot.embed_manager.embed_locations[123456789]['abc123']['message_id'] == '123456789'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_embed_locations(self, mock_bot, tmp_path):
|
||||
"""Test loading embed locations from file."""
|
||||
# Create a temporary embed locations file
|
||||
# Create a temporary embed locations file with new multi-guild format
|
||||
embed_file = tmp_path / "embed_locations.json"
|
||||
embed_file.write_text(json.dumps({
|
||||
"server1": {"channel_id": "123", "message_id": "456"}
|
||||
"123456789": {
|
||||
"server1": {"channel_id": "123", "message_id": "456"}
|
||||
}
|
||||
}))
|
||||
|
||||
mock_bot.embed_manager.storage_path = embed_file
|
||||
await mock_bot.embed_manager.load_locations()
|
||||
|
||||
assert "server1" in mock_bot.embed_manager.embed_locations
|
||||
assert 123456789 in mock_bot.embed_manager.embed_locations
|
||||
assert "server1" in mock_bot.embed_manager.embed_locations[123456789]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_embed_locations(self, mock_bot, tmp_path):
|
||||
@@ -674,13 +686,16 @@ class TestPterodactylBot:
|
||||
mock_bot.embed_manager.storage_path = embed_file
|
||||
|
||||
mock_bot.embed_manager.embed_locations = {
|
||||
"server1": {"channel_id": "123", "message_id": "456"}
|
||||
123456789: {
|
||||
"server1": {"channel_id": "123", "message_id": "456"}
|
||||
}
|
||||
}
|
||||
|
||||
await mock_bot.embed_manager.save_locations()
|
||||
|
||||
saved_data = json.loads(embed_file.read_text())
|
||||
assert "server1" in saved_data
|
||||
assert "123456789" in saved_data
|
||||
assert "server1" in saved_data["123456789"]
|
||||
|
||||
|
||||
# ==========================================
|
||||
@@ -715,7 +730,7 @@ class TestIntegration:
|
||||
server_obj = ServerData.model_validate(sample_server_data)
|
||||
resources_obj = ServerResources.model_validate(sample_resources_data)
|
||||
|
||||
embed, view = await mock_bot.embed_manager.create_server_embed(server_obj, resources_obj)
|
||||
embed, view = await mock_bot.embed_manager.create_server_embed(server_obj, resources_obj, 123456789)
|
||||
|
||||
assert embed is not None
|
||||
assert view is not None
|
||||
|
||||
Reference in new issue
Block a user