package engine import ( "errors" "math" "path/filepath" "reflect" "strings" "testing" "time" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) type overrideLifecycleSettings struct { Target string `yaml:"target"` Count int `yaml:"count"` Enabled bool `yaml:"enabled"` Text string `yaml:"text"` Items []string `yaml:"items"` Maybe *string `yaml:"maybe"` Labels map[string]string `yaml:"labels"` APIKey Secret `yaml:"api_key"` } type overrideLifecycleConfig struct { Version string `yaml:"version"` Settings overrideLifecycleSettings `yaml:"settings"` } func TestOverridesRunAtHighestInputPrecedenceBeforeResolutionAndValidation(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports: [{path: base.yml}]\nversion: v1\n") writeImportFile(t, directory, "base.yml", `settings: target: base count: 4 enabled: true text: base items: [base] maybe: base labels: {base: retained} api_key: BASE_SECRET `) writeImportFile(t, directory, filepath.Join("overlays", "testing.yml"), `settings: target: overlay labels: {overlay: retained} `) validated := false overrides := []Override{ {Path: Keys("settings", "target"), Value: " cli ", Flag: "--target"}, {Path: Keys("settings", "count"), Value: 0, Flag: "--count"}, {Path: Keys("settings", "enabled"), Value: false, Flag: "--enabled"}, {Path: Keys("settings", "text"), Value: "", Flag: "--text"}, {Path: Keys("settings", "items"), Value: []any{}, Flag: "--items"}, {Path: Keys("settings", "maybe"), Value: nil, Flag: "--maybe"}, {Path: Keys("settings", "labels"), Value: map[string]any{"cli": "selected"}, Flag: "--label"}, {Path: Keys("settings", "api_key"), Value: "CLI_SECRET", Flag: "--api-key-env"}, } result, err := testLoadWithSecretLookup(File(mainPath), func(name string) (string, bool) { if name != "CLI_SECRET" { t.Fatalf("looked up superseded secret name %q", name) } return "credential-value", true }, Options[overrideLifecycleConfig]{ Imports: true, Overlay: "testing", Overrides: overrides, Definition: Definition[overrideLifecycleConfig]{ Defaults: func() overrideLifecycleConfig { return overrideLifecycleConfig{Settings: overrideLifecycleSettings{Target: "default", Count: 9, Text: "default", Items: []string{"default"}}} }, Version: &VersionRule{Path: Keys("version"), Check: func(version Version, validation *Validation) error { if version.Text != "v1" { validation.Error(Keys("version"), "unsupported version") } return nil }}, Resolve: func(configuration *overrideLifecycleConfig, resolution *Resolution) error { Normalize(resolution, Keys("settings", "target"), &configuration.Settings.Target, strings.TrimSpace(configuration.Settings.Target), "trimmed target") return nil }, Validate: func(configuration *overrideLifecycleConfig, validation *Validation) { validated = configuration.Settings.Target == "cli" && configuration.Settings.APIKey.Value() == "credential-value" }, }, }) if err != nil { t.Fatalf("testLoadWithSecretLookup() error = %v", err) } want := overrideLifecycleSettings{ Target: "cli", Labels: map[string]string{"base": "retained", "overlay": "retained", "cli": "selected"}, APIKey: result.Config.Settings.APIKey, } if !validated || result.Config.Settings.Count != 0 || result.Config.Settings.Enabled || result.Config.Settings.Text != "" || len(result.Config.Settings.Items) != 0 || result.Config.Settings.Items == nil || result.Config.Settings.Maybe != nil || result.Config.Settings.Target != want.Target || !reflect.DeepEqual(result.Config.Settings.Labels, want.Labels) { t.Fatalf("effective settings = %#v", result.Config.Settings) } sources := result.Sources() if len(sources) != 3+len(overrides) || sources[0].Layer != LayerBase || sources[1].Layer != LayerBase || sources[2].Layer != LayerOverlay { t.Fatalf("sources = %#v", sources) } for index, override := range overrides { if sources[index+3].Layer != LayerCLI || sources[index+3].Label != override.Flag || sources[index+3].Path != "" { t.Fatalf("CLI source %d = %#v", index, sources[index+3]) } } history := result.Metadata().History(Keys("settings", "target")) wantLayers := []Layer{LayerDefault, LayerBase, LayerOverlay, LayerCLI, LayerResolution} if got := historyLayers(history); !reflect.DeepEqual(got, wantLayers) { t.Fatalf("target history layers = %#v, want %#v; history %#v", got, wantLayers, history) } } func TestOverridesSupportDefaultsOnlyMapMergeListReplacementAndNumericKeys(t *testing.T) { type config struct { Labels map[string]string `yaml:"labels"` Items []int `yaml:"items"` Numbers map[string]int `yaml:"numbers"` Dynamic any `yaml:"dynamic"` } labels := map[string]any{"0": "zero", "new": "value"} items := []any{0, 2} dynamic := map[string]any{ "int": int8(1), "uint": uint16(2), "float": float32(3.5), "bool": false, "null": nil, "list": []any{"value"}, "map": map[string]any{"key": "value"}, } result, err := testLoad(NoSource(), Options[config]{ Definition: Definition[config]{Defaults: func() config { return config{Labels: map[string]string{"kept": "default"}, Items: []int{9}} }}, Overrides: []Override{ {Path: parseOverrideTestPath(t, "/labels"), Value: labels, Flag: "--labels"}, {Path: Keys("items"), Value: items, Flag: "--items"}, {Path: parseOverrideTestPath(t, "/numbers/0"), Value: 7, Flag: "--number"}, {Path: Keys("dynamic"), Value: dynamic, Flag: "--dynamic"}, }, }) labels["new"] = "mutated" items[0] = 99 dynamic["int"] = 99 if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result.Config.Labels, map[string]string{"kept": "default", "0": "zero", "new": "value"}) || !reflect.DeepEqual(result.Config.Items, []int{0, 2}) || result.Config.Numbers["0"] != 7 || result.Config.Dynamic.(map[string]any)["int"] != int64(1) { t.Fatalf("defaults-only overrides = %#v", result.Config) } var effective strings.Builder if err := result.WriteEffective(&effective); err != nil { t.Fatal(err) } if strings.Contains(effective.String(), "mutated") || !strings.Contains(effective.String(), "new: value") { t.Fatalf("detached effective output = %q", effective.String()) } if info, ok := result.Metadata().Lookup(Keys("labels", "0")); !ok || !info.Explicit || info.Origin.Layer != LayerCLI { t.Fatalf("numeric mapping key metadata = %#v, %v", info, ok) } } func TestOverrideValidationRejectsPathsOverlapValuesAndProtectedTargets(t *testing.T) { type config struct { Version string `yaml:"version"` Scalar string `yaml:"scalar"` Items []string `yaml:"items"` Nested map[string]any `yaml:"nested"` Unknown string `yaml:"unknown"` } base := Bytes("override-errors.yml", []byte("version: 4\nscalar: value\nitems: [one]\nnested: {}\n")) version := &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }} cycle := map[string]any{} cycle["self"] = cycle deep := any("leaf") for range maxDocumentDepth + 1 { deep = []any{deep} } for _, test := range []struct { name string overrides []Override code Code }{ {name: "root", overrides: []Override{{Path: Keys(), Value: "x", Flag: "--root"}}, code: CodeOverrideInvalidPath}, {name: "index", overrides: []Override{{Path: Keys("items").Index(0), Value: "x", Flag: "--item"}}, code: CodeOverrideInvalidPath}, {name: "parsed sequence index", overrides: []Override{{Path: parseOverrideTestPath(t, "/items/0"), Value: "x", Flag: "--item"}}, code: CodeOverrideInvalidPath}, {name: "scalar ancestor", overrides: []Override{{Path: Keys("scalar", "child"), Value: "x", Flag: "--child"}}, code: CodeOverrideInvalidPath}, {name: "duplicate", overrides: []Override{{Path: Keys("scalar"), Value: "a", Flag: "--first"}, {Path: Keys("scalar"), Value: "b", Flag: "--second"}}, code: CodeOverrideDuplicate}, {name: "overlap", overrides: []Override{{Path: Keys("nested"), Value: map[string]any{}, Flag: "--nested"}, {Path: Keys("nested", "child"), Value: "x", Flag: "--child"}}, code: CodeOverrideOverlap}, {name: "unsupported", overrides: []Override{{Path: Keys("scalar"), Value: time.Second, Flag: "--scalar"}}, code: CodeOverrideInvalidValue}, {name: "cycle", overrides: []Override{{Path: Keys("nested"), Value: cycle, Flag: "--nested"}}, code: CodeOverrideInvalidValue}, {name: "nonfinite", overrides: []Override{{Path: Keys("scalar"), Value: math.Inf(1), Flag: "--scalar"}}, code: CodeOverrideInvalidValue}, {name: "depth", overrides: []Override{{Path: Keys("nested"), Value: deep, Flag: "--nested"}}, code: CodeOverrideLimitExceeded}, {name: "record bytes", overrides: []Override{{Path: Keys("scalar"), Value: strings.Repeat("x", maxSourceBytes+1), Flag: "--scalar"}}, code: CodeOverrideLimitExceeded}, {name: "directive path", overrides: []Override{{Path: Keys("configkit", "x"), Value: true, Flag: "--directive"}}, code: CodeOverrideDirective}, {name: "directive value", overrides: []Override{{Path: Keys("nested"), Value: map[string]any{"configkit": map[string]any{}}, Flag: "--nested"}}, code: CodeOverrideDirective}, {name: "version", overrides: []Override{{Path: Keys("version"), Value: "v2", Flag: "--version"}}, code: CodeOverrideVersion}, {name: "version ancestor", overrides: []Override{{Path: Keys("version"), Value: map[string]any{"child": "v2"}, Flag: "--version"}}, code: CodeOverrideVersion}, {name: "label", overrides: []Override{{Path: Keys("scalar"), Value: "x", Flag: "\nCANARY"}}, code: CodeOverrideInvalidLabel}, } { t.Run(test.name, func(t *testing.T) { result, err := testLoad(base, Options[config]{Definition: Definition[config]{Version: version}, Overrides: test.overrides}) if result != nil { t.Fatalf("result = %#v, want nil", result) } loadError := requireSecretLoadCode(t, err, test.code) if strings.Contains(fmtLoadError(loadError), strings.Repeat("x", 64)) { t.Fatal("override diagnostic leaked a raw value") } }) } } func TestOverrideOverlapIndexPreservesFirstAcceptedSource(t *testing.T) { type config struct { Nested map[string]any `yaml:"nested"` } base := Bytes("override-overlap.yml", []byte("nested: {}\n")) _, err := testLoad(base, Options[config]{Overrides: []Override{ {Path: Keys("nested"), Value: map[string]any{}, Flag: "--nested"}, {Path: Keys("nested", "first"), Value: "one", Flag: "--first"}, {Path: Keys("nested", "second"), Value: "two", Flag: "--second"}, }}) if err == nil { t.Fatal("load succeeded with overlapping overrides") } var loadErr *LoadError if !errors.As(err, &loadErr) { t.Fatalf("error = %T %v, want *LoadError", err, err) } diagnostics := loadErr.Diagnostics() var conflicts []Diagnostic for _, diagnostic := range diagnostics { if diagnostic.Code == CodeOverrideOverlap { conflicts = append(conflicts, diagnostic) } } if len(conflicts) != 2 { t.Fatalf("overlap diagnostics = %#v, want two conflicts", diagnostics) } for index, path := range []string{"/nested/first", "/nested/second"} { if conflicts[index].Path.String() != path || len(conflicts[index].Related) != 1 || conflicts[index].Related[0].SourceID != "source-2" { t.Fatalf("conflict[%d] = %#v, want path %q related to first CLI source", index, conflicts[index], path) } } } func TestOverrideOverlapIndexIncludesAcceptedPathWithInvalidValue(t *testing.T) { type config struct { Nested map[string]any `yaml:"nested"` } base := Bytes("override-value-overlap.yml", []byte("nested: {}\n")) _, err := testLoad(base, Options[config]{Overrides: []Override{ {Path: Keys("nested"), Value: time.Second, Flag: "--nested"}, {Path: Keys("nested", "child"), Value: "value", Flag: "--child"}, }}) if err == nil { t.Fatal("load succeeded with an invalid value and overlapping path") } var loadErr *LoadError if !errors.As(err, &loadErr) { t.Fatalf("error = %T %v, want *LoadError", err, err) } var invalidValue, overlap bool for _, diagnostic := range loadErr.Diagnostics() { switch diagnostic.Code { case CodeOverrideInvalidValue: invalidValue = diagnostic.Path.String() == "/nested" case CodeOverrideOverlap: overlap = diagnostic.Path.String() == "/nested/child" && len(diagnostic.Related) == 1 && diagnostic.Related[0].SourceID == "source-2" } } if !invalidValue || !overlap { t.Fatalf("diagnostics = %#v, want invalid parent value and child overlap related to its source", loadErr.Diagnostics()) } } func TestRejectedOverridePathDoesNotCauseOverlap(t *testing.T) { type config struct { Scalar string `yaml:"scalar"` } base := Bytes("override-rejected-overlap.yml", []byte("scalar: value\n")) _, err := testLoad(base, Options[config]{Overrides: []Override{ {Path: Keys("scalar", "child"), Value: "invalid", Flag: "--child"}, {Path: Keys("scalar"), Value: "valid", Flag: "--scalar"}, }}) if err == nil { t.Fatal("load succeeded with a path traversing a scalar") } var loadErr *LoadError if !errors.As(err, &loadErr) { t.Fatalf("error = %T %v, want *LoadError", err, err) } diagnostics := loadErr.Diagnostics() if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideInvalidPath || diagnostics[0].Path.String() != "/scalar/child" { t.Fatalf("diagnostics = %#v, want only the invalid descendant path", diagnostics) } } func TestVersionRejectedOverridePathDoesNotCauseOverlap(t *testing.T) { type config struct { Nested map[string]any `yaml:"nested"` } base := Bytes("override-version-overlap.yml", []byte("nested: {}\n")) version := &VersionRule{Path: Keys("nested", "locked"), Check: func(Version, *Validation) error { return nil }} _, err := testLoad(base, Options[config]{ Definition: Definition[config]{Version: version}, Overrides: []Override{ {Path: Keys("nested"), Value: map[string]any{}, Flag: "--nested"}, {Path: Keys("nested", "other"), Value: "allowed", Flag: "--other"}, }, }) if err == nil { t.Fatal("load succeeded despite an override of a schema-version ancestor") } var loadErr *LoadError if !errors.As(err, &loadErr) { t.Fatalf("error = %T %v, want *LoadError", err, err) } diagnostics := loadErr.Diagnostics() if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideVersion || diagnostics[0].Path.String() != "/nested" { t.Fatalf("diagnostics = %#v, want only the protected ancestor path", diagnostics) } } func TestEscapedOverrideKeysStayDistinctFromMultipleSegments(t *testing.T) { type config struct { Nested map[string]any `yaml:"nested"` } base := Bytes("override-escaped-keys.yml", []byte("nested: {}\n")) _, err := testLoad(base, Options[config]{Overrides: []Override{ {Path: Keys("nested", "a/b"), Value: "single key", Flag: "--single"}, {Path: Keys("nested", "a", "b"), Value: "nested key", Flag: "--nested"}, }}) if err != nil { t.Fatalf("load rejected distinct escaped path tokens: %v", err) } } func TestOverrideCannotReplaceSchemaVersionAncestor(t *testing.T) { type schemaConfig struct { Version string `yaml:"version"` } type config struct { Schema schemaConfig `yaml:"schema"` } _, err := testLoad(Bytes("version.yml", []byte("schema: {version: 4}\n")), Options[config]{ Definition: Definition[config]{Version: &VersionRule{Path: Keys("schema", "version"), Check: func(Version, *Validation) error { return nil }}}, Overrides: []Override{{Path: Keys("schema"), Value: map[string]any{"other": "value"}, Flag: "--schema"}}, }) requireSecretLoadCode(t, err, CodeOverrideVersion) } func TestOverrideDecodeFailureUsesFlagSourceWithoutRawValue(t *testing.T) { type config struct { Count int `yaml:"count"` } result, err := testLoad(Bytes("base.yml", []byte("count: 1\n")), Options[config]{Overrides: []Override{{ Path: Keys("missing"), Value: "RAW-ARG-CANARY", Flag: "--missing", }}}) if result != nil { t.Fatalf("result = %#v", result) } var loadError *LoadError if !errors.As(err, &loadError) { t.Fatalf("error = %T", err) } diagnostics := loadError.Diagnostics() sources := loadError.Sources() if len(diagnostics) != 1 || diagnostics[0].Code != CodeDecodeUnknownField || diagnostics[0].Primary == nil || len(sources) != 2 || sources[1].Label != "--missing" || diagnostics[0].Primary.SourceID != sources[1].ID { t.Fatalf("diagnostics/sources = %#v / %#v", diagnostics, sources) } if strings.Contains(fmtLoadError(loadError), "RAW-ARG-CANARY") { t.Fatal("decode failure leaked override value") } } func TestOverrideTypeMismatchUsesNormalStrictDecode(t *testing.T) { type config struct { Count int `yaml:"count"` } _, err := testLoad(Bytes("base.yml", []byte("count: 1\n")), Options[config]{Overrides: []Override{{ Path: Keys("count"), Value: "not-an-integer", Flag: "--count", }}}) loadError := requireSecretLoadCode(t, err, CodeDecodeType) if loadError.Diagnostics()[0].Primary == nil || loadError.Sources()[1].Label != "--count" || strings.Contains(fmtLoadError(loadError), "not-an-integer") { t.Fatalf("type mismatch diagnostics/sources = %#v / %#v", loadError.Diagnostics(), loadError.Sources()) } } func TestOverrideEqualReplacementStillAppearsInHistory(t *testing.T) { type config struct { Value string `yaml:"value"` } result, err := testLoad(Bytes("equal.yml", []byte("value: same\n")), Options[config]{Overrides: []Override{{Path: Keys("value"), Value: "same", Flag: "--value"}}}) if err != nil { t.Fatal(err) } if got := historyLayers(result.Metadata().History(Keys("value"))); !reflect.DeepEqual(got, []Layer{LayerDefault, LayerBase, LayerCLI}) { t.Fatalf("equal override history = %#v", result.Metadata().History(Keys("value"))) } } func TestAffectedOverrideOriginsSortEscapedPointers(t *testing.T) { existingInput := parseCompositionDocument(t, "source-1", `values: z: old a/b: slash a~b: tilde nested: {x: old} `) existing, diagnostics := composeDocuments(existingInput) if len(diagnostics) != 0 { t.Fatal(diagnostics) } incoming := parseCompositionDocument(t, "source-2", `values: z: new a/b: new a~b: new nested: {x: new} `) existingValues, ok := documentAtPath(existing.root, Keys("values")) if !ok { t.Fatal("existing values mapping is missing") } incomingValues, ok := documentAtPath(incoming, Keys("values")) if !ok { t.Fatal("incoming values mapping is missing") } displaced := affectedOverrideOrigins(existingValues, incomingValues, existing.origins, Keys("values"), &mappingIndex{}) want := []string{"/values", "/values/a~0b", "/values/a~1b", "/values/nested", "/values/nested/x", "/values/z"} if len(displaced) != len(want) { t.Fatalf("displaced definitions = %d, want %d", len(displaced), len(want)) } for i, definition := range displaced { if got := definition.path.String(); got != want[i] { t.Fatalf("displaced[%d] path = %q, want %q", i, got, want[i]) } } } func TestOverrideAggregateLimitsFailBeforeApplication(t *testing.T) { base, diagnostics := composeDocuments(&document.Node{Kind: document.Mapping}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } base.inputNodes = 1 _, diagnostics = preparePostStructuralOverrides(overridePreparationInput{ root: base.root, inputBytes: base.inputBytes, inputNodes: base.inputNodes, }, nil, []Override{{Path: Keys("value"), Value: "x", Flag: "--value"}}, nil, importLimits{sources: 1, bytes: 1, nodes: 2}) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideLimitExceeded || len(base.root.Mapping) != 0 { t.Fatalf("bounded preparation = %#v; base %#v", diagnostics, base.root) } } func TestPreparedOverrideBatchDetachesInputsAndAllocatesSourcesInOrder(t *testing.T) { base, diagnostics := composeDocuments(parseCompositionDocument(t, "base", "settings: {}\n")) if len(diagnostics) != 0 { t.Fatal(diagnostics) } sources := []SourceInfo{{ID: "source-1", Label: "base", Layer: LayerBase, Kind: SourceKindMapping}} firstValue := map[string]any{"nested": map[string]any{"name": "original"}} secondValue := []any{map[string]any{"enabled": true}} batch, diagnostics := preparePostStructuralOverrides(overridePreparationInput{ root: base.root, inputBytes: base.inputBytes, inputNodes: base.inputNodes, }, sources, []Override{ {Path: Keys("settings", "first"), Value: firstValue, Flag: "--first"}, {Path: Keys("settings", "second"), Value: secondValue, Flag: "--second"}, }, nil, defaultImportLimits()) if len(diagnostics) != 0 { t.Fatalf("prepare post-structural overrides: %#v", diagnostics) } if len(batch.overrides) != 2 || len(batch.sources) != 3 { t.Fatalf("prepared counts = %d overrides, %d sources", len(batch.overrides), len(batch.sources)) } for index, want := range []string{"source-2", "source-3"} { if batch.overrides[index].source.ID != want || batch.sources[index+1].ID != want { t.Errorf("prepared source %d = %#v / %#v, want %s", index, batch.overrides[index].source, batch.sources[index+1], want) } } if batch.sources[1].Label != "--first" || batch.sources[2].Label != "--second" { t.Fatalf("synthetic source order = %#v", batch.sources) } firstValue["nested"].(map[string]any)["name"] = "changed after preparation" preparedName, found := documentAtPath(batch.overrides[0].value, Keys("nested", "name")) if !found || preparedName.Text != "original" { t.Fatalf("prepared override aliases input value: %#v, %v", preparedName, found) } preparedName.Text = "changed prepared snapshot" if firstValue["nested"].(map[string]any)["name"] != "changed after preparation" { t.Fatal("prepared override mutation changed caller input") } batch.sources[0].Label = "changed batch source" if sources[0].Label != "base" { t.Fatal("prepared source list aliases the input source list") } if len(base.root.Mapping) != 1 || base.root.Mapping[0].Value.Kind != document.Mapping || len(base.root.Mapping[0].Value.Mapping) != 0 { t.Fatalf("preparation mutated assembled input: %#v", base.root) } } func TestPostStructuralOverridePreparationRejectsInheritanceDirectives(t *testing.T) { base, diagnostics := composeDocuments(parseCompositionDocument(t, "base", "child: {}\nitems: []\n")) if len(diagnostics) != 0 { t.Fatal(diagnostics) } tests := []struct { name string override Override path string }{ { name: "supplied path", override: Override{Path: Keys("child", "extends"), Value: "parent", Flag: "--extends"}, path: "/child/extends", }, { name: "nested value", override: Override{Path: Keys("items"), Value: []any{map[string]any{"extends": "parent"}}, Flag: "--items"}, path: "/items/0/extends", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, diagnostics := preparePostStructuralOverrides(overridePreparationInput{ root: base.root, inputBytes: base.inputBytes, inputNodes: base.inputNodes, }, nil, []Override{test.override}, nil, defaultImportLimits()) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverrideDirective || diagnostics[0].Path.String() != test.path { t.Fatalf("post-structural diagnostics = %#v, want directive at %s", diagnostics, test.path) } }) } } func historyLayers(history []Change) []Layer { layers := make([]Layer, len(history)) for index, change := range history { layers[index] = change.Origin.Layer } return layers } func parseOverrideTestPath(t *testing.T, value string) Path { t.Helper() path, err := ParsePath(value) if err != nil { t.Fatal(err) } return path } func fmtLoadError(loadError *LoadError) string { var output strings.Builder _ = WriteDiagnostics(&output, loadError.Diagnostics()) return loadError.Error() + output.String() } func TestApplyDecodedOverridesUsesSchemaAndPreservesTypedOwnership(t *testing.T) { t.Parallel() type child struct { Name string `yaml:"name"` Enabled bool `yaml:"enabled"` } type config struct { Nested child `yaml:"nested"` Labels map[string]child `yaml:"labels"` Items []child `yaml:"items"` Empty []string `yaml:"empty"` Count int `yaml:"count"` Enabled bool `yaml:"enabled"` Label string `yaml:"label"` Small namedSmall `yaml:"small"` Text validatedText `yaml:"text"` Delay time.Duration `yaml:"delay"` APIKey Secret `yaml:"api_key"` Maybe *string `yaml:"maybe"` } defaultMaybe := "default" defaults := config{ Nested: child{Name: "default", Enabled: true}, Labels: map[string]child{"default": {Name: "default", Enabled: true}}, Items: []child{{Name: "default", Enabled: true}}, Empty: []string{"default"}, Count: 8, Enabled: true, Label: "default", Small: 7, Text: "default", Delay: time.Second, Maybe: &defaultMaybe, } input := parseDocumentForDecode(t, "nested: {name: source, enabled: true}\nlabels: {old: {name: source, enabled: true}}\nitems: [{name: discarded, enabled: true}]\nempty: [source]\ncount: 7\nenabled: true\nlabel: source\nsmall: 3\ntext: source\ndelay: 1s\napi_key: SOURCE_SECRET\nmaybe: source\n") schema, schemaIssue := buildSchema(reflect.TypeFor[config](), testSecretBinding()) if schemaIssue != nil { t.Fatalf("buildSchema() issue = %#v", schemaIssue) } decoded, decodeIssues := decodeConfiguration(schema, reflect.ValueOf(defaults), input) if len(decodeIssues) != 0 { t.Fatalf("decodeConfiguration() issues = %#v", decodeIssues) } batch, diagnostics := preparePostStructuralOverrides(overridePreparationInput{root: input}, nil, []Override{ {Path: Keys("nested"), Value: map[string]any{"enabled": false}, Flag: "--nested"}, {Path: Keys("labels"), Value: map[string]any{ "old": map[string]any{"enabled": false}, "new": map[string]any{"name": "cli"}, }, Flag: "--labels"}, {Path: Keys("items"), Value: []any{map[string]any{"name": "replacement"}}, Flag: "--items"}, {Path: Keys("empty"), Value: []any{}, Flag: "--empty"}, {Path: Keys("count"), Value: 0, Flag: "--count"}, {Path: Keys("enabled"), Value: false, Flag: "--enabled"}, {Path: Keys("label"), Value: "", Flag: "--label"}, {Path: Keys("small"), Value: 0, Flag: "--small"}, {Path: Keys("text"), Value: "cli text", Flag: "--text"}, {Path: Keys("delay"), Value: "250ms", Flag: "--delay"}, {Path: Keys("api_key"), Value: "CLI_SECRET", Flag: "--api-key"}, {Path: Keys("maybe"), Value: nil, Flag: "--maybe"}, }, nil, defaultImportLimits()) if len(diagnostics) != 0 { t.Fatalf("preparePostStructuralOverrides() diagnostics = %#v", diagnostics) } result, issues := applyDecodedOverrides(schema, decoded, batch, decodeContext{}) if len(issues) != 0 { t.Fatalf("applyDecodedOverrides() issues = %#v", issues) } got := result.value.Interface().(config) if got.Nested != (child{Name: "source", Enabled: false}) || !reflect.DeepEqual(got.Labels, map[string]child{ "default": {Name: "default", Enabled: true}, "old": {Name: "source", Enabled: false}, "new": {Name: "cli"}, }) || !reflect.DeepEqual(got.Items, []child{{Name: "replacement"}}) || got.Count != 0 || got.Enabled || got.Empty == nil || len(got.Empty) != 0 || got.Label != "" || got.Small != 0 || got.Text != "cli text" || got.Delay != 250*time.Millisecond || got.APIKey.name != "CLI_SECRET" || got.Maybe != nil { t.Fatalf("post-decode configuration = %#v", got) } if result.preCLI == result.final || !reflect.DeepEqual(result.preCLI, decoded.final) || len(result.overrides) != len(batch.overrides) { t.Fatalf("result snapshots/contributions = pre:%p final:%p overrides:%d", result.preCLI, result.final, len(result.overrides)) } if _, explicit := result.presence["/items/0/enabled"]; explicit { t.Fatalf("replacement list inherited presence from its discarded item: %#v", result.presence) } if result.presence["/items/0/name"].location.SourceID != batch.overrides[2].source.ID || result.presence["/nested"].location.SourceID != "source-1" || result.origins["/items/0/name"].Layer != LayerCLI || result.origins["/nested/name"].Layer != LayerBase || result.origins["/nested/enabled"].Layer != LayerCLI || result.origins["/labels/default/name"].Layer != LayerDefault { t.Fatalf("presence/origins = %#v / %#v", result.presence, result.origins) } if node, ok := documentAtPath(result.final, Keys("items").Index(0).Key("enabled")); !ok || node.Kind != document.Boolean || node.Text != "false" { t.Fatalf("replacement list default projection = %#v", node) } got.Labels["new"] = child{Name: "mutated"} result.overrides[0].value.Mapping[0].Value.Text = "changed" if decoded.value.Interface().(config).Labels["old"].Name != "source" || batch.overrides[0].value.Mapping[0].Value.Text != "false" { t.Fatal("post-decode result mutation reached the pre-CLI value or prepared batch") } } func TestApplyDecodedOverridesRejectsSchemaErrorsWithoutPartialResult(t *testing.T) { t.Parallel() type config struct { Nested struct { Kept int `yaml:"kept"` } `yaml:"nested"` Count int `yaml:"count"` } input := parseDocumentForDecode(t, "nested: {kept: 1}\ncount: 2\n") schema := mustSchemaFor[config](t) decoded, issues := decodeConfiguration(schema, reflect.ValueOf(config{}), input) if len(issues) != 0 { t.Fatalf("decodeConfiguration() issues = %#v", issues) } for _, test := range []struct { name string override Override code Code }{ {name: "unknown nested field", override: Override{Path: Keys("nested"), Value: map[string]any{"unknown": 1}, Flag: "--nested"}, code: CodeDecodeUnknownField}, {name: "invalid type", override: Override{Path: Keys("count"), Value: "not an integer", Flag: "--count"}, code: CodeDecodeType}, } { t.Run(test.name, func(t *testing.T) { batch, diagnostics := preparePostStructuralOverrides(overridePreparationInput{root: input}, nil, []Override{test.override}, nil, defaultImportLimits()) if len(diagnostics) != 0 { t.Fatalf("preparePostStructuralOverrides() diagnostics = %#v", diagnostics) } result, issues := applyDecodedOverrides(schema, decoded, batch, decodeContext{}) if len(issues) == 0 || issues[0].code != test.code { t.Fatalf("decode issues = %#v, want code %q", issues, test.code) } if result.value.IsValid() || result.final != nil { t.Fatalf("failed assignment returned a partial result: %#v", result) } original := decoded.value.Interface().(config) if original.Count != 2 || original.Nested.Kept != 1 { t.Fatal("failed assignment mutated the pre-CLI decoded value") } }) } }