package engine import ( "context" "testing" ) // ---- SkillRegistry CRUD ---- func TestSkillRegistry_Register(t *testing.T) { r := newSkillRegistry(nil) s := &Skill{Name: "commit", Description: "Create commit"} if err := r.Register(s); err != nil { t.Fatalf("Register: %v", err) } // 重复注册应报错 if err := r.Register(&Skill{Name: "commit"}); err == nil { t.Error("expected error on duplicate registration") } } func TestSkillRegistry_RegisterBuiltin_Overrides(t *testing.T) { r := newSkillRegistry(nil) _ = r.Register(&Skill{Name: "discuss", Description: "File version"}) r.RegisterBuiltin(&Skill{Name: "discuss", Description: "Builtin version"}) s, ok := r.Get("discuss") if !ok { t.Fatal("skill not found after RegisterBuiltin") } if s.Description != "Builtin version" { t.Errorf("expected builtin to override, got %q", s.Description) } if s.Source != "bundled" { t.Errorf("expected source=bundled, got %q", s.Source) } } func TestSkillRegistry_RegisterAll(t *testing.T) { r := newSkillRegistry(nil) skills := []*Skill{ {Name: "alpha", Description: "A"}, {Name: "beta", Description: "B"}, {Name: "gamma", Description: "C"}, } r.RegisterAll(skills) all := r.List() if len(all) != 3 { t.Errorf("expected 3 skills, got %d", len(all)) } } func TestSkillRegistry_Get_NotFound(t *testing.T) { r := newSkillRegistry(nil) _, ok := r.Get("nonexistent") if ok { t.Error("expected false for nonexistent skill") } } func TestSkillRegistry_List_WithFilter(t *testing.T) { r := newSkillRegistry(nil) r.RegisterAll([]*Skill{ {Name: "a", UserInvocable: true}, {Name: "b", UserInvocable: false}, {Name: "c", UserInvocable: true}, }) userVisible := r.List(func(s *Skill) bool { return s.UserInvocable }) if len(userVisible) != 2 { t.Errorf("expected 2 user-invocable skills, got %d", len(userVisible)) } } // ---- SkillRegistry.Invoke (inline) ---- func TestSkillRegistry_InvokeInline(t *testing.T) { r := newSkillRegistry(nil) r.RegisterBuiltin(&Skill{ Name: "greet", Content: "Hello, $ARGUMENTS!", Context: ExecutionContextInline, }) ctx := context.Background() result, err := r.Invoke(ctx, "greet", "world", "sess1") if err != nil { t.Fatalf("Invoke: %v", err) } if result.Mode != ExecutionContextInline { t.Errorf("Mode: got %q, want inline", result.Mode) } if result.Content != "Hello, world!" { t.Errorf("Content: got %q", result.Content) } } func TestSkillRegistry_InvokeInline_DefaultContext(t *testing.T) { // Context 为空应默认 inline r := newSkillRegistry(nil) r.RegisterBuiltin(&Skill{Name: "default", Content: "Default $ARGUMENTS"}) result, err := r.Invoke(context.Background(), "default", "arg1", "") if err != nil { t.Fatalf("Invoke: %v", err) } if result.Mode != ExecutionContextInline { t.Errorf("expected inline mode, got %q", result.Mode) } } func TestSkillRegistry_InvokeInline_WithAllowedTools(t *testing.T) { r := newSkillRegistry(nil) r.RegisterBuiltin(&Skill{ Name: "constrained", Content: "Use only allowed tools", Context: ExecutionContextInline, AllowedTools: []string{"Read", "Glob"}, Model: "haiku", }) result, err := r.Invoke(context.Background(), "constrained", "", "") if err != nil { t.Fatalf("Invoke: %v", err) } if len(result.AllowedTools) != 2 { t.Errorf("expected 2 allowed tools, got %d", len(result.AllowedTools)) } if result.Model != "haiku" { t.Errorf("Model: got %q", result.Model) } } func TestSkillRegistry_Invoke_NotFound(t *testing.T) { r := newSkillRegistry(nil) _, err := r.Invoke(context.Background(), "nonexistent", "", "") if err == nil { t.Error("expected error for nonexistent skill") } } // ---- fork 深度限制 ---- func TestSkillRegistry_ForkDepthLimit_Downgrade(t *testing.T) { // 当深度达到限制时,fork 应降级为 inline r := newSkillRegistry(nil) // engine=nil,fork 不可用 r.RegisterBuiltin(&Skill{ Name: "deep-skill", Content: "Deep content $ARGUMENTS", Context: ExecutionContextFork, }) // 注入深度 = MaxSkillForkDepth(已达上限) ctx := withSkillDepth(context.Background(), MaxSkillForkDepth) result, err := r.Invoke(ctx, "deep-skill", "arg", "") if err != nil { t.Fatalf("Invoke: %v", err) } // 应降级为 inline if result.Mode != ExecutionContextInline { t.Errorf("expected inline downgrade, got %q", result.Mode) } if result.Content != "Deep content arg" { t.Errorf("Content: got %q", result.Content) } } func TestSkillRegistry_ForkNoEngine(t *testing.T) { // engine=nil,fork 模式在深度 0 时应报错(无法 spawn) r := newSkillRegistry(nil) r.RegisterBuiltin(&Skill{ Name: "fork-skill", Content: "Fork me", Context: ExecutionContextFork, }) // 深度 0,不会降级,但 engine=nil ctx := context.Background() _, err := r.Invoke(ctx, "fork-skill", "", "") if err == nil { t.Error("expected error when engine is nil for fork execution") } } // ---- skillDepth context ---- func TestSkillDepth_Default(t *testing.T) { d := skillDepthFromCtx(context.Background()) if d != 0 { t.Errorf("expected depth 0, got %d", d) } } func TestSkillDepth_WithValue(t *testing.T) { ctx := withSkillDepth(context.Background(), 2) d := skillDepthFromCtx(ctx) if d != 2 { t.Errorf("expected depth 2, got %d", d) } } func TestSkillDepth_Nested(t *testing.T) { ctx := context.Background() ctx = withSkillDepth(ctx, 1) ctx = withSkillDepth(ctx, 2) d := skillDepthFromCtx(ctx) if d != 2 { t.Errorf("expected depth 2, got %d", d) } } // ---- InvokeSkill (builtin.SkillExecutor 接口) ---- func TestSkillRegistry_InvokeSkill_Interface(t *testing.T) { r := newSkillRegistry(nil) r.RegisterBuiltin(&Skill{ Name: "interface-test", Content: "Interface $ARGUMENTS", }) result, err := r.InvokeSkill(context.Background(), "interface-test", "args") if err != nil { t.Fatalf("InvokeSkill: %v", err) } if result.Mode != "inline" { t.Errorf("Mode: got %q", result.Mode) } if result.Content != "Interface args" { t.Errorf("Content: got %q", result.Content) } } // ---- ListSkillEntries ---- func TestSkillRegistry_ListSkillEntries(t *testing.T) { r := newSkillRegistry(nil) r.RegisterAll([]*Skill{ {Name: "alpha", Description: "Alpha skill", WhenToUse: "For alpha tasks", ArgumentHint: "", UserInvocable: true}, {Name: "beta", Description: "Beta skill"}, }) entries := r.ListSkillEntries() if len(entries) != 2 { t.Fatalf("expected 2 entries, got %d", len(entries)) } // 找到 alpha var alpha interface{ GetName() string } _ = alpha for _, e := range entries { if e.Name == "alpha" { if e.Description != "Alpha skill" { t.Errorf("Description: got %q", e.Description) } if e.WhenToUse != "For alpha tasks" { t.Errorf("WhenToUse: got %q", e.WhenToUse) } if e.ArgumentHint != "" { t.Errorf("ArgumentHint: got %q", e.ArgumentHint) } if !e.UserInvocable { t.Error("expected UserInvocable=true") } } } } // ---- SkillRegistry 实现 builtin.SkillExecutor 接口的静态断言 ---- // TestSkillRegistry_ImplementsSkillExecutor 验证 SkillRegistry 满足接口. // 若接口不匹配,编译期报错(不需要运行). func TestSkillRegistry_ImplementsSkillExecutor(t *testing.T) { // 运行时断言(接口兼容性检查) r := newSkillRegistry(nil) // 通过 SkillExecutor 接口调用 InvokeSkill var exec interface { InvokeSkill(ctx context.Context, name, args string) (any, error) } _ = exec _ = r // 确保引用 }