package netssh_test import ( "crypto/sha256" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" ) type installerFixture struct { home, bin, assets string env []string } func newInstallerFixture(t *testing.T) installerFixture { t.Helper() root := t.TempDir() f := installerFixture{home: filepath.Join(root, "home"), bin: filepath.Join(root, "tools"), assets: filepath.Join(root, "assets")} for _, dir := range []string{f.home, f.bin, f.assets} { if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } } writeFixture(t, filepath.Join(f.bin, "curl"), `#!/bin/bash set -eu out="" while [[ $# -gt 0 ]]; do case "$1" in -o) out=$2; shift 2 ;; *) url=$1; shift ;; esac done case "$url" in */releases/latest) printf '{"tag_name": "v0.0.99"}\n'; exit ;; */checksums.txt) [[ "${CHECKSUM_MODE:-}" != missing ]] || exit 22; file="$FIXTURE_ASSETS/checksums.txt" ;; *) file="$FIXTURE_ASSETS/netssh_linux_amd64" ;; esac if [[ -n "$out" ]]; then cp "$file" "$out"; else cat "$file"; fi `, 0o755) writeFixture(t, filepath.Join(f.bin, "uname"), "#!/bin/sh\ncase \"$1\" in -s) echo Linux;; -m) echo x86_64;; esac\n", 0o755) // Any accidental runtime dependency on Python must fail the installation. writeFixture(t, filepath.Join(f.bin, "python3"), "#!/bin/sh\nexit 99\n", 0o755) binary := `#!/bin/bash set -eu case "$1" in alias) if [[ "$3" == fish ]]; then printf "alias j 'netssh'\n"; else printf "alias j='netssh'\n"; fi ;; completion) exit 0 ;; shell-init) exit 0 ;; *) exit 1 ;; esac ` writeFixture(t, filepath.Join(f.assets, "netssh_linux_amd64"), binary, 0o755) checksum := fmt.Sprintf("%x netssh_linux_amd64\n", sha256.Sum256([]byte(binary))) writeFixture(t, filepath.Join(f.assets, "checksums.txt"), checksum, 0o644) f.env = append(os.Environ(), "HOME="+f.home, "XDG_CONFIG_HOME="+filepath.Join(f.home, "config"), "XDG_CACHE_HOME="+filepath.Join(f.home, "cache"), "ZDOTDIR="+f.home, "SHELL=/bin/bash", "PATH="+f.bin+":"+os.Getenv("PATH"), "FIXTURE_ASSETS="+f.assets, "INSTALL_DIR="+filepath.Join(f.home, "local bin's"), "BASH_ENV=", "CHECKSUM_MODE=") return f } func writeFixture(t *testing.T, path, content string, mode os.FileMode) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(content), mode); err != nil { t.Fatal(err) } } func (f installerFixture) run(t *testing.T) (string, error) { t.Helper() // Piping stdin mirrors curl | bash, including EOF for any child commands. script, err := os.ReadFile("install.sh") if err != nil { t.Fatal(err) } cmd := exec.Command("bash") cmd.Env = f.env cmd.Stdin = strings.NewReader(string(script)) out, err := cmd.CombinedOutput() return string(out), err } func TestInstallerUpdatesAndConsolidatesProfilesWithoutPython(t *testing.T) { f := newInstallerFixture(t) original := "export KEEP=yes\n# netssh shell-init is documented here\necho 'netssh shell-init'\nnetssh shell-init\nnetssh shell-init # duplicate\nsource ~/.cache/netssh/aliases.sh 2>/dev/null\neval \"$(netssh alias)\"\nalias old='netssh old.example.com'\n" target := filepath.Join(f.home, "dotfiles", "bashrc") writeFixture(t, target, original, 0o600) bashrc := filepath.Join(f.home, ".bashrc") if err := os.Symlink(target, bashrc); err != nil { t.Fatal(err) } writeFixture(t, filepath.Join(f.home, ".zshrc"), "export ZSH_KEEP=yes\nnetssh shell-init\n", 0o644) writeFixture(t, filepath.Join(f.home, ".profile"), "export LOGIN_KEEP=yes\nnetssh shell-init\n", 0o644) fish := filepath.Join(f.home, "config", "fish", "config.fish") writeFixture(t, fish, "set KEEP yes\nnetssh alias --shell fish | source\n", 0o644) installed := filepath.Join(f.home, "local bin's", "netssh") writeFixture(t, installed, "old binary", 0o755) if out, err := f.run(t); err != nil { t.Fatalf("install: %v\n%s", err, out) } for _, path := range []string{bashrc, filepath.Join(f.home, ".zshrc"), fish} { data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if strings.Count(string(data), "# >>> netssh >>>") != 1 { t.Fatalf("bad managed blocks: %s", data) } } zshrc, _ := os.ReadFile(filepath.Join(f.home, ".zshrc")) if !strings.Contains(string(zshrc), "compdef _netssh netssh") || !strings.Contains(string(zshrc), "/.zfunc' $fpath)") { t.Fatalf("zsh completion not registered: %s", zshrc) } data, _ := os.ReadFile(bashrc) if strings.Contains(string(data), "compdef") { t.Fatalf("zsh code in bash profile: %s", data) } for _, kept := range []string{"export KEEP=yes", "# netssh shell-init is documented here", "echo 'netssh shell-init'"} { if !strings.Contains(string(data), kept) { t.Fatalf("unrelated line lost: %s", kept) } } if strings.Contains(string(data), "alias old=") || strings.Contains(string(data), "eval ") { t.Fatalf("legacy entries remain: %s", data) } if info, err := os.Lstat(bashrc); err != nil || info.Mode()&os.ModeSymlink == 0 { t.Fatalf("symlink lost: %v", err) } if info, _ := os.Stat(target); info.Mode().Perm() != 0o600 { t.Fatal("permissions changed") } backups, _ := filepath.Glob(target + ".netssh-backup.*") if len(backups) != 1 { t.Fatalf("backups: %v", backups) } backup, _ := os.ReadFile(backups[0]) if string(backup) != original { t.Fatal("backup lost original content") } before := string(data) if out, err := f.run(t); err != nil { t.Fatalf("second install: %v\n%s", err, out) } data, _ = os.ReadFile(bashrc) if string(data) != before { t.Fatalf("not idempotent:\n%s", data) } backups, _ = filepath.Glob(target + ".netssh-backup.*") if len(backups) != 1 { t.Fatal("unchanged profile backed up again") } // Execute the generated Bash block to verify paths containing quotes and spaces. cmd := exec.Command("bash", "-c", `. "$HOME/.bashrc"; alias j`) cmd.Env = f.env if out, err := cmd.CombinedOutput(); err != nil || !strings.Contains(string(out), "alias j='netssh'") { t.Fatalf("source: %v\n%s", err, out) } } func TestInstallerRejectsUnverifiedDownloads(t *testing.T) { for _, mode := range []string{"missing", "mismatch", "no-entry", "duplicate"} { t.Run(mode, func(t *testing.T) { f := newInstallerFixture(t) f.env = append(f.env, "CHECKSUM_MODE="+mode) sums := filepath.Join(f.assets, "checksums.txt") switch mode { case "mismatch": writeFixture(t, sums, strings.Repeat("0", 64)+" netssh_linux_amd64\n", 0o644) case "no-entry": writeFixture(t, sums, strings.Repeat("0", 64)+" other_binary\n", 0o644) case "duplicate": data, _ := os.ReadFile(sums) writeFixture(t, sums, string(data)+string(data), 0o644) } installed := filepath.Join(f.home, "local bin's", "netssh") writeFixture(t, installed, "old binary", 0o755) profile := filepath.Join(f.home, ".bashrc") writeFixture(t, profile, "old profile\n", 0o600) if out, err := f.run(t); err == nil { t.Fatalf("accepted invalid checksum: %s", out) } data, _ := os.ReadFile(installed) if string(data) != "old binary" { t.Fatal("binary changed") } data, _ = os.ReadFile(profile) if string(data) != "old profile\n" { t.Fatal("profile changed") } }) } } func TestInstallerRefusesBrokenManagedBlock(t *testing.T) { f := newInstallerFixture(t) profile := filepath.Join(f.home, ".bashrc") original := "export KEEP=yes\n# >>> netssh >>>\nunfinished\n" writeFixture(t, profile, original, 0o644) if out, err := f.run(t); err == nil { t.Fatalf("accepted broken block: %s", out) } data, _ := os.ReadFile(profile) if string(data) != original { t.Fatal("profile changed") } } func TestInstallerUsesShasumAndFindsExistingBinary(t *testing.T) { if _, err := exec.LookPath("shasum"); err != nil { t.Skip("shasum not available") } f := newInstallerFixture(t) // Hide sha256sum (and Python) as on a standard macOS installation. for _, name := range []string{"shasum", "sed", "awk", "mktemp", "rm", "dirname", "readlink", "mkdir", "cat", "install", "mv", "cp", "chmod", "cmp", "perl"} { tool, err := exec.LookPath(name) if err != nil { t.Fatal(err) } if err := os.Symlink(tool, filepath.Join(f.bin, name)); err != nil { t.Fatal(err) } } f.env = append(f.env, "PATH="+f.bin, "INSTALL_DIR=") installed := filepath.Join(f.bin, "netssh") writeFixture(t, installed, "#!/bin/sh\nexit 1\n", 0o755) if out, err := f.run(t); err != nil { t.Fatalf("shasum update: %v\n%s", err, out) } data, _ := os.ReadFile(installed) expected, _ := os.ReadFile(filepath.Join(f.assets, "netssh_linux_amd64")) if string(data) != string(expected) { t.Fatal("existing installation was not replaced") } } func TestInstallerRespectsZdotdir(t *testing.T) { f := newInstallerFixture(t) zshDir := filepath.Join(f.home, "custom-zsh") f.env = append(f.env, "SHELL=/bin/zsh", "ZDOTDIR="+zshDir) old := filepath.Join(f.home, ".zshrc") writeFixture(t, old, "export KEEP=yes\nnetssh shell-init\n", 0o644) if out, err := f.run(t); err != nil { t.Fatalf("zsh setup: %v\n%s", err, out) } data, _ := os.ReadFile(filepath.Join(zshDir, ".zshrc")) if strings.Count(string(data), "# >>> netssh >>>") != 1 { t.Fatalf("missing custom zsh setup: %s", data) } data, _ = os.ReadFile(old) if string(data) != "export KEEP=yes\n" { t.Fatalf("old zsh setup not cleaned: %s", data) } } func TestInstallerUsesMacOSCacheLayout(t *testing.T) { f := newInstallerFixture(t) writeFixture(t, filepath.Join(f.bin, "uname"), "#!/bin/sh\ncase \"$1\" in -s) echo Darwin;; -m) echo x86_64;; esac\n", 0o755) data, _ := os.ReadFile(filepath.Join(f.assets, "netssh_linux_amd64")) writeFixture(t, filepath.Join(f.assets, "checksums.txt"), fmt.Sprintf("%x netssh_darwin_amd64\n", sha256.Sum256(data)), 0o644) f.env = append(f.env, "XDG_CACHE_HOME=") if out, err := f.run(t); err != nil { t.Fatalf("macOS setup: %v\n%s", err, out) } if _, err := os.Stat(filepath.Join(f.home, "Library", "Caches", "netssh", "aliases.sh")); err != nil { t.Fatal(err) } } func TestInstallerChecksDependenciesBeforeDownloading(t *testing.T) { for _, missing := range [][]string{{"awk", "cmp"}, {"curl"}, {"sha256sum", "shasum"}} { t.Run(strings.Join(missing, "+"), func(t *testing.T) { f := newInstallerFixture(t) // Build an isolated PATH containing every dependency except the selected tools. for _, name := range []string{"curl", "uname", "sed", "awk", "mktemp", "rm", "dirname", "readlink", "mkdir", "cat", "install", "mv", "cp", "chmod", "cmp", "sha256sum"} { skip := false for _, absent := range missing { if name == absent { skip = true } } if skip { if err := os.Remove(filepath.Join(f.bin, name)); err != nil && !os.IsNotExist(err) { t.Fatal(err) } continue } if _, err := os.Stat(filepath.Join(f.bin, name)); err == nil { continue } path, err := exec.LookPath(name) if err != nil { t.Fatal(err) } if err := os.Symlink(path, filepath.Join(f.bin, name)); err != nil { t.Fatal(err) } } marker := filepath.Join(f.assets, "curl-was-called") // Replacing curl with a marker makes any premature network operation observable. if missing[0] != "curl" { writeFixture(t, filepath.Join(f.bin, "curl"), "#!/bin/bash\nprintf called > \"$FIXTURE_ASSETS/curl-was-called\"\nexit 99\n", 0o755) } f.env = append(f.env, "PATH="+f.bin) installed := filepath.Join(f.home, "local bin's", "netssh") writeFixture(t, installed, "old binary", 0o755) out, err := f.run(t) if err == nil { t.Fatalf("missing dependencies accepted: %s", out) } for _, name := range missing { if !strings.Contains(out, name) { t.Errorf("missing diagnostic for %s: %s", name, out) } } if _, err := os.Stat(marker); !os.IsNotExist(err) { t.Fatal("curl ran before dependency checks completed") } data, _ := os.ReadFile(installed) if string(data) != "old binary" { t.Fatal("binary changed") } if _, err := os.Stat(filepath.Join(f.home, ".bashrc")); !os.IsNotExist(err) { t.Fatal("profile created") } }) } } func TestInstallerChecksSudoBeforeDownloadingWhenNeeded(t *testing.T) { if os.Geteuid() == 0 { t.Skip("root can write directories regardless of mode bits") } f := newInstallerFixture(t) for _, name := range []string{"sed", "awk", "mktemp", "rm", "dirname", "readlink", "mkdir", "cat", "install", "mv", "cp", "chmod", "cmp", "sha256sum"} { path, err := exec.LookPath(name) if err != nil { t.Fatal(err) } if err := os.Symlink(path, filepath.Join(f.bin, name)); err != nil { t.Fatal(err) } } locked := filepath.Join(f.home, "locked") if err := os.Mkdir(locked, 0o555); err != nil { t.Fatal(err) } t.Cleanup(func() { os.Chmod(locked, 0o755) }) writeFixture(t, filepath.Join(f.bin, "curl"), "#!/bin/bash\nprintf called > \"$FIXTURE_ASSETS/curl-was-called\"\nexit 99\n", 0o755) f.env = append(f.env, "PATH="+f.bin, "INSTALL_DIR="+filepath.Join(locked, "bin")) out, err := f.run(t) if err == nil || !strings.Contains(out, "sudo") { t.Fatalf("missing sudo diagnostic: %v\n%s", err, out) } if _, err := os.Stat(filepath.Join(f.assets, "curl-was-called")); !os.IsNotExist(err) { t.Fatal("download ran before sudo check") } }