package skills import ( "os" "path/filepath" "strings" "testing" ) func writeSkill(t *testing.T, root, name, content string) { t.Helper() dir := filepath.Join(root, name) if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(content), 0o644); err != nil { t.Fatal(err) } } // TestLoad 验证 frontmatter 解析、优先级覆盖与缺省名。 func TestLoad(t *testing.T) { user := t.TempDir() proj := t.TempDir() writeSkill(t, user, "deploy", "---\nname: deploy\ndescription: \"How to deploy\"\nallowed-tools: read, grep\n---\n\nStep 1: run make deploy\n") writeSkill(t, user, "notes", "No frontmatter here, just body.\n") writeSkill(t, proj, "deploy", "---\ndescription: Project override\n---\nProject deploy steps.\n") got := Load(proj, user) // 项目级优先 if len(got) != 2 { t.Fatalf("want 2 skills, got %d: %+v", len(got), got) } byName := map[string]Skill{} for _, s := range got { byName[s.Name] = s } dep := byName["deploy"] if dep.Description != "Project override" || !strings.Contains(dep.Body, "Project deploy steps") { t.Fatalf("project skill should win: %+v", dep) } notes := byName["notes"] if notes.Name != "notes" || !strings.Contains(notes.Body, "just body") { t.Fatalf("missing frontmatter should fall back to dir name: %+v", notes) } if len(dep.AllowedTools) != 0 { t.Fatalf("override should not inherit allowed-tools from user version") } // 用户级版本的字段 gotUser := Load(user) var found bool for _, s := range gotUser { if s.Name == "deploy" { found = true if len(s.AllowedTools) != 2 || s.AllowedTools[0] != "read" || s.AllowedTools[1] != "grep" { t.Fatalf("allowed-tools parse: %+v", s.AllowedTools) } if !strings.Contains(s.Body, "make deploy") { t.Fatalf("body: %q", s.Body) } } } if !found { t.Fatal("user deploy skill missing") } } // TestPromptSection:列表注入、预算截断、空列表为空。 func TestPromptSection(t *testing.T) { if PromptSection(nil) != "" { t.Fatal("empty skills should produce empty section") } sk := []Skill{{Name: "deploy", Description: "How to deploy", AllowedTools: []string{"read"}}} sec := PromptSection(sk) if !strings.Contains(sec, "- deploy: How to deploy") || !strings.Contains(sec, "allowed-tools: read") { t.Fatalf("section: %q", sec) } many := make([]Skill, 200) for i := range many { many[i] = Skill{Name: strings.Repeat("s", 40) + string(rune('a'+i%26)) + string(rune(i))} } if got := PromptSection(many); len(got) > maxPromptChars+100 { t.Fatalf("budget exceeded: %d", len(got)) } }