Files
kandev/apps/backend/internal/agent/runtime/lifecycle/skill/inject_test.go
T
2026-05-16 20:07:57 +01:00

159 lines
4.6 KiB
Go

package skill
import (
"os"
"path/filepath"
"strings"
"testing"
)
// ensureGit creates a minimal .git directory structure.
func ensureGit(t *testing.T, dir string) {
t.Helper()
if err := os.MkdirAll(filepath.Join(dir, ".git", "info"), 0o755); err != nil {
t.Fatal(err)
}
}
func TestInjectSkills_WritesUnderProjectSkillDir(t *testing.T) {
worktree := t.TempDir()
ensureGit(t, worktree)
skills := []Skill{
{Slug: "code-review", Content: "# Code Review"},
{Slug: "memory", Content: "# Memory"},
}
if err := injectSkills(worktree, ".claude/skills", skills); err != nil {
t.Fatalf("injectSkills: %v", err)
}
for _, slug := range []string{"code-review", "memory"} {
path := filepath.Join(worktree, ".claude", "skills", "kandev-"+slug, "SKILL.md")
if _, err := os.Stat(path); err != nil {
t.Errorf("missing %s: %v", path, err)
}
}
}
func TestInjectSkills_CleanSlateRemovesPreviousKandevDirs(t *testing.T) {
worktree := t.TempDir()
ensureGit(t, worktree)
if err := injectSkills(worktree, ".agents/skills", []Skill{
{Slug: "skill-a", Content: "# A"},
{Slug: "skill-b", Content: "# B"},
}); err != nil {
t.Fatalf("first inject: %v", err)
}
// Re-inject with only A — B's directory must be gone.
if err := injectSkills(worktree, ".agents/skills", []Skill{
{Slug: "skill-a", Content: "# A"},
}); err != nil {
t.Fatalf("second inject: %v", err)
}
skillsDir := filepath.Join(worktree, ".agents", "skills")
if _, err := os.Stat(filepath.Join(skillsDir, "kandev-skill-b")); !os.IsNotExist(err) {
t.Errorf("deassigned kandev-skill-b should be removed")
}
if _, err := os.Stat(filepath.Join(skillsDir, "kandev-skill-a", "SKILL.md")); err != nil {
t.Errorf("still-assigned kandev-skill-a should exist: %v", err)
}
}
func TestInjectSkills_PreservesUserSkills(t *testing.T) {
worktree := t.TempDir()
ensureGit(t, worktree)
userSkill := filepath.Join(worktree, ".claude", "skills", "team-skill")
if err := os.MkdirAll(userSkill, 0o755); err != nil {
t.Fatal(err)
}
if err := injectSkills(worktree, ".claude/skills", []Skill{
{Slug: "code-review", Content: "# CR"},
}); err != nil {
t.Fatalf("injectSkills: %v", err)
}
if _, err := os.Stat(userSkill); err != nil {
t.Errorf("user-managed skill should be preserved: %v", err)
}
}
func TestInjectSkills_SkipsInvalidSlug(t *testing.T) {
worktree := t.TempDir()
ensureGit(t, worktree)
if err := injectSkills(worktree, ".agents/skills", []Skill{
{Slug: "../escape", Content: "evil"},
{Slug: "ok-slug", Content: "# ok"},
}); err != nil {
t.Fatalf("injectSkills: %v", err)
}
if _, err := os.Stat(filepath.Join(worktree, ".agents", "skills", "kandev-ok-slug", "SKILL.md")); err != nil {
t.Errorf("valid slug should land: %v", err)
}
// The invalid slug must not have produced any directory.
entries, _ := os.ReadDir(filepath.Join(worktree, ".agents", "skills"))
for _, e := range entries {
if strings.Contains(e.Name(), "escape") {
t.Errorf("invalid slug %q wrote a directory", e.Name())
}
}
}
func TestEnsureGitExclude_AppendsPatternIdempotent(t *testing.T) {
worktree := t.TempDir()
ensureGit(t, worktree)
for i := 0; i < 3; i++ {
if err := ensureGitExclude(worktree, ".claude/skills"); err != nil {
t.Fatalf("call %d: %v", i, err)
}
}
data, err := os.ReadFile(filepath.Join(worktree, ".git", "info", "exclude"))
if err != nil {
t.Fatalf("read exclude: %v", err)
}
pattern := ".claude/skills/kandev-*"
if got := strings.Count(string(data), pattern); got != 1 {
t.Errorf("pattern appears %d times, want 1; got %q", got, string(data))
}
}
func TestEnsureGitExclude_LinkedWorktreeGitFile(t *testing.T) {
mainGitDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(mainGitDir, "info"), 0o755); err != nil {
t.Fatal(err)
}
worktree := t.TempDir()
gitFile := filepath.Join(worktree, ".git")
if err := os.WriteFile(gitFile, []byte("gitdir: "+mainGitDir+"\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := ensureGitExclude(worktree, ".agents/skills"); err != nil {
t.Fatalf("ensureGitExclude: %v", err)
}
data, err := os.ReadFile(filepath.Join(mainGitDir, "info", "exclude"))
if err != nil {
t.Fatalf("read exclude: %v", err)
}
if !strings.Contains(string(data), ".agents/skills/kandev-*") {
t.Errorf("pattern not appended: %q", string(data))
}
}
func TestInjectSkills_NoGitDirIsNotFatal(t *testing.T) {
worktree := t.TempDir() // no .git
if err := injectSkills(worktree, ".agents/skills", []Skill{
{Slug: "ok", Content: "# ok"},
}); err != nil {
t.Fatalf("injectSkills: %v", err)
}
if _, err := os.Stat(filepath.Join(worktree, ".agents", "skills", "kandev-ok", "SKILL.md")); err != nil {
t.Errorf("skill should still land without .git: %v", err)
}
}