Remove everything netssh and install.sh set up, also leftovers of older versions: - netssh blocks and unmarked legacy lines in bash, zsh, and fish profiles - completion files for bash, zsh (incl. the oh-my-zsh cache), fish, and the command alias; files not generated by netssh are kept - the cache directory with hosts, aliases, and per-instance caches - the config file and local notes; the config is first copied to a private netssh.yaml.backup-<timestamp> because it holds API tokens - the netssh binary; for protected paths the sudo command is printed --keep-config keeps config and notes, --dry-run only lists the plan, and --yes skips the confirmation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
4.1 KiB
Go
120 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/hook"
|
|
)
|
|
|
|
func uninstallFixture(t *testing.T) (home string) {
|
|
t.Helper()
|
|
home = t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("ZDOTDIR", home)
|
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
|
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
|
|
t.Setenv("ZSH", "")
|
|
t.Setenv("ZSH_CACHE_DIR", "")
|
|
t.Setenv("NETSSH_SHELL_ALIAS", "")
|
|
t.Setenv("NETSSH_CACHE_PATH", "")
|
|
t.Setenv("PATH", filepath.Join(home, "nobin"))
|
|
files := map[string]string{
|
|
".bashrc": "export KEEP=1\n# >>> netssh >>>\nnetssh shell-init\n# <<< netssh <<<\n",
|
|
".zshrc": "plugins=(git)\nnetssh shell-init # netssh cache auto-refresh\nsource ~/.cache/netssh/aliases.sh 2>/dev/null # netssh aliases\n",
|
|
".profile": "export UNRELATED=1\n",
|
|
".config/netssh.yaml": "netbox:\n url: \"https://netbox.example.com\"\n token: \"secret\"\nshell:\n alias: \"j\"\n",
|
|
".config/netssh/notes/abc.txt": "note",
|
|
".cache/netssh/hosts.json": "{}",
|
|
".cache/netssh/instances-0011/hosts.json": "{}",
|
|
".local/share/bash-completion/completions/netssh": "# bash completion for netssh",
|
|
".zfunc/_netssh": hook.ZshCompletionScript,
|
|
".zfunc/_j": "#compdef j\n# netssh command alias completion — generated automatically\n",
|
|
".zfunc/_other": "#compdef other\n",
|
|
}
|
|
for name, content := range files {
|
|
path := filepath.Join(home, name)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return home
|
|
}
|
|
|
|
func runUninstall(t *testing.T, args ...string) string {
|
|
t.Helper()
|
|
cmd := rootCmd()
|
|
var out strings.Builder
|
|
cmd.SetOut(&out)
|
|
cmd.SetArgs(append([]string{"uninstall"}, args...))
|
|
if err := cmd.Execute(); err != nil {
|
|
t.Fatalf("uninstall: %v\n%s", err, out.String())
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
func TestUninstallRemovesEverythingAndBacksUpConfig(t *testing.T) {
|
|
home := uninstallFixture(t)
|
|
if out := runUninstall(t, "--dry-run"); !strings.Contains(out, ".zshrc") || strings.Contains(out, ".profile") {
|
|
t.Fatalf("plan:\n%s", out)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(home, ".cache", "netssh")); err != nil {
|
|
t.Fatal("dry run removed files")
|
|
}
|
|
runUninstall(t, "--yes")
|
|
|
|
for name, want := range map[string]string{".bashrc": "export KEEP=1\n", ".zshrc": "plugins=(git)\n", ".profile": "export UNRELATED=1\n"} {
|
|
data, _ := os.ReadFile(filepath.Join(home, name))
|
|
if string(data) != want {
|
|
t.Errorf("%s: %q", name, data)
|
|
}
|
|
}
|
|
for _, gone := range []string{".cache/netssh", ".config/netssh.yaml", ".config/netssh", ".zfunc/_netssh", ".zfunc/_j", ".local/share/bash-completion/completions/netssh"} {
|
|
if _, err := os.Stat(filepath.Join(home, gone)); !os.IsNotExist(err) {
|
|
t.Errorf("%s not removed: %v", gone, err)
|
|
}
|
|
}
|
|
if _, err := os.Stat(filepath.Join(home, ".zfunc", "_other")); err != nil {
|
|
t.Error("unrelated completion removed")
|
|
}
|
|
backups, _ := filepath.Glob(filepath.Join(home, ".config", "netssh.yaml.backup-*"))
|
|
if len(backups) != 1 {
|
|
t.Fatalf("backups: %v", backups)
|
|
}
|
|
if info, _ := os.Stat(backups[0]); info.Mode().Perm() != 0o600 {
|
|
t.Error("backup with token is not private")
|
|
}
|
|
}
|
|
|
|
func TestUninstallKeepConfig(t *testing.T) {
|
|
home := uninstallFixture(t)
|
|
runUninstall(t, "--yes", "--keep-config")
|
|
for _, kept := range []string{".config/netssh.yaml", ".config/netssh/notes/abc.txt"} {
|
|
if _, err := os.Stat(filepath.Join(home, kept)); err != nil {
|
|
t.Errorf("%s removed", kept)
|
|
}
|
|
}
|
|
if _, err := os.Stat(filepath.Join(home, ".cache", "netssh")); !os.IsNotExist(err) {
|
|
t.Error("cache kept")
|
|
}
|
|
}
|
|
|
|
func TestUninstallAbortsWithoutConfirmation(t *testing.T) {
|
|
home := uninstallFixture(t)
|
|
cmd := rootCmd()
|
|
cmd.SetOut(&strings.Builder{})
|
|
cmd.SetIn(strings.NewReader("n\n"))
|
|
cmd.SetArgs([]string{"uninstall"})
|
|
if err := cmd.Execute(); err == nil {
|
|
t.Fatal("no confirmation required")
|
|
}
|
|
if _, err := os.Stat(filepath.Join(home, ".config", "netssh.yaml")); err != nil {
|
|
t.Fatal("config removed without confirmation")
|
|
}
|
|
}
|