Files
ssh-netbox-wrapper/cmd/netssh/uninstall_test.go
T
Sebastian UnterschützandClaude Opus 5.5 c2e590bf52 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>
2026-10-28 11:23:28 +01:00

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")
}
}