package config import ( "testing" "time" pkgtel "scrabble/pkg/telemetry" ) // TestLoadTelemetryDefaults verifies the gateway telemetry defaults: the // "scrabble-gateway" service name and both exporters off. func TestLoadTelemetryDefaults(t *testing.T) { c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if c.Telemetry.ServiceName != defaultServiceName { t.Errorf("Telemetry.ServiceName = %q, want %q", c.Telemetry.ServiceName, defaultServiceName) } if c.Telemetry.TracesExporter != pkgtel.ExporterNone || c.Telemetry.MetricsExporter != pkgtel.ExporterNone { t.Errorf("exporters = %q/%q, want none/none", c.Telemetry.TracesExporter, c.Telemetry.MetricsExporter) } } // TestLoadRejectsUnsupportedExporter verifies an exporter outside the supported // set fails validation. func TestLoadRejectsUnsupportedExporter(t *testing.T) { t.Setenv("GATEWAY_OTEL_METRICS_EXPORTER", "prometheus") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for an unsupported exporter, got nil") } } // TestLoadMaxBodyBytes verifies the body-cap default and that a non-positive // override fails validation. func TestLoadMaxBodyBytes(t *testing.T) { c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if c.MaxBodyBytes != DefaultMaxBodyBytes { t.Errorf("MaxBodyBytes = %d, want %d", c.MaxBodyBytes, DefaultMaxBodyBytes) } t.Setenv("GATEWAY_MAX_BODY_BYTES", "0") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for a non-positive body cap, got nil") } } // TestLoadMinClientVersion verifies the client-version gate config: dormant (empty) by // default, a parseable version accepted, and an unparseable one rejected. func TestLoadMinClientVersion(t *testing.T) { c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if c.MinClientVersion != "" { t.Errorf("MinClientVersion = %q, want empty (gate dormant)", c.MinClientVersion) } t.Setenv("GATEWAY_MIN_CLIENT_VERSION", "v1.16.0") if c, err = Load(); err != nil { t.Fatalf("Load with a valid min version: %v", err) } if c.MinClientVersion != "v1.16.0" { t.Errorf("MinClientVersion = %q, want %q", c.MinClientVersion, "v1.16.0") } t.Setenv("GATEWAY_MIN_CLIENT_VERSION", "dev") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for an unparseable GATEWAY_MIN_CLIENT_VERSION, got nil") } } // TestLoadRecommendedClientVersion verifies the soft-tier config: dormant (empty) by default, a // parseable version accepted (standing alone with no minimum, or at/above one), an unparseable one // rejected, and a recommended below the minimum rejected. func TestLoadRecommendedClientVersion(t *testing.T) { c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if c.RecommendedClientVersion != "" { t.Errorf("RecommendedClientVersion = %q, want empty (soft tier dormant)", c.RecommendedClientVersion) } // Stands alone with no minimum configured. t.Setenv("GATEWAY_RECOMMENDED_CLIENT_VERSION", "v1.20.0") if c, err = Load(); err != nil { t.Fatalf("Load with a valid recommended version (no min): %v", err) } if c.RecommendedClientVersion != "v1.20.0" { t.Errorf("RecommendedClientVersion = %q, want %q", c.RecommendedClientVersion, "v1.20.0") } // At or above the minimum is accepted. t.Setenv("GATEWAY_MIN_CLIENT_VERSION", "v1.16.0") if _, err = Load(); err != nil { t.Fatalf("Load with recommended >= min: %v", err) } // Below the minimum is rejected. t.Setenv("GATEWAY_RECOMMENDED_CLIENT_VERSION", "v1.10.0") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for a recommended version below the minimum, got nil") } // Unparseable is rejected. t.Setenv("GATEWAY_RECOMMENDED_CLIENT_VERSION", "dev") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for an unparseable GATEWAY_RECOMMENDED_CLIENT_VERSION, got nil") } } // TestLoadAbuseDefaults verifies the anti-abuse ban defaults: disabled (prod-only), // the agreed thresholds, and no honeytoken. func TestLoadAbuseDefaults(t *testing.T) { c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } want := DefaultAbuse() if c.Abuse != want { t.Errorf("Abuse = %+v, want %+v", c.Abuse, want) } if c.Abuse.BanEnabled { t.Error("ban must default to disabled (enabled only in prod)") } } // TestLoadAbuseOverrides verifies the anti-abuse environment variables are parsed. func TestLoadAbuseOverrides(t *testing.T) { t.Setenv("GATEWAY_ABUSE_BAN_ENABLED", "true") t.Setenv("GATEWAY_ABUSE_BAN_THRESHOLD", "50") t.Setenv("GATEWAY_ABUSE_BAN_WINDOW", "90s") t.Setenv("GATEWAY_ABUSE_BAN_DURATION", "30m") t.Setenv("GATEWAY_HONEYTOKEN", "deadbeef") c, err := Load() if err != nil { t.Fatalf("Load: %v", err) } want := AbuseConfig{ BanEnabled: true, BanThreshold: 50, BanWindow: 90 * time.Second, BanDuration: 30 * time.Minute, Honeytoken: "deadbeef", } if c.Abuse != want { t.Errorf("Abuse = %+v, want %+v", c.Abuse, want) } } // TestLoadAbuseRejectsBadThreshold verifies an enabled ban with a non-positive // threshold fails validation. func TestLoadAbuseRejectsBadThreshold(t *testing.T) { t.Setenv("GATEWAY_ABUSE_BAN_ENABLED", "true") t.Setenv("GATEWAY_ABUSE_BAN_THRESHOLD", "0") if _, err := Load(); err == nil { t.Fatal("Load: expected an error for an enabled ban with a zero threshold, got nil") } }