package admin import ( "context" "errors" "strings" "testing" ) func TestFileConfigurationLoaderUsesStrictResolvedLoader(t *testing.T) { t.Parallel() configuration := strings.Replace(fileLoaderConfiguration, "proxyAuth: {type: response}", "proxyAuth: {type: static, username: alice, passwordFile: /run/secrets/proxy}", 1) resolver := &memoryConfigurationResolver{files: map[string][]byte{ "configs/proxy-pool.yaml": []byte(configuration), "/run/secrets/proxy": []byte("resolved-secret\r\n"), }} loader, err := NewFileConfigurationLoader("configs/proxy-pool.yaml", resolver) if err != nil { t.Fatalf("NewFileConfigurationLoader() error = %v", err) } loaded, err := loader.LoadConfiguration(context.Background()) if err != nil { t.Fatalf("LoadConfiguration() error = %v", err) } if loaded.Source != "configs/proxy-pool.yaml" || loaded.Value == nil { t.Fatalf("LoadConfiguration() = %+v", loaded) } auth := loaded.Value.Upstreams["provider-a"].ProxyAuth if auth.Password != "resolved-secret" || auth.PasswordFile != "" { t.Fatalf("resolved proxy auth = %+v", auth) } } func TestFileConfigurationLoaderRejectsUnknownFields(t *testing.T) { t.Parallel() resolver := &memoryConfigurationResolver{files: map[string][]byte{ "config.yaml": []byte(fileLoaderConfiguration + "\nunknownRootField: true\n"), }} loader, err := NewFileConfigurationLoader("config.yaml", resolver) if err != nil { t.Fatalf("NewFileConfigurationLoader() error = %v", err) } if _, err := loader.LoadConfiguration(context.Background()); !errors.Is(err, ErrInvalidConfiguration) || !strings.Contains(err.Error(), "unknownRootField") { t.Fatalf("LoadConfiguration() error = %v, want strict unknown field error", err) } } func TestFileConfigurationLoaderClassifiesReadFailureAsUnavailable(t *testing.T) { t.Parallel() loader, err := NewFileConfigurationLoader("missing.yaml", &memoryConfigurationResolver{}) if err != nil { t.Fatalf("NewFileConfigurationLoader() error = %v", err) } if _, err := loader.LoadConfiguration(context.Background()); !errors.Is(err, ErrUnavailable) { t.Fatalf("LoadConfiguration() error = %v, want %v", err, ErrUnavailable) } } func TestFileConfigurationLoaderClassifiesSecretReadFailureAsUnavailable(t *testing.T) { t.Parallel() configuration := strings.Replace(fileLoaderConfiguration, "proxyAuth: {type: response}", "proxyAuth: {type: static, username: alice, passwordFile: /run/secrets/missing}", 1) resolver := &memoryConfigurationResolver{files: map[string][]byte{ "config.yaml": []byte(configuration), }} loader, err := NewFileConfigurationLoader("config.yaml", resolver) if err != nil { t.Fatalf("NewFileConfigurationLoader() error = %v", err) } if _, err := loader.LoadConfiguration(context.Background()); !errors.Is(err, ErrUnavailable) || errors.Is(err, ErrInvalidConfiguration) { t.Fatalf("LoadConfiguration() error = %v, want unavailable secret source", err) } } func TestFileConfigurationLoaderHonorsCancellationBeforeIO(t *testing.T) { t.Parallel() resolver := &memoryConfigurationResolver{} loader, err := NewFileConfigurationLoader("config.yaml", resolver) if err != nil { t.Fatalf("NewFileConfigurationLoader() error = %v", err) } ctx, cancel := context.WithCancel(context.Background()) cancel() if _, err := loader.LoadConfiguration(ctx); !errors.Is(err, context.Canceled) { t.Fatalf("LoadConfiguration() error = %v, want context cancellation", err) } if resolver.reads != 0 { t.Fatalf("resolver reads = %d, want 0", resolver.reads) } } func TestNewFileConfigurationLoaderRejectsInvalidDependencies(t *testing.T) { t.Parallel() resolver := &memoryConfigurationResolver{} for _, test := range []struct { path string resolver *memoryConfigurationResolver }{ {resolver: resolver}, {path: "config.yaml"}, } { if _, err := NewFileConfigurationLoader(test.path, test.resolver); !errors.Is(err, ErrInvalidConfigurationLoader) { t.Fatalf("NewFileConfigurationLoader(%q) error = %v", test.path, err) } } } type memoryConfigurationResolver struct { files map[string][]byte reads int } func (*memoryConfigurationResolver) LookupEnv(string) (string, bool) { return "", false } func (resolver *memoryConfigurationResolver) ReadFile(path string) ([]byte, error) { resolver.reads++ content, exists := resolver.files[path] if !exists { return nil, errors.New("fixture file not found") } return append([]byte(nil), content...), nil } const fileLoaderConfiguration = ` version: 1 security: {requireProtectionOnPublicListen: true} gateway: enabled: true listen: 127.0.0.1:8080 auth: {mode: none} limits: {maxConcurrentConnections: 20000} retry: {maxAttempts: 2, retryMethods: [GET, HEAD]} destinationPolicy: {denyPrivateNetworks: true, denyLoopback: true, denyLinkLocal: true} routing: - name: gateway enabled: true purpose: gateway match: {hostRegex: '.*'} upstreams: [provider-a] strategy: {type: leastConnections} onUnavailable: {action: reject} upstreams: provider-a: enabled: true exposure: [gateway] provider: {billingMode: subscription, protocols: [http, https]} api: {url: https://provider-a.example/proxies, method: GET, auth: {type: none}, template: '{{.}}'} proxyAuth: {type: response} pool: {maxSize: 2000} capacity: {maxConcurrencyPerProxy: 20} refill: {reconcileInterval: 1s, minimumAvailableSlots: 100, targetAvailableSlots: 200} lifecycle: {ttl: 5m, allocationSafetyMargin: 30s} fetch: {estimatedIPsPerCall: 100, requestInterval: 1s, timeout: 3s, maxAttempts: 3, maxInFlight: 1} check: {interval: 30s, jitter: 20, maxInFlight: 100, timeout: 2s, maxAttempts: 2, maxConsecutiveFailures: 3} `