package engine import ( "errors" "os" "path/filepath" "reflect" "runtime" "testing" ) type overlayTestConfig struct { Settings struct { Kept string `yaml:"kept"` Changed string `yaml:"changed"` Added string `yaml:"added"` } `yaml:"settings"` Values []string `yaml:"values"` Nullable *string `yaml:"nullable"` Value any `yaml:"value"` Schema map[string]string `yaml:"schema"` } func TestLoadAppliesSelectedOverlayAfterBaseAndBeforeDefaults(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: imports: [{path: base.yml}] settings: kept: main changed: base values: [base, stale] nullable: base value: {child: base} `) basePath := writeImportFile(t, directory, "base.yml", "settings: {added: imported}\n") overlayPath := writeImportFile(t, directory, "overlays/testing.yml", `settings: changed: overlay values: [overlay] nullable: null value: scalar `) result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{ Imports: true, Overlay: "testing", Definition: Definition[overlayTestConfig]{Defaults: func() overlayTestConfig { defaultValue := "default" return overlayTestConfig{ Values: []string{"default"}, Nullable: &defaultValue, Settings: struct { Kept string `yaml:"kept"` Changed string `yaml:"changed"` Added string `yaml:"added"` }{Kept: "default", Changed: "default", Added: "default"}, } }}, }) if err != nil { t.Fatal(err) } if result.Config.Settings.Kept != "main" || result.Config.Settings.Changed != "overlay" || result.Config.Settings.Added != "imported" { t.Fatalf("settings = %#v", result.Config.Settings) } if !reflect.DeepEqual(result.Config.Values, []string{"overlay"}) || result.Config.Nullable != nil || result.Config.Value != "scalar" { t.Fatalf("overlay values = values %#v nullable %#v value %#v", result.Config.Values, result.Config.Nullable, result.Config.Value) } sources := result.Sources() wantPaths := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, basePath), canonicalTestPath(t, overlayPath)} gotPaths := make([]string, len(sources)) for index, source := range sources { gotPaths[index] = source.Path } if !reflect.DeepEqual(gotPaths, wantPaths) || sources[2].Layer != LayerOverlay { t.Fatalf("sources = %#v", sources) } changed, _ := result.Metadata().Lookup(Keys("settings", "changed")) kept, _ := result.Metadata().Lookup(Keys("settings", "kept")) if changed.Origin.Layer != LayerOverlay || changed.Origin.Locations[0].SourceID != "source-3" || kept.Origin.Layer != LayerBase { t.Fatalf("origins = changed %#v kept %#v", changed, kept) } if _, ok := result.Metadata().Lookup(Keys("value", "child")); ok { t.Fatal("removed child remains effective") } childHistory := result.Metadata().History(Keys("value", "child")) if len(childHistory) < 2 || childHistory[len(childHistory)-1].Path.String() != "/value" || childHistory[len(childHistory)-1].Origin.Layer != LayerOverlay { t.Fatalf("removed child history = %#v", childHistory) } if _, ok := result.Metadata().Lookup(Keys("values").Index(1)); ok { t.Fatal("removed sequence index remains effective") } indexHistory := result.Metadata().History(Keys("values").Index(1)) if len(indexHistory) < 2 || indexHistory[len(indexHistory)-1].Path.String() != "/values" { t.Fatalf("removed sequence history = %#v", indexHistory) } } func TestLoadSelectedOverlayAppliesCollectionOperationsAndEvidence(t *testing.T) { t.Parallel() type item struct { ID string `yaml:"id"` Value string `yaml:"value"` Children []item `yaml:"children"` } type config struct { Items []item `yaml:"items"` } tests := []struct { name string mode string overlay string want []item }{ { name: "append", mode: "append", overlay: `configkit: sequences: - destination: [items] key: id mode: append items: [{id: c, value: added}] `, want: []item{{ID: "a", Value: "old"}, {ID: "b", Value: "kept"}, {ID: "c", Value: "added"}}, }, { name: "upsert replaces complete records at retained positions", mode: "upsert", overlay: `configkit: sequences: - destination: [items] key: id mode: upsert items: [{id: a, value: new}] `, want: []item{{ID: "a", Value: "new"}, {ID: "b", Value: "kept"}}, }, { name: "replace", mode: "replace", overlay: `configkit: sequences: - destination: [items] key: id mode: replace items: [{id: c, value: replacement}] `, want: []item{{ID: "c", Value: "replacement"}}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: a, value: old, children: [{id: child}]}, {id: b, value: kept}]\n") writeImportFile(t, directory, "overlays/testing.yml", test.overlay) result, err := testLoad[config](File(mainPath), Options[config]{Overlay: "testing"}) if err != nil { t.Fatal(err) } if len(result.Config.Items) != len(test.want) { t.Fatalf("items = %#v, want %#v", result.Config.Items, test.want) } for index, want := range test.want { if result.Config.Items[index].ID != want.ID || result.Config.Items[index].Value != want.Value { t.Fatalf("items[%d] = %#v, want id %q value %q", index, result.Config.Items[index], want.ID, want.Value) } } if _, found := result.Metadata().Lookup(Keys("configkit")); found { t.Fatal("overlay collection controls reached application metadata") } if test.mode == "append" { base, _ := result.Metadata().Lookup(Keys("items").Index(0).Key("value")) added, _ := result.Metadata().Lookup(Keys("items").Index(2).Key("value")) if base.Origin.Layer != LayerBase || added.Origin.Layer != LayerOverlay { t.Fatalf("append origins = base %#v added %#v", base.Origin, added.Origin) } history := result.Metadata().History(Keys("items").Index(2).Key("value")) if len(history) == 0 || history[len(history)-1].Origin.Layer != LayerOverlay { t.Fatalf("append history = %#v", history) } } if test.mode == "upsert" { if _, found := result.Metadata().Lookup(Keys("items").Index(0).Key("children").Index(0)); found { t.Fatal("upsert retained a discarded child") } history := result.Metadata().History(Keys("items").Index(0).Key("children").Index(0)) if len(history) < 2 { t.Fatalf("discarded child history = %#v", history) } } }) } } func TestLoadSelectedOverlayReconcilesPendingAndRetainedCollectionPolicy(t *testing.T) { t.Parallel() requireCode := func(t *testing.T, main, overlay string, code Code) { t.Helper() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", main) writeImportFile(t, directory, "overlays/testing.yml", overlay) _, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) var loadError *LoadError if !errors.As(err, &loadError) || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != code { t.Fatalf("load error = %#v, want %s", err, code) } } t.Run("key only validates retained records", func(t *testing.T) { requireCode(t, "items: [{id: duplicate}, {id: duplicate}]\n", `configkit: sequences: - destination: [items] key: id `, CodeSequenceConflict) }) t.Run("mode only retains lower identity", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: sequences: - destination: [items] key: id mode: append items: [{id: base}] `) writeImportFile(t, directory, "overlays/testing.yml", `configkit: sequences: - destination: [items] mode: append items: [{id: overlay}] `) result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}, {ID: "overlay"}}) { t.Fatalf("items = %#v", result.Config.Items) } }) t.Run("lower explicit mode remains active when overlay omits it", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: sequences: - destination: [items] key: id mode: append items: [{id: base}] `) writeImportFile(t, directory, "overlays/testing.yml", "items: [{id: overlay}]\n") result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) if err != nil || !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}, {ID: "overlay"}}) { t.Fatalf("retained operation = %#v, %v", result, err) } }) t.Run("overlay identity must agree with retained identity", func(t *testing.T) { requireCode(t, `configkit: sequences: - destination: [items] key: id items: [{id: base}] `, `configkit: sequences: - destination: [items] key: slug items: [{id: overlay}] `, CodeSequenceConflict) }) t.Run("replace accepts null and append rejects it", func(t *testing.T) { for _, test := range []struct { mode string want Code }{ {mode: "replace"}, {mode: "append", want: CodeSequenceInvalid}, } { overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: null\n" if test.want != "" { requireCode(t, "items: [{id: base}]\n", overlay, test.want) continue } directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: base}]\n") writeImportFile(t, directory, "overlays/testing.yml", overlay) result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) if err != nil || result.Config.Items != nil { t.Fatalf("replace null = %#v, %v", result, err) } } }) t.Run("empty contributions and kind mismatches", func(t *testing.T) { for _, test := range []struct { name, mode, value string want int wantCode Code }{ {name: "append empty", mode: "append", value: "[]", want: 1}, {name: "upsert empty", mode: "upsert", value: "[]", want: 1}, {name: "replace empty", mode: "replace", value: "[]", want: 0}, {name: "append mapping", mode: "append", value: "{id: overlay}", wantCode: CodeSequenceInvalid}, } { t.Run(test.name, func(t *testing.T) { overlay := "configkit:\n sequences:\n - destination: [items]\n key: id\n mode: " + test.mode + "\nitems: " + test.value + "\n" if test.wantCode != "" { requireCode(t, "items: [{id: base}]\n", overlay, test.wantCode) return } directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "items: [{id: base}]\n") writeImportFile(t, directory, "overlays/testing.yml", overlay) result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) if err != nil || len(result.Config.Items) != test.want { t.Fatalf("%s result = %#v, %v", test.name, result, err) } }) } }) } func TestLoadSelectedOverlayKeepsCollectionPoliciesIndependent(t *testing.T) { t.Parallel() directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", `configkit: sequences: - destination: [items] key: id mode: append - destination: [catalog, items] key: id mode: append items: [{id: base}] catalog: {items: [{id: catalog-base}]} `) writeImportFile(t, directory, "overlays/testing.yml", `configkit: sequences: - destination: [items] mode: upsert - destination: [catalog, items] mode: append items: [{id: base}] catalog: {items: [{id: catalog-overlay}]} `) result, err := testLoad[ordinarySequenceConfig](File(mainPath), Options[ordinarySequenceConfig]{Overlay: "testing"}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result.Config.Items, []ordinarySequenceItem{{ID: "base"}}) || !reflect.DeepEqual(result.Config.Catalog.Items, []ordinarySequenceItem{{ID: "catalog-base"}, {ID: "catalog-overlay"}}) { t.Fatalf("independent overlay policies = items %#v, catalog %#v", result.Config.Items, result.Config.Catalog.Items) } } func TestLoadOverlaySelectionUsesOneFixedYAMLFile(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "settings: {kept: base}\n") writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: selected}\n") writeImportFile(t, directory, "profiles/testing.yml", "settings: {changed: legacy}\n") writeImportFile(t, directory, "overlays/default.yml", "settings: {changed: forbidden}\n") for _, name := range []string{"", "default"} { result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: name}) if err != nil || result.Config.Settings.Changed != "" || len(result.Sources()) != 1 { t.Fatalf("inactive overlay %q = %#v, %v", name, result, err) } } selected, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}) if err != nil || selected.Config.Settings.Changed != "selected" || len(selected.Sources()) != 2 || selected.Sources()[1].Label != "overlays/testing.yml" || selected.Sources()[1].Layer != LayerOverlay { t.Fatalf("selected overlay = %#v, %v", selected, err) } t.Run("legacy directory is not selected", func(t *testing.T) { legacyDirectory := t.TempDir() legacyMain := writeImportFile(t, legacyDirectory, "main.yml", "settings: {kept: base}\n") writeImportFile(t, legacyDirectory, "profiles/testing.yml", "settings: {changed: legacy}\n") requireOverlayLoadCode(t, File(legacyMain), Options[overlayTestConfig]{Overlay: "testing"}, CodeOverlayMissing) }) for _, name := range []string{"../testing", "testing.yml", "one,two", "with space", "é", "bad\x00name"} { t.Run(name, func(t *testing.T) { requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: name}, CodeOptionsInvalid) }) } requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "missing"}, CodeOverlayMissing) writeImportFile(t, directory, "overlays/fallback.yaml", "settings: {changed: fallback}\n") requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "fallback"}, CodeOverlayMissing) } func TestLoadOverlayRejectsDirectivesVersionsAndBaseAliases(t *testing.T) { versionRule := &VersionRule{Path: Keys("schema", "version"), Check: func(version Version, _ *Validation) error { if !version.Present || version.Text != "4" { return errors.New("unexpected version") } return nil }} for _, test := range []struct { name string overlay string code Code }{ {name: "root directive", overlay: "configkit: {}\n", code: CodeOverlayDirective}, {name: "nested directive", overlay: "settings: {configkit: {}}\n", code: CodeOverlayDirective}, {name: "equal integer version", overlay: "schema: {version: 4}\n", code: CodeOverlayVersion}, {name: "ancestor scalar", overlay: "schema: null\n", code: CodeOverlayVersion}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n") writeImportFile(t, directory, "overlays/testing.yml", test.overlay) requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing", Definition: Definition[overlayTestConfig]{Version: versionRule}}, test.code) }) } t.Run("mapping sibling is allowed", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "schema: {version: 4}\n") writeImportFile(t, directory, "overlays/testing.yml", "schema: {other: allowed}\n") result, err := testLoad[overlayTestConfig](File(mainPath), Options[overlayTestConfig]{Overlay: "testing", Definition: Definition[overlayTestConfig]{Version: versionRule}}) if err != nil || result.Config.Schema["other"] != "allowed" { t.Fatalf("mapping sibling = %#v, %v", result, err) } }) t.Run("overlay aliases imported base", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: overlays/testing.yml}]}\n") writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: value}\n") requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Imports: true, Overlay: "testing"}, CodeOverlayRepeated) }) } func TestLoadOverlayKeepsBaseFailuresAndFilesystemContainment(t *testing.T) { t.Run("base parse precedes overlay", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "settings: [\n") writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: overlay}\n") requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeYAMLInvalid) }) t.Run("overlay parse", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "{}\n") writeImportFile(t, directory, "overlays/testing.yml", "settings: [\n") loadError := requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeYAMLInvalid) if loadError.Diagnostics()[0].Primary.SourceID != "source-2" || len(loadError.Sources()) != 2 { t.Fatalf("overlay parse attribution = %#v / %#v", loadError.Diagnostics(), loadError.Sources()) } }) if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { t.Run("symlink escape", func(t *testing.T) { root := t.TempDir() outside := t.TempDir() outsideOverlay := writeImportFile(t, outside, "testing.yml", "settings: {changed: outside}\n") if err := os.Mkdir(filepath.Join(root, "overlays"), 0o755); err != nil { t.Fatal(err) } if err := os.Symlink(outsideOverlay, filepath.Join(root, "overlays/testing.yml")); err != nil { t.Skipf("symlinks unavailable: %v", err) } mainPath := writeImportFile(t, root, "main.yml", "{}\n") requireOverlayLoadCode(t, File(mainPath), Options[overlayTestConfig]{Overlay: "testing"}, CodeOverlayOutsideRoot) }) } t.Run("byte source context", func(t *testing.T) { directory := t.TempDir() writeImportFile(t, directory, "overlays/testing.yml", "settings: {changed: selected}\n") result, err := testLoad[overlayTestConfig](Bytes("s3://bucket/main.yml", []byte("{}\n")), Options[overlayTestConfig]{ Overlay: "testing", Filesystem: &FilesystemContext{BaseDir: directory, Root: directory}, }) if err != nil || result.Config.Settings.Changed != "selected" { t.Fatalf("byte overlay = %#v, %v", result, err) } requireOverlayLoadCode(t, Bytes("memory.yml", []byte("{}\n")), Options[overlayTestConfig]{Overlay: "testing"}, CodeOptionsInvalid) }) } func TestLoadOverlayCountsAgainstAggregateBudgets(t *testing.T) { for _, test := range []struct { name string limits func(parsedSource, int) importLimits }{ {name: "source count", limits: func(_ parsedSource, _ int) importLimits { return importLimits{sources: 1, bytes: maxTotalBytes, nodes: maxDocumentNodes} }}, {name: "aggregate bytes", limits: func(main parsedSource, overlayBytes int) importLimits { return importLimits{sources: 2, bytes: main.bytes + int64(overlayBytes) - 1, nodes: maxDocumentNodes} }}, {name: "aggregate nodes", limits: func(main parsedSource, _ int) importLimits { return importLimits{sources: 2, bytes: maxTotalBytes, nodes: countInputNodes(main.document) + 1} }}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "{}\n") overlayData := "settings: {changed: selected}\n" writeImportFile(t, directory, "overlays/testing.yml", overlayData) main, diagnostics := parseSource(File(mainPath), "source-1", defaultParseLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } base, diagnostics := composeDocuments(main.document) if len(diagnostics) != 0 { t.Fatal(diagnostics) } base.inputBytes = main.bytes base.inputNodes = countInputNodes(main.document) _, _, diagnostics = loadSelectedOverlayDocument(File(mainPath), nil, main, base, sourceInfos(main.source), "testing", nil, test.limits(main, len(overlayData))) if len(diagnostics) == 0 || diagnostics[0].Code != CodeOverlayLimitExceeded { t.Fatalf("diagnostics = %#v", diagnostics) } }) } } func requireOverlayLoadCode(t *testing.T, source Source, options Options[overlayTestConfig], code Code) *LoadError { t.Helper() result, err := testLoad[overlayTestConfig](source, options) if result != nil || err == nil { t.Fatalf("result, error = %#v, %v", result, err) } var loadError *LoadError if !errors.As(err, &loadError) { t.Fatalf("error type = %T", err) } diagnostics := loadError.Diagnostics() if len(diagnostics) == 0 || diagnostics[0].Code != code { t.Fatalf("diagnostics = %#v, want %s", diagnostics, code) } return loadError }