Files
Agent-System-Backend/internal/workflow/workflow_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

103 lines
3.5 KiB
Go

package workflow
import (
"encoding/json"
"strconv"
"strings"
"testing"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
func known(names ...string) func(string) bool {
set := map[string]struct{}{}
for _, n := range names {
set[n] = struct{}{}
}
return func(name string) bool {
_, ok := set[name]
return ok
}
}
func TestValidateRejectsBadDefinitions(t *testing.T) {
okTool := known("time_now")
cases := []struct {
name string
def core.WorkflowDef
lim Limits
want string
}{
{"dup", core.WorkflowDef{ID: "a", Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}, {ID: "s", Kind: "card"}}}, DeclarativeLimits(), "重复"},
{"kind", core.WorkflowDef{ID: "a", Steps: []core.WorkflowStep{{ID: "s", Kind: "nope"}}}, DeclarativeLimits(), "不合法"},
{"denied", core.WorkflowDef{ID: "a", Steps: []core.WorkflowStep{{ID: "s", Kind: "tool", Tool: "run_python"}}}, DeclarativeLimits(), "不能调用"},
{"missing", core.WorkflowDef{ID: "a", Steps: []core.WorkflowStep{{ID: "s", Kind: "tool", Tool: "no_such"}}}, DeclarativeLimits(), "未注册"},
{"over", core.WorkflowDef{ID: "a", Steps: manySteps(21)}, DeclarativeLimits(), "超出上限"},
{"dynamic", core.WorkflowDef{ID: "a", Steps: manySteps(9)}, DynamicLimits(), "超出上限"},
{"script", core.WorkflowDef{ID: "a", Steps: []core.WorkflowStep{{ID: "s", Kind: "script", Script: "scripts/demo.py"}}}, DynamicLimits(), "不能包含 script"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := Validate(tc.def, tc.lim, okTool)
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("got %v, want substring %q", err, tc.want)
}
})
}
good := core.WorkflowDef{ID: "a", MaxRetries: 6, Steps: []core.WorkflowStep{
{ID: "s", Kind: "tool", Tool: "time_now"},
{ID: "p", Kind: "script", Script: "scripts/demo.py", Critic: &core.StepCritic{Criteria: "有结果", MaxRetries: 3}},
}}
if err := Validate(good, DeclarativeLimits(), okTool); err != nil {
t.Fatal(err)
}
}
func manySteps(n int) []core.WorkflowStep {
out := make([]core.WorkflowStep, n)
for i := range out {
out[i] = core.WorkflowStep{ID: "s" + strconv.Itoa(i), Kind: "hitl"}
}
return out
}
func TestRenderKeepsArgsJSON(t *testing.T) {
raw := json.RawMessage(`{"q":"关于 {{slots.topic}}","note":"{{input}}"}`)
got, err := RenderRaw(raw, Vars{Input: `他说 "好"`, Slots: map[string]string{"topic": "主题"}})
if err != nil {
t.Fatal(err)
}
var m map[string]string
if err := json.Unmarshal(got, &m); err != nil {
t.Fatalf("not json: %s (%v)", got, err)
}
if m["q"] != "关于 主题" || m["note"] != `他说 "好"` {
t.Fatalf("%s", got)
}
text := Render("{{steps.draft.output}} / {{slots.missing}}", Vars{Steps: map[string]string{"draft": "正文"}})
if text != "正文 / " {
t.Fatalf("%q", text)
}
}
func TestByIntentAndReplace(t *testing.T) {
reg := NewRegistry()
def := core.WorkflowDef{ID: "demo.brief", IntentID: "demo.brief", Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}}}
reg.Set([]core.WorkflowDef{def}, map[string]string{ScriptKey("demo.brief", "scripts/demo.py"): "print(1)"})
got, ok := reg.ByIntent("demo.brief")
if !ok || got.ID != "demo.brief" {
t.Fatalf("%+v %v", got, ok)
}
body, ok := reg.Script("demo.brief", "scripts/demo.py")
if !ok || body != "print(1)" {
t.Fatalf("script %q %v", body, ok)
}
reg.Set(nil, nil)
if _, ok := reg.ByIntent("demo.brief"); ok {
t.Fatal("cleared registry still has the workflow")
}
if _, ok := reg.Get("demo.brief"); ok {
t.Fatal("cleared registry still has the id")
}
}