package config import ( "fmt" "strings" "testing" ) func TestLoadIdentitySecretStoreUsesNewEnvironmentNamesAndIgnoresLegacyBusinessValues(t *testing.T) { t.Setenv("IDENTITY_SECRET_STORE", "file") t.Setenv("IDENTITY_SECRET_DIR", ".test-secrets/identity") t.Setenv("OIDC_ISSUER", "https://legacy-sensitive.example/issuer") t.Setenv("OIDC_INTROSPECTION_CLIENT_SECRET", "legacy-sensitive-secret") t.Setenv("OIDC_SESSION_ENCRYPTION_KEY", "legacy-sensitive-session-key") cfg := Load() if cfg.IdentitySecretStore != "file" || cfg.IdentitySecretDir != ".test-secrets/identity" { t.Fatalf("identity SecretStore = %q %q", cfg.IdentitySecretStore, cfg.IdentitySecretDir) } printed := fmt.Sprintf("%+v", cfg) for _, legacyValue := range []string{"legacy-sensitive.example", "legacy-sensitive-secret", "legacy-sensitive-session-key"} { if strings.Contains(printed, legacyValue) { t.Fatalf("legacy identity business setting was loaded into Config: %q", legacyValue) } } } func TestValidateIdentityFileSecretStoreRequiresDirectory(t *testing.T) { cfg := Config{IdentitySecretStore: "file", AsyncWorkerHardLimit: 2048, AsyncWorkerRefreshIntervalSeconds: 5} if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "IDENTITY_SECRET_DIR") { t.Fatalf("Validate() error = %v, want missing identity secret directory", err) } cfg.IdentitySecretDir = ".test-secrets/identity" if err := cfg.Validate(); err != nil { t.Fatalf("valid file SecretStore was rejected: %v", err) } } func TestValidateIdentityKubernetesSecretStore(t *testing.T) { cfg := Config{ IdentitySecretStore: "kubernetes", IdentityKubernetesSecretName: "easyai-gateway-identity", AsyncWorkerHardLimit: 2048, AsyncWorkerRefreshIntervalSeconds: 5, } if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "namespace") { t.Fatalf("Validate() error = %v, want missing namespace", err) } cfg.IdentityKubernetesNamespace = "easyai" if err := cfg.Validate(); err != nil { t.Fatalf("valid Kubernetes SecretStore was rejected: %v", err) } } func TestValidateIdentitySecurityEventTiming(t *testing.T) { cfg := Config{ IdentitySecurityEventHeartbeatIntervalSeconds: 60, IdentitySecurityEventStaleAfterSeconds: 60, IdentitySecurityEventClockSkewSeconds: 60, AsyncWorkerHardLimit: 2048, AsyncWorkerRefreshIntervalSeconds: 5, } if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "heartbeat") { t.Fatalf("Validate() error = %v, want invalid stale threshold", err) } } func TestValidateAsyncWorkerSettings(t *testing.T) { cfg := Config{AsyncWorkerHardLimit: 10001, AsyncWorkerRefreshIntervalSeconds: 5} if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "HARD_LIMIT") { t.Fatalf("Validate() error = %v, want invalid hard limit", err) } cfg.AsyncWorkerHardLimit = 2048 cfg.AsyncWorkerRefreshIntervalSeconds = 0 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "REFRESH_INTERVAL") { t.Fatalf("Validate() error = %v, want invalid refresh interval", err) } t.Setenv("AI_GATEWAY_ASYNC_WORKER_HARD_LIMIT", "not-an-integer") loaded := Load() if err := loaded.Validate(); err == nil || !strings.Contains(err.Error(), "HARD_LIMIT") { t.Fatalf("Validate() error = %v, want invalid non-integer hard limit", err) } } func TestLoadAsyncQueueWorkerEnabled(t *testing.T) { cfg := Load() if !cfg.AsyncQueueWorkerEnabled { t.Fatal("async queue worker must be enabled by default") } t.Setenv("AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED", "false") cfg = Load() if cfg.AsyncQueueWorkerEnabled { t.Fatal("async queue worker remained enabled after explicit disable") } } func TestProcessRolePrecedenceAndCompatibility(t *testing.T) { t.Setenv("AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED", "false") cfg := Load() if got := cfg.EffectiveProcessRole(); got != "all" { t.Fatalf("legacy configuration changed process role to %q", got) } if cfg.RunsAsyncExecutionWorker() { t.Fatal("legacy async worker disable was ignored") } t.Setenv("AI_GATEWAY_PROCESS_ROLE", "worker") cfg = Load() if got := cfg.EffectiveProcessRole(); got != "worker" { t.Fatalf("effective process role = %q, want worker", got) } if cfg.RunsPublicHTTP() || !cfg.RunsAsyncExecutionWorker() || !cfg.RunsBackgroundWorkers() { t.Fatalf("worker role capabilities are inconsistent: %+v", cfg) } t.Setenv("AI_GATEWAY_PROCESS_ROLE", "api") cfg = Load() if !cfg.RunsPublicHTTP() || cfg.RunsAsyncExecutionWorker() || cfg.RunsBackgroundWorkers() { t.Fatalf("api role capabilities are inconsistent: %+v", cfg) } } func TestValidateProcessRoleAndDatabasePool(t *testing.T) { cfg := Load() cfg.ProcessRole = "invalid" if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "PROCESS_ROLE") { t.Fatalf("Validate() error = %v, want invalid process role", err) } cfg.ProcessRole = "api" cfg.DatabaseMaxConns = -1 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "DATABASE_MAX_CONNS") { t.Fatalf("Validate() error = %v, want invalid database max conns", err) } cfg.DatabaseMaxConns = 16 cfg.MediaMaterializationConcurrency = 257 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "MEDIA_MATERIALIZATION_CONCURRENCY") { t.Fatalf("Validate() error = %v, want invalid media materialization concurrency", err) } t.Setenv("AI_GATEWAY_MEDIA_MATERIALIZATION_CONCURRENCY", "12") if got := Load().MediaMaterializationConcurrency; got != 12 { t.Fatalf("media materialization concurrency = %d, want 12", got) } cfg = Load() cfg.MediaRequestConcurrency = 1025 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "MEDIA_REQUEST_CONCURRENCY") { t.Fatalf("Validate() error = %v, want invalid media request concurrency", err) } t.Setenv("AI_GATEWAY_MEDIA_REQUEST_CONCURRENCY", "96") if got := Load().MediaRequestConcurrency; got != 96 { t.Fatalf("media request concurrency = %d, want 96", got) } } func TestValidateTaskHistorySettings(t *testing.T) { cfg := Load() cfg.TaskRetentionDays = 30 cfg.TaskAnalysisRetentionDays = 31 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "ANALYSIS_RETENTION") { t.Fatalf("Validate() error = %v, want invalid analysis retention", err) } cfg.TaskAnalysisRetentionDays = 7 cfg.TaskCleanupIntervalSeconds = 59 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "CLEANUP_INTERVAL") { t.Fatalf("Validate() error = %v, want invalid cleanup interval", err) } cfg.TaskCleanupIntervalSeconds = 300 cfg.TaskCleanupBatchSize = 5001 if err := cfg.Validate(); err == nil || !strings.Contains(err.Error(), "CLEANUP_BATCH") { t.Fatalf("Validate() error = %v, want invalid cleanup batch", err) } }