package skillpack import ( "context" "encoding/json" "strings" "testing" ) func mustPack(t *testing.T, name, body string, extra map[string]string) *Pack { t.Helper() tr := packTree(name, body) for k, v := range extra { tr[k] = []byte(v) } p, err := LoadPack(tr) if err != nil { t.Fatal(err) } return p } func TestCatalog(t *testing.T) { packs := []*Pack{ mustPack(t, "zebra", "z", nil), mustPack(t, "alpha", "a", nil), } cat := Catalog(packs) if !strings.Contains(cat, "skill_use") { t.Error("catalog should tell the model how to load a skill") } ai := strings.Index(cat, "alpha") zi := strings.Index(cat, "zebra") if ai < 0 || zi < 0 || ai > zi { t.Errorf("catalog should list packs sorted by name:\n%s", cat) } if Catalog(nil) != "" { t.Error("empty catalog should be empty string") } } func TestActivate_SkillUseTool(t *testing.T) { ctx := context.Background() packs := []*Pack{ mustPack(t, "pdf", "Use pdfplumber.", map[string]string{"scripts/x.py": "print()"}), } sk := Activate(packs, "/stage") if sk == nil { t.Fatal("expected a non-nil skill") } if sk.Instructions() != Catalog(packs) { t.Error("skill instructions should be the catalog") } tb := sk.Tools() tool, ok := tb.Get("skill_use") if !ok { t.Fatal("skill_use tool missing from toolbox") } // load an existing pack out, err := tool.Handler(ctx, json.RawMessage(`{"name":"pdf"}`)) if err != nil { t.Fatal(err) } body, _ := out.(string) if !strings.Contains(body, "Use pdfplumber.") { t.Errorf("skill_use body missing instructions: %q", body) } if !strings.Contains(body, "scripts/x.py") || !strings.Contains(body, "/stage/pdf") { t.Errorf("skill_use should list bundled files under the staged dir: %q", body) } // unknown pack returns guidance, not an error out, err = tool.Handler(ctx, json.RawMessage(`{"name":"nope"}`)) if err != nil { t.Fatal(err) } if s, _ := out.(string); !strings.Contains(s, "No skill named") { t.Errorf("unknown skill should return guidance: %q", s) } } func TestActivate_Empty(t *testing.T) { if Activate(nil, "") != nil { t.Error("no packs should activate to a nil skill") } } func TestNilPackElementsAreSafe(t *testing.T) { packs := []*Pack{nil, mustPack(t, "real", "b", nil), {Manifest: nil}} // Neither Catalog nor Activate may panic on nil / malformed elements. if got := Catalog(packs); !strings.Contains(got, "real") { t.Errorf("catalog should include the valid pack and skip nils: %q", got) } sk := Activate(packs, "") if sk == nil { t.Fatal("a valid pack among nils should still activate") } if _, ok := sk.Tools().Get("skill_use"); !ok { t.Error("skill_use missing") } // All-nil activates to nothing rather than panicking. if Activate([]*Pack{nil, {Manifest: nil}}, "") != nil { t.Error("only-nil packs should activate to nil") } } func TestResolveFromCache(t *testing.T) { ctx := context.Background() cache := NewMemoryPackCache() p := mustPack(t, "alpha", "a", nil) cache.Put(ctx, p.Digest, p.Tree) subs := []Subscription{ {Name: "alpha", PinnedDigest: p.Digest, Enabled: true}, {Name: "disabled", PinnedDigest: p.Digest, Enabled: false}, } packs, err := Resolve(ctx, cache, subs) if err != nil { t.Fatal(err) } if len(packs) != 1 || packs[0].Manifest.Name != "alpha" { t.Fatalf("resolve should skip disabled subs; got %d packs", len(packs)) } // missing from cache is an error subs = []Subscription{{Name: "ghost", PinnedDigest: "deadbeef", Enabled: true}} if _, err := Resolve(ctx, cache, subs); err == nil { t.Fatal("expected error resolving an uncached pin") } } func TestStage(t *testing.T) { dir := t.TempDir() p := mustPack(t, "pdf", "b", map[string]string{"scripts/x.py": "print()"}) staged, err := Stage(p, dir) if err != nil { t.Fatal(err) } if !strings.HasSuffix(staged, "/pdf") { t.Errorf("staged dir = %q", staged) } }