package paths import ( "os" "path/filepath" "strings" "testing" ) func TestResolveUsesXDGOverrides(t *testing.T) { dir := t.TempDir() t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config")) t.Setenv("XDG_STATE_HOME", filepath.Join(dir, "state")) t.Setenv("XDG_CACHE_HOME", filepath.Join(dir, "cache")) t.Setenv("XDG_RUNTIME_DIR", filepath.Join(dir, "run")) layout, err := Resolve() if err != nil { t.Fatalf("Resolve: %v", err) } if layout.ConfigDir != filepath.Join(dir, "config", "banger") { t.Errorf("ConfigDir = %q", layout.ConfigDir) } if layout.StateDir != filepath.Join(dir, "state", "banger") { t.Errorf("StateDir = %q", layout.StateDir) } if layout.CacheDir != filepath.Join(dir, "cache", "banger") { t.Errorf("CacheDir = %q", layout.CacheDir) } if layout.RuntimeDir != filepath.Join(dir, "run", "banger") { t.Errorf("RuntimeDir = %q", layout.RuntimeDir) } if !strings.HasSuffix(layout.SocketPath, "bangerd.sock") { t.Errorf("SocketPath = %q", layout.SocketPath) } if !strings.HasSuffix(layout.DBPath, "state.db") { t.Errorf("DBPath = %q", layout.DBPath) } } func TestResolveUserForHomeUsesProvidedHome(t *testing.T) { home := filepath.Join(t.TempDir(), "owner") layout, err := ResolveUserForHome(home) if err != nil { t.Fatalf("ResolveUserForHome: %v", err) } if layout.ConfigDir != filepath.Join(home, ".config", "banger") { t.Fatalf("ConfigDir = %q", layout.ConfigDir) } if layout.StateDir != filepath.Join(home, ".local", "state", "banger") { t.Fatalf("StateDir = %q", layout.StateDir) } if layout.KnownHostsPath != filepath.Join(home, ".local", "state", "banger", "ssh", "known_hosts") { t.Fatalf("KnownHostsPath = %q", layout.KnownHostsPath) } } func TestResolveSystemUsesFixedPaths(t *testing.T) { layout := ResolveSystem() if layout.SocketPath != "/run/banger/bangerd.sock" { t.Fatalf("SocketPath = %q", layout.SocketPath) } if layout.StateDir != "/var/lib/banger" { t.Fatalf("StateDir = %q", layout.StateDir) } if layout.KnownHostsPath != "/var/lib/banger/ssh/known_hosts" { t.Fatalf("KnownHostsPath = %q", layout.KnownHostsPath) } } func TestResolveFallsBackWhenRuntimeUnset(t *testing.T) { t.Setenv("XDG_RUNTIME_DIR", "") layout, err := Resolve() if err != nil { t.Fatalf("Resolve: %v", err) } if !strings.Contains(layout.RuntimeDir, "banger-runtime-") { t.Errorf("expected fallback runtime dir, got %q", layout.RuntimeDir) } } func TestEnsureCreatesAllDirs(t *testing.T) { base := t.TempDir() layout := Layout{ ConfigDir: filepath.Join(base, "config"), StateDir: filepath.Join(base, "state"), CacheDir: filepath.Join(base, "cache"), RuntimeDir: filepath.Join(base, "runtime"), VMsDir: filepath.Join(base, "state/vms"), ImagesDir: filepath.Join(base, "state/images"), KernelsDir: filepath.Join(base, "state/kernels"), OCICacheDir: filepath.Join(base, "cache/oci"), } if err := Ensure(layout); err != nil { t.Fatalf("Ensure: %v", err) } for _, dir := range []string{ layout.ConfigDir, layout.StateDir, layout.CacheDir, layout.RuntimeDir, layout.VMsDir, layout.ImagesDir, layout.KernelsDir, layout.OCICacheDir, } { info, err := os.Stat(dir) if err != nil { t.Errorf("stat %q: %v", dir, err) continue } if !info.IsDir() { t.Errorf("%q is not a directory", dir) } } // RuntimeDir holds sockets; must be 0700. info, err := os.Stat(layout.RuntimeDir) if err != nil { t.Fatalf("stat runtime: %v", err) } if perm := info.Mode().Perm(); perm != 0o700 { t.Errorf("RuntimeDir mode = %#o, want 0700", perm) } // Idempotent. if err := Ensure(layout); err != nil { t.Fatalf("Ensure (second run): %v", err) } } func TestEnsureTightensStaleRuntimeDirMode(t *testing.T) { base := t.TempDir() runtime := filepath.Join(base, "runtime") if err := os.MkdirAll(runtime, 0o755); err != nil { t.Fatalf("MkdirAll: %v", err) } if err := Ensure(Layout{RuntimeDir: runtime}); err != nil { t.Fatalf("Ensure: %v", err) } info, err := os.Stat(runtime) if err != nil { t.Fatalf("stat: %v", err) } if perm := info.Mode().Perm(); perm != 0o700 { t.Errorf("mode = %#o, want 0700 after Ensure", perm) } } func TestBangerdPathEnvOverride(t *testing.T) { t.Setenv("BANGER_DAEMON_BIN", "/tmp/custom-bangerd") got, err := BangerdPath() if err != nil { t.Fatalf("BangerdPath: %v", err) } if got != "/tmp/custom-bangerd" { t.Errorf("got %q, want /tmp/custom-bangerd", got) } } func TestBangerdPathFindsSiblingBinary(t *testing.T) { t.Setenv("BANGER_DAEMON_BIN", "") root := t.TempDir() sibling := filepath.Join(root, "bangerd") if err := os.WriteFile(sibling, []byte("#!/bin/sh\n"), 0o755); err != nil { t.Fatalf("WriteFile: %v", err) } original := executablePath executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil } t.Cleanup(func() { executablePath = original }) got, err := BangerdPath() if err != nil { t.Fatalf("BangerdPath: %v", err) } if got != sibling { t.Errorf("got %q, want %q", got, sibling) } } func TestBangerdPathNotFound(t *testing.T) { t.Setenv("BANGER_DAEMON_BIN", "") root := t.TempDir() original := executablePath executablePath = func() (string, error) { return filepath.Join(root, "banger"), nil } t.Cleanup(func() { executablePath = original }) if _, err := BangerdPath(); err == nil { t.Fatal("expected error when no sibling bangerd exists") } }