Files
Agent-System-Backend/internal/squadadapter/adapter_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

181 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package squadadapter
import (
"context"
"errors"
"strings"
"testing"
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/subagent"
"git-ssh.u1680832.nyat.app/weichen/Agent-Squad.git/squad"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
func TestMapPlanAndStatus(t *testing.T) {
p := &squad.WorkflowPlan{Title: "示例编排", Steps: []squad.StepSpec{
{ID: "s1", Title: "梳理", Status: "completed"},
{ID: "s2", Title: "成文", Status: "running"},
{ID: "s3", Title: "终审", Status: "skipped"},
{ID: "s4", Title: "待办", Status: "pending"},
}}
snap := mapPlan(p)
if snap.Title != "示例编排" || len(snap.Steps) != 4 {
t.Fatalf("snapshot: %+v", snap)
}
want := []string{"done", "in_progress", "skipped", "pending"}
for i, w := range want {
if snap.Steps[i].Status != w {
t.Errorf("step %d: want %s got %s", i, w, snap.Steps[i].Status)
}
}
}
func TestBuiltinRolesValid(t *testing.T) {
reg := squad.NewRoleRegistry()
for _, r := range builtinRoles() {
if err := reg.Register(r); err != nil {
t.Errorf("role %s invalid: %v", r.Role, err)
}
if r.Backend != "agent-native" {
t.Errorf("role %s backend: %s", r.Backend, r.Backend)
}
}
}
func TestIsHoldStatus(t *testing.T) {
for _, s := range []string{"waiting_human", "awaiting_confirmation", "awaiting_clarification", "quality_review"} {
if !isHoldStatus(s) {
t.Errorf("%s should be hold", s)
}
}
for _, s := range []string{"completed", "executing", "failed"} {
if isHoldStatus(s) {
t.Errorf("%s should not be hold", s)
}
}
}
// subagentFakeRc 只实现 SpawnSubagent 的最小 Ctx 桩(其余方法本测试不触达)。
type subagentFakeRc struct {
gotName string
gotTask string
payload core.AgentResultPayload
err error
}
func (f *subagentFakeRc) Context() context.Context { return context.Background() }
func (f *subagentFakeRc) Run() core.Run { return core.Run{} }
func (f *subagentFakeRc) Settings() core.EffectiveSettings { return nil }
func (f *subagentFakeRc) Emit(typ core.StreamEventType, payload any) error { return nil }
func (f *subagentFakeRc) AskHITL(card core.HITLCard) (core.HITLResponse, error) {
return core.HITLResponse{}, nil
}
func (f *subagentFakeRc) UpdatePlan(p core.PlanSnapshot) error { return nil }
func (f *subagentFakeRc) WriteArtifact(name, kind, mime string, content []byte) (core.Artifact, error) {
return core.Artifact{}, nil
}
func (f *subagentFakeRc) SpawnSubagent(name, task, hint string) (core.AgentResultPayload, error) {
f.gotName, f.gotTask = name, task
return f.payload, f.err
}
func TestSubagentFactoryBridgesSpawnSubagent(t *testing.T) {
rc := &subagentFakeRc{payload: core.AgentResultPayload{Status: core.RunCompleted, Text: "子代理结论"}}
res, err := SubagentFactory(rc)(context.Background(), subagent.Request{
Goal: "查证数据", Inputs: "docs/v2", WriteScope: "research/",
NonGoals: "不做实现", DoneCriteria: "三来源对照",
Deliverables: []string{"notes.md"},
OutputFormat: "Markdown:结论先行,附对照表",
Tools: []string{"web_search", "read_file"},
})
if err != nil {
t.Fatal(err)
}
if !res.Success || res.Text != "子代理结论" {
t.Fatalf("bridge result wrong: %+v", res)
}
for _, want := range []string{"查证数据", "docs/v2", "research/", "不做实现", "三来源对照", "notes.md",
"输出格式:Markdown:结论先行,附对照表", "工具面(可用工具,不得越界):web_search, read_file"} {
if !strings.Contains(rc.gotTask, want) {
t.Fatalf("task brief missing %q:\n%s", want, rc.gotTask)
}
}
// 未完成状态:Success=false 且 Err 带结束状态。
rc2 := &subagentFakeRc{payload: core.AgentResultPayload{Status: core.RunFailed, Summary: "预算耗尽"}}
res2, err := SubagentFactory(rc2)(context.Background(), subagent.Request{Goal: "g", DoneCriteria: "d"})
if err != nil {
t.Fatal(err)
}
if res2.Success || !strings.Contains(res2.Err, "failed") || res2.Text != "预算耗尽" {
t.Fatalf("failure mapping wrong: %+v", res2)
}
// SpawnSubagent 报错透传。
rc3 := &subagentFakeRc{err: errors.New("spawn refused")}
if _, err := SubagentFactory(rc3)(context.Background(), subagent.Request{Goal: "g", DoneCriteria: "d"}); err == nil {
t.Fatal("spawn error must propagate")
}
}
func TestBuiltinRolesExplicitExecConfig(t *testing.T) {
roles := builtinRoles()
byKey := map[string]squad.RoleSpec{}
for _, r := range roles {
byKey[r.Role] = r
}
if len(roles) != 4 {
t.Fatalf("want 4 builtin roles, got %d", len(roles))
}
// 全员 BashPermission 显式声明。
for _, r := range roles {
switch r.BashPermission {
case squad.BashPermissionDeny, squad.BashPermissionAsk, squad.BashPermissionAllow:
default:
t.Fatalf("role %s bash_permission not explicit: %q", r.Role, r.BashPermission)
}
}
// 调研员:search + fetch;审校员:grep/glob;撰稿员:ask + 三套技能 + 生成工具。
for _, want := range []string{"web_search", "fetch_url"} {
if !containsStr(byKey["researcher"].Tools, want) {
t.Fatalf("researcher tools missing %s: %v", want, byKey["researcher"].Tools)
}
}
for _, want := range []string{"grep", "glob"} {
if !containsStr(byKey["reviewer"].Tools, want) {
t.Fatalf("reviewer tools missing %s: %v", want, byKey["reviewer"].Tools)
}
}
w := byKey["writer"]
if w.BashPermission != squad.BashPermissionAsk {
t.Fatalf("writer needs ask (run_python for docs), got %q", w.BashPermission)
}
if !containsStr(w.Tools, "run_python") || !containsStr(w.Tools, "load_skill") {
t.Fatalf("writer tools missing generation path: %v", w.Tools)
}
if len(w.Skills) != 3 || !containsStr(w.Skills, "docx-author") || !containsStr(w.Skills, "pptx-author") {
t.Fatalf("writer skills wrong: %v", w.Skills)
}
}
func containsStr(list []string, want string) bool {
for _, v := range list {
if v == want {
return true
}
}
return false
}
// Q3 WP2:agent-system 退役后,未注入 AgentNativeOptions 时解析器为空——
// 步骤后端解析 fail-closed,不再静默走 SpawnSubagent 压缩路径。
func TestBuildBackendsWithoutNativeOptionsIsEmpty(t *testing.T) {
saved := AgentNativeOptions
AgentNativeOptions = nil
defer func() { AgentNativeOptions = saved }()
r := BuildBackends(&subagentFakeRc{})
if _, _, err := r.Resolve(squad.RoleSpec{Role: "writer", Backend: "agent-native"}, "agent-native"); err == nil {
t.Fatal("无内核工厂时解析必须 fail-closed")
}
}