package eino import ( "testing" "github.com/sundynix/sundynix-dispatcher/internal/dsl" ) func TestEvalCondition(t *testing.T) { b := &board{refs: []string{"a", "b"}, toolOut: []string{"x"}, answer: "12345", profile: "p"} cases := []struct { cond string want bool }{ {"", true}, // 空 → 真 {"2>1", true}, // 字面量比较 {"1>2", false}, // {"refs > 0", true}, // refs=2 {"refs == 2", true}, // {"refs < 1", false}, // {"tools >= 1", true}, // tools=1 {"tools != 0", true}, // {"answer <= 5", true}, // answer 长度 5 {"answer > 5", false}, // {"profile == 1", true}, {"乱写的条件", true}, // 非空但无法解析 → 默认真 } for _, c := range cases { if got := evalCondition(c.cond, b); got != c.want { t.Errorf("evalCondition(%q) = %v, want %v", c.cond, got, c.want) } } } func TestResolveOperand(t *testing.T) { b := &board{refs: []string{"a"}, toolOut: []string{"x", "y"}, answer: "汉字三", profile: ""} cases := []struct { s string want float64 }{ {"refs", 1}, {"tools", 2}, {"answer", 3}, // 3 个 rune {"profile", 0}, {"42", 42}, {"unknown", 0}, // 非关键字非数字 → 0 } for _, c := range cases { if got := resolveOperand(c.s, b); got != c.want { t.Errorf("resolveOperand(%q) = %v, want %v", c.s, got, c.want) } } } func TestAggregate(t *testing.T) { parts := []string{"甲", "乙", "甲", " ", ""} if got := aggregate("拼接", parts); len(got) != 1 || got[0] != "甲\n---\n乙\n---\n甲" { t.Errorf("拼接 = %v", got) } if got := aggregate("去重合并", parts); len(got) != 1 || got[0] != "甲\n---\n乙" { t.Errorf("去重合并 = %v", got) } if got := aggregate("未知策略", []string{"a", "b"}); len(got) != 1 || got[0] != "a\n---\nb" { t.Errorf("默认应为拼接, got %v", got) } if got := aggregate("拼接", []string{"", " "}); got != nil { t.Errorf("全空应返回 nil, got %v", got) } } func TestBranchNode_Labeled(t *testing.T) { o := &Orchestrator{} tr := &execTracer{} // sink 为 nil → 所有发射均空操作 byID := map[string]dsl.Node{"A": {ID: "A"}, "B": {ID: "B"}} // true 边故意列在第二位,验证按标签而非边序选路。 outs := []dsl.Edge{ {Source: "br", Target: "B", SourceHandle: "false"}, {Source: "br", Target: "A", SourceHandle: "true"}, } n := dsl.Node{ID: "br", Kind: "branch", Config: map[string]any{"condition": "2>1"}} if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "A" { t.Errorf("条件真应走 true 标签(A), got %v", got) } n.Config["condition"] = "1>2" if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "B" { t.Errorf("条件假应走 false 标签(B), got %v", got) } } func TestBranchNode_OrderFallback(t *testing.T) { o := &Orchestrator{} tr := &execTracer{} byID := map[string]dsl.Node{"A": {ID: "A"}, "B": {ID: "B"}} outs := []dsl.Edge{{Source: "br", Target: "A"}, {Source: "br", Target: "B"}} // 无标签 n := dsl.Node{ID: "br", Kind: "branch", Config: map[string]any{"condition": "2>1"}} if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "A" { t.Errorf("无标签条件真应走第一条边(A), got %v", got) } n.Config["condition"] = "1>2" if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "B" { t.Errorf("无标签条件假应走第二条边(B), got %v", got) } // 单出边 + 条件假 → 不继续。 single := []dsl.Edge{{Source: "br", Target: "A"}} n.Config["condition"] = "1>2" if got := o.branchNode(n, &board{}, single, byID, tr); len(got) != 0 { t.Errorf("单边条件假应剪掉, got %v", got) } } func TestSmallHelpers(t *testing.T) { cfg := map[string]any{"s": " hi ", "n": 7, "b": true, "nil": nil} if cstr(cfg, "s") != "hi" { t.Error("cstr 应去空白") } if cstr(cfg, "n") != "7" { t.Error("cstr 非字符串应 fmt 转换") } if cstr(cfg, "missing") != "" || cstr(cfg, "nil") != "" || cstr(nil, "x") != "" { t.Error("cstr 缺失/nil 应空串") } if !cbool(cfg, "b") || cbool(cfg, "s") || cbool(nil, "b") { t.Error("cbool 行为不符") } if countLines("") != 0 || countLines("a") != 1 || countLines("a\nb\nc") != 3 { t.Error("countLines 行为不符") } if labelOf(dsl.Node{Label: "L"}, "d") != "L" || labelOf(dsl.Node{}, "d") != "d" { t.Error("labelOf 行为不符") } es := []dsl.Edge{{Target: "x"}, {Target: "y"}} got := targetsOf(es) if len(got) != 2 || got[0] != "x" || got[1] != "y" { t.Errorf("targetsOf = %v", got) } }