package config import ( "bytes" "errors" "os" "path/filepath" "strings" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) func TestWorkerKeyNormalizationPreservesExplicitCount(t *testing.T) { t.Parallel() for _, test := range []struct { name, workers string want int errorPath string }{ {name: "omitted", workers: "{}", want: 4}, {name: "padded", workers: "{' extract ': 2}", want: 2}, {name: "explicit invalid", workers: "{' extract ': 0}", errorPath: "/concurrency/stage_workers/extract"}, {name: "collision", workers: "{' extract ': 2, extract: 3}", errorPath: "/concurrency/stage_workers/extract"}, } { t.Run(test.name, func(t *testing.T) { loaded, err := loadConfigSource(configkit.Bytes("workers.yml", []byte( "version: 5\nconcurrency:\n total_llm: 4\n stage_workers: "+test.workers+"\n")), LoadOptions{}) if test.errorPath != "" { assertDocumentDiagnosticAt(t, err, test.errorPath) return } if err != nil { t.Fatal(err) } if got := loaded.Config.Concurrency.StageWorkers["extract"]; got != test.want { t.Fatalf("workers = %d, want %d", got, test.want) } var explanation bytes.Buffer if err := loaded.WriteExplanation(&explanation, "/concurrency/stage_workers"); err != nil { t.Fatal(err) } if test.name == "padded" && (!strings.Contains(explanation.String(), "normalization") || !strings.Contains(explanation.String(), " extract ") || strings.Contains(explanation.String(), "derived extraction workers")) { t.Fatalf("explicit worker provenance = %s", explanation.String()) } }) } } func TestDocumentLoadAppliesOperationalDefaultsAndNormalization(t *testing.T) { t.Parallel() loaded, err := loadDocumentSource(configkit.Bytes("document.yml", []byte(`version: 5 concurrency: total_llm: 4 promptkit: profile_dir: " ./profiles " local_backend: endpoint: " https://localhost:8080/v1 " output: directory: " ./output " cache: chunk_plans: mode: " refresh " directory: " " checkpoints: directory: " " debug: directory: " ./debug " `)), LoadOptions{}) if err != nil { t.Fatal(err) } if loaded.Config.Concurrency.TotalLLM == nil || *loaded.Config.Concurrency.TotalLLM != 4 || loaded.Config.Concurrency.StageWorkers["extract"] != 4 { t.Fatalf("concurrency = %#v", loaded.Config.Concurrency) } workingDirectory, err := os.Getwd() if err != nil { t.Fatal(err) } if loaded.Config.PromptKit.ProfileDir == nil || *loaded.Config.PromptKit.ProfileDir != filepath.Join(workingDirectory, "profiles") || loaded.Config.PromptKit.LocalBackend == nil || loaded.Config.PromptKit.LocalBackend.Endpoint == nil || *loaded.Config.PromptKit.LocalBackend.Endpoint != "https://localhost:8080/v1" || loaded.Config.PromptKit.LocalBackend.ConcurrencyLimit == nil || *loaded.Config.PromptKit.LocalBackend.ConcurrencyLimit != 0 { t.Fatalf("promptkit = %#v", loaded.Config.PromptKit) } if loaded.Config.Output.Directory == nil || *loaded.Config.Output.Directory != filepath.Join(workingDirectory, "output") || loaded.Config.Cache.ChunkPlans.Mode == nil || *loaded.Config.Cache.ChunkPlans.Mode != "refresh" || loaded.Config.Cache.ChunkPlans.Directory == nil || *loaded.Config.Cache.ChunkPlans.Directory != "" || loaded.Config.Cache.Checkpoints.Directory == nil || *loaded.Config.Cache.Checkpoints.Directory != "" || loaded.Config.Debug.Directory == nil || *loaded.Config.Debug.Directory != filepath.Join(workingDirectory, "debug") { t.Fatalf("state = %#v, %#v, %#v", loaded.Config.Output, loaded.Config.Cache, loaded.Config.Debug) } if diagnostics := loaded.Diagnostics(); len(diagnostics) != 0 { t.Fatalf("diagnostics = %#v", diagnostics) } extractHistory := loaded.Metadata().History(configkit.Keys("concurrency", "stage_workers", "extract")) if len(extractHistory) == 0 || extractHistory[len(extractHistory)-1].Origin.Operation != configkit.ChangeDerivedDefault { t.Fatalf("extract history = %#v", extractHistory) } outputHistory := loaded.Metadata().History(configkit.Keys("output", "directory")) if len(outputHistory) == 0 || outputHistory[len(outputHistory)-1].Origin.Operation != configkit.ChangeNormalization { t.Fatalf("output history = %#v", outputHistory) } } func TestDocumentLoadDerivesWorkersAfterOverlayAndCLIOverrides(t *testing.T) { t.Parallel() directory := t.TempDir() main := filepath.Join(directory, "config.yml") overlays := filepath.Join(directory, "overlays") if err := os.Mkdir(overlays, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(main, []byte("version: 5\nconcurrency:\n total_llm: 4\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(overlays, "development.yml"), []byte("concurrency:\n total_llm: 3\n"), 0o600); err != nil { t.Fatal(err) } overlaid, err := loadDocument(main, LoadOptions{Overlay: "development"}) if err != nil { t.Fatal(err) } if overlaid.Config.Concurrency.TotalLLM == nil || *overlaid.Config.Concurrency.TotalLLM != 3 || overlaid.Config.Concurrency.StageWorkers["extract"] != 3 { t.Fatalf("overlaid concurrency = %#v", overlaid.Config.Concurrency) } overridden, err := loadDocument(main, LoadOptions{Overrides: []configkit.Override{{ Path: configkit.Keys("concurrency", "total_llm"), Value: 2, Flag: "--total-llm", }}}) if err != nil { t.Fatal(err) } if overridden.Config.Concurrency.TotalLLM == nil || *overridden.Config.Concurrency.TotalLLM != 2 || overridden.Config.Concurrency.StageWorkers["extract"] != 2 { t.Fatalf("overridden concurrency = %#v", overridden.Config.Concurrency) } overlaidAndOverridden, err := loadDocument(main, LoadOptions{Overlay: "development", Overrides: []configkit.Override{{ Path: configkit.Keys("concurrency", "total_llm"), Value: 2, Flag: "--total-llm", }}}) if err != nil { t.Fatal(err) } if overlaidAndOverridden.Config.Concurrency.TotalLLM == nil || *overlaidAndOverridden.Config.Concurrency.TotalLLM != 2 || overlaidAndOverridden.Config.Concurrency.StageWorkers["extract"] != 2 { t.Fatalf("overlaid and overridden concurrency = %#v", overlaidAndOverridden.Config.Concurrency) } } func TestDocumentLoadRetainsExplicitWorkerAndRejectsInvalidOperationalValues(t *testing.T) { t.Parallel() loaded, err := loadDocumentSource(configkit.Bytes("explicit.yml", []byte(`version: 5 concurrency: total_llm: 4 stage_workers: extract: 0 `)), LoadOptions{}) if loaded != nil { t.Fatalf("loaded = %#v, want validation failure", loaded.Config) } assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/concurrency/stage_workers/extract") _, err = loadDocumentSource(configkit.Bytes("invalid.yml", []byte(`version: 5 concurrency: total_llm: 0 output: directory: "" promptkit: local_backend: endpoint: "ftp://localhost" concurrency_limit: -1 `)), LoadOptions{}) var loadError *configkit.LoadError if !errors.As(err, &loadError) { t.Fatalf("error = %v, want ConfigKit load error", err) } seen := make(map[string]bool) for _, diagnostic := range loadError.Diagnostics() { if diagnostic.Code != configkit.CodeValidateError || diagnostic.Primary == nil { t.Fatalf("diagnostic = %#v", diagnostic) } seen[diagnostic.Path.String()] = true } for _, path := range []string{"/concurrency/total_llm", "/output/directory", "/promptkit/local_backend/endpoint", "/promptkit/local_backend/concurrency_limit"} { if !seen[path] { t.Fatalf("diagnostics missing %s: %#v", path, loadError.Diagnostics()) } } } func TestDocumentLoadDistinguishesRequiredAndOptionalPathValues(t *testing.T) { t.Parallel() loaded, err := loadDocumentSource(configkit.Bytes("cache.yml", []byte(`version: 5 cache: chunk_plans: directory: " " checkpoints: directory: " " `)), LoadOptions{}) if err != nil { t.Fatal(err) } if loaded.Config.Cache.ChunkPlans.Directory == nil || *loaded.Config.Cache.ChunkPlans.Directory != "" || loaded.Config.Cache.Checkpoints.Directory == nil || *loaded.Config.Cache.Checkpoints.Directory != "" { t.Fatalf("cache directories = %#v", loaded.Config.Cache) } _, err = loadDocumentSource(configkit.Bytes("output.yml", []byte("version: 5\noutput:\n directory: ' '\n")), LoadOptions{}) assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/output/directory") _, err = loadDocumentSource(configkit.Bytes("promptkit.yml", []byte("version: 5\npromptkit:\n profile_file: null\n local_backend: {}\n")), LoadOptions{}) assertDocumentDiagnostic(t, err, configkit.CodeResolveError, "/promptkit/profile_file") _, err = loadDocumentSource(configkit.Bytes("empty-source.yml", []byte("version: 5\npromptkit:\n profile_dir: ' '\n")), LoadOptions{}) assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/promptkit/profile_dir") _, err = loadDocumentSource(configkit.Bytes("missing-endpoint.yml", []byte("version: 5\npromptkit:\n local_backend: {}\n")), LoadOptions{}) assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/promptkit/local_backend/endpoint") } func TestDocumentLoadRejectsNegativePromptKitTransientRetries(t *testing.T) { t.Parallel() _, err := loadDocumentSource(configkit.Bytes("promptkit.yml", []byte("version: 5\npromptkit:\n transient_retries: -1\n")), LoadOptions{}) assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/promptkit/transient_retries") } func assertDocumentDiagnostic(t *testing.T, err error, code configkit.Code, path string) { t.Helper() var loadError *configkit.LoadError if !errors.As(err, &loadError) { t.Fatalf("error = %v, want ConfigKit load error", err) } for _, diagnostic := range loadError.Diagnostics() { if diagnostic.Code == code && diagnostic.Path.String() == path { return } } t.Fatalf("diagnostics = %#v, want %s at %s", loadError.Diagnostics(), code, path) }