Pin Agent-Native as a submodule so the module builds without the multi-repo sibling path. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
3.6 KiB
Go
104 lines
3.6 KiB
Go
package squad
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRoleRegistryEmptyByDefault(t *testing.T) {
|
|
r := NewRoleRegistry()
|
|
if roles := r.List(); len(roles) != 0 {
|
|
t.Fatalf("registry must start empty, got %d roles (no preset roles allowed)", len(roles))
|
|
}
|
|
if _, ok := r.Recommend("法规调研"); ok {
|
|
t.Fatal("empty registry must not recommend")
|
|
}
|
|
}
|
|
|
|
func TestRoleRegistryRegisterAndRecommend(t *testing.T) {
|
|
r := NewRoleRegistry()
|
|
if err := r.Register(RoleSpec{Role: "researcher", Name: "调研", Keywords: []string{"调研", "市场"}, Backend: "stub"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := r.Register(RoleSpec{Role: "writer", Name: "撰写", Keywords: []string{"撰写"}, Responsibilities: []string{"报告成文"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := r.Register(RoleSpec{Role: "bad", BashPermission: "sometimes"}); err == nil {
|
|
t.Fatal("invalid bash_permission must fail")
|
|
}
|
|
spec, ok := r.Recommend("帮我调研市场情况")
|
|
if !ok || spec.Role != "researcher" {
|
|
t.Fatalf("recommend want researcher, got %+v ok=%v", spec, ok)
|
|
}
|
|
if spec.BashPermission != "deny" {
|
|
t.Fatalf("default bash_permission want deny, got %q", spec.BashPermission)
|
|
}
|
|
if !r.Remove("writer") {
|
|
t.Fatal("remove writer failed")
|
|
}
|
|
if r.Remove("writer") {
|
|
t.Fatal("double remove should fail")
|
|
}
|
|
}
|
|
|
|
func TestRoleRegistryDerive(t *testing.T) {
|
|
r := NewRoleRegistry()
|
|
_ = r.Register(RoleSpec{Role: "engineer", Name: "工程师", Backend: "agent-native", Model: "m1",
|
|
Keywords: []string{"工程"}, Instructions: "基线指令"})
|
|
derived, err := r.Derive(DeriveRequest{RoleKey: "gmp_expert", DomainTopic: "GMP 洁净车间"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if derived.Source != RoleSourceDerived || derived.BaseRole != "engineer" {
|
|
t.Fatalf("derived role meta wrong: %+v", derived)
|
|
}
|
|
if derived.Backend != "agent-native" || derived.Model != "m1" || derived.Instructions != "基线指令" {
|
|
t.Fatalf("derive must inherit backend/model/instructions: %+v", derived)
|
|
}
|
|
if !strings.Contains(strings.Join(derived.Responsibilities, ";"), "GMP") {
|
|
t.Fatalf("domain topic not in responsibilities: %v", derived.Responsibilities)
|
|
}
|
|
if len(derived.ForbiddenActions) == 0 {
|
|
t.Fatal("derive must append guardrail forbidden actions")
|
|
}
|
|
}
|
|
|
|
func TestBlueprintRegistryMatch(t *testing.T) {
|
|
b := NewBlueprintRegistry()
|
|
if err := b.Register(Blueprint{Key: "x", Steps: nil}); err == nil {
|
|
t.Fatal("blueprint without steps must fail")
|
|
}
|
|
_ = b.Register(Blueprint{
|
|
Key: "report", Title: "报告", Match: []string{"报告", "调研"},
|
|
Steps: []StepSpec{{ID: "s1", Role: "writer", Title: "成文"}},
|
|
})
|
|
if bp, ok := b.Match("我要一份调研报告", ""); !ok || bp.Key != "report" {
|
|
t.Fatalf("match failed: %+v ok=%v", bp, ok)
|
|
}
|
|
if _, ok := b.Match("无关文本", ""); ok {
|
|
t.Fatal("unrelated text must not match")
|
|
}
|
|
if bp, ok := b.Match("", "report"); !ok || bp.Key != "report" {
|
|
t.Fatal("explicit task key must match")
|
|
}
|
|
// 步骤默认 ID/Targets 补全。
|
|
got, _ := b.Get("report")
|
|
if got.Steps[0].Targets[0] != "output.md" {
|
|
t.Fatalf("default targets missing: %+v", got.Steps[0])
|
|
}
|
|
}
|
|
|
|
func TestHitlPolicyMergeAndStates(t *testing.T) {
|
|
merged := mergeHitlPolicy(HitlPolicy{Mode: HitlInteractive},
|
|
&HitlPolicy{Mode: HitlAuto}, &HitlPolicy{TimeoutAfter: 5e9, ExpireAction: "cancel"})
|
|
if merged.Mode != HitlAuto || merged.TimeoutAfter != 5e9 || merged.ExpireAction != "cancel" {
|
|
t.Fatalf("merge wrong: %+v", merged)
|
|
}
|
|
if !RunAwaitingClarification.IsHeld() || !RunQualityReview.IsHeld() || RunExecuting.IsHeld() {
|
|
t.Fatal("held state classification wrong")
|
|
}
|
|
if !RunCompleted.Terminal() || RunWaitingHuman.Terminal() {
|
|
t.Fatal("terminal state classification wrong")
|
|
}
|
|
}
|