nandi/oripublic Fork 0
f5c963af3c1597c0274ce4a99705dbd92a7d1cba
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

skills_test.go · 119 lines · 4.2 KBGo Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1package skills_test
2
3import (
4 "encoding/json"
5 "net/http"
6 "net/http/httptest"
7 "os"
8 "path/filepath"
9 "testing"
10
11 "rickub.com/bots-garden/ori/internal/skills"
12)
13
14// writeSkill creates <base>/.claude/skills/<dir>/SKILL.md with content.
15func writeSkill(t *testing.T, base, dir, content string) string {
16 t.Helper()
17 full := filepath.Join(base, ".claude", "skills", dir)
18 if err := os.MkdirAll(full, 0o755); err != nil {
19 t.Fatal(err)
20 }
21 path := filepath.Join(full, "SKILL.md")
22 if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
23 t.Fatal(err)
24 }
25 return path
26}
27
28func TestDiscoverParsesFrontmatterAndFallsBackToDirectoryName(t *testing.T) {
29 cwd := t.TempDir()
30 writeSkill(t, cwd, "quality", "---\nname: quality\ndescription: Audit code quality with qlty.\n---\n# Quality\n")
31 writeSkill(t, cwd, "bare-dir", "# No frontmatter here\n")
32 writeSkill(t, cwd, "quoted", "---\nname: \"fancy-name\"\ndescription: 'Single quoted: with colon'\nother: ignored\n---\n")
33 writeSkill(t, cwd, "folded", "---\nname: folded\ndescription: >\n First line of the\n description.\n---\n")
34 // A stray file (not a directory) and a directory without SKILL.md are ignored.
35 if err := os.WriteFile(filepath.Join(cwd, ".claude", "skills", "README.md"), []byte("x"), 0o644); err != nil {
36 t.Fatal(err)
37 }
38 if err := os.MkdirAll(filepath.Join(cwd, ".claude", "skills", "empty"), 0o755); err != nil {
39 t.Fatal(err)
40 }
41
42 got := skills.New(cwd, "").Discover()
43 want := []skills.Skill{
44 {Name: "bare-dir", Description: ""},
45 {Name: "fancy-name", Description: "Single quoted: with colon"},
46 {Name: "folded", Description: "First line of the description."},
47 {Name: "quality", Description: "Audit code quality with qlty."},
48 }
49 if len(got) != len(want) {
50 t.Fatalf("Discover = %+v, want %d skills", got, len(want))
51 }
52 for i := range want {
53 if got[i].Name != want[i].Name || got[i].Description != want[i].Description {
54 t.Errorf("skill[%d] = %+v, want %+v", i, got[i], want[i])
55 }
56 if got[i].Source != skills.SourceProject || filepath.Base(got[i].Path) != "SKILL.md" {
57 t.Errorf("skill[%d] source/path = %q %q", i, got[i].Source, got[i].Path)
58 }
59 }
60}
61
62func TestDiscoverMergesUserSkillsAndProjectShadowsThem(t *testing.T) {
63 cwd, home := t.TempDir(), t.TempDir()
64 writeSkill(t, home, "shared", "---\nname: shared\ndescription: user version\n---\n")
65 writeSkill(t, home, "user-only", "---\nname: user-only\ndescription: only at home\n---\n")
66 writeSkill(t, cwd, "shared", "---\nname: shared\ndescription: project version\n---\n")
67
68 got := skills.New(cwd, home).Discover()
69 if len(got) != 2 {
70 t.Fatalf("Discover = %+v, want 2 skills", got)
71 }
72 if got[0].Name != "shared" || got[0].Description != "project version" || got[0].Source != skills.SourceProject {
73 t.Errorf("shared = %+v, want the project version", got[0])
74 }
75 if got[1].Name != "user-only" || got[1].Source != skills.SourceUser {
76 t.Errorf("user-only = %+v, want the user skill", got[1])
77 }
78}
79
80func TestDiscoverWithoutSkillDirectoriesIsEmpty(t *testing.T) {
81 if got := skills.New(t.TempDir(), t.TempDir()).Discover(); len(got) != 0 {
82 t.Errorf("Discover = %+v, want none", got)
83 }
84}
85
86func TestEndpointReturnsJSONArray(t *testing.T) {
87 cwd := t.TempDir()
88 writeSkill(t, cwd, "demo", "---\nname: demo\ndescription: A demo skill\n---\n")
89 mux := http.NewServeMux()
90 for pattern, handler := range skills.New(cwd, "").Routes() {
91 mux.Handle(pattern, handler)
92 }
93
94 rec := httptest.NewRecorder()
95 mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/skills", nil))
96 if rec.Code != http.StatusOK {
97 t.Fatalf("GET /api/skills = %d", rec.Code)
98 }
99 var payload struct {
100 Skills []skills.Skill `json:"skills"`
101 }
102 if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
103 t.Fatalf("not JSON: %v", err)
104 }
105 if len(payload.Skills) != 1 || payload.Skills[0].Name != "demo" || payload.Skills[0].Description != "A demo skill" {
106 t.Errorf("skills = %+v", payload.Skills)
107 }
108
109 // No skills anywhere: still a JSON array, never null.
110 empty := http.NewServeMux()
111 for pattern, handler := range skills.New(t.TempDir(), "").Routes() {
112 empty.Handle(pattern, handler)
113 }
114 rec = httptest.NewRecorder()
115 empty.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/skills", nil))
116 if body := rec.Body.String(); body != "{\"skills\":[]}\n" {
117 t.Errorf("empty body = %q", body)
118 }
119}