nandi/oripublic Fork 0
7895c1d1c9bb1048807dc04f7246dc456c47e025
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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package skills_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"rickub.com/bots-garden/ori/internal/skills"
)

// writeSkill creates <base>/.claude/skills/<dir>/SKILL.md with content.
func writeSkill(t *testing.T, base, dir, content string) string {
	t.Helper()
	full := filepath.Join(base, ".claude", "skills", dir)
	if err := os.MkdirAll(full, 0o755); err != nil {
		t.Fatal(err)
	}
	path := filepath.Join(full, "SKILL.md")
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatal(err)
	}
	return path
}

func TestDiscoverParsesFrontmatterAndFallsBackToDirectoryName(t *testing.T) {
	cwd := t.TempDir()
	writeSkill(t, cwd, "quality", "---\nname: quality\ndescription: Audit code quality with qlty.\n---\n# Quality\n")
	writeSkill(t, cwd, "bare-dir", "# No frontmatter here\n")
	writeSkill(t, cwd, "quoted", "---\nname: \"fancy-name\"\ndescription: 'Single quoted: with colon'\nother: ignored\n---\n")
	writeSkill(t, cwd, "folded", "---\nname: folded\ndescription: >\n  First line of the\n  description.\n---\n")
	// A stray file (not a directory) and a directory without SKILL.md are ignored.
	if err := os.WriteFile(filepath.Join(cwd, ".claude", "skills", "README.md"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(cwd, ".claude", "skills", "empty"), 0o755); err != nil {
		t.Fatal(err)
	}

	got := skills.New(cwd, "").Discover()
	want := []skills.Skill{
		{Name: "bare-dir", Description: ""},
		{Name: "fancy-name", Description: "Single quoted: with colon"},
		{Name: "folded", Description: "First line of the description."},
		{Name: "quality", Description: "Audit code quality with qlty."},
	}
	if len(got) != len(want) {
		t.Fatalf("Discover = %+v, want %d skills", got, len(want))
	}
	for i := range want {
		if got[i].Name != want[i].Name || got[i].Description != want[i].Description {
			t.Errorf("skill[%d] = %+v, want %+v", i, got[i], want[i])
		}
		if got[i].Source != skills.SourceProject || filepath.Base(got[i].Path) != "SKILL.md" {
			t.Errorf("skill[%d] source/path = %q %q", i, got[i].Source, got[i].Path)
		}
	}
}

func TestDiscoverMergesUserSkillsAndProjectShadowsThem(t *testing.T) {
	cwd, home := t.TempDir(), t.TempDir()
	writeSkill(t, home, "shared", "---\nname: shared\ndescription: user version\n---\n")
	writeSkill(t, home, "user-only", "---\nname: user-only\ndescription: only at home\n---\n")
	writeSkill(t, cwd, "shared", "---\nname: shared\ndescription: project version\n---\n")

	got := skills.New(cwd, home).Discover()
	if len(got) != 2 {
		t.Fatalf("Discover = %+v, want 2 skills", got)
	}
	if got[0].Name != "shared" || got[0].Description != "project version" || got[0].Source != skills.SourceProject {
		t.Errorf("shared = %+v, want the project version", got[0])
	}
	if got[1].Name != "user-only" || got[1].Source != skills.SourceUser {
		t.Errorf("user-only = %+v, want the user skill", got[1])
	}
}

func TestDiscoverWithoutSkillDirectoriesIsEmpty(t *testing.T) {
	if got := skills.New(t.TempDir(), t.TempDir()).Discover(); len(got) != 0 {
		t.Errorf("Discover = %+v, want none", got)
	}
}

func TestEndpointReturnsJSONArray(t *testing.T) {
	cwd := t.TempDir()
	writeSkill(t, cwd, "demo", "---\nname: demo\ndescription: A demo skill\n---\n")
	mux := http.NewServeMux()
	for pattern, handler := range skills.New(cwd, "").Routes() {
		mux.Handle(pattern, handler)
	}

	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/skills", nil))
	if rec.Code != http.StatusOK {
		t.Fatalf("GET /api/skills = %d", rec.Code)
	}
	var payload struct {
		Skills []skills.Skill `json:"skills"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("not JSON: %v", err)
	}
	if len(payload.Skills) != 1 || payload.Skills[0].Name != "demo" || payload.Skills[0].Description != "A demo skill" {
		t.Errorf("skills = %+v", payload.Skills)
	}

	// No skills anywhere: still a JSON array, never null.
	empty := http.NewServeMux()
	for pattern, handler := range skills.New(t.TempDir(), "").Routes() {
		empty.Handle(pattern, handler)
	}
	rec = httptest.NewRecorder()
	empty.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/skills", nil))
	if body := rec.Body.String(); body != "{\"skills\":[]}\n" {
		t.Errorf("empty body = %q", body)
	}
}