commands_test.go

  1package commands
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"runtime"
  7	"testing"
  8
  9	"github.com/charmbracelet/crush/internal/skills"
 10	"github.com/stretchr/testify/require"
 11)
 12
 13func TestLoadFromSource_NonExistentDir(t *testing.T) {
 14	t.Parallel()
 15
 16	dir := filepath.Join(t.TempDir(), "does-not-exist")
 17
 18	cmds, err := loadFromSource(commandSource{path: dir, prefix: userCommandPrefix})
 19	require.NoError(t, err)
 20	require.Empty(t, cmds)
 21
 22	// directory must NOT have been created
 23	_, statErr := os.Stat(dir)
 24	require.True(t, os.IsNotExist(statErr))
 25}
 26
 27func TestLoadFromSource_ExistingDir(t *testing.T) {
 28	t.Parallel()
 29
 30	dir := t.TempDir()
 31	require.NoError(t, os.WriteFile(filepath.Join(dir, "hello.md"), []byte("say hello"), 0o644))
 32
 33	cmds, err := loadFromSource(commandSource{path: dir, prefix: userCommandPrefix})
 34	require.NoError(t, err)
 35	require.Len(t, cmds, 1)
 36	require.Equal(t, "user:hello", cmds[0].ID)
 37	require.Equal(t, "say hello", cmds[0].Content)
 38}
 39
 40func TestLoadAll_MixedSources(t *testing.T) {
 41	t.Parallel()
 42
 43	existing := t.TempDir()
 44	require.NoError(t, os.WriteFile(filepath.Join(existing, "cmd.md"), []byte("content"), 0o644))
 45
 46	missing := filepath.Join(t.TempDir(), "nope")
 47
 48	cmds, err := loadAll([]commandSource{
 49		{path: existing, prefix: userCommandPrefix},
 50		{path: missing, prefix: projectCommandPrefix},
 51	})
 52	require.NoError(t, err)
 53	require.Len(t, cmds, 1)
 54	require.Equal(t, "user:cmd", cmds[0].ID)
 55}
 56
 57func TestFromSkillCatalog_UserInvocableOnly(t *testing.T) {
 58	t.Parallel()
 59
 60	cmds := FromSkillCatalog([]skills.CatalogEntry{
 61		{
 62			ID:            "/skills/on/SKILL.md",
 63			Name:          "on",
 64			Description:   "Enabled.",
 65			Label:         "user:on",
 66			UserInvocable: true,
 67		},
 68		{
 69			ID:            "/skills/off/SKILL.md",
 70			Name:          "off",
 71			Description:   "Not invocable.",
 72			Label:         "user:off",
 73			UserInvocable: false,
 74		},
 75	})
 76
 77	require.Len(t, cmds, 1)
 78	require.Equal(t, "user:on", cmds[0].ID)
 79	require.Equal(t, "user:on", cmds[0].Name)
 80	require.Equal(t, "on", cmds[0].Skill.Name)
 81	require.Equal(t, "Enabled.", cmds[0].Skill.Description)
 82	require.Equal(t, "/skills/on/SKILL.md", cmds[0].Skill.SkillFilePath)
 83}
 84
 85func TestFromSkillCatalog_UsesDiscoveredSymlinkedSkills(t *testing.T) {
 86	if runtime.GOOS == "windows" {
 87		t.Skip("symlink creation requires special privileges on Windows")
 88	}
 89	t.Parallel()
 90
 91	root := t.TempDir()
 92	targetParent := t.TempDir()
 93	targetSkillDir := filepath.Join(targetParent, "linked-skill")
 94	require.NoError(t, os.MkdirAll(targetSkillDir, 0o755))
 95	require.NoError(t, os.WriteFile(
 96		filepath.Join(targetSkillDir, skills.SkillFileName),
 97		[]byte("---\nname: linked-skill\ndescription: Symlinked.\nuser-invocable: true\n---\nUse me.\n"),
 98		0o644,
 99	))
100
101	link := filepath.Join(root, "linked-skill")
102	require.NoError(t, os.Symlink(targetSkillDir, link))
103
104	_, activeSkills, _ := skills.DiscoverFromConfig(skills.DiscoveryConfig{
105		SkillsPaths: []string{root},
106	})
107	entries := skills.Catalog(activeSkills, []string{root}, "")
108	cmds := FromSkillCatalog(entries)
109
110	require.Len(t, cmds, 1)
111	require.Equal(t, "user:linked-skill", cmds[0].ID)
112	require.Equal(t, "linked-skill", cmds[0].Skill.Name)
113	require.Equal(t, filepath.Join(link, skills.SkillFileName), cmds[0].Skill.SkillFilePath)
114}