package config import ( "reflect" "strings" "testing" ) func TestDefaultConfigValues(t *testing.T) { cfg := Default() if got, want := strings.Join(cfg.Modules, ","), DefaultModulesCSV; got != want { t.Fatalf("modules mismatch: got %q want %q", got, want) } if cfg.PrimaryLLM.Model != DefaultPrimaryModel { t.Fatalf("unexpected default primary model: %q", cfg.PrimaryLLM.Model) } if cfg.PrimaryLLM.BaseURL != DefaultPrimaryBaseURL { t.Fatalf("unexpected default primary base url: %q", cfg.PrimaryLLM.BaseURL) } if cfg.PrimaryLLM.TimeoutSeconds != DefaultPrimaryLLMTimeoutSeconds { t.Fatalf("unexpected default timeout seconds: %d", cfg.PrimaryLLM.TimeoutSeconds) } if cfg.PrimaryLLM.MaxRetries != DefaultMaxRetries { t.Fatalf("unexpected default max retries: %d", cfg.PrimaryLLM.MaxRetries) } if cfg.PrimaryLLM.Concurrency != DefaultLLMConcurrency { t.Fatalf("unexpected default llm concurrency: %d", cfg.PrimaryLLM.Concurrency) } if cfg.ValidationLLM.TimeoutSeconds != nil { t.Fatalf("expected validation timeout to be unset by default") } if cfg.ValidationLLM.MaxRetries != nil { t.Fatalf("expected validation max retries to be unset by default") } if cfg.ValidationLLM.Concurrency != nil { t.Fatalf("expected validation llm concurrency to be unset by default") } if cfg.TargetSections != nil { t.Fatalf("expected target sections to be unset by default") } if cfg.WorkDir != DefaultWorkDir { t.Fatalf("unexpected default work dir: %q", cfg.WorkDir) } if cfg.WorkDirRetention != DefaultWorkDirRetention { t.Fatalf("unexpected default work dir retention: %q", cfg.WorkDirRetention) } if err := cfg.Validate(); err != nil { t.Fatalf("default config should validate: %v", err) } } func TestLoadFromEnvOverridesAndFallback(t *testing.T) { env := map[string]string{ "AUDITA_MODEL": "openai/gpt-4.1-mini", "AUDITA_BASE_URL": "https://api.openai.com/v1", "AUDITA_LLM_TIMEOUT_SECONDS": "120", "AUDITA_MAX_RETRIES": "7", "AUDITA_LLM_CONCURRENCY": "6", "AUDITA_VALIDATION_LLM_CONCURRENCY": "2", "AUDITA_VALIDATION_MAX_PROMPT_TOKENS": "4096", "AUDITA_MAX_SECTION_TOKENS": "9000", "AUDITA_MIN_SECTION_TOKENS": "3000", "AUDITA_TARGET_SECTIONS": "5", "AUDITA_GLOSSARY_CONFIDENCE_THRESHOLD": "0.9", "AUDITA_GRAMMAR_CONFIDENCE_THRESHOLD": "0.7", "AUDITA_HOMOPHONES_CONFIDENCE_THRESHOLD": "0.6", "AUDITA_SPOKEN_WORD_CONFIDENCE_THRESHOLD": "0.5", "AUDITA_NORMALIZE_MAX_SEGMENT_GAP": "2.5", "AUDITA_NORMALIZE_ELLIPSIS_GAP": "2.0", "AUDITA_NORMALIZE_MAX_SEGMENT_DURATION": "30.0", "AUDITA_NORMALIZE_MAX_SEGMENT_TOKENS": "1024", "AUDITA_WORK_DIR": "/var/tmp/audita", "AUDITA_WORK_DIR_RETENTION": "always", "OPENROUTER_API_KEY": "fallback-key", } cfg, err := loadFromLookup(mapLookup(env)) if err != nil { t.Fatalf("loadFromLookup returned error: %v", err) } if cfg.PrimaryLLM.APIKey != "fallback-key" { t.Fatalf("expected OPENROUTER_API_KEY fallback, got %q", cfg.PrimaryLLM.APIKey) } if cfg.PrimaryLLM.Model != env["AUDITA_MODEL"] { t.Fatalf("unexpected model: %q", cfg.PrimaryLLM.Model) } if cfg.PrimaryLLM.BaseURL != env["AUDITA_BASE_URL"] { t.Fatalf("unexpected base url: %q", cfg.PrimaryLLM.BaseURL) } if cfg.TargetSections == nil || *cfg.TargetSections != 5 { t.Fatalf("unexpected target sections: %#v", cfg.TargetSections) } if cfg.PrimaryLLM.Concurrency != 6 { t.Fatalf("unexpected primary llm concurrency: %d", cfg.PrimaryLLM.Concurrency) } if cfg.ValidationLLM.Concurrency == nil || *cfg.ValidationLLM.Concurrency != 2 { t.Fatalf("unexpected validation llm concurrency: %#v", cfg.ValidationLLM.Concurrency) } if cfg.WorkDirRetention != WorkDirRetentionAlways { t.Fatalf("unexpected work dir retention: %q", cfg.WorkDirRetention) } } func TestLoadFromEnvUsesAuditaLLMAPIKeyOverFallback(t *testing.T) { env := map[string]string{ "AUDITA_LLM_API_KEY": "primary-key", "OPENROUTER_API_KEY": "fallback-key", } cfg, err := loadFromLookup(mapLookup(env)) if err != nil { t.Fatalf("loadFromLookup returned error: %v", err) } if cfg.PrimaryLLM.APIKey != "primary-key" { t.Fatalf("expected AUDITA_LLM_API_KEY to win, got %q", cfg.PrimaryLLM.APIKey) } } func TestApplyCLIOverridesPrecedence(t *testing.T) { cfg := Default() cfg.PrimaryLLM.Model = "env-model" cfg.WorkDir = "/env/work" model := "cli-model" workDir := "/cli/work" modules := "grammar" llmConcurrency := 5 overrides := CLIOverrides{ PrimaryModel: &model, WorkDir: &workDir, ModulesCSV: &modules, PrimaryLLMConcurrency: &llmConcurrency, } if err := cfg.ApplyCLIOverrides(overrides); err != nil { t.Fatalf("ApplyCLIOverrides failed: %v", err) } if cfg.PrimaryLLM.Model != "cli-model" { t.Fatalf("expected CLI model override, got %q", cfg.PrimaryLLM.Model) } if cfg.WorkDir != "/cli/work" { t.Fatalf("expected CLI work dir override, got %q", cfg.WorkDir) } if !reflect.DeepEqual(cfg.Modules, []string{"grammar"}) { t.Fatalf("unexpected modules: %#v", cfg.Modules) } if cfg.PrimaryLLM.Concurrency != 5 { t.Fatalf("expected CLI llm concurrency override, got %d", cfg.PrimaryLLM.Concurrency) } } func TestValidationFailures(t *testing.T) { cfg := Default() cfg.PrimaryLLM.TimeoutSeconds = -1 cfg.PrimaryLLM.Concurrency = 0 validationConcurrency := 5 cfg.ValidationLLM.Concurrency = &validationConcurrency cfg.ValidationMaxPromptTokens = 0 cfg.MaxSectionTokens = 100 cfg.MinSectionTokens = 200 cfg.Thresholds.Grammar = 1.5 cfg.WorkDirRetention = WorkDirRetention("sometimes") err := cfg.Validate() if err == nil { t.Fatalf("expected validation error") } message := err.Error() for _, expected := range []string{ "primary llm timeout seconds", "primary llm concurrency", "validation llm concurrency must be less than or equal to primary llm concurrency", "validation max prompt tokens", "min section tokens", "grammar confidence threshold", "work dir retention", } { if !strings.Contains(message, expected) { t.Fatalf("expected error to contain %q, got %q", expected, message) } } } func TestEffectiveValidationLLMInheritance(t *testing.T) { cfg := Default() cfg.PrimaryLLM.APIKey = "primary-key" cfg.PrimaryLLM.Model = "primary-model" cfg.PrimaryLLM.BaseURL = "https://primary.example/v1" cfg.PrimaryLLM.TimeoutSeconds = 111 cfg.PrimaryLLM.MaxRetries = 2 cfg.PrimaryLLM.Concurrency = 7 effective := cfg.EffectiveValidationLLMConfig() if effective.APIKey != "primary-key" || effective.Model != "primary-model" || effective.BaseURL != "https://primary.example/v1" || effective.TimeoutSeconds != 111 || effective.MaxRetries != 2 || effective.Concurrency != 7 { t.Fatalf("unexpected inherited config: %#v", effective) } validationTimeout := 222 validationRetries := 9 cfg.ValidationLLM.APIKey = "validation-key" cfg.ValidationLLM.Model = "validation-model" cfg.ValidationLLM.BaseURL = "https://validation.example/v1" cfg.ValidationLLM.TimeoutSeconds = &validationTimeout cfg.ValidationLLM.MaxRetries = &validationRetries validationConcurrency := 4 cfg.ValidationLLM.Concurrency = &validationConcurrency effective = cfg.EffectiveValidationLLMConfig() if effective.APIKey != "validation-key" || effective.Model != "validation-model" || effective.BaseURL != "https://validation.example/v1" || effective.TimeoutSeconds != 222 || effective.MaxRetries != 9 || effective.Concurrency != 4 { t.Fatalf("unexpected overridden validation config: %#v", effective) } } func TestValidationLLMConcurrencyCannotExceedPrimary(t *testing.T) { cfg := Default() cfg.PrimaryLLM.Concurrency = 2 validationConcurrency := 3 cfg.ValidationLLM.Concurrency = &validationConcurrency if err := cfg.Validate(); err == nil { t.Fatal("expected validation error when validation llm concurrency exceeds primary") } validationConcurrency = 2 cfg.ValidationLLM.Concurrency = &validationConcurrency if err := cfg.Validate(); err != nil { t.Fatalf("expected equal concurrency to validate, got %v", err) } } func TestCLIPrimaryLLMConcurrencyOverrideDrivesEffectiveValidationConcurrencyWhenValidationUnset(t *testing.T) { cfg := Default() llmConcurrency := 6 if err := cfg.ApplyCLIOverrides(CLIOverrides{PrimaryLLMConcurrency: &llmConcurrency}); err != nil { t.Fatalf("ApplyCLIOverrides failed: %v", err) } if cfg.ValidationLLM.Concurrency != nil { t.Fatalf("expected validation concurrency to remain unset, got %#v", cfg.ValidationLLM.Concurrency) } if cfg.EffectiveValidationLLMConfig().Concurrency != 6 { t.Fatalf("expected inherited validation concurrency 6, got %d", cfg.EffectiveValidationLLMConfig().Concurrency) } } func TestRedactedConfig(t *testing.T) { cfg := Default() cfg.PrimaryLLM.APIKey = "secret-primary" cfg.ValidationLLM.APIKey = "secret-validation" redacted := cfg.Redacted() if redacted.PrimaryLLM.APIKey != redactedSecret { t.Fatalf("expected primary api key to be redacted, got %q", redacted.PrimaryLLM.APIKey) } if redacted.ValidationLLM.APIKey != redactedSecret { t.Fatalf("expected validation api key to be redacted, got %q", redacted.ValidationLLM.APIKey) } if cfg.PrimaryLLM.APIKey != "secret-primary" { t.Fatalf("redaction should not mutate original config") } } func mapLookup(values map[string]string) func(string) (string, bool) { return func(key string) (string, bool) { value, ok := values[key] return value, ok } }