package config import ( "strings" "testing" "gitea.maximumdirect.net/eric/notarius/internal/core/diagnostics" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) func TestApplyEnvOverridesOperationalValues(t *testing.T) { cfg := Default() cfg.Pipelines["example"] = pipeline.PipelineProfile{ID: "example", Input: pipeline.Binding("before")} err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{ "NOTARIUS_TOTAL_LLM_CONCURRENCY": "3", "NOTARIUS_STAGE_WORKERS_EXTRACT": "2", "NOTARIUS_WORK_DIR": "/tmp/notarius-env", "NOTARIUS_DIAGNOSTICS_RETENTION": "never", "NOTARIUS_WORKSPACE_DIR": "/var/lib/notarius-env", "NOTARIUS_WORKSPACE_DIAGNOSTICS_ENABLED": "false", "NOTARIUS_WORKSPACE_DIAGNOSTICS_RETENTION": "always", "NOTARIUS_WORKSPACE_RESUME_ENABLED": "true", "NOTARIUS_WORKSPACE_DEBUG_ENABLED": "true", "NOTARIUS_PIPELINE_INPUT": "after", })) if err != nil { t.Fatalf("ApplyEnvOverrides: %v", err) } if cfg.Scriptorium.ProfileDir != "" || cfg.Scriptorium.ProfileFile != "" { t.Fatalf("LLM environment overrides must not change Scriptorium config: %+v", cfg.Scriptorium) } if cfg.Concurrency.TotalLLM != 3 { t.Fatalf("unexpected total concurrency: %d", cfg.Concurrency.TotalLLM) } if got := cfg.Concurrency.StageWorkers["extract"]; got != 2 { t.Fatalf("extract workers = %d, want 2", got) } if cfg.Workspace.Directory != "/var/lib/notarius-env" { t.Fatalf("unexpected workspace directory: %q", cfg.Workspace.Directory) } if cfg.DiagnosticsEnabled() { t.Fatalf("expected workspace diagnostics disabled") } if cfg.Diagnostics.WorkDir != "/var/lib/notarius-env/diagnostics" || cfg.Diagnostics.Retention != diagnostics.RetentionAlways { t.Fatalf("unexpected diagnostics config: %+v", cfg.Diagnostics) } if !cfg.Workspace.Resume.Enabled { t.Fatalf("expected workspace resume enabled") } if !cfg.Workspace.Debug.Enabled { t.Fatalf("expected workspace debug enabled") } if cfg.Pipelines["example"].Input.Module != "before" { t.Fatalf("environment overrides must not change pipeline wiring: %+v", cfg.Pipelines["example"]) } } func TestApplyEnvOverridesRejectsInvalidIntegers(t *testing.T) { for _, name := range []string{"NOTARIUS_TOTAL_LLM_CONCURRENCY", "NOTARIUS_STAGE_WORKERS_EXTRACT"} { t.Run(name, func(t *testing.T) { cfg := Default() err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{name: "many"})) if err == nil || !strings.Contains(err.Error(), name) { t.Fatalf("expected named integer error, got %v", err) } }) } } func TestStageWorkerEnvironmentPrecedenceAndDefaulting(t *testing.T) { fileCfg, err := ParseFileConfigYAML([]byte(` version: 2 concurrency: total_llm: 4 stage_workers: extract: 2 `)) if err != nil { t.Fatalf("ParseFileConfigYAML() error = %v", err) } cfg := Default() if err := cfg.applyFileConfigWithLookup(fileCfg, emptyLookup); err != nil { t.Fatalf("ApplyFileConfig() error = %v", err) } if err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{ "NOTARIUS_TOTAL_LLM_CONCURRENCY": "5", "NOTARIUS_STAGE_WORKERS_EXTRACT": "3", })); err != nil { t.Fatalf("ApplyEnvOverrides() error = %v", err) } if cfg.Concurrency.TotalLLM != 5 || cfg.Concurrency.StageWorkers["extract"] != 3 { t.Fatalf("effective concurrency = %#v, want total 5 and extract 3", cfg.Concurrency) } if err := cfg.Validate(); err != nil { t.Fatalf("Validate(overridden) error = %v, want nil", err) } defaulted := Default() if err := defaulted.applyEnvOverridesWithLookup(mapLookup(map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6"})); err != nil { t.Fatalf("ApplyEnvOverrides(defaulted) error = %v", err) } if got := defaulted.Concurrency.StageWorkers["extract"]; got != 6 { t.Fatalf("defaulted extract workers = %d, want effective total 6", got) } } func TestStageWorkerRangeValidationUsesFinalEnvironmentTotal(t *testing.T) { fileCfg, err := ParseFileConfigYAML([]byte(` version: 2 concurrency: total_llm: 4 stage_workers: extract: 5 `)) if err != nil { t.Fatalf("ParseFileConfigYAML() error = %v", err) } cfg := Default() if err := cfg.applyFileConfigWithLookup(fileCfg, emptyLookup); err != nil { t.Fatalf("ApplyFileConfig() error = %v", err) } if err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6"})); err != nil { t.Fatalf("ApplyEnvOverrides() error = %v", err) } if err := cfg.Validate(); err != nil { t.Fatalf("Validate() error = %v, want final total to make extract workers valid", err) } } func TestApplyEnvOverridesRejectsInvalidBooleans(t *testing.T) { for _, name := range []string{ "NOTARIUS_WORKSPACE_DIAGNOSTICS_ENABLED", "NOTARIUS_WORKSPACE_RESUME_ENABLED", "NOTARIUS_WORKSPACE_DEBUG_ENABLED", } { t.Run(name, func(t *testing.T) { cfg := Default() err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{name: "maybe"})) if err == nil || !strings.Contains(err.Error(), name) { t.Fatalf("expected named boolean error, got %v", err) } }) } } func TestApplyEnvOverridesLegacyDiagnosticsRemainCompatibleWithoutWorkspace(t *testing.T) { cfg := Default() err := cfg.applyEnvOverridesWithLookup(mapLookup(map[string]string{ "NOTARIUS_WORK_DIR": "/tmp/notarius-env", "NOTARIUS_DIAGNOSTICS_RETENTION": "never", })) if err != nil { t.Fatalf("ApplyEnvOverrides: %v", err) } if cfg.Diagnostics.WorkDir != "/tmp/notarius-env" { t.Fatalf("diagnostics work dir = %q, want legacy env", cfg.Diagnostics.WorkDir) } if cfg.Diagnostics.Retention != diagnostics.RetentionNever { t.Fatalf("diagnostics retention = %q, want legacy env", cfg.Diagnostics.Retention) } } func TestLoadFromEnvUsesDefaultConfig(t *testing.T) { t.Setenv("NOTARIUS_TOTAL_LLM_CONCURRENCY", "2") cfg, err := LoadFromEnv() if err != nil { t.Fatalf("LoadFromEnv: %v", err) } if cfg.Scriptorium.ProfileDir != "" || cfg.Scriptorium.ProfileFile != "" { t.Fatalf("unexpected Scriptorium config from env: %+v", cfg.Scriptorium) } if cfg.Concurrency.TotalLLM != 2 { t.Fatalf("expected env concurrency override, got %+v", cfg.Concurrency) } if got := cfg.Concurrency.StageWorkers["extract"]; got != 2 { t.Fatalf("expected extract workers to default to total, got %d", got) } }