From ecde7957f7376913a12e4b0ab64aaa9428ab6755 Mon Sep 17 00:00:00 2001 From: "Enot (ded) Skelly" Date: Mon, 15 Jun 2026 14:35:14 -0700 Subject: [PATCH] feat(config): pull out resolution into package and add tests also adds a string method for pretty config printing --- cmd/beacon/main.go | 47 +++++-------------------- internal/config/config.go | 55 +++++++++++++++++++++++++++++ internal/config/config_test.go | 63 ++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 39 deletions(-) diff --git a/cmd/beacon/main.go b/cmd/beacon/main.go index 28affe2..f2b302c 100644 --- a/cmd/beacon/main.go +++ b/cmd/beacon/main.go @@ -88,40 +88,9 @@ func main() { log.Fatalf("failed to load config: %v", err) } - telemetryResolution := cfg.Telemetry.Resolution.Duration - if telemetryResolution == 0 { - telemetryResolution = time.Hour - } - telemetryRetention := cfg.Telemetry.Retention.Duration - if telemetryRetention == 0 { - telemetryRetention = 28 * 24 * time.Hour // 4 weeks - } - packetRetention := cfg.Packets.Retention.Duration - if packetRetention == 0 { - packetRetention = 30 * 24 * time.Hour // 30 days - } + resolved := config.Resolve(cfg) - maxConnsPerIP := cfg.WebSocket.MaxConnectionsPerIP - if maxConnsPerIP == 0 { - maxConnsPerIP = 5 - } - - // resolve background intervals with defaults - viewRefreshInterval := cfg.Background.ViewRefresh.Duration - if viewRefreshInterval == 0 { - viewRefreshInterval = time.Hour - } - reconfirmInterval := cfg.Background.Reconfirm.Duration - if reconfirmInterval == 0 { - reconfirmInterval = time.Hour - } - cleanupInterval := cfg.Background.Cleanup.Duration - if cleanupInterval == 0 { - cleanupInterval = time.Hour - } - - log.Printf("config: loaded — telemetryResolution=%s telemetryRetention=%s packetRetention=%s maxConnsPerIP=%d viewRefresh=%s reconfirm=%s cleanup=%s", - telemetryResolution, telemetryRetention, packetRetention, maxConnsPerIP, viewRefreshInterval, reconfirmInterval, cleanupInterval) + log.Printf("config: loaded — %s", resolved) // ── Hub ────────────────────────────────────────────────────────────────── h := hub.New() @@ -238,7 +207,7 @@ func main() { URL: getEnv("MQTT_BROKER_1_URL"), Username: getEnv("MQTT_BROKER_1_USERNAME"), Password: getEnv("MQTT_BROKER_1_PASSWORD"), - TelemetryResolution: telemetryResolution, + TelemetryResolution: resolved.TelemetryResolution, AllowedIATAs: allowedIATAs, }, store, @@ -253,7 +222,7 @@ func main() { URL: getEnv("MQTT_BROKER_2_URL"), Username: getEnv("MQTT_BROKER_2_USERNAME"), Password: getEnv("MQTT_BROKER_2_PASSWORD"), - TelemetryResolution: telemetryResolution, + TelemetryResolution: resolved.TelemetryResolution, AllowedIATAs: allowedIATAs, }, store, @@ -271,14 +240,14 @@ func main() { go broker2.Start(ctx) scheduler := background.New([]background.Task{ - background.ViewRefreshTask(store, viewRefreshInterval), - background.CleanupTask(store, telemetryRetention, packetRetention, cleanupInterval), - background.ReconfirmTask(store, reconfirmInterval), + background.ViewRefreshTask(store, resolved.ViewRefreshInterval), + background.CleanupTask(store, resolved.TelemetryRetention, resolved.PacketRetention, resolved.CleanupInterval), + background.ReconfirmTask(store, resolved.ReconfirmInterval), }) go scheduler.Start(ctx) // ── HTTP server ────────────────────────────────────────────────────────── - r := router.New(h, reader, []*ingest.Worker{broker1, broker2}, maxConnsPerIP, cfg.CORS) + r := router.New(h, reader, []*ingest.Worker{broker1, broker2}, resolved.MaxConnsPerIP, cfg.CORS) srv := &http.Server{ Addr: addr, diff --git a/internal/config/config.go b/internal/config/config.go index 50002f1..043c425 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,7 @@ package config import ( + "fmt" "os" "time" @@ -27,6 +28,17 @@ type Config struct { Background BackgroundConfig `yaml:"background"` } +// ResolvedConfig holds all runtime configuration with defaults applied. +type ResolvedConfig struct { + TelemetryResolution time.Duration + TelemetryRetention time.Duration + PacketRetention time.Duration + MaxConnsPerIP int + ViewRefreshInterval time.Duration + ReconfirmInterval time.Duration + CleanupInterval time.Duration +} + // BackgroundConfig controls the intervals for background maintenance tasks. type BackgroundConfig struct { // ViewRefresh is how often materialized views are refreshed. @@ -227,3 +239,46 @@ func Load(path string) (*Config, error) { } return cfg, nil } + +// Resolve returns a ResolvedConfig with defaults applied for any zero values. +func Resolve(cfg *Config) ResolvedConfig { + r := ResolvedConfig{ + TelemetryResolution: cfg.Telemetry.Resolution.Duration, + TelemetryRetention: cfg.Telemetry.Retention.Duration, + PacketRetention: cfg.Packets.Retention.Duration, + MaxConnsPerIP: cfg.WebSocket.MaxConnectionsPerIP, + ViewRefreshInterval: cfg.Background.ViewRefresh.Duration, + ReconfirmInterval: cfg.Background.Reconfirm.Duration, + CleanupInterval: cfg.Background.Cleanup.Duration, + } + if r.TelemetryResolution == 0 { + r.TelemetryResolution = time.Hour + } + if r.TelemetryRetention == 0 { + r.TelemetryRetention = 28 * 24 * time.Hour + } + if r.PacketRetention == 0 { + r.PacketRetention = 30 * 24 * time.Hour + } + if r.MaxConnsPerIP == 0 { + r.MaxConnsPerIP = 5 + } + if r.ViewRefreshInterval == 0 { + r.ViewRefreshInterval = time.Hour + } + if r.ReconfirmInterval == 0 { + r.ReconfirmInterval = time.Hour + } + if r.CleanupInterval == 0 { + r.CleanupInterval = time.Hour + } + return r +} + +func (r ResolvedConfig) String() string { + return fmt.Sprintf( + "telemetryResolution=%s telemetryRetention=%s packetRetention=%s maxConnsPerIP=%d viewRefresh=%s reconfirm=%s cleanup=%s", + r.TelemetryResolution, r.TelemetryRetention, r.PacketRetention, + r.MaxConnsPerIP, r.ViewRefreshInterval, r.ReconfirmInterval, r.CleanupInterval, + ) +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ffeb3f6..85050d1 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -5,7 +5,9 @@ package config import ( "os" + "strings" "testing" + "time" ) func TestLoad_FileNotFound(t *testing.T) { @@ -69,3 +71,64 @@ func TestLoad_InvalidYAML(t *testing.T) { t.Fatal("expected error for invalid YAML, got nil") } } + +func TestResolve_Defaults(t *testing.T) { + r := Resolve(&Config{}) + if r.TelemetryResolution != time.Hour { + t.Errorf("expected TelemetryResolution 1h, got %v", r.TelemetryResolution) + } + if r.TelemetryRetention != 28*24*time.Hour { + t.Errorf("expected TelemetryRetention 672h, got %v", r.TelemetryRetention) + } + if r.PacketRetention != 30*24*time.Hour { + t.Errorf("expected PacketRetention 720h, got %v", r.PacketRetention) + } + if r.MaxConnsPerIP != 5 { + t.Errorf("expected MaxConnsPerIP 5, got %d", r.MaxConnsPerIP) + } + if r.ViewRefreshInterval != time.Hour { + t.Errorf("expected ViewRefreshInterval 1h, got %v", r.ViewRefreshInterval) + } + if r.ReconfirmInterval != time.Hour { + t.Errorf("expected ReconfirmInterval 1h, got %v", r.ReconfirmInterval) + } + if r.CleanupInterval != time.Hour { + t.Errorf("expected CleanupInterval 1h, got %v", r.CleanupInterval) + } +} + +func TestResolve_ExplicitValues(t *testing.T) { + cfg := &Config{} + cfg.Telemetry.Resolution.Duration = 30 * time.Minute + cfg.Telemetry.Retention.Duration = 14 * 24 * time.Hour + cfg.Packets.Retention.Duration = 7 * 24 * time.Hour + cfg.WebSocket.MaxConnectionsPerIP = 10 + cfg.Background.ViewRefresh.Duration = 2 * time.Hour + cfg.Background.Reconfirm.Duration = 3 * time.Hour + cfg.Background.Cleanup.Duration = 4 * time.Hour + + r := Resolve(cfg) + if r.TelemetryResolution != 30*time.Minute { + t.Errorf("expected 30m, got %v", r.TelemetryResolution) + } + if r.MaxConnsPerIP != 10 { + t.Errorf("expected 10, got %d", r.MaxConnsPerIP) + } + if r.ViewRefreshInterval != 2*time.Hour { + t.Errorf("expected 2h, got %v", r.ViewRefreshInterval) + } +} + +func TestResolvedConfig_String(t *testing.T) { + r := Resolve(&Config{}) + s := r.String() + if s == "" { + t.Error("expected non-empty string") + } + if !strings.Contains(s, "telemetryResolution=") { + t.Error("expected telemetryResolution in string") + } + if !strings.Contains(s, "maxConnsPerIP=") { + t.Error("expected maxConnsPerIP in string") + } +}