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" overrides := CLIOverrides{ PrimaryModel: &model, WorkDir: &workDir, ModulesCSV: &modules, } 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) } } func TestValidationFailures(t *testing.T) { cfg := Default() cfg.PrimaryLLM.TimeoutSeconds = -1 cfg.PrimaryLLM.Concurrency = 0 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 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 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 } }