ananke/internal/service/daemon_test.go

134 lines
4.4 KiB
Go
Raw Normal View History

package service
import (
"context"
"io"
"log"
"path/filepath"
"strings"
"testing"
"scm.bstein.dev/bstein/ananke/internal/config"
"scm.bstein.dev/bstein/ananke/internal/metrics"
"scm.bstein.dev/bstein/ananke/internal/state"
)
// TestDaemonRunRejectsDisabledUPS runs one orchestration or CLI step.
// Signature: TestDaemonRunRejectsDisabledUPS(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestDaemonRunRejectsDisabledUPS(t *testing.T) {
d := &Daemon{
cfg: config.Config{
UPS: config.UPS{Enabled: false},
},
log: log.New(io.Discard, "", 0),
}
if err := d.Run(context.Background()); err == nil {
t.Fatalf("expected UPS-disabled run to fail")
}
}
// TestDaemonRunRejectsMissingTargets runs one orchestration or CLI step.
// Signature: TestDaemonRunRejectsMissingTargets(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestDaemonRunRejectsMissingTargets(t *testing.T) {
d := &Daemon{
cfg: config.Config{
UPS: config.UPS{Enabled: true},
},
log: log.New(io.Discard, "", 0),
}
if err := d.Run(context.Background()); err == nil {
t.Fatalf("expected empty-target run to fail")
}
}
// TestDaemonTargetList runs one orchestration or CLI step.
// Signature: TestDaemonTargetList(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestDaemonTargetList(t *testing.T) {
d := &Daemon{
targets: []Target{
{Name: "Pyrphoros", Target: "pyrphoros@localhost"},
{Name: "Statera", Target: "statera@localhost"},
},
}
got := d.targetList()
if !strings.Contains(got, "Pyrphoros=pyrphoros@localhost") || !strings.Contains(got, "Statera=statera@localhost") {
t.Fatalf("unexpected target list: %q", got)
}
}
// TestDaemonResolveSSHPathsPreferConfigured runs one orchestration or CLI step.
// Signature: TestDaemonResolveSSHPathsPreferConfigured(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestDaemonResolveSSHPathsPreferConfigured(t *testing.T) {
d := &Daemon{
cfg: config.Config{
SSHConfigFile: "/tmp/custom-ssh-config",
SSHIdentityFile: "/tmp/custom-ssh-key",
},
}
if got := d.resolveSSHConfigFile(); got != "/tmp/custom-ssh-config" {
t.Fatalf("unexpected config path: %q", got)
}
if got := d.resolveSSHIdentityFile(); got != "/tmp/custom-ssh-key" {
t.Fatalf("unexpected identity path: %q", got)
}
}
// TestStartMetricsServerRequiresBindAddress runs one orchestration or CLI step.
// Signature: TestStartMetricsServerRequiresBindAddress(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestStartMetricsServerRequiresBindAddress(t *testing.T) {
d := &Daemon{
cfg: config.Config{
Metrics: config.Metrics{
Enabled: true,
BindAddr: "",
Path: "/metrics",
},
},
log: log.New(io.Discard, "", 0),
exporter: nil,
}
d.exporter = d.ensureExporterForTest()
if err := d.startMetricsServer(); err == nil {
t.Fatalf("expected missing bind address error")
}
}
// TestTriggerShutdownSkipsDuplicateWhenIntentActive runs one orchestration or CLI step.
// Signature: TestTriggerShutdownSkipsDuplicateWhenIntentActive(t *testing.T).
// Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve.
func TestTriggerShutdownSkipsDuplicateWhenIntentActive(t *testing.T) {
tmp := t.TempDir()
intentPath := filepath.Join(tmp, "intent.json")
if err := state.MustWriteIntent(intentPath, state.IntentShuttingDown, "already-running", "test"); err != nil {
t.Fatalf("seed intent: %v", err)
}
d := &Daemon{
cfg: config.Config{
State: config.State{
IntentPath: intentPath,
},
},
log: log.New(io.Discard, "", 0),
exporter: nil,
}
d.exporter = d.ensureExporterForTest()
if err := d.triggerShutdown(context.Background(), "duplicate-check"); err != nil {
t.Fatalf("expected duplicate shutdown trigger to be ignored: %v", err)
}
}
// ensureExporterForTest runs one orchestration or CLI step.
// Signature: (d *Daemon) ensureExporterForTest() *metrics.Exporter.
// Why: local helper keeps setup concise while preserving explicit behavior in each test.
func (d *Daemon) ensureExporterForTest() *metrics.Exporter {
if d.exporter == nil {
d.exporter = metrics.New()
}
return d.exporter
}