package engine import ( "path/filepath" "reflect" "testing" "testing/fstest" ) func TestLayeredDestinationStrictCompositionBoundaries(t *testing.T) { t.Parallel() tests := []struct { name string inputs []Input wantPath []string want any alsoPath []string alsoWant any }{ { name: "disjoint peers at one destination", inputs: []Input{ {Source: Bytes("left", []byte("left: one\n")), Destination: Keys("application")}, {Source: Bytes("right", []byte("right: two\n")), Destination: Keys("application")}, }, wantPath: []string{"application", "right"}, want: "two", alsoPath: []string{"application", "left"}, alsoWant: "one", }, { name: "different destinations", inputs: []Input{ {Source: Bytes("left", []byte("value: shared\n")), Destination: Keys("left")}, {Source: Bytes("right", []byte("value: shared\n")), Destination: Keys("right")}, }, wantPath: []string{"right", "value"}, want: "shared", }, { name: "ancestor and descendant destinations", inputs: []Input{ {Source: Bytes("parent", []byte("branch: {base: one}\n")), Destination: Keys("application")}, {Source: Bytes("child", []byte("leaf: two\n")), Destination: Keys("application", "branch")}, }, wantPath: []string{"application", "branch", "leaf"}, want: "two", alsoPath: []string{"application", "branch", "base"}, alsoWant: "one", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() loaded, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[map[string]any]{ Underlays: []Underlay{{Inputs: test.inputs}}, }) if err != nil { t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) if got := layeredMapValue(t, result.Config, test.wantPath...); got != test.want { t.Fatalf("value at %v = %#v, want %#v", test.wantPath, got, test.want) } if len(test.alsoPath) != 0 { if got := layeredMapValue(t, result.Config, test.alsoPath...); got != test.alsoWant { t.Fatalf("value at %v = %#v, want %#v", test.alsoPath, got, test.alsoWant) } } }) } for _, test := range []struct { name string left string right string }{ {name: "equal values conflict", left: "value: same\n", right: "value: same\n"}, {name: "unequal values conflict", left: "value: left\n", right: "value: right\n"}, } { t.Run(test.name, func(t *testing.T) { t.Parallel() inputs := []Input{ {Source: Bytes("left", []byte(test.left)), Destination: Keys("application")}, {Source: Bytes("right", []byte(test.right)), Destination: Keys("application")}, } loaded, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[map[string]any]{ Underlays: []Underlay{{Inputs: inputs}}, }) if loaded != nil || groupedErrorCode(t, err) != CodeComposeConflict { t.Fatalf("strict composition result = %#v, %v", loaded, err) } }) } t.Run("strict conflict is not hidden by an upper override", func(t *testing.T) { inputs := []Input{ {Source: Bytes("left", []byte("value: left\n")), Destination: Keys("application")}, {Source: Bytes("right", []byte("value: right\n")), Destination: Keys("application")}, } loaded, err := LoadLayered( Input{Source: Bytes("main", []byte("value: upper\n")), Destination: Keys("application")}, testSecretBinding(), LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: inputs}}}, ) if loaded != nil || groupedErrorCode(t, err) != CodeComposeConflict { t.Fatalf("strict composition result = %#v, %v", loaded, err) } }) } func TestLayeredDestinationMatchesExplicitlyWrappedInput(t *testing.T) { t.Parallel() load := func(input Input) map[string]any { t.Helper() loaded, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[map[string]any]{ Underlays: []Underlay{{Inputs: []Input{input}}}, }) if err != nil { t.Fatalf("LoadLayered: %v", err) } return testLoaded(loaded).Config } placed := load(Input{Source: Bytes("placed", []byte("field: value\n")), Destination: Keys("application")}) authored := load(Input{Source: Bytes("authored", []byte("application: {field: value}\n"))}) if !reflect.DeepEqual(placed, authored) { t.Fatalf("placed configuration %#v differs from explicitly wrapped %#v", placed, authored) } } func TestEmptyMountedMappingRetainsLowerChildren(t *testing.T) { t.Parallel() destination := Keys("collections", "weather") loaded, err := LoadLayered( Input{Source: Bytes("empty-main", []byte("{}\n")), Destination: destination}, testSecretBinding(), LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{{ Source: Bytes("lower", []byte("prompt: {title: retained}\n")), Destination: destination, }}}}}, ) if err != nil { t.Fatalf("LoadLayered: %v", err) } if got := layeredMapValue(t, testLoaded(loaded).Config, "collections", "weather", "prompt", "title"); got != "retained" { t.Fatalf("lower child beneath empty mounted mapping = %#v, want retained", got) } } func TestLayeredDestinationOverlayRetainsOrdinaryValueSemantics(t *testing.T) { t.Parallel() filesystem := NewFilesystem("bundle", fstest.MapFS{ "overlays/test.yml": &fstest.MapFile{Data: []byte("settings:\n metadata:\n version: overlay\n")}, }) loaded, err := LoadLayered( Input{Source: Bytes("main", []byte("mapping: {shared: main}\nsequence: [9]\ncleared: null\nenabled: false\nmetadata: {version: main}\n")), Destination: Keys("settings")}, testSecretBinding(), LayeredOptions[map[string]any]{ Underlays: []Underlay{{Inputs: []Input{{ Source: Bytes("underlay", []byte("mapping: {retained: lower, shared: lower}\nsequence: [1, 2]\ncleared: {old: true}\nenabled: true\ncount: 7\nmetadata: {version: lower, label: retained}\n")), Destination: Keys("settings"), }}}}, Overlay: "test", OverlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem}, Overrides: []Override{{Path: Keys("settings", "count"), Value: 0, Flag: "--count"}}, }, ) if err != nil { t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) settings := layeredMap(t, result.Config, "settings") mapping := layeredMap(t, settings, "mapping") if mapping["retained"] != "lower" || mapping["shared"] != "main" { t.Fatalf("recursive mapping overlay = %#v", mapping) } sequence, ok := settings["sequence"].([]any) if !ok || len(sequence) != 1 || sequence[0] != int64(9) { t.Fatalf("sequence overlay = %#v", settings["sequence"]) } if value, present := settings["cleared"]; !present || value != nil { t.Fatalf("explicit null = %#v, present %v", value, present) } if value, ok := settings["enabled"].(bool); !ok || value { t.Fatalf("explicit false = %#v", settings["enabled"]) } if value, ok := settings["count"].(int64); !ok || value != 0 { t.Fatalf("explicit zero override = %#v", settings["count"]) } metadata := layeredMap(t, settings, "metadata") if metadata["version"] != "overlay" || metadata["label"] != "retained" { t.Fatalf("version-only overlay = %#v", metadata) } if _, present := settings["settings"]; present { t.Fatalf("upper tiers were incorrectly nested under the main destination: %#v", result.Config) } } func TestLayeredDestinationChecksVersionOnceOnComposedMain(t *testing.T) { t.Parallel() type appConfig struct { Version int8 `yaml:"version"` Count int `yaml:"count"` Main string `yaml:"main"` Imported string `yaml:"imported"` Lower string `yaml:"lower"` } type config struct { App appConfig `yaml:"app"` } filesystem := NewFilesystem("bundle", fstest.MapFS{ "main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: fragment.yml}]}\nversion: 6\ncount: 4\nmain: main\n")}, "fragment.yml": &fstest.MapFile{Data: []byte("imported: included\n")}, }) checks := 0 loaded, err := LoadLayered( Input{Source: filesystem.File("main.yml"), Imports: true, Destination: Keys("app")}, testSecretBinding(), LayeredOptions[config]{ Definition: Definition[config]{Version: &VersionRule{Path: Keys("app", "version"), Check: func(version Version, _ *Validation) error { checks++ if !version.Present || version.Text != "6" { t.Errorf("nested main version = %#v", version) } return nil }}}, Underlays: []Underlay{{Inputs: []Input{{ Source: Bytes("lower", []byte("lower: underlay\n")), Destination: Keys("app"), }}}}, }, ) if err != nil { t.Fatalf("LoadLayered: %v", err) } result := testLoaded(loaded) if checks != 1 { t.Fatalf("version hook calls = %d, want 1", checks) } if result.Config.App.Version != 6 || result.Config.App.Count != 4 || result.Config.App.Main != "main" || result.Config.App.Imported != "included" || result.Config.App.Lower != "underlay" { t.Fatalf("nested app = %#v", result.Config.App) } } func TestLayeredDestinationVersionClaimsUsePlacedApplicationPaths(t *testing.T) { t.Parallel() rule := &VersionRule{Path: Keys("app", "schema", "version"), Check: func(Version, *Validation) error { return nil }} for _, test := range []struct { name string body string wantCode Code wantValue any }{ {name: "underlay direct claim", body: "schema: {version: v2}\n", wantCode: CodeUnderlayVersion}, {name: "underlay ancestor claim", body: "schema: null\n", wantCode: CodeUnderlayVersion}, {name: "underlay mapping ancestor without claim", body: "schema: {name: ordinary}\n", wantValue: "ordinary"}, } { t.Run(test.name, func(t *testing.T) { t.Parallel() loaded, err := LoadLayered( Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[map[string]any]{ Definition: Definition[map[string]any]{Version: rule}, Underlays: []Underlay{{Inputs: []Input{{ Source: Bytes("underlay", []byte(test.body)), Destination: Keys("app"), }}}}, }, ) if test.wantCode != "" { if loaded != nil || groupedErrorCode(t, err) != test.wantCode { t.Fatalf("version claim result = %#v, %v", loaded, err) } return } if err != nil { t.Fatalf("nonclaiming mapping ancestor was rejected: %v", err) } result := testLoaded(loaded) if got := layeredMapValue(t, result.Config, "app", "schema", "name"); got != test.wantValue { t.Fatalf("nested ordinary value = %#v", got) } }) } for _, test := range []struct { name string body string wantCode Code }{ {name: "imported direct claim", body: "schema: {version: v2}\n", wantCode: CodeImportVersion}, {name: "imported ancestor claim", body: "schema: null\n", wantCode: CodeImportVersion}, {name: "imported mapping ancestor without claim", body: "schema: {name: ordinary}\n"}, } { t.Run(test.name, func(t *testing.T) { t.Parallel() directory := t.TempDir() writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\n") writeImportFile(t, directory, "fragment.yml", test.body) loaded, err := LoadLayered( Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[map[string]any]{ Definition: Definition[map[string]any]{Version: rule}, Underlays: []Underlay{{Inputs: []Input{{ Source: File(filepath.Join(directory, "main.yml")), Imports: true, Destination: Keys("app"), }}}}, }, ) if test.wantCode != "" { if loaded != nil || groupedErrorCode(t, err) != test.wantCode { t.Fatalf("version claim result = %#v, %v", loaded, err) } return } if err != nil { t.Fatalf("nonclaiming imported mapping ancestor was rejected: %v", err) } result := testLoaded(loaded) if got := layeredMapValue(t, result.Config, "app", "schema", "name"); got != "ordinary" { t.Fatalf("nested ordinary value = %#v", got) } }) } } func layeredMap(t *testing.T, value map[string]any, key string) map[string]any { t.Helper() result, ok := value[key].(map[string]any) if !ok { t.Fatalf("%q = %#v, want mapping", key, value[key]) } return result } func layeredMapValue(t *testing.T, value map[string]any, keys ...string) any { t.Helper() for _, key := range keys[:len(keys)-1] { value = layeredMap(t, value, key) } return value[keys[len(keys)-1]] }