fa646f25a6
Release / release (push) Successful in 49s
- **Aliases**: Generate shell alias files (`aliases.sh` for bash/zsh, `netssh.fish` for fish) from cached hosts. Regenerate on each shell startup and cache refresh to keep aliases updated. - **Hooks**: Extend shell hook functionality to include alias file support. Install and uninstall commands updated for bash, zsh, and fish. - **Tests**: Add unit tests to verify alias file generation, path resolution, and idempotent hook installation. - **Docs**: Update README with instructions for alias file usage, installation, and relation to hooks.
211 lines
6.1 KiB
Go
211 lines
6.1 KiB
Go
package hook_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/hook"
|
|
)
|
|
|
|
func TestAliasesPath_UnderCacheDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
orig := os.Getenv("XDG_CACHE_HOME")
|
|
os.Setenv("XDG_CACHE_HOME", dir)
|
|
defer os.Setenv("XDG_CACHE_HOME", orig)
|
|
|
|
p := hook.AliasesPath()
|
|
if !strings.HasPrefix(p, dir) {
|
|
t.Errorf("AliasesPath should be under XDG_CACHE_HOME, got %q", p)
|
|
}
|
|
if !strings.HasSuffix(p, "aliases.sh") {
|
|
t.Errorf("AliasesPath should end with aliases.sh, got %q", p)
|
|
}
|
|
}
|
|
|
|
func TestFishAliasesPath_UnderConfigDir(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 := hook.FishAliasesPath()
|
|
if !strings.HasPrefix(p, dir) {
|
|
t.Errorf("FishAliasesPath should be under XDG_CONFIG_HOME, got %q", p)
|
|
}
|
|
if !strings.HasSuffix(p, "netssh.fish") {
|
|
t.Errorf("FishAliasesPath should end with netssh.fish, got %q", p)
|
|
}
|
|
}
|
|
|
|
// TestWriteAliasFiles_ShFile verifies the bash/zsh file is always written.
|
|
func TestWriteAliasFiles_ShFile(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
orig := os.Getenv("XDG_CACHE_HOME")
|
|
os.Setenv("XDG_CACHE_HOME", cacheDir)
|
|
defer os.Setenv("XDG_CACHE_HOME", orig)
|
|
|
|
entries := []hook.AliasEntry{
|
|
{Name: "web01", Host: "web01.example.com"},
|
|
{Name: "db01", Host: "db01.example.com"},
|
|
}
|
|
if err := hook.WriteAliasFiles(entries); err != nil {
|
|
t.Fatalf("WriteAliasFiles: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(hook.AliasesPath())
|
|
if err != nil {
|
|
t.Fatalf("aliases.sh not created: %v", err)
|
|
}
|
|
content := string(data)
|
|
|
|
for _, want := range []string{
|
|
"alias web01='netssh web01.example.com'",
|
|
"alias db01='netssh db01.example.com'",
|
|
} {
|
|
if !strings.Contains(content, want) {
|
|
t.Errorf("aliases.sh missing %q\ncontent:\n%s", want, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestWriteAliasFiles_FishFile verifies the fish file is written when fish config dir exists.
|
|
func TestWriteAliasFiles_FishFile(t *testing.T) {
|
|
configDir := t.TempDir()
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", configDir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
// Create the fish config directory so WriteAliasFiles knows fish is set up.
|
|
fishDir := filepath.Join(configDir, "fish")
|
|
os.MkdirAll(fishDir, 0o755)
|
|
|
|
cacheDir := t.TempDir()
|
|
origCache := os.Getenv("XDG_CACHE_HOME")
|
|
os.Setenv("XDG_CACHE_HOME", cacheDir)
|
|
defer os.Setenv("XDG_CACHE_HOME", origCache)
|
|
|
|
entries := []hook.AliasEntry{{Name: "web01", Host: "web01.example.com"}}
|
|
if err := hook.WriteAliasFiles(entries); err != nil {
|
|
t.Fatalf("WriteAliasFiles: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(hook.FishAliasesPath())
|
|
if err != nil {
|
|
t.Fatalf("netssh.fish not created: %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "alias web01 'netssh web01.example.com'") {
|
|
t.Errorf("fish alias file missing expected line\ncontent:\n%s", string(data))
|
|
}
|
|
}
|
|
|
|
// TestWriteAliasFiles_SkipsFishWhenNotConfigured verifies that fish file is NOT written
|
|
// when fish is not set up (no ~/.config/fish directory).
|
|
func TestWriteAliasFiles_SkipsFishWhenNotConfigured(t *testing.T) {
|
|
configDir := t.TempDir() // no fish subdir
|
|
orig := os.Getenv("XDG_CONFIG_HOME")
|
|
os.Setenv("XDG_CONFIG_HOME", configDir)
|
|
defer os.Setenv("XDG_CONFIG_HOME", orig)
|
|
|
|
cacheDir := t.TempDir()
|
|
origCache := os.Getenv("XDG_CACHE_HOME")
|
|
os.Setenv("XDG_CACHE_HOME", cacheDir)
|
|
defer os.Setenv("XDG_CACHE_HOME", origCache)
|
|
|
|
entries := []hook.AliasEntry{{Name: "web01", Host: "web01.example.com"}}
|
|
if err := hook.WriteAliasFiles(entries); err != nil {
|
|
t.Fatalf("WriteAliasFiles: %v", err)
|
|
}
|
|
|
|
if _, err := os.Stat(hook.FishAliasesPath()); err == nil {
|
|
t.Error("fish alias file should not be created when fish is not configured")
|
|
}
|
|
}
|
|
|
|
// TestWriteAliasFiles_EmptyEntries verifies an empty (but valid) file is written.
|
|
func TestWriteAliasFiles_EmptyEntries(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
orig := os.Getenv("XDG_CACHE_HOME")
|
|
os.Setenv("XDG_CACHE_HOME", cacheDir)
|
|
defer os.Setenv("XDG_CACHE_HOME", orig)
|
|
|
|
if err := hook.WriteAliasFiles(nil); err != nil {
|
|
t.Fatalf("WriteAliasFiles with nil entries: %v", err)
|
|
}
|
|
data, err := os.ReadFile(hook.AliasesPath())
|
|
if err != nil {
|
|
t.Fatalf("aliases.sh not created: %v", err)
|
|
}
|
|
// Should contain only the header comment, no alias lines.
|
|
if strings.Contains(string(data), "alias ") {
|
|
t.Error("aliases.sh should have no alias lines for empty entries")
|
|
}
|
|
}
|
|
|
|
// --- InstallAliasesSource ---
|
|
|
|
func TestInstallAliasesSource_Fresh(t *testing.T) {
|
|
profile := writeProfile(t, "existing line\n")
|
|
|
|
installed, err := hook.InstallAliasesSource(profile)
|
|
if err != nil {
|
|
t.Fatalf("InstallAliasesSource: %v", err)
|
|
}
|
|
if !installed {
|
|
t.Error("should report installed=true on first install")
|
|
}
|
|
|
|
data, _ := os.ReadFile(profile)
|
|
if !strings.Contains(string(data), hook.AliasesMarker) {
|
|
t.Error("profile should contain aliases source line")
|
|
}
|
|
}
|
|
|
|
func TestInstallAliasesSource_Idempotent(t *testing.T) {
|
|
profile := writeProfile(t, "")
|
|
|
|
hook.InstallAliasesSource(profile)
|
|
installed, err := hook.InstallAliasesSource(profile)
|
|
if err != nil {
|
|
t.Fatalf("second InstallAliasesSource: %v", err)
|
|
}
|
|
if installed {
|
|
t.Error("second call should report installed=false")
|
|
}
|
|
|
|
data, _ := os.ReadFile(profile)
|
|
if count := strings.Count(string(data), hook.AliasesMarker); count != 1 {
|
|
t.Errorf("aliases source line should appear once, got %d", count)
|
|
}
|
|
}
|
|
|
|
// TestUninstall_AlsoRemovesAliasesSourceLine verifies that Uninstall removes both lines.
|
|
func TestUninstall_AlsoRemovesAliasesSourceLine(t *testing.T) {
|
|
content := "export PATH=$PATH\n" +
|
|
hook.Line + "\n" +
|
|
hook.AliasesSourceLine + "\n" +
|
|
"export FOO=bar\n"
|
|
profile := writeProfile(t, content)
|
|
|
|
removed, err := hook.Uninstall(profile)
|
|
if err != nil {
|
|
t.Fatalf("Uninstall: %v", err)
|
|
}
|
|
if !removed {
|
|
t.Error("should report removed=true")
|
|
}
|
|
|
|
data, _ := os.ReadFile(profile)
|
|
s := string(data)
|
|
if strings.Contains(s, hook.Marker) {
|
|
t.Error("shell-init line should be removed")
|
|
}
|
|
if strings.Contains(s, hook.AliasesMarker) {
|
|
t.Error("aliases source line should be removed")
|
|
}
|
|
if !strings.Contains(s, "export PATH") || !strings.Contains(s, "export FOO") {
|
|
t.Error("other content should be preserved")
|
|
}
|
|
}
|