package config import ( "errors" "os" "path/filepath" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" ) 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 TestDocumentLoadDerivesWorkersAfterProfileAndCLIOverrides(t *testing.T) { t.Parallel() directory := t.TempDir() main := filepath.Join(directory, "config.yml") profiles := filepath.Join(directory, "profiles") if err := os.Mkdir(profiles, 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(profiles, "development.yml"), []byte("concurrency:\n total_llm: 3\n"), 0o600); err != nil { t.Fatal(err) } profiled, err := loadDocument(main, LoadOptions{Profile: "development"}) if err != nil { t.Fatal(err) } if profiled.Config.Concurrency.TotalLLM == nil || *profiled.Config.Concurrency.TotalLLM != 3 || profiled.Config.Concurrency.StageWorkers["extract"] != 3 { t.Fatalf("profiled concurrency = %#v", profiled.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) } } 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 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) }