Round 3 fixes: cancelled polling, aggregator invalid_count, filter state, scheduler atomicity, HTTP exception handler, tests

This commit is contained in:
祀梦
2026-04-05 10:20:23 +08:00
parent 49e440cb41
commit dc5f050683
32 changed files with 321 additions and 163 deletions

View File

@@ -45,3 +45,13 @@ class TestHealthAPI:
assert isinstance(data["database"], str)
assert isinstance(data["scheduler"], str)
assert isinstance(data["version"], str)
@pytest.mark.asyncio
async def test_404_not_found_unified_format(self, client):
"""测试 404 返回统一格式"""
response = await client.get("/api/not-exist")
assert response.status_code == 404
data = response.json()
assert data["code"] == 404
assert "message" in data
assert data["data"] is None

View File

@@ -140,11 +140,11 @@ class TestPluginsAPI:
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"):
if task_data["status"] in ("completed", "failed", "cancelled"):
break
assert task_data is not None
assert task_data["status"] == "completed"
assert task_data["status"] in ("completed", "cancelled")
@pytest.mark.asyncio
async def test_crawl_nonexistent_plugin(self, client):
@@ -170,8 +170,8 @@ class TestPluginsAPI:
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"):
if task_data["status"] in ("completed", "failed", "cancelled"):
break
assert task_data is not None
assert task_data["status"] == "completed"
assert task_data["status"] in ("completed", "cancelled")

View File

@@ -137,6 +137,15 @@ class TestProxiesAPI:
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
@pytest.mark.asyncio
async def test_export_proxies_json_empty_database(self, client):
"""测试 GET /api/proxies/export/json - 空数据库"""
response = await client.get("/api/proxies/export/json")
assert response.status_code == 200
assert response.headers["content-type"] == "application/json"
# 空数据库应返回空列表 JSON
assert response.content.strip() == b"[]"
@pytest.mark.asyncio
async def test_export_proxies_invalid_format(self, client):
"""测试 GET /api/proxies/export/invalid - 无效格式"""

View File

@@ -73,6 +73,25 @@ class TestSchedulerAPI:
assert data["code"] == 200
assert data["data"]["started"] is True
@pytest.mark.asyncio
async def test_validate_now_returns_valid_job(self, client):
"""测试 POST /api/scheduler/validate-now 返回有效 job_id"""
await client.post("/api/scheduler/start")
response = await client.post("/api/scheduler/validate-now")
assert response.status_code == 200
data = response.json()
assert data["code"] == 200
job_id = data["data"]["job_id"]
assert isinstance(job_id, str) and len(job_id) > 0
# 通过应用状态验证 job 已被提交到 executor
from app.api.main import create_app
# 使用 client 的 app 实例
app = client._transport.app
executor = app.state.executor
job = executor.get_job(job_id)
assert job is not None
@pytest.mark.asyncio
async def test_scheduler_full_workflow(self, client):
"""测试调度器完整工作流"""

View File

@@ -135,3 +135,32 @@ class TestSettingsAPI:
# 验证一致性
for key, value in test_settings.items():
assert saved_settings[key] == value, f"设置项 {key} 不一致"
@pytest.mark.asyncio
async def test_settings_roundtrip_with_validation_targets(self, client):
"""测试设置读写一致性 - 包含数组类型的 validation_targets"""
test_settings = {
"crawl_timeout": 30,
"validation_timeout": 10,
"max_retries": 3,
"default_concurrency": 50,
"min_proxy_score": 0,
"proxy_expiry_days": 7,
"auto_validate": True,
"validate_interval_minutes": 30,
"validation_targets": [
"http://example.com/1",
"https://example.com/2",
],
}
# 写入设置
response = await client.post("/api/settings", json=test_settings)
assert response.status_code == 200
data = response.json()
assert data["data"]["validation_targets"] == test_settings["validation_targets"]
# 读取设置
response = await client.get("/api/settings")
saved_settings = response.json()["data"]
assert saved_settings["validation_targets"] == test_settings["validation_targets"]