Host hrbrain, markdown, pat, and profile under dingtalk-misc, retire their standalone packages, and rename dws-shared to dingtalk-shared across live paths, coverage, installers, and policy. Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
13 KiB
Go
313 lines
13 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/huh"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type skillSetupFileInfo struct {
|
|
name string
|
|
mode os.FileMode
|
|
}
|
|
|
|
func (i skillSetupFileInfo) Name() string { return i.name }
|
|
func (i skillSetupFileInfo) Size() int64 { return 0 }
|
|
func (i skillSetupFileInfo) Mode() os.FileMode { return i.mode }
|
|
func (i skillSetupFileInfo) ModTime() time.Time { return time.Time{} }
|
|
func (i skillSetupFileInfo) IsDir() bool { return i.mode.IsDir() }
|
|
func (i skillSetupFileInfo) Sys() any { return nil }
|
|
|
|
func skillSetupCoverageCommand(t *testing.T, mode string, yes bool) *cobra.Command {
|
|
t.Helper()
|
|
cmd := newSkillSetupCommand()
|
|
root := &cobra.Command{Use: "dws"}
|
|
root.PersistentFlags().Bool("dry-run", false, "")
|
|
root.AddCommand(cmd)
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(io.Discard)
|
|
_ = cmd.Flags().Set("mode", mode)
|
|
_ = cmd.Flags().Set("yes", map[bool]string{true: "true", false: "false"}[yes])
|
|
return cmd
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSkillSetupHighLevelRemainingCoverage(t *testing.T) {
|
|
oldMode := skillSetupResolveMode
|
|
oldSource := skillSetupResolveSource
|
|
oldTargets := skillSetupResolveTargets
|
|
oldList := skillSetupListMulti
|
|
oldFilter := skillSetupFilterMulti
|
|
oldConfirm := skillSetupConfirm
|
|
oldMono := skillSetupInstallMono
|
|
oldMulti := skillSetupInstallMulti
|
|
t.Cleanup(func() {
|
|
skillSetupResolveMode = oldMode
|
|
skillSetupResolveSource = oldSource
|
|
skillSetupResolveTargets = oldTargets
|
|
skillSetupListMulti = oldList
|
|
skillSetupFilterMulti = oldFilter
|
|
skillSetupConfirm = oldConfirm
|
|
skillSetupInstallMono = oldMono
|
|
skillSetupInstallMulti = oldMulti
|
|
})
|
|
fail := errors.New("failure")
|
|
skillSetupResolveMode = func(mode string, _ bool, _ io.Writer) (string, error) { return mode, nil }
|
|
skillSetupResolveSource = func(string, string) (string, func(), error) { return "source", func() {}, nil }
|
|
skillSetupResolveTargets = func(string, string) ([]string, error) { return []string{filepath.Join(t.TempDir(), "dest")}, nil }
|
|
skillSetupFilterMulti = func(all, _, _ []string) ([]string, error) { return all, nil }
|
|
|
|
skillSetupListMulti = func(string) ([]string, error) { return nil, fail }
|
|
if err := skillSetupCoverageCommand(t, skillSetupModeMulti, true).RunE(skillSetupCoverageCommand(t, skillSetupModeMulti, true), nil); err == nil {
|
|
t.Fatal("multi list failure should propagate")
|
|
}
|
|
skillSetupListMulti = func(string) ([]string, error) { return nil, nil }
|
|
cmd := skillSetupCoverageCommand(t, skillSetupModeMulti, true)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("empty multi source should fail")
|
|
}
|
|
skillSetupListMulti = func(string) ([]string, error) { return []string{"dingtalk-shared", "dingtalk-doc"}, nil }
|
|
skillSetupFilterMulti = func([]string, []string, []string) ([]string, error) { return nil, fail }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMulti, true)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("multi filter failure should propagate")
|
|
}
|
|
skillSetupFilterMulti = func(all, _, _ []string) ([]string, error) { return all, nil }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMulti, true)
|
|
_ = cmd.Root().PersistentFlags().Set("dry-run", "true")
|
|
if err := cmd.RunE(cmd, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
skillSetupConfirm = func(io.Writer, string, string, []string, []string) (bool, error) { return false, fail }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMono, false)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("confirmation failure should propagate")
|
|
}
|
|
skillSetupConfirm = func(io.Writer, string, string, []string, []string) (bool, error) { return false, nil }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMono, false)
|
|
if err := cmd.RunE(cmd, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
skillSetupResolveMode = func(string, bool, io.Writer) (string, error) { return "unknown", nil }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMono, true)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("unknown resolved mode should fail")
|
|
}
|
|
skillSetupResolveMode = func(mode string, _ bool, _ io.Writer) (string, error) { return mode, nil }
|
|
skillSetupInstallMono = func(string, []string, io.Writer, io.Writer) (int, int, error) { return 0, 0, fail }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMono, true)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("mono install failure should propagate")
|
|
}
|
|
skillSetupInstallMono = func(string, []string, io.Writer, io.Writer) (int, int, error) { return 1, 0, nil }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMono, true)
|
|
if err := cmd.RunE(cmd, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
skillSetupInstallMulti = func(string, []string, []string, io.Writer, io.Writer) (int, int, error) { return 0, 0, fail }
|
|
cmd = skillSetupCoverageCommand(t, skillSetupModeMulti, true)
|
|
if err := cmd.RunE(cmd, nil); err == nil {
|
|
t.Fatal("multi install failure should propagate")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSkillSetupLowLevelRemainingCoverage(t *testing.T) {
|
|
oldRunForm, oldInteractive := skillSetupRunForm, skillSetupInteractive
|
|
oldReadDir, oldStat := skillSetupReadDir, skillSetupStat
|
|
oldExecutable, oldGetwd, oldHome := skillSetupExecutable, skillSetupGetwd, skillSetupUserHomeDir
|
|
oldRemove, oldMkdir := skillSetupRemoveAll, skillSetupMkdirAll
|
|
oldCopyDir, oldWalk, oldRel := skillSetupCopyDir, skillSetupWalk, skillSetupRel
|
|
oldReadlink, oldOpen, oldOpenFile, oldCopy := skillSetupReadlink, skillSetupOpen, skillSetupOpenFile, skillSetupCopy
|
|
t.Cleanup(func() {
|
|
skillSetupRunForm, skillSetupInteractive = oldRunForm, oldInteractive
|
|
skillSetupReadDir, skillSetupStat = oldReadDir, oldStat
|
|
skillSetupExecutable, skillSetupGetwd, skillSetupUserHomeDir = oldExecutable, oldGetwd, oldHome
|
|
skillSetupRemoveAll, skillSetupMkdirAll = oldRemove, oldMkdir
|
|
skillSetupCopyDir, skillSetupWalk, skillSetupRel = oldCopyDir, oldWalk, oldRel
|
|
skillSetupReadlink, skillSetupOpen, skillSetupOpenFile, skillSetupCopy = oldReadlink, oldOpen, oldOpenFile, oldCopy
|
|
})
|
|
fail := errors.New("failure")
|
|
|
|
skillSetupInteractive = func() bool { return true }
|
|
skillSetupRunForm = func(*huh.Form) error { return fail }
|
|
if _, err := resolveSkillSetupMode("", false, io.Discard); err == nil {
|
|
t.Fatal("interactive mode failure should propagate")
|
|
}
|
|
skillSetupRunForm = func(*huh.Form) error { return nil }
|
|
if got, err := resolveSkillSetupMode("", false, io.Discard); err != nil || got != skillSetupModeMono {
|
|
t.Fatalf("interactive default choice = %q, %v", got, err)
|
|
}
|
|
|
|
source := writeMultiSkillSource(t, []string{"dingtalk-doc"})
|
|
if err := os.WriteFile(filepath.Join(source, "README"), []byte("file"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := listMultiSkillNames(source); err != nil || len(got) != 1 {
|
|
t.Fatalf("listed multi skills = %#v, %v", got, err)
|
|
}
|
|
t.Setenv("DWS_SKILL_SOURCE", source)
|
|
if got, err := resolveSkillSetupSource("", skillSetupModeMulti); err != nil || got != source {
|
|
t.Fatalf("environment skill source = %q, %v", got, err)
|
|
}
|
|
t.Setenv("DWS_SKILL_SOURCE", "")
|
|
legacyRoot := t.TempDir()
|
|
legacyMono := filepath.Join(legacyRoot, "skills", skillSetupModeMono)
|
|
if err := os.MkdirAll(legacyMono, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(legacyMono, "SKILL.md"), []byte("skill"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
skillSetupExecutable = func() (string, error) { return filepath.Join(legacyRoot, "dws"), nil }
|
|
skillSetupStat = oldStat
|
|
if got, err := resolveSkillSetupSource("", skillSetupModeMono); err != nil || got != legacyMono {
|
|
t.Fatalf("legacy executable source = %q, %v", got, err)
|
|
}
|
|
t.Setenv("DWS_SKILL_SOURCE", "env-source")
|
|
if got := skillSourceCandidates("", skillSetupModeMono); len(got) < 2 {
|
|
t.Fatalf("environment source candidates = %#v", got)
|
|
}
|
|
t.Setenv("DWS_SKILL_SOURCE", "")
|
|
skillSetupExecutable = func() (string, error) { return "", fail }
|
|
skillSetupGetwd = func() (string, error) { return "", fail }
|
|
skillSetupUserHomeDir = func() (string, error) { return "", fail }
|
|
if _, err := resolveSkillSetupSource("", skillSetupModeMono); err == nil {
|
|
t.Fatal("missing fallback source should fail")
|
|
}
|
|
_ = skillSourceCandidates("explicit", skillSetupModeMono)
|
|
|
|
skillSetupReadDir = func(string) ([]os.DirEntry, error) { return nil, fail }
|
|
if isSkillSourceRoot("missing", skillSetupModeMulti) {
|
|
t.Fatal("unreadable multi source accepted")
|
|
}
|
|
skillSetupUserHomeDir = func() (string, error) { return "", fail }
|
|
if _, err := resolveSkillSetupTargets("all", skillSetupModeMono); err == nil {
|
|
t.Fatal("HOME failure should propagate")
|
|
}
|
|
|
|
monoDest := filepath.Join(t.TempDir(), "skills", "dws")
|
|
multiRoot := filepath.Dir(monoDest)
|
|
if err := os.MkdirAll(filepath.Join(multiRoot, "dingtalk-doc"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
skillSetupReadDir, skillSetupStat = oldReadDir, oldStat
|
|
var out, errOut bytes.Buffer
|
|
skillSetupRunForm = func(*huh.Form) error { return fail }
|
|
if _, err := confirmSkillSetup(&out, skillSetupModeMulti, "src", []string{monoDest}, []string{"dingtalk-doc"}); err == nil {
|
|
t.Fatal("confirmation form failure should propagate")
|
|
}
|
|
skillSetupRunForm = func(*huh.Form) error { return nil }
|
|
if ok, err := confirmSkillSetup(&out, skillSetupModeMono, "src", []string{monoDest}, nil); err != nil || ok {
|
|
t.Fatalf("EOF confirmation = %v, %v", ok, err)
|
|
}
|
|
skillSetupRemoveAll = func(string) error { return fail }
|
|
cleanupMutualExclusion(monoDest, skillSetupModeMono, &out, &errOut)
|
|
|
|
skillSetupCopyDir = func(string, string) error { return fail }
|
|
skillSetupRemoveAll = func(string) error { return fail }
|
|
_, skipped, _ := installSkillToHomes("src", []string{"a"}, &out, &errOut)
|
|
if skipped != 1 {
|
|
t.Fatal("mono remove failure not skipped")
|
|
}
|
|
skillSetupRemoveAll = func(string) error { return nil }
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return fail }
|
|
_, skipped, _ = installSkillToHomes("src", []string{"b"}, &out, &errOut)
|
|
if skipped != 1 {
|
|
t.Fatal("mono mkdir failure not skipped")
|
|
}
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return nil }
|
|
_, skipped, _ = installSkillToHomes("src", []string{"c"}, &out, &errOut)
|
|
if skipped != 1 {
|
|
t.Fatal("mono copy failure not skipped")
|
|
}
|
|
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return fail }
|
|
_, skipped, _ = installMultiSkillToHomes("src", []string{"one", "two"}, []string{filepath.Join(t.TempDir(), "dest")}, &out, &errOut)
|
|
if skipped != 2 {
|
|
t.Fatal("multi mkdir failure count mismatch")
|
|
}
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return nil }
|
|
skillSetupRemoveAll = func(string) error { return fail }
|
|
_, skipped, _ = installMultiSkillToHomes("src", []string{"one"}, []string{filepath.Join(t.TempDir(), "dest")}, &out, &errOut)
|
|
if skipped != 1 {
|
|
t.Fatal("multi remove failure count mismatch")
|
|
}
|
|
skillSetupRemoveAll = func(string) error { return nil }
|
|
_, skipped, _ = installMultiSkillToHomes("src", []string{"one"}, []string{filepath.Join(t.TempDir(), "dest")}, &out, &errOut)
|
|
if skipped != 1 {
|
|
t.Fatal("multi copy failure count mismatch")
|
|
}
|
|
|
|
skillSetupWalk = func(string, filepath.WalkFunc) error { return fail }
|
|
if err := copyDir("src", "dst"); !errors.Is(err, fail) {
|
|
t.Fatalf("walk failure = %v", err)
|
|
}
|
|
skillSetupWalk = func(_ string, fn filepath.WalkFunc) error {
|
|
return fn("path", skillSetupFileInfo{name: "path"}, nil)
|
|
}
|
|
skillSetupRel = func(string, string) (string, error) { return "", fail }
|
|
if err := copyDir("src", "dst"); !errors.Is(err, fail) {
|
|
t.Fatalf("relative-path failure = %v", err)
|
|
}
|
|
skillSetupRel = func(string, string) (string, error) { return "file", nil }
|
|
skillSetupWalk = func(_ string, fn filepath.WalkFunc) error {
|
|
return fn("link", skillSetupFileInfo{name: "link", mode: os.ModeSymlink}, nil)
|
|
}
|
|
skillSetupReadlink = func(string) (string, error) { return "", fail }
|
|
if err := copyDir("src", "dst"); !errors.Is(err, fail) {
|
|
t.Fatalf("readlink failure = %v", err)
|
|
}
|
|
for _, target := range []string{"relative-target", "/absolute-target"} {
|
|
skillSetupReadlink = func(string) (string, error) { return target, nil }
|
|
_ = copyDir("src", "dst")
|
|
}
|
|
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return fail }
|
|
if err := copyFileContent("src", "dst", 0o600); !errors.Is(err, fail) {
|
|
t.Fatalf("copy mkdir failure = %v", err)
|
|
}
|
|
skillSetupMkdirAll = func(string, os.FileMode) error { return nil }
|
|
skillSetupOpen = func(string) (*os.File, error) { return nil, fail }
|
|
if err := copyFileContent("src", "dst", 0o600); !errors.Is(err, fail) {
|
|
t.Fatalf("copy open failure = %v", err)
|
|
}
|
|
in, err := os.CreateTemp(t.TempDir(), "in")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
skillSetupOpen = func(string) (*os.File, error) { return in, nil }
|
|
skillSetupOpenFile = func(string, int, os.FileMode) (*os.File, error) { return nil, fail }
|
|
if err := copyFileContent("src", "dst", 0o600); !errors.Is(err, fail) {
|
|
t.Fatalf("copy output-open failure = %v", err)
|
|
}
|
|
outFile, err := os.CreateTemp(t.TempDir(), "out")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
skillSetupOpen = func(string) (*os.File, error) { return in, nil }
|
|
skillSetupOpenFile = func(string, int, os.FileMode) (*os.File, error) { return outFile, nil }
|
|
skillSetupCopy = func(io.Writer, io.Reader) (int64, error) { return 0, fail }
|
|
if err := copyFileContent("src", "dst", 0o600); !errors.Is(err, fail) {
|
|
t.Fatalf("copy content failure = %v", err)
|
|
}
|
|
|
|
closed, err := os.CreateTemp(t.TempDir(), "closed")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = closed.Close()
|
|
if isCharDevice(closed) {
|
|
t.Fatal("closed file is not a character device")
|
|
}
|
|
_ = fs.ValidPath("path")
|
|
}
|