feat: add netssh uninstall
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>
This commit is contained in:
1 parent
786768ec53
commit
c2e590bf52
6 files changed
+445
-1
No files matched your search
@@ -0,0 +1,119 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user