Split the monolithic config.go (~120KB) into focused modules: - auth.go: JWT, TOTP, Turnstile, RateLimit configs - billing.go: Billing and Pricing configs - database.go: Database and Redis configs - gateway.go: Gateway and Upstream configs - gateway_sub.go: Gateway sub-configurations - ops_and_cache.go: Ops and Cache configs - platforms.go: Platform-specific configs - security.go: Security-related configs - server.go: Server configuration - config_defaults.go: Default values - config_defaults_detail.go: Detailed defaults - config_helpers.go: Helper functions - config_validate.go: Validation logic - config_validate_gateway.go: Gateway validation This improves: - Code maintainability and readability - Faster compilation (smaller files) - Easier navigation and debugging - Better separation of concerns
202 lines
7.8 KiB
Go
202 lines
7.8 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Test: Config Domain Split — Type Definitions & Struct Integrity
|
|
// 验证拆分后的 15 个域文件中的所有类型定义正确、字段完整
|
|
// =============================================================================
|
|
|
|
func TestConfigStructIntegrity(t *testing.T) {
|
|
t.Parallel()
|
|
cfg := Config{}
|
|
|
|
assert.IsType(t, ServerConfig{}, cfg.Server)
|
|
assert.IsType(t, H2CConfig{}, cfg.Server.H2C)
|
|
assert.IsType(t, CORSConfig{}, CORSConfig{})
|
|
assert.IsType(t, ConcurrencyConfig{}, ConcurrencyConfig{})
|
|
|
|
assert.IsType(t, SecurityConfig{}, cfg.Security)
|
|
assert.IsType(t, URLAllowlistConfig{}, cfg.Security.URLAllowlist)
|
|
assert.IsType(t, ResponseHeaderConfig{}, cfg.Security.ResponseHeaders)
|
|
assert.IsType(t, CSPConfig{}, cfg.Security.CSP)
|
|
assert.IsType(t, ProxyFallbackConfig{}, cfg.Security.ProxyFallback)
|
|
assert.IsType(t, ProxyProbeConfig{}, cfg.Security.ProxyProbe)
|
|
|
|
assert.IsType(t, DatabaseConfig{}, cfg.Database)
|
|
assert.IsType(t, RedisConfig{}, cfg.Redis)
|
|
|
|
assert.IsType(t, JWTConfig{}, cfg.JWT)
|
|
assert.IsType(t, TotpConfig{}, cfg.Totp)
|
|
assert.IsType(t, TurnstileConfig{}, cfg.Turnstile)
|
|
assert.IsType(t, DefaultConfig{}, cfg.Default)
|
|
assert.IsType(t, RateLimitConfig{}, cfg.RateLimit)
|
|
|
|
assert.IsType(t, BillingConfig{}, cfg.Billing)
|
|
assert.IsType(t, PricingConfig{}, cfg.Pricing)
|
|
|
|
assert.IsType(t, GatewayConfig{}, cfg.Gateway)
|
|
assert.IsType(t, GatewayOpenAIWSConfig{}, cfg.Gateway.OpenAIWS)
|
|
assert.IsType(t, GatewayUsageRecordConfig{}, cfg.Gateway.UsageRecord)
|
|
assert.IsType(t, TLSFingerprintConfig{}, cfg.Gateway.TLSFingerprint)
|
|
assert.IsType(t, UserMessageQueueConfig{}, cfg.Gateway.UserMessageQueue)
|
|
assert.IsType(t, GatewaySchedulingConfig{}, cfg.Gateway.Scheduling)
|
|
assert.IsType(t, GatewayOpenAIWSSchedulerScoreWeights{}, cfg.Gateway.OpenAIWS.SchedulerScoreWeights)
|
|
|
|
assert.IsType(t, SoraConfig{}, cfg.Sora)
|
|
assert.IsType(t, GeminiConfig{}, cfg.Gemini)
|
|
assert.IsType(t, UpdateConfig{}, cfg.Update)
|
|
assert.IsType(t, IdempotencyConfig{}, cfg.Idempotency)
|
|
assert.IsType(t, LinuxDoConnectConfig{}, cfg.LinuxDo)
|
|
assert.IsType(t, OIDCConnectConfig{}, cfg.OIDC)
|
|
|
|
assert.IsType(t, OpsConfig{}, cfg.Ops)
|
|
assert.IsType(t, LogConfig{}, cfg.Log)
|
|
assert.IsType(t, DashboardCacheConfig{}, cfg.Dashboard)
|
|
assert.IsType(t, DashboardAggregationConfig{}, cfg.DashboardAgg)
|
|
assert.IsType(t, UsageCleanupConfig{}, cfg.UsageCleanup)
|
|
assert.IsType(t, ConcurrencyConfig{}, cfg.Concurrency)
|
|
assert.IsType(t, TokenRefreshConfig{}, cfg.TokenRefresh)
|
|
|
|
assert.IsType(t, APIKeyAuthCacheConfig{}, cfg.APIKeyAuth)
|
|
assert.IsType(t, SubscriptionCacheConfig{}, cfg.SubscriptionCache)
|
|
}
|
|
|
|
func TestServerConfigDefaults(t *testing.T) {
|
|
s := ServerConfig{}
|
|
if s.Host != "" { t.Error("Host should default to empty") }
|
|
if s.Port != 0 { t.Error("Port should default to 0") }
|
|
}
|
|
|
|
func TestH2CConfigFields(t *testing.T) {
|
|
h := H2CConfig{
|
|
Enabled: true, MaxConcurrentStreams: 100, IdleTimeout: 75,
|
|
MaxReadFrameSize: 1 << 20, MaxUploadBufferPerConnection: 2 << 20,
|
|
}
|
|
if !h.Enabled { t.Error("Enabled should be true") }
|
|
if h.MaxConcurrentStreams != 100 { t.Error("MaxConcurrentStreams mismatch") }
|
|
}
|
|
|
|
func TestSecurityConfigFields(t *testing.T) {
|
|
s := SecurityConfig{}
|
|
if s.URLAllowlist.Enabled { t.Error("URLAllowlist.Enabled should be false") }
|
|
if s.ProxyFallback.AllowDirectOnError { t.Error("AllowDirectOnError should be false") }
|
|
if s.ProxyProbe.InsecureSkipVerify { t.Error("InsecureSkipVerify should be false") }
|
|
}
|
|
|
|
func TestDatabaseConfig_DSN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg DatabaseConfig
|
|
check func(DatabaseConfig) string
|
|
contains []string
|
|
exclude []string
|
|
}{
|
|
{"no password", DatabaseConfig{Host: "localhost", Port: 5432, User: "u", DBName: "db", SSLMode: "s"},
|
|
func(c DatabaseConfig) string { return c.DSN() },
|
|
[]string{"host=localhost", "port=5432"}, []string{"password="}},
|
|
{"with password", DatabaseConfig{Host: "h", Port: 1, User: "u", Password: "p", DBName: "d", SSLMode: "s"},
|
|
func(c DatabaseConfig) string { return c.DSN() },
|
|
[]string{"password=p"}, nil},
|
|
{"tz default", DatabaseConfig{Host: "h", Port: 1, User: "u", DBName: "d", SSLMode: "s"},
|
|
func(c DatabaseConfig) string { return c.DSNWithTimezone("") },
|
|
[]string{"TimeZone=Asia/Shanghai"}, nil},
|
|
{"tz custom", DatabaseConfig{Host: "h", Port: 1, User: "u", DBName: "d", SSLMode: "s"},
|
|
func(c DatabaseConfig) string { return c.DSNWithTimezone("UTC") },
|
|
[]string{"TimeZone=UTC"}, nil},
|
|
}
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := tc.check(tc.cfg)
|
|
for _, sub := range tc.contains { assert.Contains(t, got, sub) }
|
|
for _, sub := range tc.exclude { assert.NotContains(t, got, sub) }
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRedisConfig_Address(t *testing.T) {
|
|
r := RedisConfig{Host: "redis.local", Port: 6380}
|
|
if r.Address() != "redis.local:6380" { t.Errorf("Address = %q", r.Address()) }
|
|
}
|
|
|
|
func TestJWTConfigFields(t *testing.T) {
|
|
j := JWTConfig{Secret: strings.Repeat("x", 32), ExpireHour: 24, RefreshTokenExpireDays: 30}
|
|
if j.RefreshTokenExpireDays != 30 { t.Error("RefreshTokenExpireDays mismatch") }
|
|
}
|
|
|
|
func TestTotpConfigFields(t *testing.T) {
|
|
tc := TotpConfig{EncryptionKeyConfigured: true}
|
|
if !tc.EncryptionKeyConfigured { t.Error("should be true") }
|
|
}
|
|
|
|
func TestGatewayConstants(t *testing.T) {
|
|
policies := []string{UsageRecordOverflowPolicyDrop, UsageRecordOverflowPolicySample, UsageRecordOverflowPolicySync}
|
|
unique := make(map[string]bool, len(policies))
|
|
for _, p := range policies { unique[p] = true }
|
|
if len(unique) != len(policies) { t.Error("overflow policies must be unique") }
|
|
|
|
if UMQModeSerialize == UMQModeThrottle { t.Error("modes must differ") }
|
|
|
|
strategies := []string{ConnectionPoolIsolationProxy, ConnectionPoolIsolationAccount, ConnectionPoolIsolationAccountProxy}
|
|
uniqueS := map[string]bool{}
|
|
for _, s := range strategies { uniqueS[s] = true }
|
|
if len(uniqueS) != len(strategies) { t.Error("strategies must be unique") }
|
|
}
|
|
|
|
func TestUserMessageQueueConfig_Methods(t *testing.T) {
|
|
q := UserMessageQueueConfig{}
|
|
if q.WaitTimeout() != 30*time.Second { t.Error("default WaitTimeout should be 30s") }
|
|
|
|
q.WaitTimeoutMs = 5000
|
|
if q.WaitTimeout() != 5*time.Second { t.Error("custom WaitTimeout should be 5s") }
|
|
|
|
q.Mode = UMQModeThrottle
|
|
if q.GetEffectiveMode() != UMQModeThrottle { t.Error("mode should be throttle") }
|
|
|
|
q.Mode = ""
|
|
q.Enabled = true
|
|
if q.GetEffectiveMode() != UMQModeSerialize { t.Error("enabled+empty → serialize") }
|
|
|
|
q.Enabled = false
|
|
if q.GetEffectiveMode() != "" { t.Error("disabled+empty → empty") }
|
|
}
|
|
|
|
func TestSoraConfigFields(t *testing.T) {
|
|
s := SoraConfig{
|
|
Client: SoraClientConfig{BaseURL: "https://sora.example.com"},
|
|
Storage: SoraStorageConfig{Type: "local"},
|
|
}
|
|
if s.Client.BaseURL != "https://sora.example.com" { t.Error("BaseURL mismatch") }
|
|
}
|
|
|
|
func TestGeminiConfigFields(t *testing.T) {
|
|
g := GeminiConfig{Quota: GeminiQuotaConfig{Policy: "conservative"}}
|
|
if g.Quota.Policy != "conservative" { t.Error("Policy mismatch") }
|
|
}
|
|
|
|
func TestOpsAndCacheConfigFields(t *testing.T) {
|
|
lc := LogConfig{Level: "info"}
|
|
if lc.Level != "info" { t.Error("Level mismatch") }
|
|
|
|
r := DashboardAggregationRetentionConfig{UsageLogsDays: 90, UsageBillingDedupDays: 365}
|
|
if r.UsageBillingDedupDays < r.UsageLogsDays { t.Error("invariant violation: dedup >= logs") }
|
|
}
|
|
|
|
func TestBillingAndPricingConfig(t *testing.T) {
|
|
bc := BillingConfig{CircuitBreaker: CircuitBreakerConfig{Enabled: true}}
|
|
if !bc.CircuitBreaker.Enabled { t.Error("should be enabled") }
|
|
}
|
|
|
|
func TestConstants(t *testing.T) {
|
|
if RunModeStandard != "standard" { t.Error("RunModeStandard wrong") }
|
|
if RunModeSimple != "simple" { t.Error("RunModeSimple wrong") }
|
|
if !strings.Contains(DefaultCSPPolicy, "__CSP_NONCE__") { t.Error("CSPPolicy missing nonce placeholder") }
|
|
}
|