Files
ProxyPool/tests/integration/test_plugins_api.py
祀梦 b972b64616 refactor: 全面重构核心架构,消除反复修改的根因
- 删除 ValidationQueue 双轨持久化队列,替换为纯内存 AsyncWorkerPool
- 引入统一后台任务框架 JobExecutor(Job/CrawlJob/ValidateAllJob)
- 新增 PluginRunner 统一插件执行(超时、重试、健康检查、统计)
- 重构 SchedulerService 职责收敛为仅定时触发 ValidateAllJob
- 使用 AsyncExitStack 重构 lifespan,安全管理长生命周期资源
- 路由层瘦身 50%+,业务异常上抛由全局中间件统一处理
- 实现设置全热更新(WorkerPool 并发、Validator 超时即时生效)
- 前端 Store 强制写后重新拉取,消除乐观更新数据不同步
- 删除 queue.py / task_repo.py / task_service.py
- 新增 execution 单元测试,全部 85 个测试通过
2026-04-04 22:36:57 +08:00

178 lines
6.7 KiB
Python

"""插件 API 集成测试 - 测试 /api/plugins/* 所有接口"""
import pytest
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.asyncio
async def test_crawl_plugin(self, client):
"""测试 POST /api/plugins/{id}/crawl - 异步任务模式"""
import asyncio
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 = None
for _ in range(10):
await asyncio.sleep(0.3)
res = await client.get(f"/api/tasks/{task_id}")
assert res.status_code == 200
task_data = res.json()["data"]
if task_data["status"] in ("completed", "failed"):
break
assert task_data is not None
assert task_data["status"] == "completed"
@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.asyncio
async def test_crawl_all_plugins(self, client):
"""测试 POST /api/plugins/crawl-all - 异步任务模式"""
import asyncio
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 = None
for _ in range(10):
await asyncio.sleep(0.3)
res = await client.get(f"/api/tasks/{task_id}")
assert res.status_code == 200
task_data = res.json()["data"]
if task_data["status"] in ("completed", "failed"):
break
assert task_data is not None
assert task_data["status"] == "completed"