157 lines
5.5 KiB
Go
157 lines
5.5 KiB
Go
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}
|
|
lifecycle: {ttl: 5m, allocationSafetyMargin: 30s}
|
|
fetch: {requestInterval: 1s, timeout: 3s, maxAttempts: 3, maxInFlight: 1}
|
|
check: {interval: 30s, jitter: 20, maxInFlight: 100, timeout: 2s, maxAttempts: 2, maxConsecutiveFailures: 3}
|
|
`
|