package hook_test import ( "os" "path/filepath" "strings" "testing" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/hook" ) // --- ProfilePath --- func TestProfilePath_Bash(t *testing.T) { orig := os.Getenv("HOME") os.Setenv("HOME", t.TempDir()) defer os.Setenv("HOME", orig) p, err := hook.ProfilePath("bash") if err != nil { t.Fatalf("ProfilePath(bash): %v", err) } if !strings.HasSuffix(p, ".bashrc") { t.Errorf("bash profile should end with .bashrc, got %q", p) } } func TestProfilePath_Zsh(t *testing.T) { orig := os.Getenv("HOME") os.Setenv("HOME", t.TempDir()) defer os.Setenv("HOME", orig) p, err := hook.ProfilePath("zsh") if err != nil { t.Fatalf("ProfilePath(zsh): %v", err) } if !strings.HasSuffix(p, ".zshrc") { t.Errorf("zsh profile should end with .zshrc, got %q", p) } } func TestProfilePath_Fish(t *testing.T) { dir := t.TempDir() orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) p, err := hook.ProfilePath("fish") if err != nil { t.Fatalf("ProfilePath(fish): %v", err) } if !strings.HasSuffix(p, "config.fish") { t.Errorf("fish profile should end with config.fish, got %q", p) } } func TestProfilePath_UnknownShell(t *testing.T) { if _, err := hook.ProfilePath("ksh"); err == nil { t.Error("unknown shell should return an error") } } // --- IsInstalled --- func TestIsInstalled_Present(t *testing.T) { profile := writeProfile(t, hook.Line+"\n") if !hook.IsInstalled(profile) { t.Error("IsInstalled should return true when marker is present") } } func TestIsInstalled_Absent(t *testing.T) { profile := writeProfile(t, "export PATH=$PATH\n") if hook.IsInstalled(profile) { t.Error("IsInstalled should return false when marker is absent") } } func TestIsInstalled_MissingFile(t *testing.T) { if hook.IsInstalled("/nonexistent/path/.bashrc") { t.Error("IsInstalled should return false for missing file") } } // --- Install --- func TestInstall_Fresh(t *testing.T) { profile := filepath.Join(t.TempDir(), ".bashrc") installed, err := hook.Install(profile) if err != nil { t.Fatalf("Install: %v", err) } if !installed { t.Error("should report installed=true on fresh install") } if !hook.IsInstalled(profile) { t.Error("profile should contain the hook line after Install") } } func TestInstall_Idempotent(t *testing.T) { profile := filepath.Join(t.TempDir(), ".bashrc") hook.Install(profile) installed, err := hook.Install(profile) if err != nil { t.Fatalf("second Install: %v", err) } if installed { t.Error("second Install should report installed=false") } data, _ := os.ReadFile(profile) if count := strings.Count(string(data), hook.Marker); count != 1 { t.Errorf("hook line should appear exactly once, got %d", count) } } func TestInstall_CreatesProfileAndParentDirs(t *testing.T) { profile := filepath.Join(t.TempDir(), "nested", "dir", ".zshrc") if _, err := hook.Install(profile); err != nil { t.Fatalf("Install should create missing directories: %v", err) } if _, err := os.Stat(profile); err != nil { t.Error("profile file should have been created") } } func TestInstall_PreservesExistingContent(t *testing.T) { profile := writeProfile(t, "export PATH=$PATH:/usr/local/bin\n") hook.Install(profile) data, _ := os.ReadFile(profile) content := string(data) if !strings.Contains(content, "export PATH") { t.Error("Install should not remove existing content") } if !strings.Contains(content, hook.Marker) { t.Error("hook line should be appended") } } func TestInstall_AppendedAtEnd(t *testing.T) { profile := writeProfile(t, "existing line\n") hook.Install(profile) data, _ := os.ReadFile(profile) lines := strings.Split(strings.TrimRight(string(data), "\n"), "\n") last := lines[len(lines)-1] if last != hook.Line { t.Errorf("hook line should be the last line, got %q", last) } } // --- Uninstall --- func TestUninstall_Removes(t *testing.T) { profile := writeProfile(t, "export PATH=$PATH\n"+hook.Line+"\nexport FOO=bar\n") removed, err := hook.Uninstall(profile) if err != nil { t.Fatalf("Uninstall: %v", err) } if !removed { t.Error("should report removed=true") } if hook.IsInstalled(profile) { t.Error("hook line should have been removed") } } func TestUninstall_PreservesOtherContent(t *testing.T) { profile := writeProfile(t, "export FOO=bar\n"+hook.Line+"\nexport BAZ=qux\n") hook.Uninstall(profile) data, _ := os.ReadFile(profile) content := string(data) if !strings.Contains(content, "export FOO=bar") { t.Error("content before hook should be preserved") } if !strings.Contains(content, "export BAZ=qux") { t.Error("content after hook should be preserved") } } func TestUninstall_NotPresent(t *testing.T) { profile := writeProfile(t, "export PATH=$PATH\n") removed, err := hook.Uninstall(profile) if err != nil { t.Fatalf("Uninstall: %v", err) } if removed { t.Error("should report removed=false when hook was not present") } } func TestUninstall_MissingFile(t *testing.T) { removed, err := hook.Uninstall("/nonexistent/.bashrc") if err != nil { t.Errorf("Uninstall on missing file should not error: %v", err) } if removed { t.Error("should report removed=false for missing file") } } func TestUninstall_CollapsesExtraBlankLines(t *testing.T) { // blank line before hook + hook + blank line after = three consecutive newlines after removal profile := writeProfile(t, "line1\n\n"+hook.Line+"\n\nline2\n") hook.Uninstall(profile) data, _ := os.ReadFile(profile) if strings.Contains(string(data), "\n\n\n") { t.Error("three consecutive blank lines should be collapsed after uninstall") } } // --- Roundtrip --- func TestInstallUninstall_Roundtrip(t *testing.T) { profile := writeProfile(t, "existing content\n") hook.Install(profile) if !hook.IsInstalled(profile) { t.Fatal("should be installed after Install") } hook.Uninstall(profile) if hook.IsInstalled(profile) { t.Fatal("should not be installed after Uninstall") } data, _ := os.ReadFile(profile) if !strings.Contains(string(data), "existing content") { t.Error("original content should survive install+uninstall cycle") } } // --- ReloadNote --- func TestReloadNote_ContainsPath(t *testing.T) { note := hook.ReloadNote("/home/user/.bashrc") if !strings.Contains(note, "/home/user/.bashrc") { t.Errorf("ReloadNote should contain the profile path, got %q", note) } } // --- helpers --- func writeProfile(t *testing.T, content string) string { t.Helper() f := filepath.Join(t.TempDir(), "profile") if err := os.WriteFile(f, []byte(content), 0o644); err != nil { t.Fatal(err) } return f }