65e939889e
- ParseAndAssemble 加 validateTopology:拦重复/空节点 id + 悬挂边
(source/target 指向不存在节点),避免坏图进编排后被 compose 静默跳过
- 保守策略:空图 / 报告任务 {topic} / 非标准载荷一律宽松放过,不误伤合法提交
- 单测覆盖合法/重复id/空id/悬挂边×2/空端点/放过场景
- live:悬挂边 POST /tasks → 422(错误指明具体边与缺失节点);合法图 → 202
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.2 KiB
Go
72 lines
2.2 KiB
Go
package dsl
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseAndAssemble_OK(t *testing.T) {
|
|
raw := json.RawMessage(`{"version":"1","nodes":[{"id":"a","kind":"input"}],"edges":[]}`)
|
|
task, err := ParseAndAssemble(raw)
|
|
if err != nil {
|
|
t.Fatalf("合法 DSL 不应报错: %v", err)
|
|
}
|
|
if !strings.HasPrefix(task.ID, "task_") {
|
|
t.Errorf("任务 ID 应以 task_ 前缀, got %q", task.ID)
|
|
}
|
|
if string(task.Graph) != string(raw) {
|
|
t.Error("Graph 应原样透传 DSL")
|
|
}
|
|
if task.Meta == nil {
|
|
t.Error("Meta 应已初始化(供网关注入身份)")
|
|
}
|
|
}
|
|
|
|
func TestParseAndAssemble_Errors(t *testing.T) {
|
|
if _, err := ParseAndAssemble(json.RawMessage(``)); err == nil {
|
|
t.Error("空 DSL 应报错")
|
|
}
|
|
if _, err := ParseAndAssemble(json.RawMessage(`{bad`)); err == nil {
|
|
t.Error("非法 JSON 应报错")
|
|
}
|
|
}
|
|
|
|
func TestParseAndAssemble_Topology(t *testing.T) {
|
|
ok := func(raw string) {
|
|
t.Helper()
|
|
if _, err := ParseAndAssemble(json.RawMessage(raw)); err != nil {
|
|
t.Errorf("应通过: %s\n err=%v", raw, err)
|
|
}
|
|
}
|
|
bad := func(raw, wantSub string) {
|
|
t.Helper()
|
|
_, err := ParseAndAssemble(json.RawMessage(raw))
|
|
if err == nil {
|
|
t.Errorf("应被拒: %s", raw)
|
|
return
|
|
}
|
|
if !strings.Contains(err.Error(), wantSub) {
|
|
t.Errorf("错误应含 %q, got %q", wantSub, err.Error())
|
|
}
|
|
}
|
|
|
|
// 合法:多节点 + 边引用齐全。
|
|
ok(`{"nodes":[{"id":"a","kind":"input"},{"id":"b","kind":"agent"}],"edges":[{"source":"a","target":"b"}]}`)
|
|
// 宽松放过:空图 / 报告任务的 {topic} / 无 nodes 字段。
|
|
ok(`{"nodes":[],"edges":[]}`)
|
|
ok(`{"topic":"季度报告"}`)
|
|
ok(`{"version":"1"}`)
|
|
|
|
// 拒绝:重复 id。
|
|
bad(`{"nodes":[{"id":"a"},{"id":"a"}],"edges":[]}`, "重复")
|
|
// 拒绝:空 id。
|
|
bad(`{"nodes":[{"id":""}],"edges":[]}`, "缺少 id")
|
|
// 拒绝:悬挂边(目标节点不存在)。
|
|
bad(`{"nodes":[{"id":"a"}],"edges":[{"source":"a","target":"ghost"}]}`, "不存在的目标节点")
|
|
// 拒绝:悬挂边(源节点不存在,即便无节点)。
|
|
bad(`{"nodes":[],"edges":[{"source":"x","target":"y"}]}`, "不存在的源节点")
|
|
// 拒绝:边端点为空。
|
|
bad(`{"nodes":[{"id":"a"}],"edges":[{"source":"a","target":""}]}`, "不能为空")
|
|
}
|