Files
magpie/internal/library/ccswitch_test.go
T
yetone ce9fc6758a library: on Windows without the right to make symlinks, skills are linked with junctions (#973) — with Developer Mode off and magpie not elevated, os.Symlink fails with "A required privilege is not held by the client": a skill installed from a folder became a detached copy, and bringing in a skill from ~/.agents/skills failed outright. The library's folder links (an agent's link to the library's skill, a folder source, a skill brought in from a link or the shared folder, a carried skill's folder, relink) now go through dirLink, which on that error makes a directory junction instead: one every user may make, followed by every program as a symlink to a folder is, made natively (FSCTL_SET_REPARSE_POINT, no mklink). A target a junction can't reach (a network share) still gets the old copy. Go reports a junction as irregular, not a symlink, so ours, unlink, carry's clearing of the library, a project's place and the CC Switch update now tell a link by linkEntry (symlink or junction) and take it away with os.Remove, leaving what it points at, even when where it points can't be read. On the Windows box with every privilege stripped from the token (CreateRestrictedToken DISABLE_MAX_PRIVILEGE, whoami /priv shows SeChangeNotify only) and an explicit sandbox environment, the library's tests went from 16 failures to none (TestSkillHow, TestProjectSkillsLinked, TestCarry, TestSkillCopyEditReachesTheOthers, TestImportSharedSkillForKimi …); with the privilege they pass too. TestJunctionIsALink (Windows: magpie's own, read through, unlinked without its target, none to a share) is new; the folder fixtures that ignored a failed os.Symlink now use dirLink, and the tests' link checks accept a junction. go test -race ./internal/library/ on the Mac, go vet, windows/linux(nogui) builds pass.
2026-10-06 13:32:03 +08:00

83 lines
2.8 KiB
Go

package library
import (
"database/sql"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
_ "modernc.org/sqlite"
)
// A skill the library links to in CC Switch's folder is updated from the
// GitHub repository CC Switch installed it from, and becomes the library's
// own; CC Switch's folder is left as it was.
func TestCCSwitchSkillUpdates(t *testing.T) {
h := sandbox(t)
ccs := filepath.Join(h, ".cc-switch")
write(t, filepath.Join(ccs, "skills/pdf/SKILL.md"), "---\nname: pdf\ndescription: old\n---\n")
write(t, filepath.Join(ccs, "skills/mine/SKILL.md"), "---\nname: mine\n---\n")
db, err := sql.Open("sqlite", filepath.Join(ccs, "cc-switch.db"))
if err != nil {
t.Fatal(err)
}
for _, q := range []string{
`CREATE TABLE skills (id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, directory TEXT NOT NULL, repo_owner TEXT, repo_name TEXT, repo_branch TEXT DEFAULT 'main')`,
`INSERT INTO skills VALUES ('1','pdf','','pdf','owner','repo','main'), ('2','mine','','mine',NULL,NULL,NULL)`,
} {
if _, err := db.Exec(q); err != nil {
t.Fatal(err)
}
}
db.Close()
var asked []string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
asked = append(asked, r.URL.Path)
w.Write(tarball(t, map[string]string{
"skills/pdf/SKILL.md": "---\nname: pdf\ndescription: new\n---\n",
"skills/pdf/forms.md": "forms",
}))
}))
defer srv.Close()
old := tarballURL
tarballURL = func(repo, ref string) string { return srv.URL + "/" + repo + "/" + ref }
defer func() { tarballURL = old }()
ok(t)(InstallSkills(filepath.Join(ccs, "skills"), []string{"pdf", "mine"}, []string{"claude"}))
v, err := Read(nil)
if err != nil {
t.Fatal(err)
}
origins := map[string]string{}
for _, s := range v.Skills {
origins[s.Name] = s.Origin
}
if origins["pdf"] != "https://github.com/owner/repo/tree/main" || origins["mine"] != "" {
t.Fatalf("origins %v", origins)
}
if _, err := UpdateSkill("mine"); err == nil {
t.Error("one CC Switch has no repository for was updated")
}
ok(t)(UpdateSkill("pdf"))
if asked[len(asked)-1] != "/owner/repo/main" {
t.Errorf("asked %v", asked)
}
if s := read(t, filepath.Join(h, ".claude/skills/pdf/forms.md")); s != "forms" {
t.Errorf("claude's pdf: %q", s)
}
if s := read(t, filepath.Join(ccs, "skills/pdf/SKILL.md")); !strings.Contains(s, "old") {
t.Errorf("CC Switch's folder changed: %q", s)
}
if fi, err := os.Lstat(skillDir("pdf")); err != nil || linkEntry(fi) {
t.Errorf("the library's pdf is still a link: %v", err)
}
l, _ := load()
if s := l.skill("pdf"); s.Source == nil || s.Source.Kind != "github" || s.Source.Repo != "owner/repo" || s.Source.Path != "skills/pdf" {
t.Errorf("source %+v", s.Source)
}
ok(t)(UpdateSkill("pdf")) // from GitHub now, like any other
}