feat(config): pull out resolution into package and add tests

also adds a string method for pretty config printing
This commit is contained in:
Enot (ded) Skelly committed 2026-06-15 14:35:14 -07:00
1 parent bb989afeec
commit ecde7957f7
3 files changed
+126 -39

No files matched your search

+8 -39
View File
@@ -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,
+55
View File
@@ -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,
)
}
+63
View File
@@ -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")
}
}