Files
Cyrene_v2/internal/capability/skills/loader_test.go
T

177 lines
5.0 KiB
Go

package skills
import (
"errors"
"path/filepath"
"strings"
"testing"
)
const validSkillYAML = `
name: daily-brief
description: Morning briefing
prompt: You are a briefing assistant.
steps:
- name: collect
prompt: Gather data
depends: []
- name: summarize
prompt: Summarize the picks
depends: [collect]
refs:
- name: calendar
type: tool
value: calendar-tool
optional: false
guardrails:
- id: no-pii
message: Never output PII
enforced: true
`
func TestLoader_Load_正常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
sk, err := l.Load(strings.NewReader(validSkillYAML))
if err != nil {
t.Fatalf("Load: %v", err)
}
if sk.Name() != "daily-brief" {
t.Fatalf("Name = %q, want daily-brief", sk.Name())
}
if sk.Description() != "Morning briefing" {
t.Fatalf("Description = %q, want Morning briefing", sk.Description())
}
if sk.Prompt() != "You are a briefing assistant." {
t.Fatalf("Prompt = %q, want base prompt", sk.Prompt())
}
steps := sk.Steps()
if len(steps) != 2 {
t.Fatalf("Steps length = %d, want 2", len(steps))
}
if steps[0].Name != "collect" || steps[0].Prompt != "Gather data" {
t.Fatalf("Steps[0] = %+v, want collect/Gather data", steps[0])
}
if len(steps[0].Depends) != 0 {
t.Fatalf("Steps[0].Depends = %+v, want empty", steps[0].Depends)
}
if steps[1].Name != "summarize" || len(steps[1].Depends) != 1 || steps[1].Depends[0] != "collect" {
t.Fatalf("Steps[1] = %+v, want summarize depending on collect", steps[1])
}
refs := sk.Refs()
if len(refs) != 1 || refs[0].Name != "calendar" || refs[0].Type != "tool" || refs[0].Value != "calendar-tool" || refs[0].Optional {
t.Fatalf("Refs = %+v, want calendar/tool/calendar-tool/non-optional", refs)
}
guards := sk.Guardrails()
if len(guards) != 1 || guards[0].ID != "no-pii" || guards[0].Message != "Never output PII" || !guards[0].Enforced {
t.Fatalf("Guardrails = %+v, want no-pii/Never output PII/enforced", guards)
}
}
func TestLoader_Load_MissingName_异常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
src := "description: x\nprompt: y\n"
_, err := l.Load(strings.NewReader(src))
if !errors.Is(err, ErrInvalidSkill) {
t.Fatalf("Load err = %v, want ErrInvalidSkill", err)
}
}
func TestLoader_Load_InvalidYAML_异常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
if _, err := l.Load(strings.NewReader("name: [unclosed")); err == nil {
t.Fatalf("expected a YAML decode error, got nil")
}
}
func TestLoader_Load_NilReader_异常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
if _, err := l.Load(nil); !errors.Is(err, ErrInvalidSkill) {
t.Fatalf("Load(nil) err = %v, want ErrInvalidSkill", err)
}
}
func TestLoader_LoadFile_正常(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "skill.yaml")
writeFile(t, path, validSkillYAML)
l := NewLoader(testLogger())
sk, err := l.LoadFile(path)
if err != nil {
t.Fatalf("LoadFile: %v", err)
}
if sk.Name() != "daily-brief" {
t.Fatalf("Name = %q, want daily-brief", sk.Name())
}
}
func TestLoader_LoadFile_Missing_异常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
if _, err := l.LoadFile(filepath.Join(t.TempDir(), "nope.yaml")); err == nil {
t.Fatalf("expected error for missing file, got nil")
}
}
func TestLoader_LoadDir_正常(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "a.yaml"), validSkillYAML)
writeFile(t, filepath.Join(dir, "b.yml"), strings.Replace(validSkillYAML, "daily-brief", "code-review", 1))
writeFile(t, filepath.Join(dir, "notes.txt"), "not a skill") // skipped
l := NewLoader(testLogger())
got, err := l.LoadDir(dir)
if err != nil {
t.Fatalf("LoadDir: %v", err)
}
if len(got) != 2 {
t.Fatalf("LoadDir returned %d skills, want 2", len(got))
}
if got[0].Name() != "daily-brief" || got[1].Name() != "code-review" {
t.Fatalf("LoadDir names = %q,%q, want daily-brief,code-review", got[0].Name(), got[1].Name())
}
}
func TestLoader_LoadDir_BadFile_异常(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "bad.yaml"), "name: [broken")
writeFile(t, filepath.Join(dir, "ok.yaml"), validSkillYAML)
l := NewLoader(testLogger())
if _, err := l.LoadDir(dir); err == nil {
t.Fatalf("expected error for malformed yaml in dir, got nil")
}
}
func TestLoader_LoadDir_NotFound_异常(t *testing.T) {
t.Parallel()
l := NewLoader(testLogger())
if _, err := l.LoadDir(filepath.Join(t.TempDir(), "nothing")); err == nil {
t.Fatalf("expected error for missing dir, got nil")
}
}
// TestLoader_LoadDir_OnlyYAML confirms non-YAML files are skipped rather than
// treated as skills.
func TestLoader_LoadDir_OnlyYAML_正常(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "one.yaml"), validSkillYAML)
writeFile(t, filepath.Join(dir, "two.yml"), validSkillYAML)
writeFile(t, filepath.Join(dir, "readme.md"), "# docs")
l := NewLoader(testLogger())
got, err := l.LoadDir(dir)
if err != nil {
t.Fatalf("LoadDir: %v", err)
}
if len(got) != 2 {
t.Fatalf("LoadDir returned %d skills, want 2", len(got))
}
}