feat: enhance BotManager to support lazy loading of platform adapters

This commit is contained in:
SXP-Simon
2026-02-08 17:07:29 +08:00
parent b8c1481ec1
commit 25391380b5
2 changed files with 101 additions and 6 deletions
+10 -6
View File
@@ -265,25 +265,29 @@ class BotManager:
):
platform_id = platform.metadata.id
# Detect platform name from metadata
# 尝试获取平台名称
platform_name = None
if hasattr(platform.metadata, "name"):
platform_name = platform.metadata.name
elif hasattr(platform.metadata, "type"):
platform_name = platform.metadata.type
# Store platform instance regardless of bot_client state
logger.debug(f"Discovered platform: {platform_id} ({platform_name}), client ready: {bool(bot_client)}")
# 无论 bot_client 状态如何,都存储平台实例
self._platforms[platform_id] = platform
if bot_client:
self.set_bot_instance(bot_client, platform_id, platform_name)
discovered[platform_id] = bot_client
else:
# Try to set adapter even without bot_instance (if possible) or just mark for lazy load
# For now, just log that we found a platform but no client yet
logger.debug(f"Found platform {platform_id} ({platform_name}) but client is not ready yet.")
# 懒加载场景:找到平台但客户端尚未准备好。
# 将其添加到 discovered,以便 main.py 知道我们找到了某些东西。
# 使用平台对象作为日志记录的占位符。
logger.info(f"Platform {platform_id} found but client is not ready. Will lazy load.")
discovered[platform_id] = platform
# Log adapter creation results
# 记录适配器创建结果
if self._adapters:
logger.info(
f"已创建 {len(self._adapters)} 个 PlatformAdapter: "
@@ -0,0 +1,91 @@
import unittest
import sys
import os
from unittest.mock import MagicMock
# Add paths
plugin_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
if plugin_root not in sys.path:
sys.path.insert(0, plugin_root)
astrbot_root = os.path.abspath(os.path.join(plugin_root, "../../../"))
if astrbot_root not in sys.path:
sys.path.insert(0, astrbot_root)
from src.core.bot_manager import BotManager
from src.infrastructure.platform.factory import PlatformAdapterFactory
class TestBotManagerLazyLoad(unittest.TestCase):
def setUp(self):
self.config_manager = MagicMock()
self.config_manager.get_bot_qq_ids.return_value = []
self.bot_manager = BotManager(self.config_manager)
# Mock Context and PlatformManager
self.context = MagicMock()
self.platform_manager = MagicMock()
self.context.platform_manager = self.platform_manager
self.bot_manager.set_context(self.context)
def test_lazy_load_discord(self):
"""Test that BotManager lazily loads Discord adapter when client becomes ready later"""
# 1. Setup a Mock Platform that is NOT ready yet (no client attribute or None)
mock_platform = MagicMock()
mock_platform.metadata.id = "discord"
mock_platform.metadata.name = "discord"
# Ensure it has NO client/bot attributes initially
del mock_platform.client
del mock_platform.bot
del mock_platform.get_client
# Mock platform manager returning this platform
self.platform_manager.get_insts.return_value = [mock_platform]
# 2. Run auto discovery (Simulate on_platform_loaded)
# Since it's async, we run it synchronously
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
discovered = loop.run_until_complete(self.bot_manager.auto_discover_bot_instances())
# Verify it was "discovered" (added to dict) but likely as a placeholder or not fully init
# Based on my code: discovered[platform_id] = platform (if client missing)
self.assertIn("discord", discovered)
self.assertEqual(discovered["discord"], mock_platform)
# Verify internal state: stored in _platforms but NOT in _bot_instances yet
self.assertIn("discord", self.bot_manager._platforms)
self.assertNotIn("discord", self.bot_manager._bot_instances)
# 3. Simulate Client becoming ready
mock_client = MagicMock() # The DiscordBotClient
mock_platform.client = mock_client # Now it has the client
# 4. Call get_bot_instance - should trigger lazy load
# We need to mock PlatformAdapterFactory to support "discord" and return a mock adapter
# Actually factory already supports it, but we need to ensure it doesn't fail on creation
# The create method takes (platform_name, bot_instance, config)
# We assume "discord" is registered (it is in factory.py)
# But we need to make sure DiscordAdapter can be instantiated with our mock client
# DiscordAdapter needs discord module. If not present, it logs error.
# We should patch discord module if needed, but in Docker it exists.
# Let's try calling get_bot_instance
instance = self.bot_manager.get_bot_instance("discord")
# 5. Verify instance is returned and stored
self.assertIsNotNone(instance)
self.assertEqual(instance, mock_client) # get_bot_instance returns the bot_client (not adapter)
# Verify it's now in _bot_instances
self.assertIn("discord", self.bot_manager._bot_instances)
# Verify adapter was created
self.assertIn("discord", self.bot_manager._adapters)
loop.close()
if __name__ == "__main__":
unittest.main()