From 25391380b5205c883cea3a79552ec4ef88bd375c Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Sun, 8 Feb 2026 17:07:29 +0800 Subject: [PATCH] feat: enhance BotManager to support lazy loading of platform adapters --- src/core/bot_manager.py | 16 ++-- tests/unit/core/test_bot_manager_lazy_load.py | 91 +++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 tests/unit/core/test_bot_manager_lazy_load.py diff --git a/src/core/bot_manager.py b/src/core/bot_manager.py index 1903ef5..b08490b 100644 --- a/src/core/bot_manager.py +++ b/src/core/bot_manager.py @@ -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: " diff --git a/tests/unit/core/test_bot_manager_lazy_load.py b/tests/unit/core/test_bot_manager_lazy_load.py new file mode 100644 index 0000000..1300389 --- /dev/null +++ b/tests/unit/core/test_bot_manager_lazy_load.py @@ -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()