package config import ( "os" "path/filepath" "strings" "testing" ) const validConfig = ` version: 1 security: requireProtectionOnPublicListen: true gateway: enabled: true listen: 127.0.0.1:8080 auth: mode: none distribution: enabled: true listen: 127.0.0.1:8081 auth: mode: none extraction: fulfillment: partial maxCountPerRequest: 20 minRemainingTTL: 30s maxHealthCheckAge: 15s reserveForGateway: 5 routing: - name: extract enabled: true purpose: extract upstreams: [provider-a] strategy: type: sequential switchAfterEmptyFetch: 5 onUnavailable: action: reject upstreams: provider-a: enabled: true exposure: [gateway, extract] provider: billingMode: fetch protocols: [http] api: url: https://provider.example/proxies method: GET template: '{{.}}' auth: type: none proxyAuth: type: response pool: maxSize: 100 capacity: maxConcurrencyPerProxy: 10 lifecycle: ttl: 120s allocationSafetyMargin: 10s fetch: requestInterval: 1s timeout: 3s maxAttempts: 5 maxInFlight: 1 maxTotal: 1000 check: interval: 30s jitter: 20 maxInFlight: 100 timeout: 2s maxAttempts: 2 maxConsecutiveFailures: 3 urls: [http://connect.rom.miui.com/generate_204] ` func TestLoadStrictValidConfiguration(t *testing.T) { cfg, err := Load(strings.NewReader(validConfig)) if err != nil { t.Fatalf("Load(): %v", err) } if cfg.Version != 1 || cfg.Upstreams["provider-a"].Pool.MaxSize != 100 { t.Fatalf("unexpected config: %+v", cfg) } } func TestLoadRejectsUnknownFields(t *testing.T) { _, err := Load(strings.NewReader(validConfig + "unknownField: true\n")) if err == nil || !strings.Contains(err.Error(), "unknownField") { t.Fatalf("Load() error = %v, want unknown field error", err) } } func TestValidateRejectsUnprotectedPublicListener(t *testing.T) { cfg, err := Load(strings.NewReader(strings.Replace(validConfig, "listen: 127.0.0.1:8080", "listen: 0.0.0.0:8080", 1))) if err == nil || !strings.Contains(err.Error(), "gateway") || !strings.Contains(err.Error(), "public") { t.Fatalf("Load() error = %v, want unprotected public listener error", err) } if cfg != nil { t.Fatal("invalid config must not be returned") } } func TestValidateRejectsMissingUpstreamReference(t *testing.T) { broken := strings.Replace(validConfig, "upstreams: [provider-a]", "upstreams: [missing]", 1) _, err := Load(strings.NewReader(broken)) if err == nil || !strings.Contains(err.Error(), "missing") { t.Fatalf("Load() error = %v, want missing upstream error", err) } } func TestValidateSeparatesPoolAndFetchLimits(t *testing.T) { broken := strings.Replace(validConfig, "maxTotal: 1000", "maxTotal: 50", 1) _, err := Load(strings.NewReader(broken)) if err == nil || !strings.Contains(err.Error(), "maxTotal") { t.Fatalf("Load() error = %v, want maxTotal validation error", err) } } func TestShippedConfigurationsAreValid(t *testing.T) { paths, err := filepath.Glob(filepath.Join("..", "..", "examples", "config", "*.yaml")) if err != nil { t.Fatalf("Glob(): %v", err) } paths = append(paths, filepath.Join("..", "..", "configs", "proxy-pool.yaml")) if len(paths) != 21 { t.Fatalf("configuration count = %d, want 21", len(paths)) } for _, path := range paths { path := path t.Run(filepath.Base(path), func(t *testing.T) { file, err := os.Open(path) if err != nil { t.Fatalf("Open(): %v", err) } defer file.Close() if _, err := Load(file); err != nil { t.Fatalf("Load(): %v", err) } }) } }