package codeorch import ( "context" "errors" "os" "strings" "testing" "time" "unicode/utf8" ) func TestScriptCallsTwoToolsAndReportsTheOneThatRan(t *testing.T) { body := "import agentwf as wf\nprint(wf.tool('alpha', {}))\nprint(wf.tool('beta', {}))\n" out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) { if call.Name == "beta" { return "", errors.New("beta 失败") } return "A", nil }) text := out.ModelText() if len(out.Calls) != 1 || out.Calls[0] != "alpha" { t.Fatalf("calls %+v", out.Calls) } if !strings.Contains(text, "已执行:alpha") || !strings.Contains(text, "beta 失败") { t.Fatalf("text %q err %v", text, out.Err) } } func TestForbiddenToolIsRejectedBeforeDispatchRunsIt(t *testing.T) { body := "import agentwf as wf\nprint(wf.tool('read_file', {}))\nprint(wf.tool('run_shell', {}))\n" var ran []string out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) { if Forbidden(call.Name) { return "", errors.New("不能调用 " + call.Name) } ran = append(ran, call.Name) return "ok", nil }) if len(ran) != 1 || ran[0] != "read_file" { t.Fatalf("ran %+v", ran) } if !strings.Contains(out.ModelText(), "不能调用 run_shell") { t.Fatalf("text %q", out.ModelText()) } } func TestLongOutputIsClippedAndStored(t *testing.T) { body := "import agentwf as wf\nprint(wf.tool('big', {}))\n" dir := t.TempDir() out := Run(context.Background(), dir, body, Limits{MaxModelChars: 8, FullDir: dir}, nil, func(_ context.Context, call Call) (string, error) { return strings.Repeat("测", 20), nil }) if out.FullPath == "" { t.Fatal("expected a full-text file") } full, err := os.ReadFile(out.FullPath) if err != nil { t.Fatal(err) } if utf8.RuneCount(full) < 20 { t.Fatalf("full text too short: %q", full) } if !strings.HasSuffix(out.Output, "…(超长截断)") || utf8.RuneCountInString(strings.TrimSuffix(out.Output, "…(超长截断)")) != 8 { t.Fatalf("model output not clipped: %q", out.Output) } if !strings.Contains(out.ModelText(), out.FullPath) { t.Fatalf("model text missing path: %q", out.ModelText()) } } func TestResultGivenToScriptIsCapped(t *testing.T) { got := clipBytes(strings.Repeat("x", 20), 8) if got != "xxxxxxxx" { t.Fatalf("clip %q", got) } if !utf8.ValidString(clipBytes("测测测", 4)) { t.Fatal("clip broke utf-8") } } func TestTimeoutIsCapped(t *testing.T) { if (Limits{Timeout: 900 * time.Second, MaxTimeout: 600 * time.Second}).bounded() != 600*time.Second { t.Fatal("timeout must clamp to the max") } if (Limits{}).bounded() != defaultTimeout { t.Fatal("empty limits must use the 120s default") } }