chore: import Local-Cursor v1.1.8 source snapshot
Includes the current working directory changes. Historical commits and release binaries are excluded to limit Gitea storage. Source base: d7515973fbfd6dd2001fa156c483455b8081c7cb
This commit is contained in:
154
internal/skills/skills_test.go
Normal file
154
internal/skills/skills_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package skills
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInstallUninstallIdempotent(t *testing.T) {
|
||||
project := t.TempDir()
|
||||
|
||||
if err := Install(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("install: %v", err)
|
||||
}
|
||||
// 幂等:重复安装不报错。
|
||||
if err := Install(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("reinstall: %v", err)
|
||||
}
|
||||
if !IsInstalled(project, "fd-engineering") {
|
||||
t.Fatal("expected installed after Install")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(project, ".cursor", "skills", "fd-engineering", "SKILL.md")); err != nil {
|
||||
t.Fatalf("SKILL.md missing: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(project, ".cursor", "skills", "fd-engineering", "references", "execute.md")); err != nil {
|
||||
t.Fatalf("references missing: %v", err)
|
||||
}
|
||||
|
||||
// 命令文件应分发到 .cursor/commands/。
|
||||
commandPath := filepath.Join(project, ".cursor", "commands", "FD-commit.md")
|
||||
if _, err := os.Stat(commandPath); err != nil {
|
||||
t.Fatalf("command file missing: %v", err)
|
||||
}
|
||||
// 命令模板不应进入 skill 目录。
|
||||
if _, err := os.Stat(filepath.Join(project, ".cursor", "skills", "fd-engineering", "commands")); !os.IsNotExist(err) {
|
||||
t.Fatal("commands template must not be copied into skill dir")
|
||||
}
|
||||
|
||||
if err := Uninstall(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("uninstall: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(commandPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("command file should be removed: %v", err)
|
||||
}
|
||||
if IsInstalled(project, "fd-engineering") {
|
||||
t.Fatal("expected not installed after Uninstall")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallRefreshesBuiltinAndRetiredFiles(t *testing.T) {
|
||||
project := t.TempDir()
|
||||
if err := Install(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("install: %v", err)
|
||||
}
|
||||
|
||||
target := filepath.Join(project, ".cursor", "skills", "fd-engineering")
|
||||
skillPath := filepath.Join(target, "SKILL.md")
|
||||
if err := os.WriteFile(skillPath, []byte("old skill\n"), 0o644); err != nil {
|
||||
t.Fatalf("write stale skill: %v", err)
|
||||
}
|
||||
workflowPath := filepath.Join(target, "references", "workflow.md")
|
||||
if err := os.Remove(workflowPath); err != nil {
|
||||
t.Fatalf("remove workflow: %v", err)
|
||||
}
|
||||
for _, name := range []string{"native-tools.md", "mode-switch.md"} {
|
||||
path := filepath.Join(target, "references", name)
|
||||
if err := os.WriteFile(path, []byte("retired\n"), 0o644); err != nil {
|
||||
t.Fatalf("write retired reference: %v", err)
|
||||
}
|
||||
}
|
||||
customPath := filepath.Join(target, "references", "custom.md")
|
||||
if err := os.WriteFile(customPath, []byte("user-owned\n"), 0o644); err != nil {
|
||||
t.Fatalf("write custom reference: %v", err)
|
||||
}
|
||||
|
||||
if err := Install(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("refresh install: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(skillPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read refreshed skill: %v", err)
|
||||
}
|
||||
want, err := builtinFS.ReadFile("builtin/fd-engineering/SKILL.md")
|
||||
if err != nil {
|
||||
t.Fatalf("read embedded skill: %v", err)
|
||||
}
|
||||
if string(got) != string(want) {
|
||||
t.Fatal("existing skill was not refreshed from builtin content")
|
||||
}
|
||||
if _, err := os.Stat(workflowPath); err != nil {
|
||||
t.Fatalf("missing workflow was not restored: %v", err)
|
||||
}
|
||||
for _, name := range []string{"native-tools.md", "mode-switch.md"} {
|
||||
if _, err := os.Stat(filepath.Join(target, "references", name)); !os.IsNotExist(err) {
|
||||
t.Fatalf("retired reference %s was not removed", name)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(customPath); err != nil {
|
||||
t.Fatalf("user-owned reference must survive refresh: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstallKeepsUserCommands(t *testing.T) {
|
||||
project := t.TempDir()
|
||||
if err := Install(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("install: %v", err)
|
||||
}
|
||||
// 用户自建命令不受影响。
|
||||
userCommand := filepath.Join(project, ".cursor", "commands", "my-own.md")
|
||||
if err := os.WriteFile(userCommand, []byte("# own\n"), 0o644); err != nil {
|
||||
t.Fatalf("write user command: %v", err)
|
||||
}
|
||||
if err := Uninstall(project, "fd-engineering"); err != nil {
|
||||
t.Fatalf("uninstall: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(userCommand); err != nil {
|
||||
t.Fatalf("user command must survive: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallRejectsUnknownSkillAndBadProject(t *testing.T) {
|
||||
if err := Install(t.TempDir(), "not-exist"); err == nil {
|
||||
t.Fatal("expected error for unknown skill")
|
||||
}
|
||||
if err := Install(filepath.Join(t.TempDir(), "missing"), "fd-engineering"); err == nil {
|
||||
t.Fatal("expected error for missing project dir")
|
||||
}
|
||||
// 路径穿越防护。
|
||||
if err := Install(t.TempDir(), "../evil"); err == nil {
|
||||
t.Fatal("expected error for path traversal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListExposesCommands(t *testing.T) {
|
||||
list := List()
|
||||
if len(list) == 0 {
|
||||
t.Fatal("expected builtin skills")
|
||||
}
|
||||
var found *Skill
|
||||
for i := range list {
|
||||
if list[i].Name == "fd-engineering" {
|
||||
found = &list[i]
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("fd-engineering not listed")
|
||||
}
|
||||
if len(found.Commands) == 0 {
|
||||
t.Fatal("expected commands to be listed")
|
||||
}
|
||||
if found.Commands[0].Description == "" {
|
||||
t.Fatal("expected command description parsed from frontmatter")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user