- Add Free_Proxy_Website-style fpw_* plugins and register them - Per-plugin crawl timeout (crawl_timeout_seconds=120); remove global crawl_timeout setting - Validator: fix connect vs total timeout on save; SOCKS session LRU cache; drop redundant semaphore - Validation handler uses single DB connection; batch upsert after crawl; WorkerPool put_nowait - Remove unused max_retries from settings API/UI; settings maintenance SQL + init_db cleanup of deprecated keys - WebSocket dashboard stats; ProxyList pool_filter and API alignment - POST /api/proxies/delete-one for IPv6-safe deletes; task poll stops on 404 - pytest uses PROXYPOOL_DB_PATH=db/proxies.test.sqlite so tests do not wipe production DB - .gitignore: explicit proxies.test.sqlite patterns; fix plugin_service ValidationException import Made-with: Cursor
168 lines
6.4 KiB
Python
168 lines
6.4 KiB
Python
"""插件 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")
|