ananke/cmd/ananke/bootstrap_handoff_test.go

115 lines
4.1 KiB
Go

package main
import (
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"time"
"scm.bstein.dev/bstein/ananke/internal/config"
)
// TestBuildSSHBaseArgsIncludesConfiguredPaths runs one orchestration or CLI step.
// Signature: TestBuildSSHBaseArgsIncludesConfiguredPaths(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestBuildSSHBaseArgsIncludesConfiguredPaths(t *testing.T) {
cfg := config.Config{
SSHConfigFile: "/tmp/ssh-config",
SSHIdentityFile: "/tmp/id_ed25519",
SSHPort: 2277,
SSHJumpHost: "titan-jh",
SSHJumpUser: "atlas",
}
args := buildSSHBaseArgs(cfg)
joined := strings.Join(args, " ")
expectedPieces := []string{
"-F /tmp/ssh-config",
"-i /tmp/id_ed25519",
"-p 2277",
"-J atlas@titan-jh:2277",
}
for _, piece := range expectedPieces {
if !strings.Contains(joined, piece) {
t.Fatalf("expected ssh args to contain %q, got %q", piece, joined)
}
}
}
// TestResolveSSHConfigAndIdentityPreferExplicit runs one orchestration or CLI step.
// Signature: TestResolveSSHConfigAndIdentityPreferExplicit(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestResolveSSHConfigAndIdentityPreferExplicit(t *testing.T) {
cfg := config.Config{
SSHConfigFile: "/etc/ssh/custom-config",
SSHIdentityFile: "/etc/ssh/custom-key",
}
if got := resolveSSHConfigFile(cfg); got != cfg.SSHConfigFile {
t.Fatalf("expected explicit SSH config file, got %q", got)
}
if got := resolveSSHIdentityFile(cfg); got != cfg.SSHIdentityFile {
t.Fatalf("expected explicit SSH identity file, got %q", got)
}
}
// TestMaxIntAndStartupShutdownCooldown runs one orchestration or CLI step.
// Signature: TestMaxIntAndStartupShutdownCooldown(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestMaxIntAndStartupShutdownCooldown(t *testing.T) {
if got := maxInt(2, 5); got != 5 {
t.Fatalf("expected maxInt=5, got %d", got)
}
if got := maxInt(9, 3); got != 9 {
t.Fatalf("expected maxInt=9, got %d", got)
}
cfg := config.Config{Startup: config.Startup{ShutdownCooldownSeconds: 120}}
if got := startupShutdownCooldown(cfg); got != 120*time.Second {
t.Fatalf("expected cooldown 120s, got %s", got)
}
}
// TestBuildSSHBaseArgsNoJumpHasStableOrdering runs one orchestration or CLI step.
// Signature: TestBuildSSHBaseArgsNoJumpHasStableOrdering(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestBuildSSHBaseArgsNoJumpHasStableOrdering(t *testing.T) {
origConfigCandidates := append([]string(nil), sshConfigCandidates...)
origIdentityCandidates := append([]string(nil), sshIdentityCandidates...)
sshConfigCandidates = []string{"/path/that/does/not/exist/ssh_config"}
sshIdentityCandidates = []string{"/path/that/does/not/exist/id_ed25519"}
t.Cleanup(func() {
sshConfigCandidates = origConfigCandidates
sshIdentityCandidates = origIdentityCandidates
})
cfg := config.Config{SSHPort: 10022}
args := buildSSHBaseArgs(cfg)
want := []string{
"-o", "BatchMode=yes",
"-o", "ConnectTimeout=8",
"-o", "StrictHostKeyChecking=accept-new",
"-p", strconv.Itoa(10022),
}
if !reflect.DeepEqual(args, want) {
t.Fatalf("unexpected args order.\n got=%v\nwant=%v", args, want)
}
}
// TestResolveSSHConfigFileFallsBackToDiscoveredPath runs one orchestration or CLI step.
// Signature: TestResolveSSHConfigFileFallsBackToDiscoveredPath(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestResolveSSHConfigFileFallsBackToDiscoveredPath(t *testing.T) {
tmp := t.TempDir()
cfgPath := filepath.Join(tmp, "ssh_config")
if err := os.WriteFile(cfgPath, []byte("Host *\n"), 0o644); err != nil {
t.Fatalf("write temp ssh config: %v", err)
}
cfg := config.Config{SSHConfigFile: cfgPath}
if got := resolveSSHConfigFile(cfg); got != cfgPath {
t.Fatalf("expected fallback config path %q, got %q", cfgPath, got)
}
}