package config import ( "strings" "testing" "time" ) func TestLoadFromEnvUsesDefaults(t *testing.T) { for _, key := range []string{ "GO_OMNI_HTTP_ADDR", "GO_OMNI_REQUEST_ID_HEADER", "GO_OMNI_ENABLE_LOGGER", "GO_OMNI_TRUSTED_PROXIES", "GO_OMNI_TRANSPORT", "GO_OMNI_TLS_CERT_FILE", "GO_OMNI_TLS_KEY_FILE", "GO_OMNI_LOG_LEVEL", "GO_OMNI_LOG_FORMAT", "GO_OMNI_LOG_OUTPUT", "GO_OMNI_LOG_FILE", "GO_OMNI_SERVICE", "GO_OMNI_ENVIRONMENT", "GO_OMNI_LOG_ADD_SOURCE", "GO_OMNI_SHUTDOWN_TIMEOUT", "GO_OMNI_SLOW_REQUEST_THRESHOLD", } { t.Setenv(key, "") } cfg, err := LoadFromEnv("") if err != nil { t.Fatal(err) } if cfg.Address != ":8080" || cfg.Transport != TransportNetHTTP || cfg.TLS != (TLSConfig{}) || cfg.RequestIDHeader != "X-Request-ID" || cfg.ShutdownTimeout != 10*time.Second { t.Fatalf("unexpected defaults: %+v", cfg) } if cfg.EnableLogger || cfg.Log.Level != "info" || cfg.Log.Format != "json" || cfg.Log.Output != "stderr" { t.Fatalf("unexpected logger defaults: %+v", cfg.Log) } } func TestTransportEnvironment(t *testing.T) { for _, test := range []struct { name, transport, cert, key, errorField string }{ {"default", "", "", "", ""}, {"native", "native", "", "", ""}, {"nativeTLS", "native", "server.crt", "server.key", ""}, {"http2", "http2", "server.crt", "server.key", ""}, {"unknown", "typo", "", "", "transport"}, {"certOnly", "nethttp", "server.crt", "", "tls"}, {"keyOnly", "native", "", "server.key", "tls"}, {"http2WithoutTLS", "http2", "", "", "tls"}, } { t.Run(test.name, func(t *testing.T) { t.Setenv("TRANSPORT_TEST_TRANSPORT", test.transport) t.Setenv("TRANSPORT_TEST_TLS_CERT_FILE", test.cert) t.Setenv("TRANSPORT_TEST_TLS_KEY_FILE", test.key) cfg, err := LoadFromEnv("TRANSPORT_TEST_") if test.errorField != "" { if err == nil || !strings.Contains(err.Error(), test.errorField) { t.Fatalf("unexpected error: %v", err) } return } if err != nil { t.Fatal(err) } want := test.transport if want == "" { want = TransportNetHTTP } if cfg.Transport != want || cfg.TLS.CertFile != test.cert || cfg.TLS.KeyFile != test.key { t.Fatalf("settings: %+v", cfg) } }) } } func TestLoadFromEnvOverridesSettings(t *testing.T) { t.Setenv("GO_OMNI_HTTP_ADDR", "127.0.0.1:9090") t.Setenv("GO_OMNI_REQUEST_ID_HEADER", "X-Correlation-ID") t.Setenv("GO_OMNI_TRUSTED_PROXIES", "127.0.0.1, 10.0.0.0/8") t.Setenv("GO_OMNI_ENABLE_LOGGER", "true") t.Setenv("GO_OMNI_LOG_LEVEL", "debug") t.Setenv("GO_OMNI_LOG_FORMAT", "text") t.Setenv("GO_OMNI_LOG_OUTPUT", "stdout") t.Setenv("GO_OMNI_SERVICE", "billing") t.Setenv("GO_OMNI_ENVIRONMENT", "test") t.Setenv("GO_OMNI_LOG_ADD_SOURCE", "true") t.Setenv("GO_OMNI_SHUTDOWN_TIMEOUT", "3s") t.Setenv("GO_OMNI_SLOW_REQUEST_THRESHOLD", "500ms") cfg, err := LoadFromEnv("") if err != nil { t.Fatal(err) } if cfg.Address != "127.0.0.1:9090" || cfg.RequestIDHeader != "X-Correlation-ID" || !cfg.EnableLogger || len(cfg.TrustedProxies) != 2 || cfg.TrustedProxies[1] != "10.0.0.0/8" { t.Fatalf("unexpected application settings: %+v", cfg) } if cfg.ShutdownTimeout != 3*time.Second || cfg.Log.Level != "debug" || cfg.Log.Format != "text" || cfg.Log.Output != "stdout" || cfg.Log.Service != "billing" || cfg.Log.Environment != "test" || !cfg.Log.AddSource { t.Fatalf("unexpected logger settings: %+v", cfg.Log) } if cfg.SlowRequestThreshold != 500*time.Millisecond { t.Fatal(cfg.SlowRequestThreshold) } } func TestLoadFromEnvRejectsInvalidValues(t *testing.T) { t.Setenv("GO_OMNI_ENABLE_LOGGER", "sometimes") if _, err := LoadFromEnv(""); err == nil || !strings.Contains(err.Error(), "ENABLE_LOGGER") { t.Fatalf("expected invalid logger flag error, got %v", err) } t.Setenv("GO_OMNI_ENABLE_LOGGER", "") t.Setenv("GO_OMNI_SHUTDOWN_TIMEOUT", "later") if _, err := LoadFromEnv(""); err == nil || !strings.Contains(err.Error(), "SHUTDOWN_TIMEOUT") { t.Fatalf("expected invalid timeout error, got %v", err) } t.Setenv("GO_OMNI_SHUTDOWN_TIMEOUT", "1s") t.Setenv("GO_OMNI_TRUSTED_PROXIES", "proxy.example.com") if _, err := LoadFromEnv(""); err == nil || !strings.Contains(err.Error(), "trustedProxies") { t.Fatalf("expected invalid trusted proxy error, got %v", err) } t.Setenv("GO_OMNI_TRUSTED_PROXIES", "") cfg := Default() cfg.Address = "" if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "address") { t.Fatalf("expected invalid address error, got %v", err) } cfg = Default() cfg.RequestIDHeader = " " if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "requestIdHeader") { t.Fatalf("expected invalid request header error, got %v", err) } } func TestWithConfigAndWithLogConfig(t *testing.T) { settings := Default() settings.Log.Service = "orders" settings.TrustedProxies = []string{"127.0.0.1"} cfg := Default() WithConfig(settings)(&cfg) if cfg.Log.Service != "orders" || len(cfg.TrustedProxies) != 1 { t.Fatalf("complete config was not applied: %+v", cfg) } settings.TrustedProxies[0] = "10.0.0.0/8" if cfg.TrustedProxies[0] != "127.0.0.1" { t.Fatal("WithConfig should copy trusted proxy settings") } WithLogConfig(Default().Log)(&cfg) if cfg.Log.Service != "" { t.Fatalf("log config was not replaced: %+v", cfg.Log) } } func TestApplyEnvPreservesProjectDefaults(t *testing.T) { cfg := Default() cfg.Address = "127.0.0.1:8123" cfg.Log.RedactKeys = []string{"bankAccount"} cfg.TrustedProxies = []string{"127.0.0.1"} t.Setenv("OVERLAY_TEST_LOG_LEVEL", "debug") got, err := ApplyEnv(cfg, "OVERLAY_TEST_") if err != nil { t.Fatal(err) } if got.Address != cfg.Address || got.Log.Level != "debug" { t.Fatal(got) } got.Log.RedactKeys[0] = "changed" got.TrustedProxies[0] = "192.0.2.1" if cfg.Log.RedactKeys[0] != "bankAccount" || cfg.TrustedProxies[0] != "127.0.0.1" { t.Fatal("caller-owned defaults mutated") } }