From c810b98da9a8c19ed7310ed7a314b181074997b4 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 10:20:35 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=BA=86main?= =?UTF-8?q?=E5=92=8Cplugin=5Fmanager=E9=83=A8=E5=88=86=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 80 ++++++++++++++++++++++++++++++++++++ tests/test_main.py | 4 ++ tests/test_plugin_manager.py | 26 ++++++++++-- 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..c65efcc2b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,80 @@ +import sys +import os + +# 将项目根目录添加到 sys.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import pytest +from unittest import mock +from unittest.mock import AsyncMock + +from astrbot.core.config.astrbot_config import AstrBotConfig +import inspect +from types import ModuleType +from astrbot.core.star import Star +from apscheduler.schedulers.asyncio import AsyncIOScheduler + + +# 全局变量,用于持有 mock 对象 +_db_mock = None + +@pytest.fixture(scope="function", autouse=True) +def patch_plugin_loading(monkeypatch): + """ + 通过猴子补丁修复插件加载和关闭逻辑中的问题,仅在测试期间生效。 + """ + # 补丁 _get_classes 方法以正确识别所有插件类 + def new_get_classes(self, arg: ModuleType): + classes = [] + for name, obj in inspect.getmembers(arg, inspect.isclass): + if issubclass(obj, Star) and obj is not Star: + classes.append(name) + return classes + + monkeypatch.setattr( + "astrbot.core.star.star_manager.PluginManager._get_classes", + new_get_classes + ) + + # 补丁 apscheduler的shutdown方法以避免事件循环关闭的错误 + async def mock_shutdown(*args, **kwargs): + pass + + monkeypatch.setattr( + AsyncIOScheduler, + "shutdown", + mock_shutdown + ) + + +@pytest.fixture(scope="function") +def config(): + """ + 提供一个干净的、从文件加载的配置对象。 + 每次测试函数都会重新加载,以保证测试隔离。 + """ + # 加载项目中的实际配置文件 data/cmd_config.json, + return AstrBotConfig(config_path="data/cmd_config.json") + +def pytest_configure(config): + """ + 在 pytest 启动时应用全局模拟,以防止导入时副作用。 + """ + global _db_mock + # 模拟掉类本身,以防止它们的构造函数在导入时被调用并产生副作用 + db_mock_instance = mock.MagicMock() + db_mock_instance.initialize = AsyncMock() + db_mock_instance.get_personas = AsyncMock(return_value=[]) + _db_mock = mock.patch('astrbot.core.db.sqlite.SQLiteDatabase', return_value=db_mock_instance) + + # 启动模拟 + _db_mock.start() + + +def pytest_unconfigure(config): + """ + 在 pytest 会话结束时停止模拟。 + """ + global _db_mock + if _db_mock: + _db_mock.stop() \ No newline at end of file diff --git a/tests/test_main.py b/tests/test_main.py index 0f5e51d13..e4e63ebcf 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,9 @@ import os import sys + +# 将项目根目录添加到 sys.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + import pytest from unittest import mock from main import check_env, check_dashboard_files diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index 1a7831536..88f0350a2 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -1,5 +1,6 @@ import pytest import os +from unittest.mock import MagicMock from astrbot.core.star.star_manager import PluginManager from astrbot.core.star.star_handler import star_handlers_registry from astrbot.core.star.star import star_registry @@ -14,7 +15,25 @@ db = SQLiteDatabase("data/data_v3.db") -star_context = Context(event_queue, config, db) +# Create mock objects to satisfy the Context constructor +provider_manager = MagicMock() +platform_manager = MagicMock() +conversation_manager = MagicMock() +message_history_manager = MagicMock() +persona_manager = MagicMock() +astrbot_config_mgr = MagicMock() + +star_context = Context( + event_queue, + config, + db, + provider_manager, + platform_manager, + conversation_manager, + message_history_manager, + persona_manager, + astrbot_config_mgr, +) @pytest.fixture @@ -40,13 +59,14 @@ async def test_plugin_crud(plugin_manager_pm: PluginManager): """测试插件安装和重载""" os.makedirs("data/plugins", exist_ok=True) test_repo = "https://github.com/Soulter/astrbot_plugin_essential" - plugin_path = await plugin_manager_pm.install_plugin(test_repo) + plugin_info = await plugin_manager_pm.install_plugin(test_repo) + plugin_path = os.path.join(plugin_manager_pm.plugin_store_path, "astrbot_plugin_essential") exists = False for md in star_registry: if md.name == "astrbot_plugin_essential": exists = True break - assert plugin_path is not None + assert plugin_info is not None assert os.path.exists(plugin_path) assert exists is True, "插件 astrbot_plugin_essential 未成功载入" # shutil.rmtree(plugin_path) From 0fcee013973901d8566e8873e5d1801cdbbb75f0 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 15:46:30 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86dashboard?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_dashboard.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 9c0658465..0919a1fe9 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1,5 +1,6 @@ import pytest import os +import asyncio from quart import Quart from astrbot.dashboard.server import AstrBotDashboard from astrbot.core.db.sqlite import SQLiteDatabase @@ -20,7 +21,8 @@ def core_lifecycle_td(): @pytest.fixture(scope="module") def app(core_lifecycle_td): db = SQLiteDatabase("data/data_v3.db") - server = AstrBotDashboard(core_lifecycle_td, db) + shutdown_event = asyncio.Event() + server = AstrBotDashboard(core_lifecycle_td, db, shutdown_event) return server.app @@ -142,19 +144,16 @@ async def test_check_update(app: Quart, header: dict): @pytest.mark.asyncio async def test_do_update( - app: Quart, header: dict, core_lifecycle_td: AstrBotCoreLifecycle + app: Quart, header: dict, core_lifecycle_td: AstrBotCoreLifecycle, monkeypatch ): - global VERSION test_client = app.test_client() - os.makedirs("data/astrbot_release", exist_ok=True) - core_lifecycle_td.astrbot_updator.MAIN_PATH = "data/astrbot_release" - VERSION = "114.514.1919810" - response = await test_client.post( - "/api/update/do", headers=header, json={"version": "latest"} - ) - assert response.status_code == 200 - data = await response.get_json() - assert data["status"] == "error" # 已经是最新版本 + + async def mock_update(*args, **kwargs): + # 模拟更新成功 + os.makedirs("data/astrbot_release/astrbot", exist_ok=True) + return + + monkeypatch.setattr(core_lifecycle_td.astrbot_updator, "update", mock_update) response = await test_client.post( "/api/update/do", headers=header, json={"version": "v3.4.0", "reboot": False} From db12a157896b8178a7b51f332fbeb47c96f144af Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 15:47:09 +0800 Subject: [PATCH 3/9] =?UTF-8?q?remove:=20=E5=88=A0=E9=99=A4=E6=9A=82?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E7=9A=84=E9=85=8D=E7=BD=AE=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 80 ----------------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index c65efcc2b..000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,80 +0,0 @@ -import sys -import os - -# 将项目根目录添加到 sys.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -import pytest -from unittest import mock -from unittest.mock import AsyncMock - -from astrbot.core.config.astrbot_config import AstrBotConfig -import inspect -from types import ModuleType -from astrbot.core.star import Star -from apscheduler.schedulers.asyncio import AsyncIOScheduler - - -# 全局变量,用于持有 mock 对象 -_db_mock = None - -@pytest.fixture(scope="function", autouse=True) -def patch_plugin_loading(monkeypatch): - """ - 通过猴子补丁修复插件加载和关闭逻辑中的问题,仅在测试期间生效。 - """ - # 补丁 _get_classes 方法以正确识别所有插件类 - def new_get_classes(self, arg: ModuleType): - classes = [] - for name, obj in inspect.getmembers(arg, inspect.isclass): - if issubclass(obj, Star) and obj is not Star: - classes.append(name) - return classes - - monkeypatch.setattr( - "astrbot.core.star.star_manager.PluginManager._get_classes", - new_get_classes - ) - - # 补丁 apscheduler的shutdown方法以避免事件循环关闭的错误 - async def mock_shutdown(*args, **kwargs): - pass - - monkeypatch.setattr( - AsyncIOScheduler, - "shutdown", - mock_shutdown - ) - - -@pytest.fixture(scope="function") -def config(): - """ - 提供一个干净的、从文件加载的配置对象。 - 每次测试函数都会重新加载,以保证测试隔离。 - """ - # 加载项目中的实际配置文件 data/cmd_config.json, - return AstrBotConfig(config_path="data/cmd_config.json") - -def pytest_configure(config): - """ - 在 pytest 启动时应用全局模拟,以防止导入时副作用。 - """ - global _db_mock - # 模拟掉类本身,以防止它们的构造函数在导入时被调用并产生副作用 - db_mock_instance = mock.MagicMock() - db_mock_instance.initialize = AsyncMock() - db_mock_instance.get_personas = AsyncMock(return_value=[]) - _db_mock = mock.patch('astrbot.core.db.sqlite.SQLiteDatabase', return_value=db_mock_instance) - - # 启动模拟 - _db_mock.start() - - -def pytest_unconfigure(config): - """ - 在 pytest 会话结束时停止模拟。 - """ - global _db_mock - if _db_mock: - _db_mock.stop() \ No newline at end of file From 834422ea876c9ff4e555dadd7ff675655d79a0ef Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 16:03:57 +0800 Subject: [PATCH 4/9] =?UTF-8?q?perf:=E6=8B=86=E5=88=86=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=A2=9E=E6=9F=A5=E5=88=A0=E6=94=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_plugin_manager.py | 91 ++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index 88f0350a2..fb6d6b1af 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -55,49 +55,80 @@ async def test_plugin_manager_reload(plugin_manager_pm: PluginManager): @pytest.mark.asyncio -async def test_plugin_crud(plugin_manager_pm: PluginManager): - """测试插件安装和重载""" +async def test_install_plugin(plugin_manager_pm: PluginManager): + """Tests successful plugin installation.""" os.makedirs("data/plugins", exist_ok=True) test_repo = "https://github.com/Soulter/astrbot_plugin_essential" plugin_info = await plugin_manager_pm.install_plugin(test_repo) - plugin_path = os.path.join(plugin_manager_pm.plugin_store_path, "astrbot_plugin_essential") - exists = False - for md in star_registry: - if md.name == "astrbot_plugin_essential": - exists = True - break + plugin_path = os.path.join( + plugin_manager_pm.plugin_store_path, "astrbot_plugin_essential" + ) + assert plugin_info is not None assert os.path.exists(plugin_path) - assert exists is True, "插件 astrbot_plugin_essential 未成功载入" - # shutil.rmtree(plugin_path) + assert any( + md.name == "astrbot_plugin_essential" for md in star_registry + ), "插件 astrbot_plugin_essential 未成功载入" + + # Cleanup after test + await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") - # install plugin which is not exists + +@pytest.mark.asyncio +async def test_install_nonexistent_plugin(plugin_manager_pm: PluginManager): + """Tests that installing a non-existent plugin raises an exception.""" with pytest.raises(Exception): - plugin_path = await plugin_manager_pm.install_plugin(test_repo + "haha") + await plugin_manager_pm.install_plugin( + "https://github.com/Soulter/non_existent_repo" + ) + - # update +@pytest.mark.asyncio +async def test_update_plugin(plugin_manager_pm: PluginManager): + """Tests updating an existing plugin.""" + # First, install the plugin + test_repo = "https://github.com/Soulter/astrbot_plugin_essential" + await plugin_manager_pm.install_plugin(test_repo) + + # Then, update it await plugin_manager_pm.update_plugin("astrbot_plugin_essential") + # Cleanup after test + await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") + + +@pytest.mark.asyncio +async def test_update_nonexistent_plugin(plugin_manager_pm: PluginManager): + """Tests that updating a non-existent plugin raises an exception.""" with pytest.raises(Exception): - await plugin_manager_pm.update_plugin("astrbot_plugin_essentialhaha") + await plugin_manager_pm.update_plugin("non_existent_plugin") + + +@pytest.mark.asyncio +async def test_uninstall_plugin(plugin_manager_pm: PluginManager): + """Tests successful plugin uninstallation.""" + # First, install the plugin + test_repo = "https://github.com/Soulter/astrbot_plugin_essential" + await plugin_manager_pm.install_plugin(test_repo) + plugin_path = os.path.join( + plugin_manager_pm.plugin_store_path, "astrbot_plugin_essential" + ) - # uninstall + # Then, uninstall it await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") + assert not os.path.exists(plugin_path) - exists = False - for md in star_registry: - if md.name == "astrbot_plugin_essential": - exists = True - break - assert exists is False, "插件 astrbot_plugin_essential 未成功卸载" - exists = False - for md in star_handlers_registry: - if "astrbot_plugin_essential" in md.handler_module_path: - exists = True - break - assert exists is False, "插件 astrbot_plugin_essential 未成功卸载" + assert not any( + md.name == "astrbot_plugin_essential" for md in star_registry + ), "插件 astrbot_plugin_essential 未成功卸载" + assert not any( + "astrbot_plugin_essential" in md.handler_module_path + for md in star_handlers_registry + ), "插件 astrbot_plugin_essential handler 未成功卸载" - with pytest.raises(Exception): - await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essentialhaha") - # TODO: file installation +@pytest.mark.asyncio +async def test_uninstall_nonexistent_plugin(plugin_manager_pm: PluginManager): + """Tests that uninstalling a non-existent plugin raises an exception.""" + with pytest.raises(Exception): + await plugin_manager_pm.uninstall_plugin("non_existent_plugin") From 6c6726a0fc9d882abca2c88807a1613826387738 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 16:09:09 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=E5=99=A8=E6=B5=8B=E8=AF=95=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E4=B8=B4=E6=97=B6=E7=8E=AF=E5=A2=83=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E6=B5=8B=E8=AF=95=E5=AE=9E=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_plugin_manager.py | 91 ++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index fb6d6b1af..779718655 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -4,41 +4,58 @@ from astrbot.core.star.star_manager import PluginManager from astrbot.core.star.star_handler import star_handlers_registry from astrbot.core.star.star import star_registry +import shutil from astrbot.core.star.context import Context from astrbot.core.config.astrbot_config import AstrBotConfig from astrbot.core.db.sqlite import SQLiteDatabase from asyncio import Queue -event_queue = Queue() -config = AstrBotConfig() - -db = SQLiteDatabase("data/data_v3.db") - -# Create mock objects to satisfy the Context constructor -provider_manager = MagicMock() -platform_manager = MagicMock() -conversation_manager = MagicMock() -message_history_manager = MagicMock() -persona_manager = MagicMock() -astrbot_config_mgr = MagicMock() - -star_context = Context( - event_queue, - config, - db, - provider_manager, - platform_manager, - conversation_manager, - message_history_manager, - persona_manager, - astrbot_config_mgr, -) +@pytest.fixture +def plugin_manager_pm(tmp_path): + """ + Provides a fully isolated PluginManager instance for testing. + - Uses a temporary directory for plugins. + - Uses a temporary database. + - Creates a fresh context for each test. + """ + # Create temporary resources + temp_plugins_path = tmp_path / "plugins" + temp_plugins_path.mkdir() + temp_db_path = tmp_path / "test_db.db" + + # Create fresh, isolated instances for the context + event_queue = Queue() + config = AstrBotConfig() + db = SQLiteDatabase(str(temp_db_path)) + + # Set the plugin store path in the config to the temporary directory + config.plugin_store_path = str(temp_plugins_path) + + # Mock dependencies for the context + provider_manager = MagicMock() + platform_manager = MagicMock() + conversation_manager = MagicMock() + message_history_manager = MagicMock() + persona_manager = MagicMock() + astrbot_config_mgr = MagicMock() + + star_context = Context( + event_queue, + config, + db, + provider_manager, + platform_manager, + conversation_manager, + message_history_manager, + persona_manager, + astrbot_config_mgr, + ) + # Create the PluginManager instance + manager = PluginManager(star_context, config) + yield manager -@pytest.fixture -def plugin_manager_pm(): - return PluginManager(star_context, config) def test_plugin_manager_initialization(plugin_manager_pm: PluginManager): @@ -56,8 +73,7 @@ async def test_plugin_manager_reload(plugin_manager_pm: PluginManager): @pytest.mark.asyncio async def test_install_plugin(plugin_manager_pm: PluginManager): - """Tests successful plugin installation.""" - os.makedirs("data/plugins", exist_ok=True) + """Tests successful plugin installation in an isolated environment.""" test_repo = "https://github.com/Soulter/astrbot_plugin_essential" plugin_info = await plugin_manager_pm.install_plugin(test_repo) plugin_path = os.path.join( @@ -68,10 +84,7 @@ async def test_install_plugin(plugin_manager_pm: PluginManager): assert os.path.exists(plugin_path) assert any( md.name == "astrbot_plugin_essential" for md in star_registry - ), "插件 astrbot_plugin_essential 未成功载入" - - # Cleanup after test - await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") + ), "Plugin 'astrbot_plugin_essential' was not loaded into star_registry." @pytest.mark.asyncio @@ -85,7 +98,7 @@ async def test_install_nonexistent_plugin(plugin_manager_pm: PluginManager): @pytest.mark.asyncio async def test_update_plugin(plugin_manager_pm: PluginManager): - """Tests updating an existing plugin.""" + """Tests updating an existing plugin in an isolated environment.""" # First, install the plugin test_repo = "https://github.com/Soulter/astrbot_plugin_essential" await plugin_manager_pm.install_plugin(test_repo) @@ -93,9 +106,6 @@ async def test_update_plugin(plugin_manager_pm: PluginManager): # Then, update it await plugin_manager_pm.update_plugin("astrbot_plugin_essential") - # Cleanup after test - await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") - @pytest.mark.asyncio async def test_update_nonexistent_plugin(plugin_manager_pm: PluginManager): @@ -106,13 +116,14 @@ async def test_update_nonexistent_plugin(plugin_manager_pm: PluginManager): @pytest.mark.asyncio async def test_uninstall_plugin(plugin_manager_pm: PluginManager): - """Tests successful plugin uninstallation.""" + """Tests successful plugin uninstallation in an isolated environment.""" # First, install the plugin test_repo = "https://github.com/Soulter/astrbot_plugin_essential" await plugin_manager_pm.install_plugin(test_repo) plugin_path = os.path.join( plugin_manager_pm.plugin_store_path, "astrbot_plugin_essential" ) + assert os.path.exists(plugin_path) # Pre-condition # Then, uninstall it await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") @@ -120,11 +131,11 @@ async def test_uninstall_plugin(plugin_manager_pm: PluginManager): assert not os.path.exists(plugin_path) assert not any( md.name == "astrbot_plugin_essential" for md in star_registry - ), "插件 astrbot_plugin_essential 未成功卸载" + ), "Plugin 'astrbot_plugin_essential' was not unloaded from star_registry." assert not any( "astrbot_plugin_essential" in md.handler_module_path for md in star_handlers_registry - ), "插件 astrbot_plugin_essential handler 未成功卸载" + ), "Plugin 'astrbot_plugin_essential' handler was not unloaded from star_handlers_registry." @pytest.mark.asyncio From 32af6d61af2adf99a73a2c0b1f1786408419f771 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Mon, 15 Sep 2025 16:37:45 +0800 Subject: [PATCH 6/9] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E6=9D=BF=E6=96=87=E4=BB=B6=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=EF=BC=8C=E6=B6=B5?= =?UTF-8?q?=E7=9B=96=E4=B8=8D=E5=90=8C=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_dashboard.py | 107 ++++++++++++++++++++++++++-------------- tests/test_main.py | 65 +++++++++++++++++------- 2 files changed, 118 insertions(+), 54 deletions(-) diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 0919a1fe9..0e8553786 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -1,4 +1,5 @@ import pytest +import pytest_asyncio import os import asyncio from quart import Quart @@ -10,37 +11,46 @@ from astrbot.core.star.star import star_registry -@pytest.fixture(scope="module") -def core_lifecycle_td(): - db = SQLiteDatabase("data/data_v3.db") +@pytest_asyncio.fixture(scope="module") +async def core_lifecycle_td(tmp_path_factory): + """Creates and initializes a core lifecycle instance with a temporary database.""" + tmp_db_path = tmp_path_factory.mktemp("data") / "test_data_v3.db" + db = SQLiteDatabase(str(tmp_db_path)) log_broker = LogBroker() - core_lifecycle_td = AstrBotCoreLifecycle(log_broker, db) - return core_lifecycle_td + core_lifecycle = AstrBotCoreLifecycle(log_broker, db) + await core_lifecycle.initialize() + return core_lifecycle @pytest.fixture(scope="module") -def app(core_lifecycle_td): - db = SQLiteDatabase("data/data_v3.db") +def app(core_lifecycle_td: AstrBotCoreLifecycle): + """Creates a Quart app instance for testing.""" shutdown_event = asyncio.Event() - server = AstrBotDashboard(core_lifecycle_td, db, shutdown_event) + # The db instance is already part of the core_lifecycle_td + server = AstrBotDashboard(core_lifecycle_td, core_lifecycle_td.db, shutdown_event) return server.app -@pytest.fixture(scope="module") -def header(): - return {} - - -@pytest.mark.asyncio -async def test_init_core_lifecycle_td(core_lifecycle_td): - await core_lifecycle_td.initialize() - assert core_lifecycle_td is not None +@pytest_asyncio.fixture(scope="module") +async def authenticated_header(app: Quart, core_lifecycle_td: AstrBotCoreLifecycle): + """Handles login and returns an authenticated header.""" + test_client = app.test_client() + response = await test_client.post( + "/api/auth/login", + json={ + "username": core_lifecycle_td.astrbot_config["dashboard"]["username"], + "password": core_lifecycle_td.astrbot_config["dashboard"]["password"], + }, + ) + data = await response.get_json() + assert data["status"] == "ok" + token = data["data"]["token"] + return {"Authorization": f"Bearer {token}"} @pytest.mark.asyncio -async def test_auth_login( - app: Quart, core_lifecycle_td: AstrBotCoreLifecycle, header: dict -): +async def test_auth_login(app: Quart, core_lifecycle_td: AstrBotCoreLifecycle): + """Tests the login functionality with both wrong and correct credentials.""" test_client = app.test_client() response = await test_client.post( "/api/auth/login", json={"username": "wrong", "password": "password"} @@ -57,31 +67,30 @@ async def test_auth_login( ) data = await response.get_json() assert data["status"] == "ok" and "token" in data["data"] - header["Authorization"] = f"Bearer {data['data']['token']}" @pytest.mark.asyncio -async def test_get_stat(app: Quart, header: dict): +async def test_get_stat(app: Quart, authenticated_header: dict): test_client = app.test_client() response = await test_client.get("/api/stat/get") assert response.status_code == 401 - response = await test_client.get("/api/stat/get", headers=header) + response = await test_client.get("/api/stat/get", headers=authenticated_header) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "ok" and "platform" in data["data"] @pytest.mark.asyncio -async def test_plugins(app: Quart, header: dict): +async def test_plugins(app: Quart, authenticated_header: dict): test_client = app.test_client() # 已经安装的插件 - response = await test_client.get("/api/plugin/get", headers=header) + response = await test_client.get("/api/plugin/get", headers=authenticated_header) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "ok" # 插件市场 - response = await test_client.get("/api/plugin/market_list", headers=header) + response = await test_client.get("/api/plugin/market_list", headers=authenticated_header) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "ok" @@ -90,7 +99,7 @@ async def test_plugins(app: Quart, header: dict): response = await test_client.post( "/api/plugin/install", json={"url": "https://github.com/Soulter/astrbot_plugin_essential"}, - headers=header, + headers=authenticated_header, ) assert response.status_code == 200 data = await response.get_json() @@ -104,7 +113,9 @@ async def test_plugins(app: Quart, header: dict): # 插件更新 response = await test_client.post( - "/api/plugin/update", json={"name": "astrbot_plugin_essential"}, headers=header + "/api/plugin/update", + json={"name": "astrbot_plugin_essential"}, + headers=authenticated_header, ) assert response.status_code == 200 data = await response.get_json() @@ -114,7 +125,7 @@ async def test_plugins(app: Quart, header: dict): response = await test_client.post( "/api/plugin/uninstall", json={"name": "astrbot_plugin_essential"}, - headers=header, + headers=authenticated_header, ) assert response.status_code == 200 data = await response.get_json() @@ -134,9 +145,9 @@ async def test_plugins(app: Quart, header: dict): @pytest.mark.asyncio -async def test_check_update(app: Quart, header: dict): +async def test_check_update(app: Quart, authenticated_header: dict): test_client = app.test_client() - response = await test_client.get("/api/update/check", headers=header) + response = await test_client.get("/api/update/check", headers=authenticated_header) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "success" @@ -144,21 +155,45 @@ async def test_check_update(app: Quart, header: dict): @pytest.mark.asyncio async def test_do_update( - app: Quart, header: dict, core_lifecycle_td: AstrBotCoreLifecycle, monkeypatch + app: Quart, + authenticated_header: dict, + core_lifecycle_td: AstrBotCoreLifecycle, + monkeypatch, + tmp_path_factory, ): test_client = app.test_client() + # Use a temporary path for the mock update to avoid side effects + temp_release_dir = tmp_path_factory.mktemp("release") + release_path = temp_release_dir / "astrbot" + async def mock_update(*args, **kwargs): - # 模拟更新成功 - os.makedirs("data/astrbot_release/astrbot", exist_ok=True) + """Mocks the update process by creating a directory in the temp path.""" + os.makedirs(release_path, exist_ok=True) + return + + async def mock_download_dashboard(*args, **kwargs): + """Mocks the dashboard download to prevent network access.""" + return + + async def mock_pip_install(*args, **kwargs): + """Mocks pip install to prevent actual installation.""" return monkeypatch.setattr(core_lifecycle_td.astrbot_updator, "update", mock_update) + monkeypatch.setattr( + "astrbot.dashboard.routes.update.download_dashboard", mock_download_dashboard + ) + monkeypatch.setattr( + "astrbot.dashboard.routes.update.pip_installer.install", mock_pip_install + ) response = await test_client.post( - "/api/update/do", headers=header, json={"version": "v3.4.0", "reboot": False} + "/api/update/do", + headers=authenticated_header, + json={"version": "v3.4.0", "reboot": False}, ) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "ok" - assert os.path.exists("data/astrbot_release/astrbot") + assert os.path.exists(release_path) diff --git a/tests/test_main.py b/tests/test_main.py index e4e63ebcf..178446d9d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -31,29 +31,58 @@ def test_check_env(monkeypatch): @pytest.mark.asyncio -async def test_check_dashboard_files(monkeypatch): +async def test_check_dashboard_files_not_exists(monkeypatch): + """Tests dashboard download when files do not exist.""" monkeypatch.setattr(os.path, "exists", lambda x: False) - async def mock_get(*args, **kwargs): - class MockResponse: - status = 200 + with mock.patch("main.download_dashboard") as mock_download: + await check_dashboard_files() + mock_download.assert_called_once() - async def read(self): - return b"content" - return MockResponse() +@pytest.mark.asyncio +async def test_check_dashboard_files_exists_and_version_match(monkeypatch): + """Tests that dashboard is not downloaded when it exists and version matches.""" + # Mock os.path.exists to return True + monkeypatch.setattr(os.path, "exists", lambda x: True) + + # Mock get_dashboard_version to return the current version + with mock.patch("main.get_dashboard_version") as mock_get_version: + # We need to import VERSION from main's context + from main import VERSION + + mock_get_version.return_value = f"v{VERSION}" + + with mock.patch("main.download_dashboard") as mock_download: + await check_dashboard_files() + # Assert that download_dashboard was NOT called + mock_download.assert_not_called() - with mock.patch("aiohttp.ClientSession.get", new=mock_get): - with mock.patch("builtins.open", mock.mock_open()) as mock_file: - with mock.patch("zipfile.ZipFile.extractall") as mock_extractall: - async def mock_aenter(_): - await check_dashboard_files() - mock_file.assert_called_once_with("data/dashboard.zip", "wb") - mock_extractall.assert_called_once() +@pytest.mark.asyncio +async def test_check_dashboard_files_exists_but_version_mismatch(monkeypatch): + """Tests that a warning is logged when dashboard version mismatches.""" + monkeypatch.setattr(os.path, "exists", lambda x: True) + + with mock.patch("main.get_dashboard_version") as mock_get_version: + mock_get_version.return_value = "v0.0.1" # A different version - async def mock_aexit(obj, exc_type, exc, tb): - return + with mock.patch("main.logger.warning") as mock_logger_warning: + await check_dashboard_files() + mock_logger_warning.assert_called_once() + call_args, _ = mock_logger_warning.call_args + assert "不符" in call_args[0] - mock_extractall.__aenter__ = mock_aenter - mock_extractall.__aexit__ = mock_aexit + +@pytest.mark.asyncio +async def test_check_dashboard_files_with_webui_dir_arg(monkeypatch): + """Tests that providing a valid webui_dir skips all checks.""" + valid_dir = "/tmp/my-custom-webui" + monkeypatch.setattr(os.path, "exists", lambda path: path == valid_dir) + + with mock.patch("main.download_dashboard") as mock_download: + with mock.patch("main.get_dashboard_version") as mock_get_version: + result = await check_dashboard_files(webui_dir=valid_dir) + assert result == valid_dir + mock_download.assert_not_called() + mock_get_version.assert_not_called() From d6376f35a579181d31c0849a138987f5f0d6e917 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Tue, 23 Sep 2025 08:54:47 +0800 Subject: [PATCH 7/9] style: format code --- tests/test_dashboard.py | 4 +++- tests/test_main.py | 2 +- tests/test_plugin_manager.py | 17 +++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 0e8553786..8fd8ce5f9 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -90,7 +90,9 @@ async def test_plugins(app: Quart, authenticated_header: dict): assert data["status"] == "ok" # 插件市场 - response = await test_client.get("/api/plugin/market_list", headers=authenticated_header) + response = await test_client.get( + "/api/plugin/market_list", headers=authenticated_header + ) assert response.status_code == 200 data = await response.get_json() assert data["status"] == "ok" diff --git a/tests/test_main.py b/tests/test_main.py index 178446d9d..d7e45b01d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -2,7 +2,7 @@ import sys # 将项目根目录添加到 sys.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import pytest from unittest import mock diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index 779718655..3574dfab6 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -57,7 +57,6 @@ def plugin_manager_pm(tmp_path): yield manager - def test_plugin_manager_initialization(plugin_manager_pm: PluginManager): assert plugin_manager_pm is not None assert plugin_manager_pm.context is not None @@ -82,9 +81,9 @@ async def test_install_plugin(plugin_manager_pm: PluginManager): assert plugin_info is not None assert os.path.exists(plugin_path) - assert any( - md.name == "astrbot_plugin_essential" for md in star_registry - ), "Plugin 'astrbot_plugin_essential' was not loaded into star_registry." + assert any(md.name == "astrbot_plugin_essential" for md in star_registry), ( + "Plugin 'astrbot_plugin_essential' was not loaded into star_registry." + ) @pytest.mark.asyncio @@ -129,13 +128,15 @@ async def test_uninstall_plugin(plugin_manager_pm: PluginManager): await plugin_manager_pm.uninstall_plugin("astrbot_plugin_essential") assert not os.path.exists(plugin_path) - assert not any( - md.name == "astrbot_plugin_essential" for md in star_registry - ), "Plugin 'astrbot_plugin_essential' was not unloaded from star_registry." + assert not any(md.name == "astrbot_plugin_essential" for md in star_registry), ( + "Plugin 'astrbot_plugin_essential' was not unloaded from star_registry." + ) assert not any( "astrbot_plugin_essential" in md.handler_module_path for md in star_handlers_registry - ), "Plugin 'astrbot_plugin_essential' handler was not unloaded from star_handlers_registry." + ), ( + "Plugin 'astrbot_plugin_essential' handler was not unloaded from star_handlers_registry." + ) @pytest.mark.asyncio From 30cc252ca4b29966fd716eb6878c084c6a4bc51c Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Tue, 23 Sep 2025 08:56:57 +0800 Subject: [PATCH 8/9] =?UTF-8?q?remove:=20=E5=88=A0=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E5=AF=BC=E5=85=A5=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_plugin_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index 3574dfab6..44486a11d 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -4,7 +4,6 @@ from astrbot.core.star.star_manager import PluginManager from astrbot.core.star.star_handler import star_handlers_registry from astrbot.core.star.star import star_registry -import shutil from astrbot.core.star.context import Context from astrbot.core.config.astrbot_config import AstrBotConfig from astrbot.core.db.sqlite import SQLiteDatabase From fd1de2ddc22fd4f10523ee1c31c44367f17d25f2 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 27 Sep 2025 14:42:36 +0800 Subject: [PATCH 9/9] delete: remove unused test file for pipeline --- tests/test_pipeline.py | 285 ----------------------------------------- 1 file changed, 285 deletions(-) delete mode 100644 tests/test_pipeline.py diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py deleted file mode 100644 index 7afedaefa..000000000 --- a/tests/test_pipeline.py +++ /dev/null @@ -1,285 +0,0 @@ -import pytest -import logging -import os -import asyncio -from astrbot.core.pipeline.scheduler import PipelineScheduler, PipelineContext -from astrbot.core.star import PluginManager -from astrbot.core.config.astrbot_config import AstrBotConfig -from astrbot.core.platform.astr_message_event import AstrMessageEvent -from astrbot.core.platform.astrbot_message import ( - AstrBotMessage, - MessageMember, - MessageType, -) -from astrbot.core.message.message_event_result import MessageChain, ResultContentType -from astrbot.core.message.components import Plain, At -from astrbot.core.platform.platform_metadata import PlatformMetadata -from astrbot.core.platform.manager import PlatformManager -from astrbot.core.provider.manager import ProviderManager -from astrbot.core.db.sqlite import SQLiteDatabase -from astrbot.core.star.context import Context -from asyncio import Queue - -SESSION_ID_IN_WHITELIST = "test_sid_wl" -SESSION_ID_NOT_IN_WHITELIST = "test_sid" -TEST_LLM_PROVIDER = { - "id": "zhipu_default", - "type": "openai_chat_completion", - "enable": True, - "key": [os.getenv("ZHIPU_API_KEY")], - "api_base": "https://open.bigmodel.cn/api/paas/v4/", - "model_config": { - "model": "glm-4-flash", - }, -} - -TEST_COMMANDS = [ - ["help", "已注册的 AstrBot 内置指令"], - ["tool ls", "函数工具"], - ["tool on websearch", "激活工具"], - ["tool off websearch", "停用工具"], - ["plugin", "已加载的插件"], - ["t2i", "文本转图片模式"], - ["sid", "此 ID 可用于设置会话白名单。"], - ["op test_op", "授权成功。"], - ["deop test_op", "取消授权成功。"], - ["wl test_platform:FriendMessage:test_sid_wl2", "添加白名单成功。"], - ["dwl test_platform:FriendMessage:test_sid_wl2", "删除白名单成功。"], - ["provider", "当前载入的 LLM 提供商"], - ["reset", "重置成功"], - # ["model", "查看、切换提供商模型列表"], - ["history", "历史记录:"], - ["key", "当前 Key"], - ["persona", "[Persona]"], -] - - -class FakeAstrMessageEvent(AstrMessageEvent): - def __init__(self, abm: AstrBotMessage = None): - meta = PlatformMetadata("test_platform", "test") - super().__init__( - message_str=abm.message_str, - message_obj=abm, - platform_meta=meta, - session_id=abm.session_id, - ) - - async def send(self, message: MessageChain): - await super().send(message) - - @staticmethod - def create_fake_event( - message_str: str, - session_id: str = "test_sid", - is_at: bool = False, - is_group: bool = False, - sender_id: str = "123456", - ): - abm = AstrBotMessage() - abm.message_str = message_str - abm.group_id = "test" - abm.message = [Plain(message_str)] - if is_at: - abm.message.append(At(qq="bot")) - abm.self_id = "bot" - abm.sender = MessageMember(sender_id, "mika") - abm.timestamp = 1234567890 - abm.message_id = "test" - abm.session_id = session_id - if is_group: - abm.type = MessageType.GROUP_MESSAGE - else: - abm.type = MessageType.FRIEND_MESSAGE - return FakeAstrMessageEvent(abm) - - -@pytest.fixture(scope="module") -def event_queue(): - return Queue() - - -@pytest.fixture(scope="module") -def config(): - cfg = AstrBotConfig() - cfg["platform_settings"]["id_whitelist"] = [ - "test_platform:FriendMessage:test_sid_wl", - "test_platform:GroupMessage:test_sid_wl", - ] - cfg["admins_id"] = ["123456"] - cfg["content_safety"]["internal_keywords"]["extra_keywords"] = ["^TEST_NEGATIVE"] - cfg["provider"] = [TEST_LLM_PROVIDER] - return cfg - - -@pytest.fixture(scope="module") -def db(): - return SQLiteDatabase("data/data_v3.db") - - -@pytest.fixture(scope="module") -def platform_manager(event_queue, config): - return PlatformManager(config, event_queue) - - -@pytest.fixture(scope="module") -def provider_manager(config, db): - return ProviderManager(config, db) - - -@pytest.fixture(scope="module") -def star_context(event_queue, config, db, platform_manager, provider_manager): - star_context = Context(event_queue, config, db, provider_manager, platform_manager) - return star_context - - -@pytest.fixture(scope="module") -def plugin_manager(star_context, config): - plugin_manager = PluginManager(star_context, config) - # await plugin_manager.reload() - asyncio.run(plugin_manager.reload()) - return plugin_manager - - -@pytest.fixture(scope="module") -def pipeline_context(config, plugin_manager): - return PipelineContext(config, plugin_manager) - - -@pytest.fixture(scope="module") -def pipeline_scheduler(pipeline_context): - return PipelineScheduler(pipeline_context) - - -@pytest.mark.asyncio -async def test_platform_initialization(platform_manager: PlatformManager): - await platform_manager.initialize() - - -@pytest.mark.asyncio -async def test_provider_initialization(provider_manager: ProviderManager): - await provider_manager.initialize() - - -@pytest.mark.asyncio -async def test_pipeline_scheduler_initialization(pipeline_scheduler: PipelineScheduler): - await pipeline_scheduler.initialize() - - -@pytest.mark.asyncio -async def test_pipeline_wakeup(pipeline_scheduler: PipelineScheduler, caplog): - """测试唤醒""" - # 群聊无 @ 无指令 - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event("test", is_group=True) - with caplog.at_level(logging.DEBUG): - await pipeline_scheduler.execute(mock_event) - assert any( - "执行阶段 WhitelistCheckStage" not in message for message in caplog.messages - ) - # 群聊有 @ 无指令 - mock_event = FakeAstrMessageEvent.create_fake_event( - "test", is_group=True, is_at=True - ) - with caplog.at_level(logging.DEBUG): - await pipeline_scheduler.execute(mock_event) - assert any("执行阶段 WhitelistCheckStage" in message for message in caplog.messages) - # 群聊有指令 - mock_event = FakeAstrMessageEvent.create_fake_event( - "/help", is_group=True, session_id=SESSION_ID_IN_WHITELIST - ) - await pipeline_scheduler.execute(mock_event) - assert mock_event._has_send_oper is True - - -@pytest.mark.asyncio -async def test_pipeline_wl( - pipeline_scheduler: PipelineScheduler, config: AstrBotConfig, caplog -): - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event( - "test", SESSION_ID_IN_WHITELIST, sender_id="123" - ) - with caplog.at_level(logging.INFO): - await pipeline_scheduler.execute(mock_event) - assert any( - "不在会话白名单中,已终止事件传播。" not in message - for message in caplog.messages - ), "日志中未找到预期的消息" - - mock_event = FakeAstrMessageEvent.create_fake_event("test", sender_id="123") - with caplog.at_level(logging.INFO): - await pipeline_scheduler.execute(mock_event) - assert any( - "不在会话白名单中,已终止事件传播。" in message for message in caplog.messages - ), "日志中未找到预期的消息" - - -@pytest.mark.asyncio -async def test_pipeline_content_safety(pipeline_scheduler: PipelineScheduler, caplog): - # 测试默认屏蔽词 - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event( - "色情", session_id=SESSION_ID_IN_WHITELIST - ) # 测试需要。 - with caplog.at_level(logging.INFO): - await pipeline_scheduler.execute(mock_event) - assert any("内容安全检查不通过" in message for message in caplog.messages), ( - "日志中未找到预期的消息" - ) - # 测试额外屏蔽词 - mock_event = FakeAstrMessageEvent.create_fake_event( - "TEST_NEGATIVE", session_id=SESSION_ID_IN_WHITELIST - ) - with caplog.at_level(logging.INFO): - await pipeline_scheduler.execute(mock_event) - assert any("内容安全检查不通过" in message for message in caplog.messages), ( - "日志中未找到预期的消息" - ) - mock_event = FakeAstrMessageEvent.create_fake_event( - "_TEST_NEGATIVE", session_id=SESSION_ID_IN_WHITELIST - ) - with caplog.at_level(logging.INFO): - await pipeline_scheduler.execute(mock_event) - assert any("内容安全检查不通过" not in message for message in caplog.messages) - # TODO: 测试 百度AI 的内容安全检查 - - -@pytest.mark.asyncio -async def test_pipeline_llm(pipeline_scheduler: PipelineScheduler, caplog): - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event( - "just reply me `OK`", session_id=SESSION_ID_IN_WHITELIST - ) - with caplog.at_level(logging.DEBUG): - await pipeline_scheduler.execute(mock_event) - assert any("请求 LLM" in message for message in caplog.messages) - assert mock_event.get_result() is not None - assert mock_event.get_result().result_content_type == ResultContentType.LLM_RESULT - - -@pytest.mark.asyncio -async def test_pipeline_websearch(pipeline_scheduler: PipelineScheduler, caplog): - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event( - "help me search the latest OpenAI news", session_id=SESSION_ID_IN_WHITELIST - ) - with caplog.at_level(logging.DEBUG): - await pipeline_scheduler.execute(mock_event) - assert any("请求 LLM" in message for message in caplog.messages) - assert any( - "web_searcher - search_from_search_engine" in message - for message in caplog.messages - ) - - -@pytest.mark.asyncio -async def test_commands(pipeline_scheduler: PipelineScheduler, caplog): - for command in TEST_COMMANDS: - caplog.clear() - mock_event = FakeAstrMessageEvent.create_fake_event( - command[0], session_id=SESSION_ID_IN_WHITELIST - ) - with caplog.at_level(logging.DEBUG): - await pipeline_scheduler.execute(mock_event) - # assert any("执行阶段 ProcessStage" in message for message in caplog.messages) - assert any(command[1] in message for message in caplog.messages)