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
123 lines
3.8 KiB
Go
123 lines
3.8 KiB
Go
package skills
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/tools"
|
||
)
|
||
|
||
// writeSkill 写一份符合开放规范的 SKILL.md(name/description 齐备)。
|
||
func writeSkill(t *testing.T, root, name, body string) {
|
||
t.Helper()
|
||
writeRawSkill(t, root, name, "---\nname: "+name+"\ndescription: 测试技能 "+name+"\n---\n\n"+body)
|
||
}
|
||
|
||
func writeRawSkill(t *testing.T, root, name, text 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(text), 0o644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
func TestStoreListAndLoad(t *testing.T) {
|
||
root := t.TempDir()
|
||
writeSkill(t, root, "docx-author", "## 步骤\n1. 用 run_python")
|
||
writeSkill(t, root, "xlsx-author", "没有 frontmatter 的技能正文")
|
||
writeSkill(t, root, "bad name", "目录名不合法") //nolint
|
||
// 无 frontmatter(缺 name/description):开放规范下不合规,进诊断不进索引。
|
||
writeRawSkill(t, root, "nofm", "没有任何头部")
|
||
|
||
store := NewStore(root)
|
||
list := store.List()
|
||
if len(list) != 2 {
|
||
t.Fatalf("want 2 valid skills (bad dir + nofm skipped), got %d: %+v", len(list), list)
|
||
}
|
||
byName := map[string]Skill{}
|
||
for _, s := range list {
|
||
byName[s.Name] = s
|
||
}
|
||
if byName["docx-author"].Description != "测试技能 docx-author" {
|
||
t.Fatalf("frontmatter description wrong: %+v", byName["docx-author"])
|
||
}
|
||
if !strings.Contains(byName["docx-author"].Body, "run_python") {
|
||
t.Fatalf("body must exclude frontmatter, got %q", byName["docx-author"].Body)
|
||
}
|
||
|
||
skill, err := store.Load("docx-author")
|
||
if err != nil || !strings.Contains(skill.Body, "步骤") {
|
||
t.Fatalf("load failed: %v %+v", err, skill)
|
||
}
|
||
// 同名不合规:Load 返回带字段定位的校验错误(不是笼统 unknown)。
|
||
_, err = store.Load("nofm")
|
||
if err == nil || !strings.Contains(err.Error(), "name") {
|
||
t.Fatalf("invalid skill must surface its validation error, got %v", err)
|
||
}
|
||
_, err = store.Load("nope")
|
||
if err == nil || !strings.Contains(err.Error(), "unknown skill") {
|
||
t.Fatalf("unknown skill must fail-closed, got %v", err)
|
||
}
|
||
}
|
||
|
||
func TestStoreRootPriorityAndMissingRoot(t *testing.T) {
|
||
r1, r2 := t.TempDir(), t.TempDir()
|
||
writeSkill(t, r1, "dup", "from r1")
|
||
writeSkill(t, r2, "dup", "from r2")
|
||
writeSkill(t, r2, "only-r2", "x")
|
||
store := NewStore(r1, r2, "/nonexistent/root")
|
||
list := store.List()
|
||
if len(list) != 2 {
|
||
t.Fatalf("want 2 skills, got %d", len(list))
|
||
}
|
||
skill, _ := store.Load("dup")
|
||
if !strings.Contains(skill.Body, "from r1") {
|
||
t.Fatalf("earlier root must win, got %q", skill.Body)
|
||
}
|
||
}
|
||
|
||
func TestRegisterLoaderTool(t *testing.T) {
|
||
root := t.TempDir()
|
||
writeSkill(t, root, "docx-author", "正文指令")
|
||
reg := tools.NewRegistry(t.TempDir())
|
||
RegisterLoader(reg, NewStore(root))
|
||
var found bool
|
||
for _, d := range reg.Decls() {
|
||
if d.Name == "load_skill" {
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatal("load_skill must be registered when skills exist")
|
||
}
|
||
res := reg.Execute(context.Background(), "load_skill", json.RawMessage(`{"name":"docx-author"}`))
|
||
if res.IsError || !strings.Contains(res.Content, "正文指令") {
|
||
t.Fatalf("load failed: %+v", res)
|
||
}
|
||
res = reg.Execute(context.Background(), "load_skill", json.RawMessage(`{"name":"ghost"}`))
|
||
if !res.IsError || !strings.Contains(res.Content, "unknown skill") {
|
||
t.Fatalf("ghost skill must error, got %+v", res)
|
||
}
|
||
}
|
||
|
||
func TestRegisterLoaderSkipsEmptyStore(t *testing.T) {
|
||
reg := tools.NewRegistry(t.TempDir())
|
||
RegisterLoader(reg, NewStore(t.TempDir()))
|
||
for _, d := range reg.Decls() {
|
||
if d.Name == "load_skill" {
|
||
t.Fatal("no skills = no loader tool")
|
||
}
|
||
}
|
||
RegisterLoader(reg, nil)
|
||
if len(reg.Decls()) != 0 {
|
||
t.Fatal("nil store must not register")
|
||
}
|
||
}
|