package service import ( "context" "io" "log" "os" "path/filepath" "strings" "testing" "time" "scm.bstein.dev/bstein/ananke/internal/config" "scm.bstein.dev/bstein/ananke/internal/metrics" "scm.bstein.dev/bstein/ananke/internal/ups" ) // TestDaemonRunMetricsEnabledBranchError runs one orchestration or CLI step. // Signature: TestDaemonRunMetricsEnabledBranchError(t *testing.T). // Why: covers Run path where metrics are enabled and startup fails before the ticker loop. func TestDaemonRunMetricsEnabledBranchError(t *testing.T) { stateDir := t.TempDir() orch := newDaemonTestOrchestrator(t, stateDir) d := &Daemon{ cfg: config.Config{ UPS: config.UPS{ Enabled: true, PollSeconds: 1, RuntimeSafetyFactor: 1.0, DebounceCount: 1, }, Metrics: config.Metrics{ Enabled: true, BindAddr: "", Path: "/metrics", }, State: config.State{ IntentPath: filepath.Join(stateDir, "intent.json"), }, }, orch: orch, targets: []Target{ { Name: "Pyrphoros", Target: "pyrphoros@localhost", Provider: &daemonFakeProvider{ samples: []ups.Sample{{OnBattery: false, RuntimeSeconds: 10000, RawStatus: "OL"}}, }, }, }, log: log.New(io.Discard, "", 0), exporter: metrics.New(), } err := d.Run(context.Background()) if err == nil || !strings.Contains(err.Error(), "metrics.bind_addr must not be empty") { t.Fatalf("expected metrics bind error from Run metrics-enabled path, got %v", err) } } // TestDaemonRunProviderErrorContinueBranch runs one orchestration or CLI step. // Signature: TestDaemonRunProviderErrorContinueBranch(t *testing.T). // Why: covers provider-read error continue branch where telemetry timeout does not trigger. func TestDaemonRunProviderErrorContinueBranch(t *testing.T) { stateDir := t.TempDir() orch := newDaemonTestOrchestrator(t, stateDir) d := &Daemon{ cfg: config.Config{ UPS: config.UPS{ Enabled: true, PollSeconds: 1, RuntimeSafetyFactor: 1.0, DebounceCount: 3, TelemetryTimeoutSeconds: 120, }, State: config.State{ IntentPath: filepath.Join(stateDir, "intent.json"), }, }, orch: orch, targets: []Target{ { Name: "Statera", Target: "statera@localhost", Provider: &daemonFakeProvider{ errs: []error{context.DeadlineExceeded, context.DeadlineExceeded, context.DeadlineExceeded}, }, }, }, log: log.New(io.Discard, "", 0), exporter: metrics.New(), } ctx, cancel := context.WithTimeout(context.Background(), 1200*time.Millisecond) defer cancel() if err := d.Run(ctx); err == nil { t.Fatalf("expected context cancellation while exercising provider-error continue path") } } // TestTriggerShutdownForwardedIntentWriteFailureWarningBranch runs one orchestration or CLI step. // Signature: TestTriggerShutdownForwardedIntentWriteFailureWarningBranch(t *testing.T). // Why: covers triggerShutdown branch where forwarded completion intent write fails but trigger still succeeds. func TestTriggerShutdownForwardedIntentWriteFailureWarningBranch(t *testing.T) { tmp := t.TempDir() sshPath := filepath.Join(tmp, "ssh") if err := os.WriteFile(sshPath, []byte("#!/usr/bin/env bash\nset -euo pipefail\necho forwarded\n"), 0o755); err != nil { t.Fatalf("write fake ssh: %v", err) } t.Setenv("PATH", tmp+":"+os.Getenv("PATH")) intentPath := filepath.Join(tmp, "intent-dir") if err := os.MkdirAll(intentPath, 0o755); err != nil { t.Fatalf("mkdir intent dir: %v", err) } d := &Daemon{ cfg: config.Config{ SSHUser: "atlas", SSHPort: 2277, State: config.State{ IntentPath: intentPath, }, Coordination: config.Coordination{ ForwardShutdownHost: "titan-db", ForwardShutdownConfig: "/etc/ananke/ananke.yaml", CommandTimeoutSeconds: 3, }, }, log: log.New(io.Discard, "", 0), exporter: metrics.New(), } if err := d.triggerShutdown(context.Background(), "forwarded-intent-write-fail"); err != nil { t.Fatalf("expected forwarded shutdown to succeed even when completion intent write fails, got %v", err) } } // TestTriggerShutdownLocalIntentWriteFailureWarningBranch runs one orchestration or CLI step. // Signature: TestTriggerShutdownLocalIntentWriteFailureWarningBranch(t *testing.T). // Why: covers triggerShutdown local completion intent warning branch when write fails after successful shutdown. func TestTriggerShutdownLocalIntentWriteFailureWarningBranch(t *testing.T) { stateDir := t.TempDir() orch := newDaemonTestOrchestrator(t, stateDir) intentPath := filepath.Join(stateDir, "intent-dir") if err := os.MkdirAll(intentPath, 0o755); err != nil { t.Fatalf("mkdir intent dir: %v", err) } d := &Daemon{ cfg: config.Config{ State: config.State{ IntentPath: intentPath, }, Shutdown: config.Shutdown{ EmergencySkipDrain: true, EmergencySkipEtcd: true, }, }, orch: orch, log: log.New(io.Discard, "", 0), exporter: metrics.New(), } if err := d.triggerShutdown(context.Background(), "local-intent-write-fail"); err != nil { t.Fatalf("expected local shutdown success even when completion intent write fails, got %v", err) } } // TestForwardShutdownDefaultTimeoutBranch runs one orchestration or CLI step. // Signature: TestForwardShutdownDefaultTimeoutBranch(t *testing.T). // Why: covers forwardShutdown default command-timeout branch when timeout config is unset. func TestForwardShutdownDefaultTimeoutBranch(t *testing.T) { tmp := t.TempDir() sshPath := filepath.Join(tmp, "ssh") if err := os.WriteFile(sshPath, []byte("#!/usr/bin/env bash\nset -euo pipefail\necho ok\n"), 0o755); err != nil { t.Fatalf("write fake ssh: %v", err) } t.Setenv("PATH", tmp+":"+os.Getenv("PATH")) d := &Daemon{ cfg: config.Config{ SSHUser: "atlas", Coordination: config.Coordination{ ForwardShutdownHost: "titan-db", ForwardShutdownConfig: "/etc/ananke/ananke.yaml", CommandTimeoutSeconds: 0, }, }, log: log.New(io.Discard, "", 0), } if err := d.forwardShutdown(context.Background(), "default-timeout-branch"); err != nil { t.Fatalf("expected forward shutdown with default timeout to succeed, got %v", err) } } // TestForwardShutdownKnownHostsRepairIncludesJumpHost runs one orchestration or CLI step. // Signature: TestForwardShutdownKnownHostsRepairIncludesJumpHost(t *testing.T). // Why: covers known-host repair host-list branch that appends the configured SSH jump host. func TestForwardShutdownKnownHostsRepairIncludesJumpHost(t *testing.T) { tmp := t.TempDir() attemptMarker := filepath.Join(tmp, "attempt") sshPath := filepath.Join(tmp, "ssh") script := `#!/usr/bin/env bash set -euo pipefail marker="` + attemptMarker + `" if [[ ! -f "$marker" ]]; then echo "REMOTE HOST IDENTIFICATION HAS CHANGED!" >&2 touch "$marker" exit 255 fi echo "forwarded" ` if err := os.WriteFile(sshPath, []byte(script), 0o755); err != nil { t.Fatalf("write fake ssh: %v", err) } sshKeygenPath := filepath.Join(tmp, "ssh-keygen") if err := os.WriteFile(sshKeygenPath, []byte("#!/usr/bin/env bash\nset -euo pipefail\nexit 0\n"), 0o755); err != nil { t.Fatalf("write fake ssh-keygen: %v", err) } sshKeyscanPath := filepath.Join(tmp, "ssh-keyscan") if err := os.WriteFile(sshKeyscanPath, []byte("#!/usr/bin/env bash\nset -euo pipefail\necho fake-key\n"), 0o755); err != nil { t.Fatalf("write fake ssh-keyscan: %v", err) } t.Setenv("PATH", tmp+":"+os.Getenv("PATH")) d := &Daemon{ cfg: config.Config{ SSHUser: "atlas", SSHPort: 2277, SSHConfigFile: filepath.Join(tmp, "ssh-config"), SSHIdentityFile: filepath.Join(tmp, "id_ed25519"), SSHJumpHost: "titan-jh", Coordination: config.Coordination{ ForwardShutdownHost: "titan-db", ForwardShutdownConfig: "/etc/ananke/ananke.yaml", CommandTimeoutSeconds: 3, }, }, log: log.New(io.Discard, "", 0), } if err := d.forwardShutdown(context.Background(), "repair-includes-jump-host"); err != nil { t.Fatalf("expected forward shutdown to recover after known-host repair with jump host, got %v", err) } }