"""插件 API 集成测试 - 测试 /api/plugins/* 所有接口""" import pytest from tests.task_utils import poll_task_until_terminal class TestPluginsAPI: """测试插件相关 API""" @pytest.mark.asyncio async def test_list_plugins(self, client): """测试 GET /api/plugins""" response = await client.get("/api/plugins") assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "plugins" in data["data"] assert isinstance(data["data"]["plugins"], list) @pytest.mark.asyncio async def test_list_plugins_structure(self, client): """测试 GET /api/plugins 返回结构""" response = await client.get("/api/plugins") data = response.json() if data["data"]["plugins"]: plugin = data["data"]["plugins"][0] assert "id" in plugin assert "name" in plugin assert "display_name" in plugin assert "description" in plugin assert "enabled" in plugin @pytest.mark.asyncio async def test_toggle_plugin_enable(self, client): """测试 PUT /api/plugins/{id}/toggle - 启用""" # 先获取一个插件 ID response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.put(f"/api/plugins/{plugin_id}/toggle", json={"enabled": True}) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert data["data"]["enabled"] is True @pytest.mark.asyncio async def test_toggle_plugin_disable(self, client): """测试 PUT /api/plugins/{id}/toggle - 禁用""" response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.put(f"/api/plugins/{plugin_id}/toggle", json={"enabled": False}) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert data["data"]["enabled"] is False @pytest.mark.asyncio async def test_toggle_plugin_missing_enabled(self, client): """测试 PUT /api/plugins/{id}/toggle - 缺少 enabled 参数""" response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.put(f"/api/plugins/{plugin_id}/toggle", json={}) # Pydantic 验证缺失必填字段返回 422 assert response.status_code == 422 @pytest.mark.asyncio async def test_toggle_nonexistent_plugin(self, client): """测试 PUT /api/plugins/{id}/toggle - 不存在的插件""" response = await client.put("/api/plugins/nonexistent_plugin/toggle", json={"enabled": True}) assert response.status_code == 404 @pytest.mark.asyncio async def test_get_plugin_config(self, client): """测试 GET /api/plugins/{id}/config""" response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.get(f"/api/plugins/{plugin_id}/config") assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "config" in data["data"] @pytest.mark.asyncio async def test_get_nonexistent_plugin_config(self, client): """测试 GET /api/plugins/{id}/config - 不存在的插件""" response = await client.get("/api/plugins/nonexistent_plugin/config") assert response.status_code == 404 @pytest.mark.asyncio async def test_update_plugin_config(self, client): """测试 POST /api/plugins/{id}/config""" response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.post( f"/api/plugins/{plugin_id}/config", json={"config": {"max_pages": 3}} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 @pytest.mark.network @pytest.mark.slow @pytest.mark.asyncio async def test_crawl_plugin(self, client): """测试 POST /api/plugins/{id}/crawl - 异步任务模式""" response = await client.get("/api/plugins") plugins = response.json()["data"]["plugins"] if not plugins: pytest.skip("没有可用的插件") plugin_id = plugins[0]["id"] response = await client.post(f"/api/plugins/{plugin_id}/crawl") assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "task_id" in data["data"] task_id = data["data"]["task_id"] task_data = await poll_task_until_terminal( client, task_id, max_rounds=140, interval=0.5 ) assert task_data is not None assert task_data["status"] in ("completed", "failed", "cancelled") @pytest.mark.asyncio async def test_crawl_nonexistent_plugin(self, client): """测试 POST /api/plugins/{id}/crawl - 不存在的插件""" response = await client.post("/api/plugins/nonexistent_plugin/crawl") assert response.status_code == 404 @pytest.mark.network @pytest.mark.slow @pytest.mark.asyncio async def test_crawl_all_plugins(self, client): """测试 POST /api/plugins/crawl-all - 异步任务模式""" response = await client.post("/api/plugins/crawl-all") assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "task_id" in data["data"] task_id = data["data"]["task_id"] task_data = await poll_task_until_terminal( client, task_id, max_rounds=400, interval=0.5 ) assert task_data is not None assert task_data["status"] in ("completed", "failed", "cancelled")