banger/internal/daemon/ssh_client_config_test.go
Thales Maciel 700a1e6e60
cleanup: drop pre-v0.1 migration scaffolding + legacy-behavior refs
banger hasn't shipped a public release — every "legacy", "pre-opt-in",
"previously", "migration note", "no longer" reference in the tree is
pinning against a state no real user's install has ever been in.
That scaffolding has weight: it's a coordinate system future readers
have to decode, and it keeps dead code alive.

Removed (code):
- internal/daemon/ssh_client_config.go
    - vmSSHConfigIncludeBegin / vmSSHConfigIncludeEnd constants and
      every `removeManagedBlock(existing, vm...)` call they enabled
      (legacy inline `Host *.vm` block scrub)
    - cleanupLegacySSHConfigDir (+ its caller in syncVMSSHClientConfig)
      — wiped a pre-opt-in sibling file under $ConfigDir/ssh
    - sameDirOrParent + resolvePathForComparison — only ever used
      by cleanupLegacySSHConfigDir
    - the "also check legacy marker" fallback in
      UserSSHIncludeInstalled / UninstallUserSSHInclude
- internal/store/migrations.go
    - migrateDropDeadImageColumns (migration 2) + its slice entry
    - dropColumnIfExists (orphaned after the above)
    - addColumnIfMissing + the whole "columns added across the pre-
      versioning lifetime" block at the end of migrateBaseline —
      subsumed into the baseline CREATE TABLE
    - `packages_path TEXT` column on the images table (the
      throwaway migration 2 dropped it, but there was never any
      reader)
- internal/daemon/vm.go
    - vmDNSRecordName local wrapper — was justified as "avoid
      pulling vmdns into every file"; three of four callers already
      imported vmdns directly, so inline the one stray call
- internal/cli/cli_test.go
    - TestLegacyRemovedCommandIsRejected (`tui` subcommand never
      shipped)

Removed / simplified (tests):
- ssh_client_config_test.go: dropped TestSameDirOrParentHandlesSymlinks,
  TestSyncVMSSHClientConfigPreservesUserKeyInLegacyDir,
  TestSyncVMSSHClientConfigNarrowsCleanupToLegacyFile,
  TestSyncVMSSHClientConfigLeavesUnexpectedLegacyContents,
  TestInstallUserSSHIncludeMigratesLegacyInlineBlock, plus the
  "legacy posture" regression strings in the remaining happy-path
  test; TestUninstallUserSSHIncludeRemovesBothMarkerBlocks collapsed
  to a single-block test
- migrations_test.go: dropped TestMigrateDropDeadImageColumns_AcrossInstallPaths,
  TestDropColumnIfExistsIsIdempotent; TestOpenReadOnlyDoesNotRunMigrations
  simplified to test against the baseline marker

Removed (docs):
- README.md "**Migration note.**" blockquote about the SSH-key path move
- docs/advanced.md parenthetical "(the old behaviour)"

Reworded (comments):
- Dropped "Previously this file also contained LogLevel DEBUG3..."
  history from vm_disk.go's sshdGuestConfig doc
- Dropped "Call sites that previously read vm.Runtime.{PID,...}"
  from vm_handles.go; now documents the current contract
- Dropped "Pre-v0.1 the defaults are" scaffolding in doctor_test.go
- Dropped "no longer does its own git inspection" phrasing in vm_run.go
- Dropped the "(also cleans up legacy inline block from pre-opt-in
  builds)" aside on the `ssh-config` CLI docstring
- Renamed test var `legacyKey` → `existingKey` in vm_test.go; its
  purpose was "pre-existing authorized_keys line," not banger-legacy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:56:32 -03:00

184 lines
5.5 KiB
Go

package daemon
import (
"os"
"path/filepath"
"strings"
"testing"
"banger/internal/paths"
)
// Under the opt-in contract the daemon writes its own ssh_config file
// and never touches ~/.ssh/config on its own.
func TestSyncVMSSHClientConfigWritesBangerFileOnly(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
knownHostsPath := filepath.Join(homeDir, ".local", "state", "banger", "ssh", "known_hosts")
layout := paths.Layout{
ConfigDir: filepath.Join(homeDir, ".config", "banger"),
KnownHostsPath: knownHostsPath,
}
keyPath := filepath.Join(homeDir, ".config", "banger", "ssh", "id_ed25519")
if err := syncVMSSHClientConfig(layout, keyPath); err != nil {
t.Fatalf("syncVMSSHClientConfig: %v", err)
}
// Banger's own ssh_config file has the `Host *.vm` stanza.
bangerConfig, err := os.ReadFile(BangerSSHConfigPath(layout))
if err != nil {
t.Fatalf("ReadFile(banger ssh_config): %v", err)
}
for _, want := range []string{
"Host *.vm",
"IdentityFile " + keyPath,
"UserKnownHostsFile " + knownHostsPath,
"StrictHostKeyChecking accept-new",
} {
if !strings.Contains(string(bangerConfig), want) {
t.Fatalf("banger ssh_config missing %q:\n%s", want, bangerConfig)
}
}
// ~/.ssh/config must NOT have been created or modified.
if _, err := os.Stat(filepath.Join(homeDir, ".ssh", "config")); !os.IsNotExist(err) {
t.Fatalf("~/.ssh/config should be untouched; stat err = %v", err)
}
}
func TestInstallUserSSHIncludeAddsIncludeBlock(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
layout := paths.Layout{ConfigDir: filepath.Join(homeDir, ".config", "banger")}
if err := os.MkdirAll(layout.ConfigDir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
// Write a fake banger ssh_config so Install has something to include.
if err := os.WriteFile(BangerSSHConfigPath(layout), []byte("Host *.vm\n"), 0o644); err != nil {
t.Fatalf("WriteFile(banger ssh_config): %v", err)
}
if err := InstallUserSSHInclude(layout); err != nil {
t.Fatalf("InstallUserSSHInclude: %v", err)
}
got, err := os.ReadFile(filepath.Join(homeDir, ".ssh", "config"))
if err != nil {
t.Fatalf("ReadFile(~/.ssh/config): %v", err)
}
want := "Include " + BangerSSHConfigPath(layout)
if !strings.Contains(string(got), want) {
t.Fatalf("user config missing %q:\n%s", want, got)
}
if !strings.Contains(string(got), bangerSSHIncludeBegin) {
t.Fatalf("user config missing begin marker:\n%s", got)
}
}
func TestInstallUserSSHIncludeIsIdempotent(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
layout := paths.Layout{ConfigDir: filepath.Join(homeDir, ".config", "banger")}
if err := os.MkdirAll(layout.ConfigDir, 0o755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(BangerSSHConfigPath(layout), []byte("Host *.vm\n"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
for i := 0; i < 3; i++ {
if err := InstallUserSSHInclude(layout); err != nil {
t.Fatalf("InstallUserSSHInclude (%d): %v", i, err)
}
}
got, err := os.ReadFile(filepath.Join(homeDir, ".ssh", "config"))
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if n := strings.Count(string(got), bangerSSHIncludeBegin); n != 1 {
t.Fatalf("begin markers = %d, want 1:\n%s", n, got)
}
}
func TestUninstallUserSSHIncludeRemovesIncludeBlock(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
sshDir := filepath.Join(homeDir, ".ssh")
if err := os.MkdirAll(sshDir, 0o700); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
seed := strings.Join([]string{
"Host keep",
" HostName 198.51.100.1",
"",
bangerSSHIncludeBegin,
"Include /tmp/banger-ssh-config",
bangerSSHIncludeEnd,
"",
}, "\n")
if err := os.WriteFile(filepath.Join(sshDir, "config"), []byte(seed), 0o600); err != nil {
t.Fatalf("seed: %v", err)
}
if err := UninstallUserSSHInclude(); err != nil {
t.Fatalf("UninstallUserSSHInclude: %v", err)
}
got, err := os.ReadFile(filepath.Join(sshDir, "config"))
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
gotStr := string(got)
if strings.Contains(gotStr, bangerSSHIncludeBegin) {
t.Fatalf("begin marker survived uninstall:\n%s", gotStr)
}
if !strings.Contains(gotStr, "Host keep") {
t.Fatalf("lost unrelated entry:\n%s", gotStr)
}
}
func TestUninstallUserSSHIncludeIsNoOpWhenMissing(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
if err := UninstallUserSSHInclude(); err != nil {
t.Fatalf("UninstallUserSSHInclude on missing file: %v", err)
}
// Still no ~/.ssh/config.
if _, err := os.Stat(filepath.Join(homeDir, ".ssh", "config")); !os.IsNotExist(err) {
t.Fatalf("~/.ssh/config unexpectedly created; stat err = %v", err)
}
}
func TestUserSSHIncludeInstalledDetectsMarker(t *testing.T) {
for _, tc := range []struct {
name string
seed string
wantIn bool
}{
{"missing file", "", false},
{"unrelated only", "Host other\n HostName 1.2.3.4\n", false},
{"installed", bangerSSHIncludeBegin + "\nInclude /tmp/banger\n" + bangerSSHIncludeEnd + "\n", true},
} {
t.Run(tc.name, func(t *testing.T) {
homeDir := t.TempDir()
t.Setenv("HOME", homeDir)
if tc.seed != "" {
if err := os.MkdirAll(filepath.Join(homeDir, ".ssh"), 0o700); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(filepath.Join(homeDir, ".ssh", "config"), []byte(tc.seed), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
}
got, err := UserSSHIncludeInstalled()
if err != nil {
t.Fatalf("UserSSHIncludeInstalled: %v", err)
}
if got != tc.wantIn {
t.Fatalf("got %v, want %v", got, tc.wantIn)
}
})
}
}