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