package keymgr import ( "os" "os/exec" "path/filepath" "strings" "testing" "ssh-access-tui/internal/model" ) // extract7z unpacks archivePath (encrypted with password) into a fresh temp // dir and returns that dir. Skips the test if 7z is unavailable rather than // failing, since this is exercising an external tool, not this package. func extract7z(t *testing.T, archivePath, password string) string { t.Helper() if _, err := exec.LookPath("7z"); err != nil { t.Skip("7z not available in PATH") } dest := t.TempDir() cmd := exec.Command("7z", "x", "-p"+password, "-o"+dest, archivePath) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("7z x: %v: %s", err, out) } return dest } // TestBuildUserArchive_ProxyJumpSelectsRenderer is a smoke test verifying // that BuildUserArchive's new proxyJump parameter actually switches which // client config / instructions renderer gets packed, end to end through the // real ssh-keygen/7z tools (see internal/keymgr package doc: the private key // only exists transiently on disk for this one operation). func TestBuildUserArchive_ProxyJumpSelectsRenderer(t *testing.T) { if _, err := exec.LookPath("ssh-keygen"); err != nil { t.Skip("ssh-keygen not available in PATH") } if _, err := exec.LookPath("7z"); err != nil { t.Skip("7z not available in PATH") } forwards := []model.Forward{ {HostName: "db-prod-1", Address: "10.0.0.5", Port: 22, ServiceName: "ssh"}, } tests := []struct { name string proxyJump bool }{ {"LocalForward mode", false}, {"ProxyJump mode", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scratch := t.TempDir() outDir := t.TempDir() username := "test-user" password := "TestPassw0rd123" res, err := BuildUserArchive(scratch, outDir, "bastion.example.com", 2222, username, password, forwards, tt.proxyJump) if err != nil { t.Fatalf("BuildUserArchive: %v", err) } if res.ArchivePath == "" { t.Fatalf("expected non-empty archive path") } if _, err := os.Stat(res.ArchivePath); err != nil { t.Fatalf("archive not found on disk: %v", err) } // Scratch dir must be wiped (private key must not linger). if entries, err := os.ReadDir(filepath.Join(scratch, username)); err == nil { t.Fatalf("expected scratch dir to be wiped, still has entries: %v", entries) } extractedDir := extract7z(t, res.ArchivePath, password) cfgBytes, err := os.ReadFile(filepath.Join(extractedDir, "config")) if err != nil { t.Fatalf("read extracted config: %v", err) } cfg := string(cfgBytes) if tt.proxyJump { if strings.Contains(cfg, "LocalForward") { t.Errorf("proxyJump=true archive must not contain LocalForward, got config:\n%s", cfg) } if !strings.Contains(cfg, "ProxyJump jump") { t.Errorf("proxyJump=true archive must contain ProxyJump jump, got config:\n%s", cfg) } } else { if strings.Contains(cfg, "ProxyJump") { t.Errorf("proxyJump=false archive must not contain ProxyJump, got config:\n%s", cfg) } if !strings.Contains(cfg, "LocalForward") { t.Errorf("proxyJump=false archive must contain LocalForward, got config:\n%s", cfg) } } }) } }