package engine import ( "reflect" "testing" ) func TestPostDecodeOverrideLifecycleMatchesOrdinaryAndLayeredLoads(t *testing.T) { t.Parallel() type config struct { Count int `yaml:"count"` Derived int `yaml:"derived"` } input := Bytes("settings.yml", []byte("count: 4\n")) defaultsCalls := 0 definition := Definition[config]{ Defaults: func() config { defaultsCalls++ return config{Count: 9, Derived: 1} }, Resolve: func(value *config, resolution *Resolution) error { info, ok := resolution.InputInfo(Keys("count")) if !ok || !info.Explicit || info.Origin.Layer != LayerCLI { t.Errorf("resolution count info = %#v, %v", info, ok) } if source, ok := resolution.Source("source-2"); !ok || source.Layer != LayerCLI { t.Errorf("resolution CLI source = %#v, %v", source, ok) } Default(resolution, Keys("count"), &value.Count, 99, "preserve explicit count") Default(resolution, Keys("derived"), &value.Derived, value.Count*2, "derive from CLI count", Keys("count")) Normalize(resolution, Keys("count"), &value.Count, value.Count+1, "normalize after CLI") return nil }, } options := Options[config]{Definition: definition, Overrides: []Override{{Path: Keys("count"), Value: 4, Flag: "--count"}}} ordinary, err := testLoad(input, options) if err != nil { t.Fatal(err) } layered, err := testGroupedLoad(loadRequest[config]{ main: loadInput{source: input}, definition: definition, overrides: []Override{{Path: Keys("count"), Value: 4, Flag: "--count"}}, }) if err != nil { t.Fatal(err) } if defaultsCalls != 2 { t.Fatalf("defaults factory calls = %d, want once per load", defaultsCalls) } for name, result := range map[string]*testResult[config]{"ordinary": ordinary, "layered": layered} { if result.Config.Count != 5 || result.Config.Derived != 8 { t.Errorf("%s resolved configuration = %#v", name, result.Config) } if got := historyLayers(result.Metadata().History(Keys("count"))); !reflect.DeepEqual(got, []Layer{LayerDefault, LayerBase, LayerCLI, LayerResolution}) { t.Errorf("%s count history layers = %#v", name, got) } info, ok := result.Metadata().Lookup(Keys("count")) if !ok || info.Origin.Layer != LayerResolution { t.Errorf("%s final count origin = %#v, %v", name, info, ok) } } if !reflect.DeepEqual(ordinary.Config, layered.Config) || !reflect.DeepEqual(ordinary.Metadata().History(Keys("count")), layered.Metadata().History(Keys("count"))) { t.Fatalf("ordinary and layered results differ: %#v / %#v", ordinary.Config, layered.Config) } } func TestNoSourceOverridesReachResolutionAfterDefaults(t *testing.T) { t.Parallel() type config struct { Count int `yaml:"count"` Derived int `yaml:"derived"` } calls := 0 result, err := testLoad(NoSource(), Options[config]{ Definition: Definition[config]{ Defaults: func() config { calls++ return config{Count: 2} }, Resolve: func(value *config, resolution *Resolution) error { info, ok := resolution.InputInfo(Keys("count")) if !ok || !info.Explicit || info.Origin.Layer != LayerCLI { t.Errorf("resolution count info = %#v, %v", info, ok) } Default(resolution, Keys("count"), &value.Count, 10, "preserve CLI value") Default(resolution, Keys("derived"), &value.Derived, value.Count*3, "derive from CLI count") return nil }, }, Overrides: []Override{{Path: Keys("count"), Value: 4, Flag: "--count"}}, }) if err != nil { t.Fatal(err) } if calls != 1 || result.Config.Count != 4 || result.Config.Derived != 12 { t.Fatalf("defaults-only CLI result = %#v, defaults calls %d", result.Config, calls) } if sources := result.Sources(); len(sources) != 1 || sources[0].Layer != LayerCLI { t.Fatalf("defaults-only CLI sources = %#v", sources) } } func TestCLISequenceAssignmentReplacesAppendAndUpsertResults(t *testing.T) { t.Parallel() type item struct { ID string `yaml:"id"` } type config struct { Items []item `yaml:"items"` } declaration := func(mode string, id string) Source { return Bytes(id, []byte("configkit:\n sequences:\n - destination: [items]\n key: id\n mode: "+mode+"\nitems: [{id: "+id+"}]\n")) } result, err := testGroupedLoad(loadRequest[config]{ main: loadInput{source: declaration("upsert", "main")}, underlays: []loadGroup{{inputs: []loadInput{{source: declaration("append", "underlay")}}}}, overrides: []Override{{Path: Keys("items"), Value: []any{map[string]any{"id": "cli"}}, Flag: "--items"}}, }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(result.Config.Items, []item{{ID: "cli"}}) { t.Fatalf("CLI sequence assignment = %#v, want complete replacement", result.Config.Items) } } func TestCLISecretReferenceUsesEffectiveLookupAndLocation(t *testing.T) { t.Parallel() type config struct { APIKey Secret `yaml:"api_key"` } options := Options[config]{ Definition: Definition[config]{Defaults: func() config { return config{APIKey: SecretEnv("DEFAULT_SECRET")} }}, Overrides: []Override{{Path: Keys("api_key"), Value: "CLI_SECRET", Flag: "--api-key"}}, } source := Bytes("secret-input", []byte("api_key: BASE_SECRET\n")) t.Run("effective reference is looked up", func(t *testing.T) { calls := 0 result, err := testLoadWithSecretLookup(source, func(name string) (string, bool) { calls++ if name != "CLI_SECRET" { t.Errorf("secret lookup name = %q", name) } return "effective-credential", true }, options) if err != nil || calls != 1 || result.Config.APIKey.Value() != "effective-credential" { t.Fatalf("CLI secret result = %#v, calls %d, error %v", result, calls, err) } info, ok := result.Metadata().Lookup(Keys("api_key")) if !ok || info.Origin.Layer != LayerCLI { t.Fatalf("CLI secret origin = %#v, %v", info, ok) } }) t.Run("missing effective reference uses CLI location", func(t *testing.T) { result, err := testLoadWithSecretLookup(source, func(name string) (string, bool) { if name != "CLI_SECRET" { t.Errorf("secret lookup name = %q", name) } return "", false }, options) if result != nil { t.Fatalf("missing secret returned a result: %#v", result) } loadErr, ok := err.(*LoadError) if !ok || len(loadErr.Diagnostics()) != 1 { t.Fatalf("missing secret error = %v", err) } diagnostic := loadErr.Diagnostics()[0] if diagnostic.Code != CodeSecretUnset || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" { t.Fatalf("missing secret diagnostic = %#v", diagnostic) } }) } func TestCLIOverridesCannotHideEarlierFailuresOrAddInheritance(t *testing.T) { t.Parallel() type config struct { Count int `yaml:"count"` Child map[string]any `yaml:"child"` Parent map[string]any `yaml:"parent"` } t.Run("decode error", func(t *testing.T) { _, err := testLoad(Bytes("bad-count", []byte("count: invalid\n")), Options[config]{ Overrides: []Override{{Path: Keys("count"), Value: 3, Flag: "--count"}}, }) requireSecretLoadCode(t, err, CodeDecodeType) }) t.Run("inheritance error", func(t *testing.T) { _, err := testLoad(Bytes("missing-parent", []byte("child: {extends: missing, value: source}\n")), Options[config]{ Overrides: []Override{{Path: Keys("child"), Value: map[string]any{"value": "cli"}, Flag: "--child"}}, }) requireSecretLoadCode(t, err, CodeInheritanceMissing) }) t.Run("cannot add relationship", func(t *testing.T) { _, err := testLoad(Bytes("new-relationship", []byte("child: {value: source}\nparent: {value: parent}\n")), Options[config]{ Overrides: []Override{{Path: Keys("child"), Value: map[string]any{"extends": "parent"}, Flag: "--child"}}, }) requireSecretLoadCode(t, err, CodeOverrideDirective) }) t.Run("cannot cancel relationship", func(t *testing.T) { _, err := testLoad(Bytes("cancel-relationship", []byte("child: {extends: parent}\nparent: {value: parent}\n")), Options[config]{ Overrides: []Override{{Path: Keys("child", "extends"), Value: nil, Flag: "--extends"}}, }) requireSecretLoadCode(t, err, CodeOverrideDirective) }) }