fix: 拒绝环形 DAG 并避免无效工作流堵塞任务队列

This commit is contained in:
2026-09-11 17:19:40 +08:00
parent 6eb65e4356
commit f99f8de171
5 changed files with 242 additions and 9 deletions
+85
View File
@@ -26,6 +26,91 @@ def definition() -> dict:
}
def cyclic_definition() -> dict:
"""构造带环的 DAG 定义(a → b → a),用于验证保存/发布拒绝无效工作流。
环上的两个节点互相引用对方输出,形成了无法拓扑排序的依赖:节点 ID 唯一、
边引用的节点都存在,因此只有环检测能拦住它(R04 复现定义)。
"""
return {
"name": "cyclic",
"version": 1,
"nodes": [
{"id": "a", "node_type": "echo", "inputs": {"file_uri": "b.file_uri"}},
{"id": "b", "node_type": "echo", "inputs": {"file_uri": "a.file_uri"}},
],
"edges": [{"from": "a", "to": "b"}, {"from": "b", "to": "a"}],
"entry_inputs": {"video_uri": "file"},
"final_outputs": {"result": "a.file_uri"},
}
def test_create_workflow_rejects_cycle() -> None:
"""验证保存环形 DAG 返回 422,不把无效版本写入版本表。"""
with TestClient(app) as client:
response = client.post(
"/api/admin/workflows",
json={
"id": "cyclic-flow",
"name": "Cyclic Flow",
"definition": cyclic_definition(),
},
)
assert response.status_code == 422
assert "cycle" in response.json()["detail"]
# 拒绝保存时不得留下半成品工作流/版本记录。
db = app.state.db
assert db.get_workflow("cyclic-flow") is None
assert db.list_workflow_versions("cyclic-flow") == []
# 已有工作流重新提交带环定义:同样拒绝,不追加新版本。
assert client.post(
"/api/admin/workflows",
json={"id": "cycle-check", "name": "Cycle Check", "definition": definition()},
).status_code == 200
assert client.post(
"/api/admin/workflows",
json={"id": "cycle-check", "name": "Cycle Check", "definition": cyclic_definition()},
).status_code == 422
assert db.get_workflow("cycle-check")["latest_version"] == 1
assert len(db.list_workflow_versions("cycle-check")) == 1
db.delete_workflow("cycle-check")
def test_validate_workflow_rejects_cycle() -> None:
"""验证只校验不保存的接口同样拒绝环形 DAG。"""
with TestClient(app) as client:
db = app.state.db
# 独立工作流 ID 并显式清理,避免与其他用例(共用同一个测试数据库)互相影响。
assert client.post(
"/api/admin/workflows",
json={"id": "cycle-check", "name": "Cycle Check", "definition": definition()},
).status_code == 200
response = client.post(
"/api/admin/workflows/cycle-check/validate",
json=cyclic_definition(),
)
assert response.status_code == 422
assert "cycle" in response.json()["detail"]
db.delete_workflow("cycle-check")
def test_publish_rejects_cycle_in_latest_version() -> None:
"""验证历史遗留的环形版本不能被发布(发布前重新校验最新版本定义)。"""
with TestClient(app) as client:
db = app.state.db
# 直接写库模拟修复前已保存的无效版本(保存接口现在会拒绝,只能这样构造)。
db.upsert_workflow(
{"id": "legacy-cyclic", "name": "Legacy", "published": 0, "latest_version": 1}
)
db.create_workflow_version("legacy-cyclic", 1, cyclic_definition())
response = client.post("/api/admin/workflows/legacy-cyclic/publish")
assert response.status_code == 422
assert "cycle" in response.json()["detail"]
assert db.get_workflow("legacy-cyclic")["published"] == 0
db.delete_workflow("legacy-cyclic")
def test_workflow_crud_and_publish() -> None:
"""验证工作流 CRUD、校验、发布与版本列表的完整流程。"""
with TestClient(app) as client: