package config import ( "io/fs" "os" "path" "path/filepath" "testing" ck "go.maximumdirect.net/configkit" ) func TestDefaultsAndVersion(t *testing.T) { result, err := Load(ck.Input{Source: ck.NoSource()}, "") if err != nil { t.Fatal(err) } if result.Config.Version != 3 || len(result.Config.Pipeline.Stages) != 5 { t.Fatalf("defaults: %+v", result.Config) } path := filepath.Join(t.TempDir(), "config.yml") for _, text := range []string{"version: 2\n", "version: null\n", "version: invalid\n"} { if err := os.WriteFile(path, []byte(text), 0644); err != nil { t.Fatal(err) } if _, err := Load(ck.Input{Source: ck.File(path)}, ""); err == nil { t.Fatalf("accepted %q", text) } } if err := os.WriteFile(path, []byte("pipeline:\n stages: []\n"), 0644); err != nil { t.Fatal(err) } empty, err := Load(ck.Input{Source: ck.File(path)}, "") if err != nil { t.Fatal(err) } if len(empty.Config.Pipeline.Stages) != 0 { t.Fatal("empty pipeline was not preserved") } } func TestMainContextImportsAndOverlay(t *testing.T) { dir := t.TempDir() if err := os.Mkdir(filepath.Join(dir, "overlays"), 0755); err != nil { t.Fatal(err) } for name, contents := range map[string]string{ "main.yml": "version: 3\nconfig:\n imports:\n - path: fragment.yml\noutput:\n schema: bare-segments\n", "fragment.yml": "context:\n description: imported\n", "overlays/selected.yml": "context:\n description: overlay\n", } { if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil { t.Fatal(err) } } result, err := Load(ck.Input{Source: ck.File(filepath.Join(dir, "main.yml"))}, "selected", ck.Override{Path: ck.Keys("context", "description"), Value: "override", Flag: "--description"}) if err != nil { t.Fatal(err) } if info, ok := result.Metadata().Lookup(ck.Keys("context", "description")); !ok || info.Origin.Layer != ck.LayerCLI { t.Fatalf("override origin = %+v", info) } if result.Config.Context.Description != "override" { t.Fatalf("description = %q", result.Config.Context.Description) } found := false for _, source := range result.Sources() { if source.Layer == ck.LayerMain && source.ImportedFrom == nil { context, ok := result.SourceContext(source.ID) if !ok { t.Fatal("main context missing") } data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, "fragment.yml")) if err != nil || len(data) == 0 { t.Fatalf("main context cannot access companion: %v", err) } found = true } } if !found { t.Fatal("main source missing") } if _, err := Load(ck.Input{Source: ck.File(filepath.Join(dir, "main.yml"))}, "missing"); err == nil { t.Fatal("missing overlay accepted") } if _, err := Load(ck.Input{Source: ck.NoSource()}, "selected"); err == nil { t.Fatal("overlay without context accepted") } } func TestDiscoveryAndSelectedFailure(t *testing.T) { first, second := t.TempDir(), t.TempDir() search := ck.Searches(ck.SearchDirectory(first), ck.SearchDirectory(second)) load := func() (*ck.Result[Config], error) { return Load(ck.Input{Source: ck.SearchConfig("audita", search)}, "") } result, err := load() if err != nil { t.Fatal(err) } for _, source := range result.Sources() { if source.Layer == ck.LayerMain { t.Fatalf("unexpected main source: %+v", source) } } if err := os.WriteFile(filepath.Join(second, "config.yml"), []byte("context:\n description: selected\n"), 0644); err != nil { t.Fatal(err) } result, err = load() if err != nil { t.Fatal(err) } if result.Config.Context.Description != "selected" { t.Fatalf("description = %q", result.Config.Context.Description) } if err := os.WriteFile(filepath.Join(first, "config.yml"), []byte("version: 2\n"), 0644); err != nil { t.Fatal(err) } if _, err := load(); err == nil { t.Fatal("invalid selected file fell through") } } func TestOverlayUsesMainDirectoryWithinWiderRoot(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "project") if err := os.MkdirAll(filepath.Join(dir, "overlays"), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "config.yml"), []byte("version: 3\n"), 0644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "overlays", "local.yml"), []byte("context:\n description: project\n"), 0644); err != nil { t.Fatal(err) } result, err := Load(ck.Input{Source: ck.File(filepath.Join(dir, "config.yml")), Filesystem: &ck.FilesystemContext{Root: root}}, "local") if err != nil { t.Fatal(err) } if result.Config.Context.Description != "project" { t.Fatalf("description = %q", result.Config.Context.Description) } } func TestSchemaConstraintsAndExplicitValues(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yml") for _, tc := range []struct { name, text string valid bool }{ {"nullable selections", "glossaries: null\nprotected_terms: []\nllm:\n validation_profile: null\n", true}, {"repeated modules", "pipeline:\n stages:\n - module: grammar\n - module: grammar\n", true}, {"removed backends", "backends: {}\n", false}, {"removed debug", "diagnostics:\n debug: true\n", false}, {"unknown module", "pipeline:\n stages:\n - module: unknown\n", false}, {"invalid chunk limit", "chunking:\n target_sections: 0\n", false}, {"invalid threshold", "thresholds:\n grammar: 1.1\n", false}, {"null group", "llm: null\n", false}, {"zero budgets", "llm:\n transient_retries: 0\n repair_attempts: 0\n", true}, } { t.Run(tc.name, func(t *testing.T) { if err := os.WriteFile(path, []byte(tc.text), 0644); err != nil { t.Fatal(err) } _, err := Load(ck.Input{Source: ck.File(path)}, "") if (err == nil) != tc.valid { t.Fatalf("load error: %v", err) } }) } } func TestOverridesFollowInheritance(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yml") data := `glossary_collections: common: entries: - id: a name: First category: character derived: extends: common ` if err := os.WriteFile(path, []byte(data), 0644); err != nil { t.Fatal(err) } result, err := Load(ck.Input{Source: ck.File(path)}, "", ck.Override{Path: ck.Keys("glossary_collections", "common", "entries"), Value: []any{map[string]any{"id": "a", "name": "Changed", "category": "character"}}, Flag: "--set"}) if err != nil { t.Fatalf("%v: %+v", err, err.(*ck.LoadError).Diagnostics()) } if got := result.Config.GlossaryCollections["derived"].Entries[0].Name; got != "First" { t.Fatalf("inherited name = %q", got) } if got := result.Config.GlossaryCollections["common"].Entries[0].Name; got != "Changed" { t.Fatalf("overridden name = %q", got) } }